YART is a Yet Another Ray Tracer renderer that can be accelerated by OpenCL or CUDA. Based on lessons from http://scratchapixel.com and http://pbr-book.org
The main goal is to learn ray tracing algorithms and implement C code that can be compiled on the host and simultaneously compiled and executed by an OpenCL or CUDA GPU framework.
The following packages are required on Ubuntu/Debian:
sudo apt install build-essential libsdl2-dev libsdl2-ttf-dev libassimp-dev xxd
build-essential-- GCC and standard build toolslibsdl2-dev-- SDL2 for window creation and input handlinglibsdl2-ttf-dev-- SDL2 TTF extension for on-screen text (FPS counter, etc.)libassimp-dev-- Assimp for loading 3D model files (.obj, .geo, etc.)xxd-- used to embed the OpenCL kernel source as a C header at build time
For OpenCL builds, also install the ICD loader and a platform runtime:
sudo apt install ocl-icd-opencl-dev
A platform-specific runtime is also needed: NVIDIA and AMD GPUs are covered by
their proprietary drivers; for Intel GPUs install intel-opencl-icd; for a
CPU-only OpenCL runtime install pocl-opencl-icd.
For CUDA builds, install the CUDA toolkit from https://developer.nvidia.com/cuda-downloads
(provides nvcc and cuda_runtime.h). The CUDA toolkit is not available via apt.
Three build targets are available, all producing a binary named yart.
CUDA (default):
make
or explicitly:
make yart-cuda
Requires CUDA toolkit (nvcc) and an NVIDIA GPU.
OpenCL:
make yart-opencl
Requires an OpenCL-capable GPU and the OpenCL runtime (libOpenCL).
CPU (no GPU acceleration):
make yart-cpu
Runs entirely on the CPU. No GPU required.
By default YART uses GPU acceleration. To disable it and fall back to the CPU
renderer, pass --no-accel.
When the scene is loaded and the window is opened you can look around with the mouse and walk with the WASD keys.
YART implements two fundamentally different rendering algorithms selectable at runtime.
The default mode implements the algorithm introduced by Turner Whitted in 1980. It is a deterministic, recursive ray tracer:
- At each surface hit, it shoots shadow rays to every light source to determine visibility (hard shadows).
- Reflective surfaces spawn one mirror reflection ray.
- Transparent surfaces spawn one refraction ray (and one reflection ray weighted by the Fresnel term).
- Shading follows the Phong model: diffuse + specular highlights from each light, summed analytically.
Because every decision is deterministic, a single sample per pixel converges
immediately to a clean image. The trade-off is physical accuracy: Whitted
tracing only models specular (mirror-like) light transport. Soft shadows,
color bleeding between surfaces, and realistic diffuse inter-reflections are
not possible. The scene must have at least one explicit light source
(--light).
--path enables a physically based Monte Carlo renderer inspired by
Ray Tracing in One Weekend (RTIOW). Instead of making deterministic
shading decisions, it traces random walks:
- At each hit, one scatter direction is sampled stochastically from the material's BRDF (cosine-weighted hemisphere for Lambertian surfaces, perturbed mirror reflection for metals, stochastic Fresnel for glass).
- The ray bounces until it escapes to the sky or is absorbed. The sky color
(
--backcolor/--backcolor-horizon) is the light source -- no explicit lights are needed or used. - Many paths are averaged per pixel (
--samples-per-pixel) to reduce noise. Russian Roulette terminates low-contribution paths early to keep runtime bounded.
Because every light-transport effect emerges from the same random walk, path
tracing naturally produces soft shadows, color bleeding (global
illumination), caustics, and depth of field (--defocus-angle /
--focus-dist). The cost is that it requires many samples to converge:
expect noise at low sample counts and clean images only after 32-256+ samples
per pixel depending on scene complexity.
| Property | Whitted (default) | Path Tracing (--path) |
|---|---|---|
| Shadows | Hard (shadow rays) | Soft (emerges from sampling) |
| Diffuse inter-reflection | No | Yes |
| Color bleeding | No | Yes |
| Depth of field | No | Yes |
| Light source | Explicit --light | --backcolor sky |
| Samples needed | 1 | 32-256+ |
| Convergence | Instant | Noisy then clean |
The classic Ray Tracing in One Weekend
final scene: ~480 randomly placed small spheres of mixed materials on a ground
sphere, three hero spheres, sky gradient, and camera depth of field. Each run
randomizes the small sphere placement. Requires bash and python3.
#!/usr/bin/env bash
# RTIOW final scene: ground sphere + 3 hero spheres + 22x22 random small spheres.
# Usage: bash YART-COMMANDS.txt
#
# Python generates one --object argument pair per line (flag then value) for
# each small sphere. mapfile reads them into an array so the shell passes
# them as individual argv entries -- no eval, no word-splitting surprises.
# No fixed seed: every run produces a different arrangement, matching RTIOW.
mapfile -t objects < <(python3 - <<'PYEOF'
import random, math
for a in range(-11, 11):
for b in range(-11, 11):
cx = a + 0.9 * random.random()
cz = b + 0.9 * random.random()
cy = 0.2
# Skip spheres too close to the right hero sphere, as in RTIOW
if math.sqrt((cx - 4)**2 + cz**2) <= 0.9:
continue
m = random.random()
if m < 0.8:
# Lambertian: albedo = random * random (darker, more varied)
r = random.random() * random.random()
g = random.random() * random.random()
b = random.random() * random.random()
print('--object')
print(f'type=sphere,radius=0.2,pos={cx:.3f},{cy},{cz:.3f}'
f',material=lambertian,Kd={r:.3f},{g:.3f},{b:.3f}')
elif m < 0.95:
# Mirror: albedo in [0.5, 1], fuzz in [0, 0.5]
r = 0.5 + 0.5 * random.random()
g = 0.5 + 0.5 * random.random()
b = 0.5 + 0.5 * random.random()
fz = 0.5 * random.random()
print('--object')
print(f'type=sphere,radius=0.2,pos={cx:.3f},{cy},{cz:.3f}'
f',material=mirror,Kd={r:.3f},{g:.3f},{b:.3f},fuzz={fz:.3f}')
else:
# Dielectric glass
print('--object')
print(f'type=sphere,radius=0.2,pos={cx:.3f},{cy},{cz:.3f}'
f',material=dielectric,ior=1.5')
PYEOF
)
./yart --path \
--width 1200 --height 675 --fov 20 \
--pos 13,2,3 --pitch -8 --yaw -77 \
--backcolor 80b3ff --backcolor-horizon ffffff \
--defocus-angle 0.6 --focus-dist 10.0 \
--ray-depth 8 --samples-per-pixel 32 \
--object type=sphere,radius=1000,pos=0,-1000,0,Kd=0.5,0.5,0.5 \
--object type=sphere,radius=1,pos=0,1,0,material=dielectric,ior=1.5 \
--object type=sphere,radius=1,pos=-4,1,0,material=lambertian,Kd=0.4,0.2,0.1 \
--object type=sphere,radius=1,pos=4,1,0,material=mirror,Kd=0.7,0.6,0.5 \
"${objects[@]}"# Four different spheres on a plane under a distant light
# (inspired by scratchapixel.com)
$ ./yart --object type=sphere,radius=0.3,pos=-3,1,0,Ks=0.05 \
--object type=sphere,radius=0.5,pos=-2,1,0,Ks=0.08 \
--object type=sphere,radius=0.7,pos=-0.5,1,0,Ks=0.1 \
--object type=sphere,radius=0.9,pos=1.4,1,0,Ks=0.4 \
\
--object type=mesh,file=./models/plane.geo \
\
--light dir=-0.4,-0.8,0.3,type=distant \
\
--backcolor 3cacd7 \
--pitch -10 --yaw -55 --pos 10,3,7
# One sphere under three different point lights
$ ./yart --object type=sphere,radius=0.8,pos=0,1,0,Ks=0.05 \
\
--object type=mesh,file=./models/plane.geo \
\
--light pos=-2,2,1,type=point,intensity=200,color=ff0000 \
--light pos=0,4,1,type=point,intensity=190,color=00ff00 \
--light pos=2,5,1,type=point,intensity=420,color=0000ff \
\
--backcolor 3cacd7 \
--pos=0,1,8
# Four glasses of water (inspired by scratchapixel.com)
$ ./yart --object type=mesh,file=./models/glasses.geo,material=reflect-refract,ior=1.3,smooth-shading=1,scale=0.1 \
--object type=mesh,file=./models/backdrop1.geo,albedo=0.18,smooth-shading=1,scale=0.1,pattern=line[scale=0.06,angle=45] \
\
--light type=distant,dir=0,-1,-0.5,intensity=1.6 \
\
--pos 0,3,8 --pitch -13 --backcolor 3cacd7
# Just a cow (model taken from scratchapixel.com)
./yart --object type=mesh,file=./models/cow.obj,smooth-shading=0 \
\
--light dir=-1,0,-1,type=distant \
\
--pos 29,23,38 --pitch -19 --yaw -37.5 \
--fov 27 --backcolor 3cacd7
# Smooth-shaded cyborg (model taken from learnopengl.com)
./yart --object type=mesh,file=./models/nanosuit.obj,smooth-shading=1 \
\
--light dir=-1,0,-1,type=distant \
\
--pos 29,23,38 --pitch -19 --yaw -37.5
# One glass on a reflective surface (inspired by scratchapixel.com)
./yart --object type=mesh,file=./models/cylinder.geo,material=reflect-refract,ior=1.3,smooth-shading=1,scale=0.1,0.3,0.1 \
--object type=mesh,file=./models/backdrop1.geo,albedo=0.18,r=0.1,smooth-shading=1,scale=0.1,pattern=check[scale=0.1,angle=45] \
\
--light type=distant,dir=0,-1,-0.5,intensity=1.6 \
\
--pos 0,3,8 --pitch -13 --backcolor 3cacd7
# Glass of water with a pen (inspired by scratchapixel.com)
./yart --object type=mesh,file=./models/backdrop.geo,smooth-shading=1,pattern=line[scale=0.1,angle=-45] \
--object type=mesh,file=./models/cylinder.geo,smooth-shading=1,material=reflect-refract,ior=1.5 \
--object type=mesh,file=./models/pen.geo \
\
--light type=distant,dir=-0.5,-1,0.5,intensity=2 \
\
--pos 7,22,-30 --pitch -27 --yaw -164 --backcolor 3cacd7
# Red ball, checkered plane under a point light
./yart --object type=sphere,radius=0.3,pos=0,0.3,0,Ks=0.1,Kd=0.9,0.01,0.01 \
--object type=plane,Ks=0.05,Kd=0.8,pattern=check \
\
--light pos=1,2,0,type=point,intensity=400 \
\
--pitch -20 --yaw -5 --pos 0,2,5
# Two metal shiny balls under four point lights on a reflective surface
./yart --object type=plane,Ks=0.05,Kd=0.8,r=0.4,pattern=check \
--object type=sphere,radius=1,pos=0,1,0,Ks=0.5,Kd=0.6,r=0.2,n=500 \
--object type=sphere,radius=0.5,pos=-1,0.5,-1.5,Ks=0.5,Kd=0.6,r=0.2,n=500 \
\
--light type=point,pos=-2,2.5,0,intensity=300,color=b2994c \
--light type=point,pos=1.5,2.5,-1.5,intensity=200,color=727f99 \
--light type=point,pos=1.5,2.5,1.5,intensity=100,color=b2ccb2 \
--light type=point,pos=0,3.5,0,intensity=300,color=4c4c4c \
\
--pitch -16 --yaw -91 --pos 8,3,-1









