Skip to content

Create Stunning Visual Effects with AI: The GLSL Shader Development Guide

Purpose

This post demonstrates how to use the shader-dev skill for GLSL shader development, covering 36 techniques from ray marching to path tracing.

Problem

I wanted to create a 3D scene with realistic lighting in GLSL. I started with a simple ray marching example from ShaderToy:

my-first-shader.glsl
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
// I want a sphere
float d = length(uv) - 0.5;
vec3 col = d > 0.0 ? vec3(0.0) : vec3(1.0);
fragColor = vec4(col, 1.0);
}

This worked on ShaderToy. When I tried to put it in a standalone HTML file:

Terminal window
ERROR: 'fragCoord' : undeclared identifier
ERROR: 'iResolution' : undeclared identifier
ERROR: 'mainImage' : function does not exist

I realized I didn’t understand how ShaderToy’s conventions translate to raw WebGL2. I also struggled with:

  • Which technique to use for specific effects
  • How to combine multiple techniques
  • Performance issues on mobile devices
  • Debugging when shaders don’t look right

Environment

  • WebGL2 compatible browser
  • Basic GLSL knowledge
  • ShaderToy for prototyping
  • shader-dev skill from MiniMax Skills

Solution: Organized Shader Techniques

The shader-dev skill organizes 36 GLSL techniques into categories:

technique-categories
┌─────────────────────────────────────────────────────────────┐
│ shader-dev Techniques │
├──────────────────┼──────────────────────────────────────────┤
│ Geometry & SDF │ sdf-2d, sdf-3d, csg-boolean, │
│ │ domain-repetition, domain-warping │
├──────────────────┼──────────────────────────────────────────┤
│ Ray & Lighting │ ray-marching, analytic-ray-tracing, │
│ │ path-tracing-gi, lighting-model, shadows │
├──────────────────┼──────────────────────────────────────────┤
│ Simulation │ fluid-simulation, particle-system, │
│ │ cellular-automata │
├──────────────────┼──────────────────────────────────────────┤
│ Natural Effects │ water-ocean, terrain-rendering, │
│ │ atmospheric-scattering, volumetric │
├──────────────────┼──────────────────────────────────────────┤
│ Procedural │ procedural-noise, voronoi, fractal │
├──────────────────┼──────────────────────────────────────────┤
│ Post-Processing │ post-processing, multipass-buffer, │
│ │ texture-sampling, camera-effects │
└──────────────────┴──────────────────────────────────────────┘

Technique Routing

When I asked “How do I create a 3D scene?”, the skill routed me to:

I want to create…Primary techniquesCombine with
3D objects from mathray-marching + sdf-3dlighting-model, shadows
Complex 3D shapescsg-boolean-operationssdf-3d, ray-marching
Fluid or smokefluid-simulationmultipass-buffer
Realistic lightinglighting-modelshadow-techniques, ambient-occlusion
Clouds, fog, firevolumetric-renderingprocedural-noise

This routing helped me understand that effects require multiple techniques working together.

WebGL2 Adaptation

I learned that ShaderToy uses conventions that don’t exist in raw WebGL2. Here’s how to adapt:

Fragment coordinate:

shader-adaptation.glsl
// WRONG: ShaderToy style
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
// CORRECT: WebGL2 style
vec2 uv = (2.0 * gl_FragCoord.xy - iResolution.xy) / iResolution.y;

main() wrapper:

webgl2-wrapper.glsl
#version 300 es
precision highp float;
out vec4 fragColor;
uniform vec3 iResolution;
uniform float iTime;
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
vec3 col = vec3(uv, 0.5 + 0.5 * sin(iTime));
fragColor = vec4(col, 1.0);
}
void main() {
mainImage(fragColor, gl_FragCoord.xy);
}

This pattern lets me write ShaderToy-compatible code that also works standalone.

Performance Budget

I hit performance issues on my phone. The skill recommends these limits:

performance-limits
Ray marching main loop: ≤ 128 steps
Volume sampling: ≤ 32 steps
FBM octaves: ≤ 6 layers
Total nested loops: ≤ 1000 per pixel

I reduced my ray steps from 256 to 128 and got 30fps instead of 15fps.

Code Example: Ray Marching Template

Here’s a working ray marching template:

ray-marching-template.glsl
#define MAX_STEPS 128
#define MAX_DIST 100.0
#define EPSILON 0.001
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
float map(vec3 p) {
float d = MAX_DIST;
d = min(d, sdSphere(p - vec3(0.0, 1.0, 0.0), 1.0));
d = min(d, p.y); // ground plane
return d;
}
vec3 trace(vec3 ro, vec3 rd) {
float t = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = ro + rd * t;
float d = map(p);
if (d < EPSILON || t > MAX_DIST) break;
t += d;
}
return ro + rd * t;
}

The map() function defines the scene using signed distance functions. The trace() function marches rays through the scene.

SDF Primitives

I needed more shapes than just spheres. The skill provides common SDF primitives:

sdf-primitives.glsl
// Sphere
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
// Box
float sdBox(vec3 p, vec3 b) {
vec3 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
}
// Smooth union (blend shapes together)
float opSmoothUnion(float d1, float d2, float k) {
float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
return mix(d2, d1, h) - k * h * (1.0 - h);
}

With smooth union, I created organic shapes by blending spheres:

organic-shape.glsl
float map(vec3 p) {
float d = sdSphere(p, 1.0);
d = opSmoothUnion(d, sdSphere(p - vec3(0.8, 0.0, 0.0), 0.6), 0.3);
d = opSmoothUnion(d, sdSphere(p + vec3(0.8, 0.0, 0.0), 0.6), 0.3);
return d;
}

Atmospheric Scattering

For realistic skies, I used the atmospheric-scattering technique:

atmospheric-scattering.glsl
vec3 getAtmosphere(vec3 rayDir, vec3 sunDir) {
float sunDot = max(dot(rayDir, sunDir), 0.0);
// Rayleigh scattering (blue sky)
vec3 rayleigh = vec3(5.8e-6, 13.5e-6, 33.1e-6);
vec3 scatter = rayleigh / (1.0 + sunDot * sunDot * 0.5);
// Mie scattering (sun glow)
float mie = pow(sunDot, 32.0);
return scatter + mie * vec3(1.0, 0.9, 0.7);
}

This gave me a realistic blue sky with sun glow.

Debugging Techniques

When my shaders didn’t look right, I used these debugging approaches:

What to checkCode
Surface normalscol = nor * 0.5 + 0.5;
Ray march stepscol = vec3(float(steps) / float(MAX_STEPS));
UV coordinatescol = vec3(uv, 0.0);
SDF distancecol = (d > 0.0 ? vec3(0.9,0.6,0.3) : vec3(0.4,0.7,0.85));

Visualizing intermediate values helped me identify where things went wrong.

Quick Recipes

The skill provides ready-to-use combinations:

Photorealistic SDF Scene:

photorealistic-recipe
1. Geometry: sdf-3d + csg-boolean-operations
2. Rendering: ray-marching + normal-estimation
3. Lighting: lighting-model + shadows + ambient-occlusion
4. Atmosphere: atmospheric-scattering
5. Post: post-processing + anti-aliasing

Procedural Landscape:

landscape-recipe
1. Terrain: terrain-rendering + procedural-noise
2. Texturing: texture-mapping-advanced (biplanar)
3. Sky: atmospheric-scattering
4. Water: water-ocean + lighting-model (Fresnel)

Summary

In this post, I showed how to use the shader-dev skill for GLSL shader development. The key points are:

  • 36 techniques organized by category help find the right approach
  • Technique routing tables match requests to patterns
  • WebGL2 adaptation rules convert ShaderToy code to standalone output
  • Performance budgets keep shaders fast on limited hardware
  • Debugging techniques help identify rendering issues

The shader-dev skill provides a comprehensive reference for GLSL shader techniques, from basic SDF shapes to advanced path tracing. With technique routing tables and WebGL2 adaptation rules, I can create stunning visual effects while avoiding common pitfalls.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments