A multi-threaded ray-traced/path-traced renderer built from scratch with the C++ standard library and nlohmann/json.
This project was originally created for the Computer Graphics: Rendering coursework at the University of Edinburgh in 2023, with small bug fixes added after submission.
The renderer reads scene descriptions from JSON files, renders either ray-traced or path-traced images, and writes PPM output. It has been tested on Windows 11, Ubuntu, and macOS.
Animation.mp4
Build the renderer from the Code/ directory:
cd Code
makeRun an example scene:
cd ../bin
./RunRaytracer -p simple_phongSome scenes, especially path-traced scenes, take significantly longer to render.
RunRaytracer [-r render_dir] [-s scene_dir] [-f frame_start] [-m frame_max] [-t step] [-p|-d|-q] [scene_name]
| Option | Meaning |
|---|---|
-r render_dir |
Directory to save rendered output. Defaults to ../TestSuite/. |
-s scene_dir |
Directory to load scenes from. Defaults to ../Resources/. |
-f frame_start |
Starting frame index. Defaults to 0. |
-m frame_max |
Maximum number of frames to render. Defaults to unlimited. |
-t step |
Frame step. Defaults to 1. |
-p |
Preview: override render settings with preview options. |
-d |
Decent: override render settings with ray-tracer options. |
-q |
Quality: override render settings with path-tracer options. |
scene_name |
Scene name without file extension. Defaults to scene_anim. |
Example:
./RunRaytracer -p simple_phong- Recursive ray tracing with Phong/Blinn-Phong-style shading
- Ambient, point, and area lights
- Shadows
- Reflection
- Refraction
- Binary render mode
- Monte Carlo path tracing
- Multi-bounce light transport
- Antialiasing / camera sampling
- Aperture sampling
- Area light sampling
- Blinn-Phong materials
- Physically based material workflow using roughness, metallic, and Fresnel terms
- Diffuse and specular colour controls
- Reflective and refractive materials
- Texture-mapped materials
- BVH acceleration structure
- Triangles
- Spheres and UV spheres
- Cylinders and UV cylinders
- ASCII PLY meshes
- Cube maps
- Spherical environment maps
- Multithreaded renderer
- Linear HDR framebuffer
- Exposure control
- Tone mapping
- Gamma correction
- PPM image writing
- JSON scene format
- Animation via external JSON files
- Blender animation exporter script
- PPM textures
- ASCII PLY meshes
| Path | Purpose |
|---|---|
Code/ |
Renderer source code and Makefile |
Resources/ |
Scene JSON files, animation JSON files, textures, and meshes |
TestSuite/ |
Example renders and default render output directory |
TestOutput/ |
Test output placeholder directory |
TestScripts/ |
Helper scripts for building and rendering tests |
FeatureList.txt |
Original coursework feature checklist |
Usage.txt |
Command-line usage reference |
The executable is expected to be run from bin/ after building.
- Scenes: JSON. Scene files can reference separate animation JSON files.
- Textures: PPM, P6 binary format.
- Models: PLY, ASCII 1.0 plain text. Texturing supports Blender's S-H coordinates and a custom format used by this project.
Scene JSON
|
v
Scene parser
|
v
Camera / lights / materials / geometry
|
v
BVH construction
|
v
Ray tracer / path tracer
|
v
Linear HDR framebuffer
|
v
Exposure -> tone mapping -> gamma correction
|
v
PPM output
Scene JSON Reference
{
"camera": { },
"scene": {
"backgroundcolor": [],
"shapes": [],
"lightsources": []
},
"rendermode": "preview|phong|pathtracer|binary",
"nbounces": 5,
"frames": 1,
"shadow samples": 1,
"camera samples": 1,
"animation path": "animation.json"
}Required fields:
typewidthheightfovpositionlookAtupVector
Optional fields include:
exposuregammadiameterLw
Supported light types:
ambientpointlightarealight
Point lights and area lights can define attenuation coefficients:
kcklkq
Area lights accept either a 2D size:
"size": [width, depth]or a 3D size:
"size": [width, height, depth]Implemented shape types:
trianglesphereuvspherecylinderuvcylinderplycubemapsphericalmap
Common required fields:
| Shape | Required fields |
|---|---|
triangle |
v0, v1, v2 |
sphere |
center, radius |
cylinder |
center, radius, height, axis |
ply |
path, offset, scale |
Optional fields used by some shapes include:
uv0uv1uv2aliaseulertexturescale
Observed material fields:
kakdksdiffusecolorspecularcolorspecularexponentreflectivityisreflectiveisrefractiverefractiveindexroughnessmetallictransmittancetexturepath
Implementation details:
roughness >= 0enables the BRDF path.- Roughness is squared internally for GGX-style parameterization.
- Metallic materials use a Disney-style metallic workflow.
- Fresnel reflectance uses Schlick approximation.
A scene can reference an external animation file:
"animation path": "animscene.json"Objects may define an alias; animation data can target aliases instead of autogenerated object IDs.
Implementation Notes
The renderer constructs a BVH automatically. Every primitive provides a bounding box and participates in acceleration.
Supported texture mappings include:
- UV sphere mapping
- UV cylinder mapping
- UV triangle mapping
- Cube maps
- Spherical environment maps
Textures are loaded as floating-point RGB values. Current texture support is for PPM files.
Area lights precompute a sample cache and reuse it during rendering to reduce runtime random-number generation.
Rendering is performed in linear colour space. Before output, the renderer applies:
- Exposure
- Tone mapping
- Gamma correction
Example Blender script for exporting animation data
The script below exports key-framed object position/rotation data for use with the animation JSON format. It cannot guarantee correct output for every Blender scene.
import bpy
from numpy import degrees # can be replaced with math module
# Note: this only extract key-framed animations
# 1. bake all simulations
# 2. select the objects to bake
# 3. in Object menu at top-left, select [Object > Rigid Body > Bake to Keyframes]
# 4. now, run this script
# This brings up console for debug:
# bpy.ops.wm.console_toggle()
# name of objects to exclude
exclude = ["0", "836"]
# path to save the json
file = open("path\\to\\your\\animscene.json", 'w')
# load scene, objects and frame numbers
scene = bpy.context.scene
frame_num = scene.frame_end - scene.frame_start + 1
objects = [o for o in bpy.context.scene.objects if o.name not in exclude]
file.write("{\n\t\"frames\" : %d,\n\t\"animated objects\" : %d,\n\t\"animations\" : [\n"%(frame_num, len(objects)))
template = "\t\t\t{\n\t\t\t\"pos\":[%.9f, %.9f, %.9f],\n\t\t\t\"rot\":[%.9f, %.9f, %.9f]\n\t\t\t},\n"
content = ""
for obj in objects:
object_animation = "\t{\n\t\t\"id\" : \"%s\",\n\t\t\"transform\" : [\n"%obj.name
animated_frames = ""
for frame in range(scene.frame_start, scene.frame_end+1):
scene.frame_set(frame)
loc = obj.location
rot = degrees(obj.rotation_euler)
# print(frame, loc)
animated_frames += template%(loc.x, loc.y, loc.z, rot[0], rot[1], rot[2])
object_animation += (animated_frames[:-2] + "\n\t\t]\n\t},\n")
content += object_animation
file.write(content[:-2] + "\n\t]\n}\n")
file.close()