← Back

ofxRayTracer

github.com/danoli3/ofxRayTracer · @danoli3
OF 0.12 Graphics

openFrameworks port of Kevin Suffern’s Ray Tracing from the Ground Up core (originally wxRayTracer / RTTGU), with a progressive multi-threaded CPU renderer that composites into a transparent

2 1 Last commit 1mo ago Created 11y ago

README

ofxRayTracer



openFrameworks port of Kevin Suffern’s Ray Tracing from the Ground Up core (originally wxRayTracer / RTTGU), with a progressive multi-threaded CPU renderer that composites into a transparent ofFbo.

Dependencies



# from OF root
ln -sfn /path/to/ofxRayTracer addons/ofxRayTracer

ofxImGui must also be present in addons/

cd addons/ofxRayTracer/example-imgui make -j && make RunRelease


Status



AreaState
Suffern core (World, Sphere, Plane, Matte, RayCast, Pinhole, samplers)Working
Progressive multi-thread tile renderWorking
Transparent FBO overlay (misses = alpha 0)Working
OF 3D preview underlayLive World geometry
EasyCam ↔ Pinhole sync (shared viewport)Working
ofxImGui panel (palette themes, button UI)Working
Window / 2× / 4× / standard resolutionsWorking
PNG / TIFF exportWorking
GPU / compute pathPlanned
Specular / Phong / reflective / shadowsPlanned (Whitted stub present)
BVH / meshesPlanned

Layout



ofxRayTracer/
  addon_config.mk
  src/                         # OF facade
  libs/raytracer/
    Builds/                    # Textbook World::build registry (Suffern RTTGU)
      RTBuild.h/.cpp           # register / list / run named builds
      reference/               # complete chapter demos
      student/                 # YOUR builds — see STUDENT_BUILDS.md
    Cameras/ GeometricObjects/ …
  example/                     # Minimal progressive render
  example-imgui/               # ImGui + ofxImAnim, export, resolution
  example-learn/               # Learning lab: builds + skeleton tutorials
  scripts/update_xcode_projects.sh


Textbook builds (RTTGU)



Suffern configures each scene inside World::build. Classic samples (and hadryansalles/ray-tracing-from-the-ground-up) #include one BuildXXX body into World.cpp.

Here each chapter demo is a named builder so OF can switch at runtime:

idLesson
ch03.single_sphereBare-bones SingleSphere + World::sphere
ch03.multiple_objectsFlat set_color + MultipleObjects tracer
ch14.matte_spheresMatte materials, lights, pinhole (default)
rt.rebuild(w, h, samples, "ch03.single_sphere");
rt.start();

for (const RTBuildInfo& b : ofxRayTracer::listBuilds()) {
    // b.id, b.title, b.chapter, b.description, b.skeleton (raw TODOs)
}


Add your own under libs/raytracer/Builds/student/ with RT_REGISTER_BUILD — see student/STUDENT_BUILDS.md. Each reference build also stores a skeleton string for the learning UI (keep it raw; students fill TODOs).

Examples



example-learn (textbook lab)



cd example-learn && make -j && make RunRelease

or open example-learn/example-learn.xcodeproj



example-imgui (production-style UI)



ControlAction
Render / RestartStart progressive render
Sync Camera + RenderBake EasyCam → Pinhole, then render
Window / 2× / 4×RT resolution relative to window (auto-updates on resize)
720p / 1080p / 1440p / 4K …Fixed standard sizes
Save PNG / Save TIFFExport when render is complete → bin/data/exports/
Sample buttons 1 / 4 / 9 / 16AA samples per pixel
Preview / Raytrace toggle buttonsLayer visibility
KeyAction
SPACESync camera + restart
CCancel
EExport PNG
1–4Sample presets
raytracer.setup(ofGetWidth(), ofGetHeight(), 1);
raytracer.syncFromEasyCam(cam);
raytracer.start();
// …
if (raytracer.isComplete()) {
    raytracer.savePng("exports/frame.png");       // keeps alpha
    raytracer.saveTiff("exports/frame.tif", true, ofColor::black); // optional solid bg
}


example (minimal)



Keyboard-only HUD; same core renderer.

---

Architecture



ofApp
  ├── ofEasyCam + drawPreview3D()     // interactive GL underlay
  └── ofxRayTracer
        ├── World (Suffern scene graph)
        ├── TileRenderPool (N worker threads)
        └── ofPixels → ofTexture / ofFbo (RGBA, transparent misses)


CPU render path

1. Split the view plane into tiles (default 32×32). 2. Worker threads pull tiles and call World::render_pixel → Pinhole::render_pixel → Matte / RayCast. 3. Hits write opaque RGB; misses write alpha 0 for overlay compositing. 4. Main thread merges completed tiles into ofPixels, uploads once per frame to the GPU texture/FBO. 5. Display letterboxes the buffer into the window so high-res renders (2× / 4K) stay aspect-correct and aligned with the GL preview.

---

Optimisations already in place



These are the main performance / correctness improvements over the original single-pixel OF glue:

OptimisationWhat it does
Tile-based work queueImage is split into tiles; workers grab whole tiles instead of one pixel at a time (far less channel / sync overhead).
Multi-threaded CPU poolTileRenderPool uses std::thread count defaulting to hardware concurrency.
Single GPU upload / frameWorkers fill CPU pixel buffers; the main thread batches tile merges and does one loadData per update().
Transparent miss compositingBackground rays store alpha 0 so GL preview (or a clear colour) shows through without a second mask pass.
Pinhole path restoredPer-pixel rays use Suffern pinhole UVW + view distance, not the old orthographic hack.
EasyCam ↔ Pinhole FOV matchVertical FOV maps to view-plane distance d = (s·vres/2) / tan(fov/2) so GL and RT share the same frustum.
Shared letterboxed viewportPreview cam.begin(rect) and FBO draw use the same rectangle (aspect of RT buffer), preventing the “FBO offset when orbiting” bug.
Resize debounceWindow resize rebuilds resolution after ~250 ms so continuous drag doesn’t thrash cancel/rebuild/start.
Header renames for OF 0.12RTSampler.h / RTConstants.h avoid silent includes of MetalANGLE’s Sampler.h / Constants.h.
C++17 std::shuffleReplaces removed std::random_shuffle in Suffern samplers.

Complexity note



Intersection is still O(objects) linear scan per ray (Suffern textbook style). That is fine for the demo sphere field; it will not scale to dense meshes without acceleration.

---

What can still be improved



High impact (CPU)



1. BVH / SAH (or grid) — Biggest win once triangle meshes or hundreds of primitives land. Shadow rays and reflections multiply the benefit. 2. Per-worker RNG / sampler state — Multi-sample AA and Monte Carlo need thread-local RandomNumber / count-jump indices so workers don’t race. 3. SIMD ray–sphere packets — Structure-of-arrays spheres + 4/8-wide rays (SSE/NEON) for primary visibility. 4. Russian roulette + max depth — Path/Whitted efficiency; ViewPlane::max_depth exists but materials for reflection/refraction are incomplete. 5. Sub-image GPU upload — glTexSubImage2D only dirty tiles instead of re-uploading the full frame every time a tile finishes. 6. Progressive refinement — First pass 1 spp interactive, then accumulate higher spp without discarding the buffer. 7. Scene rebuild without full World::build() — Resize currently rebuilds the default scene; keep geometry and only change ViewPlane resolution + camera.

Materials & lighting (Suffern completeness)



8. Phong / glossy specular BRDFs 9. Shadows (shadow_hit on lights) 10. Area lights + emissive materials (AreaLighting tracer is stubbed) 11. Reflective / transparent Whitted materials 12. Ambient occlusion / environment light using hemisphere sampling

GPU / hybrid (later examples)



13. Compute-shader primary rays for spheres + Matte (same feature set as now, much faster). 14. Hybrid: GPU primary visibility + CPU secondary rays / materials. 15. Metal / Vulkan RT for production; keep this core as the educational reference.

Tooling / UX



16. Scene editor — add/move spheres from ImGui; serialise builds. 17. Mesh import (ofMesh / assimp → triangle accel). 18. EXR / linear HDR export for grading pipelines. 19. Live camera scrub — low-res continuous RT while orbiting, full-res on commit.

---

Notes for OF 0.12+ / Xcode / projectGenerator



MetalANGLE ships headers named Sampler.h and Constants.h. This addon renames the Suffern headers to:

RTConstants.h file not found in Xcode



projectGenerator is not “out of date” for this — it is a nested-addon path issue:

1. Examples live inside the addon (apps/ofxRayTracer/example-imgui/), so PG often sets the ofxRayTracer group path to ofxRayTracer/ instead of ... 2. Flat Suffern includes (#include "RTConstants.h") need every libs/raytracer/ folder on the header search path. 3. Project.xcconfig from the template sets HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) only; without $(inherited) + our paths, Xcode never sees Utilities/RTConstants.h.

Fix already in the repo:

# regenerate Xcode project
projectGenerator -o"$OF_ROOT" -p"osx" -a"ofxRayTracer,ofxImGui" \
  "$OF_ROOT/apps/ofxRayTracer/example-imgui"

open the new project (not the old emptyExample.xcodeproj)

open example-imgui/example-imgui.xcodeproj


Prefer example-imgui.xcodeproj / example.xcodeproj. Do not use legacy emptyExample.xcodeproj.

After source / addon changes



Whenever files are added/removed under src/ or libs/, or addons change, regenerate Xcode projects:

./scripts/update_xcode_projects.sh          # both examples
./scripts/update_xcode_projects.sh example-imgui


This runs projectGenerator, then re-applies nested-addon fixes (ofxRayTracer group path .., App.xcconfig header paths, $(inherited)).

make builds are unaffected (makefile auto-discovers addon include dirs).

License



Suffern RTTGU sample code: non-commercial / GPL as per original notices. Addon glue / OF integration: same spirit; see file headers.

Get this addon

HTTPS
SSH