Back to projects

Personal project · 05

RayTrace

A C++20 CPU path tracer with a progressive viewer, native model inference, and a training pipeline for learned denoising and 2× image reconstruction.

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

Drag to explore

-13° prism / 8% roughness

Project 05 / Preview

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. PyTorch handles training, and exported ONNX models run through ONNX Runtime in the C++ application.

Joint denoising and 2× reconstruction

The latest study trains a 508,172-parameter model to remove noise and reconstruct an image at twice the input width and height. It reuses 59,992 examples: 57,344 synthetic area-reduced pairs and 2,648 native 2× pairs. The split contains 44,376 training examples, 7,808 validation examples, and 7,808 sealed-test examples. Synthetic and native results are measured separately because reducing an existing image does not reproduce every property of a lower-resolution render.

Training completed 69 epochs, including a lower-learning-rate extension. Epoch 59 was selected as best; training stopped after ten epochs without an improvement in the selection score. On native 2× validation, that checkpoint averaged 29.82 dB PSNR and 0.9173 SSIM, compared with 25.98 dB and 0.8768 for a-trous filtering followed by bilinear enlargement.

Those results improve on the comparison filter, but the model has not passed full quality qualification. Low-sample reconstruction and some resolution-specific checks still fall short. This is a single-seed development study; the sealed test set remains untouched, and no end-to-end rendering speedup has been established. The new checkpoint has not replaced the older bundled model.

What the images reveal

The gallery below uses newly rendered 480-pixel-wide, 64-sample inputs and independent 960-pixel-wide, 2,048-sample references. These replace the earlier enlarged validation thumbnails and are separate from the historical training metrics. The individual comparisons show the noisy image, conventional denoising, learned reconstruction, and both orders of combining the filter and model. In all three documented scenes, the model alone has higher PSNR and SSIM than either combined pipeline. Additional filtering can erase detail that reconstruction recovered; filtering first also changes the input distribution the model was trained on.

The remaining errors matter. Glass and reflections are still inaccurate in the courtyard, while thin structures and edges remain soft in the corridor. Each scene also includes additional processing of the high-sample reference itself. Those four full-render variants are diagnostics, excluded from reconstruction scores: they start with substantially more information and do not demonstrate a sampling or speed advantage.

The earlier renderer integration

The bundled model is a 476,419-parameter U-Net trained from scratch on the training split of a 2,304-example dataset. Its 17 radiance and guide channels reconstruct an HDR image for static diffuse pinhole scenes; unsupported pixels or rendering modes fall back to the raw result.

Across 360 test images from five held-out layouts at 1–32 samples per pixel, this pilot averaged 35.96 dB PSNR, compared with 25.84 dB for raw rendering and 33.97 dB for a-trous. Its SSIM was lower than a-trous, and Open Image Denoise performed better on both metrics and linear HDR error. These earlier test results use a different model and dataset from the newer 2× validation study and should not be compared directly.

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.