Initializing 3D Canvas...

Raymarching

2 min read1 page

Raymarching starts by generating a ray for each pixel. The camera origin and pixel-to-ray mapping define the viewing frustum.

Camera Ray:

1. Map pixel coordinates to normalized device coordinates. 2. Apply perspective projection with field-of-view angle. 3. Normalize the direction vector for unit-length marching steps.
python
1# Camera ray setup for raymarching
2import numpy as np
3
4def generate_ray(pixel_x, pixel_y, width, height, fov):
5 """Convert pixel coordinates to a 3D ray direction."""
6 aspect = width / height
7 # Normalize pixel to [-1, 1]
8 ndc_x = (2.0 * pixel_x / width - 1.0) * aspect
9 ndc_y = 1.0 - 2.0 * pixel_y / height
10
11 # Perspective projection
12 import math
13 z = -1.0 / math.tan(math.radians(fov) / 2.0)
14 direction = np.array([ndc_x, ndc_y, z])
15 direction /= np.linalg.norm(direction)
16 return direction
1 min read1 page

Each step advances the ray by exactly the SDF distance at the current position. This is safe because the SDF guarantees no surface exists within that radius.

Sphere Tracing:

1. Evaluate SDF at current ray position. 2. Distance value = radius of empty sphere around point. 3. Advance ray parameter by that distance. Repeat.
python
1# Single sphere tracing step
2def sphere_trace_step(origin, direction, sdf_fn, current_t):
3 """Advance ray by the SDF distance at current position."""
4 position = origin + current_t * direction
5 distance = sdf_fn(position)
6 # Safe to advance by 'distance' without missing surface
7 new_t = current_t + distance
8 return new_t, distance
2 min read1 page

The full solver loops until convergence (surface hit) or escape (exceeding max distance or iteration count).

Raymarch Loop:

1. Initialize ray parameter t = 0. 2. Each iteration: evaluate SDF, advance by d, check termination. 3. Return hit position and convergence status.
python
1# Full sphere tracing loop
2def raymarch(origin, direction, sdf_fn, max_steps=128, max_dist=100.0, eps=1e-4):
3 t = 0.0
4 for i in range(max_steps):
5 pos = origin + t * direction
6 d = sdf_fn(pos)
7 if d < eps: # Hit surface
8 return t, pos, True
9 if t > max_dist: # Escaped scene
10 return t, pos, False
11 t += d
12 return t, origin + t * direction, False
Max Steps
10.00
2 min read1 page

After the ray hits a surface, the normal is estimated via the SDF gradient using central finite differences.

Gradient Normal:

1. Sample SDF at 6 offset positions (±eps per axis). 2. Differences give partial derivatives. 3. Normalize to get unit surface normal.
python
1# Gradient-based normal estimation at hit point
2def estimate_normal(sdf_fn, pos, eps=1e-4):
3 """Central differences gradient = surface normal."""
4 nx = sdf_fn([pos[0]+eps, pos[1], pos[2]]) - sdf_fn([pos[0]-eps, pos[1], pos[2]])
5 ny = sdf_fn([pos[0], pos[1]+eps, pos[2]]) - sdf_fn([pos[0], pos[1]-eps, pos[2]])
6 nz = sdf_fn([pos[0], pos[1], pos[2]+eps]) - sdf_fn([pos[0], pos[1], pos[2]-eps])
7 import math
8 length = math.sqrt(nx*nx + ny*ny + nz*nz) or 1.0
9 return [nx/length, ny/length, nz/length]
Normal Samples
8.00
2 min read1 page

The simplest lighting model uses the dot product between the surface normal and light direction (Lambertian reflectance).

Diffuse Shade:

1. Compute N dot L between surface normal and light direction. 2. Clamp to [0, 1] to prevent negative illumination. 3. Multiply base color by this intensity.
python
1# Lambertian diffuse shading
2def shade_diffuse(normal, light_dir, base_color):
3 """N dot L diffuse lighting."""
4 import numpy as np
5 n_dot_l = max(np.dot(normal, light_dir), 0.0)
6 return [c * n_dot_l for c in base_color]
7
8# After computing the hit normal and light direction,
9# the diffuse intensity is simply clamp(N . L, 0, 1).
2 min read1 page

Soft shadows trace a secondary ray toward the light source. The closest approach to any surface during the march determines the penumbra softness.

Soft Shadows:

1. Cast ray from surface point toward light. 2. Track minimum d/t ratio along the march. 3. Smaller ratio = darker shadow (closer occluder pass).
python
1# Soft shadow via secondary sphere trace
2def soft_shadow(origin, light_dir, sdf_fn, k=8.0, max_t=50.0):
3 """Raymarch toward light; closest approach gives penumbra."""
4 result = 1.0
5 t = 0.01
6 while t < max_t:
7 d = sdf_fn(origin + t * light_dir)
8 if d < 1e-4:
9 return 0.0 # Full shadow
10 result = min(result, k * d / t)
11 t += d
12 return clamp(result, 0.0, 1.0)
13
14# k controls penumbra softness.
15# Higher k = sharper shadows, lower k = softer.
Shadow Softness (k)
8.00
2 min read1 page

SDF-based AO samples the field along the surface normal. If actual SDF values are less than expected (indicating nearby geometry), the point is occluded.

SDF AO:

1. March along normal in small steps. 2. Compare expected distance (step * i) with actual SDF. 3. Deficit indicates occlusion. Weight decreases with distance.
python
1# SDF-based ambient occlusion
2def ambient_occlusion(pos, normal, sdf_fn, steps=5, step_size=0.1):
3 """Sample SDF along normal to estimate occlusion."""
4 ao = 0.0
5 weight = 1.0
6 for i in range(1, steps + 1):
7 sample_pos = pos + normal * (i * step_size)
8 expected_dist = i * step_size
9 actual_dist = sdf_fn(sample_pos)
10 # Difference between expected and actual indicates occlusion
11 ao += weight * (expected_dist - actual_dist)
12 weight *= 0.5 # Diminishing influence
13 return max(1.0 - ao, 0.0)
AO Steps
5.00
2 min read1 page

Distance-based exponential fog blends the surface color toward a fog color based on how far the ray traveled before hitting the surface.

Fog:

1. Compute fog factor from ray travel distance and density. 2. Exponential falloff: fog = 1 - exp(-density * distance). 3. Lerp between surface color and fog color.
python
1# Distance-based fog for atmospheric effects
2def apply_fog(color, distance, fog_color, fog_density):
3 """Exponential fog blending."""
4 import math
5 fog_factor = 1.0 - math.exp(-fog_density * distance)
6 fog_factor = max(0.0, min(1.0, fog_factor))
7 # Blend between object color and fog color
8 result = [c * (1 - fog_factor) + f * fog_factor
9 for c, f in zip(color, fog_color)]
10 return result
11
12# Fog uses the ray's travel distance (t parameter)
13# to blend toward a background fog color.
Fog Density
0.15