A simple path tracer written in Rust that supports loading .obj files and rendering scenes into .exr image files.
- Path tracing with Next Event Estimation (NEE): Stratified sampling splitting direct and indirect illumination for faster convergence.
- OBJ file loading: Import triangulated
.objscenes with paired.mtlmaterial files. - Bounding Volume Hierarchy (BVH): Acceleration structure built using the Surface Area Heuristic (SAH) for efficient ray-scene intersection.
- Cosine-weighted importance sampling: Hemisphere sampling weighted by cosine for indirect illumination.
- Adaptive path termination (Russian Roulette): Configurable fixed or probabilistic path length control.
- Tile-based parallel rendering: Multi-threaded rendering using Rayon with configurable tile size and channel bound.
- EXR output: High dynamic range output in OpenEXR format.
- TOML configuration: All camera and rendering parameters are defined in an external config file.
- Configurable logging: Adjustable log verbosity via CLI.
- Rust toolchain (stable, edition 2021+)
- A triangulated
.objfile with an accompanying.mtlfile
The .mtl file must contain at least one material whose name includes light or Light. This material will be treated as an emissive light source. The ambient color (Ka) of that material defines the light emission, and the diffuse color (Kd) defines its reflectance.
All other materials are treated as diffuse (Lambertian) surfaces.
git clone <repository-url>
cd rusty-ray-tracer
cargo build --releasecargo run --release -- [OPTIONS]| Option | Short | Description | Default |
|---|---|---|---|
--object |
-o |
Path to input .obj file (only triangulated meshes supported; paired .mtl must contain a material named light or Light) |
./cornell-box.obj |
--no-graphical-coordinates |
-g |
Assume mathematical axis orientation for coordinates in .obj file (vertical z-axis) | false |
--config |
-c |
Path to TOML rendering configuration file | ./rendering.toml |
--format |
-f |
Output image format (exr or png) |
exr |
--output-name |
-n |
Output file name (without extension) | rendering |
--log |
-l |
Log level (TRACE, DEBUG, INFO, WARN, ERROR, OFF) |
OFF |
cargo run --release -- -o ./scenes/cornell-box.obj -c ./scene.toml -l INFOThis renders the scene and outputs rendering.exr in the working directory.
All camera and rendering parameters are defined in a TOML configuration file (mathematical axis orientation).
[camera]
position = [0.0, 10.0, 3.7]
view_direction = [0.0, -10.0, -1.0]
focal_distance = 2.0
window_size = [2.0, 1.125]
from_graphical_coordinates = true
[render]
width = 2560
height = 1440
rays_per_pixel = 100
tile_size = 110
channel_bound = 12
backside_intersections = "ignore"
[render.path_length]
mode = "adaptive"
min_bounces = 1
termination_probability = 0.2| Parameter | Description |
|---|---|
position |
Camera position in 3D space [x, y, z] |
view_direction |
Direction the camera looks towards [x, y, z] |
focal_distance |
Distance from camera point to the view window plane |
window_size |
Width and height of the view window [w, h] |
| Parameter | Description |
|---|---|
width |
Horizontal resolution in pixels |
height |
Vertical resolution in pixels |
rays_per_pixel |
Number of samples per pixel |
tile_size |
Tile edge length for parallel work distribution |
channel_bound |
Bounded channel capacity (number of CPU cores is a good choice) |
backside_intersections |
"ignore" or "end_ray" |
The [render.path_length] section controls ray termination. Two modes are available:
Fixed - rays are terminated after a set number of bounces:
[render.path_length]
mode = "fixed"
max_bounces = 5Adaptive (Russian Roulette) - rays are terminated probabilistically after a minimum number of bounces:
[render.path_length]
mode = "adaptive"
min_bounces = 1
termination_probability = 0.2.
├── src/
│ ├── main.rs # Entry point, CLI, scene setup, rendering orchestration
│ └── rtcore/
│ ├── mod.rs # Core: Ray, Scene, ColorStack, ViewPoint, path tracing logic
│ ├── objects.rs # Triangle, Material, BVH (BBVTNode), Intersection
│ └── utilities.rs # Random number generation helpers
├── scene.toml # Scene and render configuration
├── Cargo.toml
└── README.md
| Crate | Purpose |
|---|---|
clap |
Command-line argument parsing |
serde |
Serialization/deserialization for config |
toml |
TOML config file parsing |
nalgebra |
Linear algebra (vectors, math) |
tobj |
OBJ/MTL file loading |
exr |
OpenEXR image writing |
image |
PNG image writing (optional) |
rayon |
Data-parallel tile rendering |
crossbeam |
Bounded channels for producer/consumer pattern |
rand |
Random number generation |
tracing / tracing-subscriber |
Structured logging |
indicatif |
Progress bar |
- Configuration loading: The TOML config file is parsed to obtain camera and render parameters.
- Scene loading: The
.objfile is parsed into triangles; materials are classified as diffuse or emissive. - BVH construction: All triangles are organized into an axis-aligned bounding box tree using SAH splits.
- Rendering: The image is divided into tiles. Each tile is processed in parallel — for every pixel,
rays_per_pixelrays are cast through random sub-pixel positions. - Path tracing: Each primary ray is intersected with the BVH. At each hit point:
- Direct lighting is estimated by sampling a random point on a random light source and testing visibility (shadow ray).
- Indirect lighting is estimated by spawning a cosine-weighted random ray into the hemisphere and recursing.
- Paths are terminated by Russian Roulette or a fixed bounce limit.
- Output: The accumulated radiance per pixel is written to a
rendering.exrfile.
- Only triangulated meshes are supported.
- Specular and glossy materials are defined but not yet fully implemented (fallback to diffuse BRDF).
- Normal interpolation (
face_normalfor non-flat shading) has a known issue and falls back to flat normals when vertex normals differ.