Skip to content

SinclaM/ray-tracer-challenge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ray Tracer Challenge

This project is a simple Zig implementation of the ray tracer described in The Ray Tracer Challenge.

You can find an interactive demo of this ray tracer online at sinclam.github.io/ray-tracer-challenge.

Status

  • Chapter 1 - Tuples, Points, and Vectors
  • Chapter 2 - Drawing on a Canvas
  • Chapter 3 - Matrices
  • Chapter 4 - Matrix Transformations
  • Chapter 5 - Ray-Sphere Intersections
  • Chapter 6 - Light and Shading
  • Chapter 7 - Making a Scene
  • Chapter 8 - Shadows
  • Chapter 9 - Planes
  • Chapter 10 - Patterns
  • Chapter 11 - Reflection and Refraction
  • Chapter 12 - Cubes
  • Chapter 13 - Cylinders
  • Chapter 14 - Groups
  • Chapter 15 - Triangles
  • Chapter 16 - Constructive Solid Geometry (CSG)
  • Chapter 17 - Next Steps
  • A1 - Rendering the Cover Image
  • Bonus Chapter - Rendering soft shadows
  • Bonus Chapter - Bounding boxes and hierarchies
  • Bonus Chapter - Texture mapping

Examples

Fresnel

Patterns

Reflection and Refraction

Cubes

Groups

Teapot

Teapot model from https://groups.csail.mit.edu/graphics/classes/6.837/F03/models/teapot.obj.

Dragons

Dragon model from http://raytracerchallenge.com/bonus/assets/dragon.zip.

Nefertiti

Nefertiti bust model from https://github.com/alecjacobson/common-3d-test-models/blob/master/data/nefertiti.obj.

Constructive Solid Geometry

Earth

Earth texture from https://planetpixelemporium.com/earth.html.

Skybox

Lancellotti Chapel texture from http://www.humus.name/index.php?page=Textures.

Building from source

To build for native:

zig build -Doptimize=ReleaseFast

To target the web (populating www/ with the all the site's files):

zig build --sysroot [emsdk]/upstream/emscripten -Dtarget=wasm32-emscripten -Dcpu=generic+bulk_memory+atomics+simd128 -Doptimize=ReleaseFast
    && sed -i'' -e 's/_emscripten_return_address,/() => {},/g' www/ray-tracer-challenge.js

Performance profiling

Although the ray tracer is not (yet) heavily optimized (e.g. it does not yet leverage Zig's SIMD builtins), it is still very fast—faster in fact on a single thread than almost every other Ray Tracer Challenge implementation on multiple threads I've compared with. And there is still significant room for optimization.

The optimizations I do make are largely informed by profilers. When built for native, the binary can be profiled with valgrind --tool=callgrind and the results inspected with qcachegrind, which works well enough. Unfortunately, Valgrind's troubled state on macOS, combined with Zig's incomplete Valgrind support, means profiling is not always simple. For example, I've seen Valgrind erroneously run into SIGILL and the like. Using std.heap.raw_c_allocator on native seems to fix most of these issues.

The ray tracer currently runs about 2x slower on WebAssembly than on native, which is reasonable. I use Firefox's "performance" tab in the developer tools for profiling on the web.

I also use hyperfine for benchmarking.

Benchmarks

Below are some benchmarks for scenes that can be found on the website. These benchmarks are not rigorously controlled and averaged, but rather a general overview of speeds for various scenes. They may also change depending significantly between Zig compiler versions. For example, I noticed a perfromance regression of up to 25% going from 0.11.0 to the WIP 0.12.0 (perhaps related to this similar issue). The best way to get a feel for the performance is to try things out yourself!

All benchmarks were done on a 2019 MacBook Pro (2.6Ghz, 6-Core Intel i7; 16GB RAM; macOS 12.6.7). WASM specific benchmarks were done on Firefox 117 using 6 web workers (the maximum number of web workers Firefox will run in parallel, even on my 12 logical CPU system 🤷‍♂️). Native runs used 12 threads.

The 'WASM Preheated' category refers to renders done with the scene pre-built (scene description already parsed, objects and bounding boxes already made, textures already loaded, etc.), which is supported on the site through the arrow key camera movememnt. This preheating is irrelevant for simple scenes, but gives massive speedups for scenes that load textures or construct BVHs.

Also note that renders on the website are periodically polled for completion. Renders may actually complete up to 100ms before the reported time, which affects the benchmarks for very short renders.

Scene Resolution Native WASM WASM Preheated
Cover Scene 1280x1280 1.413 s 2.408 s 2.299 s
Cubes 600x300 0.225 s 0.418 s 0.407 s
Cylinders 800x400 0.111 s 0.221 s 0.109 s
Reflection and Refraction 400x200 0.113 s 0.213 s 0.205 s
Fresnel 600x600 0.283 s 0.429 s 0.411 s
Groups 600x200 0.091 s 0.217 s 0.202 s
Teapot 250x150 0.175 s 0.413 s 0.210 s
Dragons 500x200 6.957 s 12.663 s 2.492 s
Nefertiti 300x500 4.827 s 6.358 s 3.036 s
Constructive Solid Geometry 1280x720 0.267s 1.920 s 1.792 s
Earth 800x400 0.095 s 0.212 s 0.103 s
Skybox 800x400 1.466 s 1.531 s 0.102 s
Raytracer REPL Default 1280x720 0.210 s 0.220 s 0.209 s

Other implementations

There are many great implementations of the Ray Tracer Challenge. At many points throughout the project, I referred to others to verify my implementation, draw inspiration, or compare performance. I recommend you check out the following ray tracers:

Known limitations

The website for this project uses the SharedArrayBuffer type, which requires certain HTTP headers to be set, something that GitHub Pages does not support. To get around this, I use coi-serviceworker, which has the disadvantage of not working in Private/Incognito sessions.

Some devices (mobile phones in particular) may not have enough memory to render some of the scenes on the website, especially the "Skybox" scene.