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:
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:
ERROR: 'fragCoord' : undeclared identifierERROR: 'iResolution' : undeclared identifierERROR: 'mainImage' : function does not existI 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:
┌─────────────────────────────────────────────────────────────┐│ 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 techniques | Combine with |
|---|---|---|
| 3D objects from math | ray-marching + sdf-3d | lighting-model, shadows |
| Complex 3D shapes | csg-boolean-operations | sdf-3d, ray-marching |
| Fluid or smoke | fluid-simulation | multipass-buffer |
| Realistic lighting | lighting-model | shadow-techniques, ambient-occlusion |
| Clouds, fog, fire | volumetric-rendering | procedural-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:
// WRONG: ShaderToy stylevec2 uv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
// CORRECT: WebGL2 stylevec2 uv = (2.0 * gl_FragCoord.xy - iResolution.xy) / iResolution.y;main() wrapper:
#version 300 esprecision 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:
Ray marching main loop: ≤ 128 stepsVolume sampling: ≤ 32 stepsFBM octaves: ≤ 6 layersTotal nested loops: ≤ 1000 per pixelI 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:
#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:
// Spherefloat sdSphere(vec3 p, float r) { return length(p) - r;}
// Boxfloat 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:
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:
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 check | Code |
|---|---|
| Surface normals | col = nor * 0.5 + 0.5; |
| Ray march steps | col = vec3(float(steps) / float(MAX_STEPS)); |
| UV coordinates | col = vec3(uv, 0.0); |
| SDF distance | col = (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:
1. Geometry: sdf-3d + csg-boolean-operations2. Rendering: ray-marching + normal-estimation3. Lighting: lighting-model + shadows + ambient-occlusion4. Atmosphere: atmospheric-scattering5. Post: post-processing + anti-aliasingProcedural Landscape:
1. Terrain: terrain-rendering + procedural-noise2. Texturing: texture-mapping-advanced (biplanar)3. Sky: atmospheric-scattering4. 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:
- 👨💻 shader-dev skill in MiniMax Skills
- 👨💻 ShaderToy - GLSL Shader Playground
- 👨💻 WebGL2 Fundamentals
- 👨💻 Inigo Quilez Articles on SDF and Ray Marching
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments