Ray Tracer
This draws a scene the way light actually behaves. For every pixel it fires rays into a set of spheres, lets them bounce off matte, metal, and glass surfaces, and follows them until they hit the sky. Do that enough times per pixel and you get real reflections, refraction through glass, soft shadows, and depth of field.
It gets sharper the longer you leave it alone. The first moment is a little grainy, then it settles into a clean image within a second or two. Drag to orbit the camera, pinch or scroll to zoom, and use the sliders to trade speed for quality.
Loading the ray tracer...
How it works
// One animation frame of the path tracer, for a single pixel.
// The live page runs this in a GPU shader; here is the same idea in JavaScript.
function renderPixel(x, y, frame) {
let color = [0, 0, 0];
for (let s = 0; s < samplesPerFrame; s++) {
// Ray from the camera through the pixel, jittered a little for smooth
// edges, with a small lens offset that gives the soft depth of field.
let ray = cameraRay(x + Math.random(), y + Math.random(), lensBlur);
let paint = [1, 1, 1]; // how much light this ray can still carry back
for (let bounce = 0; bounce < maxBounces; bounce++) {
const hit = nearestSurface(ray);
if (!hit) { // ray missed everything and hit the sky
color = add(color, mul(paint, sky(ray)));
break;
}
if (hit.material === "matte") {
paint = mul(paint, hit.color);
ray = newRay(hit.point, randomHemisphere(hit.normal));
} else if (hit.material === "metal") {
paint = mul(paint, hit.color);
ray = newRay(hit.point, reflect(ray.dir, hit.normal, hit.roughness));
} else { // glass: refract through, or reflect
ray = refractOrReflect(ray, hit.normal, hit.ior); // odds from Fresnel
}
}
}
// Blend this frame into the average of every frame so far, so the picture
// gets cleaner the longer you leave the camera still.
screen[y][x] = mix(screen[y][x], color, 1 / (frame + 1));
}