Back to projects

Featured work · 04

RayTrace

A C++20 CPU path tracer with a progressive viewer and a learned reconstruction pipeline, from reproducible scene generation to trained models running in the renderer.

Rotate the prism to change the light path; vary its surface finish. All portfolio information is available in the page text.

Preparing the exhibit
Drag to explore

-13° prism / 8% roughness

Project 04 / PreviewRayTrace nested circles, converging gold rays, and wordmark

The work

Path tracing and learned image reconstruction

Overview

RayTrace is a C++20 CPU path tracer and a machine learning project built around it. I built the rendering engine, progressive desktop viewer, and training pipeline so I could work on both how an image is produced and how a model reconstructs it from fewer samples.

It started with Ray Tracing in One Weekend. The current implementation adds multithreaded rendering, imported meshes, more complete light transport, reproducible datasets, and native inference for trained models.

Light, materials, and geometry

The renderer handles textured diffuse surfaces, rough GGX metals, mirrors, and refractive glass. Multiple importance sampling combines area-light and material sampling, while a thin-lens camera supports depth of field. OBJ meshes bring in indexed geometry, smooth normals, UVs, and supported MTL materials.

A separate photon pass captures light focused through glass or ideal reflections onto diffuse surfaces. Those paths are excluded from the matching path-tracing contribution to avoid counting the same light twice. This is a biased photon-density estimate; a cheaper straight-line glass-shadow approximation is available separately.

Rendering that can be reproduced

Persistent CPU workers render 16 × 16 pixel tiles, with a bounding-volume hierarchy accelerating intersection tests. Random streams are tied to the render seed, pixel, and sample. Changing worker count or switching between BVH and linear traversal produces identical image bytes within a build.

The SDL2 viewer displays completed sample passes and supports pause, restart, cancellation, exposure changes, and raw/reference/error comparisons. The same engine runs headlessly and exports PNG or linear HDR/PFM, with settings and timing saved beside each render. Denoising and neural reconstruction leave the original accumulated samples intact.

Training on the renderer’s own data

The Python pipeline generates noisy inputs, independent high-sample references, and aligned geometry guides. Scene layouts define the train, validation, and test splits: alternate views, noise seeds, sample budgets, and crops of a layout stay together.

The bundled model is a 476,419-parameter U-Net trained from scratch on the training split of a 2,304-example dataset. It uses 17 channels of radiance and guide information to reconstruct an HDR image. PyTorch handles training, and exported ONNX models run through ONNX Runtime in the C++ application. The pilot targets static diffuse pinhole scenes; unsupported pixels or rendering modes fall back to the raw result.

What the pilot achieved

Across 360 test images from five held-out layouts at 1–32 samples per pixel, the custom model averaged 35.96 dB PSNR. Raw rendering averaged 25.84 dB, and the existing a-trous filter averaged 33.97 dB. These are multiple views and noise realizations of five layouts, rather than 360 independent scenes.

Lower average error did not mean better detail everywhere. The model’s SSIM was 0.8929, below a-trous at 0.9440. Open Image Denoise performed better on both metrics and linear HDR error. A-trous was also faster at the measured joint quality target. The pilot demonstrates learned reconstruction, without establishing a speed advantage over the existing denoiser.

Preserving detail and measuring the tradeoffs

The newer study adds boundary guides, guided-filtering models, paired augmentation, and controls for the output head and loss. I corrected a refinement head that could barely recover radiance from black input, added explicit radiance conditioning, and removed double attenuation in the newer blending path.

A paired study used two training seeds and independent 4,096- and 8,192-sample references. Its center-guided model reduced display edge error by 9.15% relative to a-trous, but whole-image HDR error was slightly worse. Measuring model-supported regions separately from unchanged fallback pixels made that tradeoff visible. None of these candidates replaced the bundled pilot.

The larger dataset now contains 57,344 validated examples across 1,024 layouts in eight procedural scene families. Full-data training has been prepared; that dataset is not evidence of a newly trained or qualified model. Temporal and variants have working implementations and development evaluations, with broader quality and speed qualification still open.

Training infrastructure and validation

Training can resume from complete checkpoints containing weights, optimizer, scheduler, and random state, including a copied best checkpoint or an earlier completed epoch. Model selection records HDR, edge, preservation, and temporal criteria alongside loss. Export tests compare Python predictions with native ONNX inference.

C++ and CLI tests cover intersections, light visibility, material sampling, deterministic concurrency, BVH equivalence, denoising, caustics, exports, and viewer controls. The ML suite checks data integrity, split isolation, augmentation, checkpoint recovery, fallback behavior, and deployment parity. Benchmarks verify matching image hashes before comparing renderer settings.