This is an implementation of a spectral path tracer that makes use of Vulkan ray tracing extensions via caldera.
- A uni-directional spectral path tracer
- Currently samples 3 wavelengths per ray
- Implemented as a single Vulkan ray tracing pipeline
- Support for instanced geometry (via instanced bottom-level acceleration structures)
- Sampling using either pmj02 or sobol sequences (see links below)
- BSDF importance sampling
- Diffuse and mirror "ideal" surfaces
- Smooth or rough fresnel dieletrics and conductors
- Diffuse with dielectric coating
- Importance sampling of lights
- Quad/disc/sphere or triangle mesh shaped emitters
- Dome or solid angle distant lights
- Multiple importance sampling between BSDFs and lights
- Simple fixed material model
- Reflectance from per-instance constant and/or texture
- All other parameters are either per-instance or global constants (for now)
- Interactive renderer with moveable camera and debug UI
The implementation makes use of the following Vulkan extensions:
VK_KHR_ray_tracing_pipeline
andVK_KHR_acceleration_structure
for tracing of rays. For now rays are traced using a single pipeline with all shading/sampling/traversal for a full path with multiple bounces.- The pipeline contains several different intersection and hit shaders to handle ray tracing against analytic shapes such as spheres and discs in addition to triangle meshes.
VK_KHR_buffer_device_address
to reference a GPU buffer using auint64_t
device address. This extension is required byVK_KHR_ray_tracing_pipeline
for the shader binding table and vastly simplifies the setup of buffers, avoiding a lot of descriptor management code. The device address can be cast to a buffer reference of any type, so this can be useful to implement more generic GPU data structures.VK_EXT_descriptor_indexing
for "bindless" texturing. This extension is also required byVK_KHR_ray_tracing_pipeline
and allows us to bundle all textures into a single descriptor set with an array per texture type, and refer to them by index when they need to be sampled. Since different GPU threads can require different texture indices during ray tracing, we additionally make use of "non-uniform indexing" in the shader code.
Here are some of the references used when creating the renderer:
- Hero Wavelength Spectral Sampling to associate more than one wavelength with each ray (where possible) for reduced colour noise
- Continuous Multiple Importance Sampling to sample all wavelengths of the ray proportional to the illuminant, to reduce colour noise even more
- CIE 1931 Colour Matching Functions to convert spectral samples to the XYZ colour space
- CIE Standard Illuminants has spectral data for lights
- An RGB to Spectrum Conversion for Reflectances to use Rec709/sRGB colours as reflectance in a spectral renderer
- RefractiveIndex.info for measured material properties for dielectrics and conductors
- Chromatic Adaptation description by Bruce Lindbloom to adjust the white point of XYZ samples
- Filmic Tonemap Operators for a Rec709/sRGB tonemap by Hejl and Burgess-Dawson
- BakingLab for a fit of the ACES tonemap
- Progressive Multi-Jittered Sample Sequences for random sequences, implemented in the pmj crate
- Practical Hash-based Owen Scrambling to shuffle/scramble sequences that use the Sobol direction numbers by Joe/Kuo
- Survey of Efficient Representations for Independent Unit Vectors covers many options for efficient encoding of unit vectors, the renderer uses octohedral encoding in 32 bits
- Sampling the GGX Distribution of Visible Normals to sample rough surfaces (all rough surfaces in the renderer have GGX-distributed microfacets)
- Memo On Fresnel Equations for the equations of fresnel dielectrics and conductors
- Misunderstanding Layering approximation for energy conservation in layered diffuse and specular materials
- The Physically Based Rendering From Theory To Implementation (PBRT) book, I have the second edition but the 3rd edition is available for free online!
The following crates have been super useful in making the app:
- ultraviolet: maths library with nice API, used for vectors and transforms
- bytemuck: safely alias data as bytes, used for copying to GPU buffers
- imgui/imgui-winit-support: rust API for the fantastic Dear ImGui, for debug UI
- serde/serde_json: amazing lib to generate serialisation for rust data structures, used to load Tungsten format JSON scenes
- stb: rust API for the stb libraries, used for image IO and BC compression
- winit: cross platform windowing and events
The code can be run as follows (will show a Cornell box by default):
make && cargo run --release --example path_tracer --
By default this will create a window with a progressive renderer and debug UI for many parameters. Additionally you can drag with the mouse and use W/A/S/D on the keyboard to move the camera. For command-line help run as:
make && cargo run --release --example path_tracer -- help
Several of the images below are loaded from Tungsten format scenes. These can be loaded into the renderer by running using the commandline:
make && cargo run --release --example path_tracer -- tungsten <scene_json_file_name>
As is tradition, here are some boxes under a couple of different lighting conditions (original Cornell box data, and a variant with a mirror material and distant lights).
Here is a variation on the classic Veach multiple importance sampling scene, showing 64 samples per pixel with BSDF sampling only, 64 with light sampling only, then 32 samples of each weighted using multiple importance sampling. These images demonstrate how multiple importance sampling effectively combines BSDF and light sampling to reduce variance over the whole image.
BSDF Sampling Only | Light Sampling Only | Combine with MIS |
---|---|---|
Here is a test scene for some conductors using spectral reflectance data from refractiveindex.info for copper, iron and gold under a uniform illuminant (the colours are entirely from the reflectance data, there is no additional tinting).
If we change the illuminant to F10 (which has a very spiky distribution), we can check the effect that wavelength importance sampling has on colour noise. The following images use gold lit with F10, all with 8 paths per pixel and 3 wavelengths per path. The first image samples wavelengths uniformly, the second samples only the hero wavelength for that path proportional to F10, the third image samples all wavelengths for that path proportional to F10 (reproducing part of the result of Continuous Multiple Importance Sampling):
Uniform Sampling | Sample Hero Wavelength Only | Sample All Wavelengths |
---|---|---|
The next set of images are rendered from these excellent rendering resources by Benedikt Bitterli and blendswap.com artists nacimus, Wig42, cekuhnen, Jay-Artist, thecali, NewSee2l035 and aXel.
There is a barely started exporter for Blender, but support for materials beyond a simple texture map is a bit out of scope for now. This image uses the "Classroom" Blender demo file, with highly approximated materials and only sunlight:
- Denoiser?
- Adaptive sampling
- HDR display output
- Rough dielectrics
- Smooth conductors
- Generic clearcoat?
- IOR parameters for conductors
- IOR parameters for dielectrics
- Interior media
- Sobol sampler
- Thin lens camera
- Volumetrics
- Image-based dome light
- More flexible materials (graphs?)
- Disc primitive
- Triangle mesh emitter?
- Microfacet multi-scattering?
- Path re-use?
- Spectral rendering?
- Spiky illuminants (F10)