Skip to main content
GameDev.net gamedev.net

Raymarching a Sphere

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

Run the shader to adjust these controls.

What it demonstrates

Raymarching renders a 3D scene with no geometry at all. Each pixel fires a ray and walks it forward, asking the distance function how far it is safe to move. When the answer is small enough, the ray has arrived. That is the whole algorithm, kept in one file with the step count and hit threshold exposed so both failure modes are reachable.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image7
1 uniform float uCameraDistance; // @param 1.6..6.0 = 3.0 "Camera distance"
2 uniform int uMaxSteps; // @param 4..128 = 64 "Max steps"
3 uniform float uSurfaceEpsilon; // @param 0.0005..0.05 = 0.002 "Surface epsilon"
4
5 float mapScene(vec3 position) {
6 return length(position) - 1.0;
7 }
8
9 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
10 vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
11 vec3 origin = vec3(0.0, 0.0, -uCameraDistance);
12 vec3 direction = normalize(vec3(uv, 1.0));
13
14 float travelled = 0.0;
15 float hit = -1.0;
16 for (int i = 0; i < 128; i++) {
17 if (i >= uMaxSteps) break;
18 vec3 position = origin + direction * travelled;
19 float dist = mapScene(position);
20 if (dist < uSurfaceEpsilon) {
21 hit = travelled;
22 break;
23 }
24 travelled += dist;
25 if (travelled > 12.0) break;
26 }
27
28 vec3 sky = mix(vec3(0.05, 0.07, 0.12), vec3(0.14, 0.20, 0.32), uv.y + 0.5);
29 if (hit < 0.0) {
30 fragColor = vec4(sky, 1.0);
31 return;
32 }
33
34 vec3 position = origin + direction * hit;
35 vec3 normal = normalize(position);
36 vec3 toLight = normalize(vec3(0.6, 0.8, -0.5));
37 float diffuse = max(dot(normal, toLight), 0.0);
38 float rim = pow(1.0 - max(dot(normal, -direction), 0.0), 3.0);
39 vec3 color = vec3(0.20, 0.45, 0.78) * (0.12 + 0.88 * diffuse);
40 color += vec3(0.35, 0.55, 0.85) * rim * 0.6;
41 fragColor = vec4(color, 1.0);
42 }
43

Learn from this shader

How it works

The fragment coordinate becomes a point on an image plane, and the ray direction is that point pushed one unit forward and normalised. Marching by exactly the distance the field returns is the key insight: nothing is nearer than that, so a full step can never pass through a surface. Near a surface the steps become tiny, which is why arrival is a threshold rather than zero. Two escapes bound the loop, a step budget and a maximum travel distance, so a ray aimed at empty space terminates. The sphere uses an exact normal, which for a unit sphere is the surface point itself.

Try changing

Reduce Max steps until the silhouette dissolves into bands: the rays that fail first are the grazing ones, which take many small steps near the surface. Raise Surface epsilon and watch the sphere inflate and its edge soften, since the ray stops further out. Pull the camera in close to see the field of view widen.

Using it in a game

This is the core loop behind volumetric fog, cloud and smoke rendering, screen space refraction, and every procedural scene that has no mesh. Step counts are the cost, and grazing rays are the worst case, so real uses clamp the budget and accept the artefacts you just produced deliberately.

Explore the techniques

Continue with curated explanations and progressively related examples.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...