CPU raytracer
CPU-only raytracer with interactive X-window or image output. Supports basic geometric primitives, lighting scenarios, and ambient occlusion approximation.
As presumably most of computer science students, at a certain point in time, there will be the ambition to come up with an own CPU ray-tracer/-caster implementation. While there should already be enough options to make use of in the wild, the main purpose of this project was to get a glimpse on the actual algebra behind.
Learnings from this project lead to the more recent maze light-tracer game implementation.
Apart from calculus related to spatial primitives, there also are other interesting aspects of this topic. These include for example techniques such as ambient occlusion, using Xlib, or the evaluation of possible optimizations (whether exact, numeric, or approximating). Currently, the supported features comprise:
- Textured geometric primitives such as spheres, planes, and cylinders
- Multiple light sources represented for example by points or lit areas
- Ambient occlusion approximation instead of real recursive shadows
- Multithreading support
- Precomputed lightmaps for static lights
- Colored light sources (color mixing)
- Interactive X11-window and/or PPM image output
- Movement and clipping support
- Multi-frame-rendering for “video” output
A simple example scene consisting of 20 objects and 7 light sources can be seen below.
This video has been created by combining 20 frames rendered to separate images. A live screencast of animated X-window output would have been possible as well.
Scene description
A scene description file defines all primitives to draw, several general configuration options, and if/how the camera should move to produce several frames. We’ll assume these datatypes in the following:
- vertex
-
A 3d coordinate vector written as
(100,700,300)
. - unsigned/percentage/bool
-
Positive integer value, number between
0
and100
, or0
/1
, respectively. - float
-
Signed float/double number such as
1.23
. - color
-
An RGB color in hex notation, e.g.
0xffffff
. - texture
-
An image/color texture definition, such as
foo.ppm
or0xffffff
. Images must be in PPM format and can be suffixed by:(0-3)[xy]
– indicating a number of clockwise 90° rotations and whether it should be mirrored along the x- or y-axis. If prefixed with percentage+
, this determines the object’s “radiosity”, i.e. the object’s intrinsic brightness.
Currently supported drawing primitives and light sources are:
sphere
texture texture vertex center unsigned radius:- A sphere with a given position and radius.
plane
texture texture vertex point vertex direction vertex direction:- A general plane given by a position and two direction vectors, thus actually being a parallelogram.
axisplane
texture texture vertex point vertex direction:- A better optimizable rectangle that follows axis coordinates. Defined by a point and a diagonal that has to contain a single 0.
cylinder
texture texture vertex start vertex end unsigned radius:- Creates a hollow cylinder between the two given points and a radius.
globallight
percentage brightness:- Global illumination value. Will be added to all pixels unconditionally, thus creating minimal lighting.
raylight
percentage brightness:- Virtual lighting added from the camera viewport itself. Visible objects will be illuminated depending on their surface’s normal (a straight look at 90° yields its full value).
pointlight
vertex point color color percentage brightness unsigned len:- Creates a single colored light at the given point with a maximum length for dropoff.
arealight
vertex point vertex direction percentage brightness unsigned len:-
Defines an illuminated surface (or an approximation thereof), starting at a given point with a
given diagonal (similar to
axisplane
). Also requires a brightness percentage and a maximum distance for dropoff.
Some experimental shaders are also available as work in progress:
depthblur
unsigned mindist unsigned maxdistdepthfog
color color unsigned start unsigned densityvolumetriclight
vertex point percentage brightness unsigned len
General per-scene configuration options are:
set ao_len
float- Ambient occlusion ray length: How far along the surface’s normal and for which radius to check for colliding objects. Feature disabled for 0.
set ao_lin
bool- Linear ambient occlusion, which converges more slowly towards 1. Otherwise, this yields less difference between just-a-bit-occluded and just-not-occluded-anymore.
set ao_os|ao_fac
float-
Ambient occlusion weight offset and factor. Both should add up to 1, so for example each one
0.5
. set alight_samples
unsigned-
Number of samples per
arealight
dimension. Giving 3 here will thus trace 9 individual lights. set supersample
unsigned- Supersamples the final result in a postprocessing step. Smoothes the image and helps with aliasing artifacts. 0 disables this feature and e.g. 2 will half the image’s dimensions by computing 4 pixel averages.
Camera viewport and output control:
camera
vertex origin vertex direction unsigned raster-width unsigned raster-height:- Defines the camera’s (starting) position and viewing direction. The length of the direction vector yields the distance from the camera to the rasterization plane and thus controls the field of view (FOV). The raster dimensions effectively control the output’s width and height.
pan
unsigned frames vertex camera-movement vertex camera-pan:- Enables multi-frame output by defining camera movement. The given number of frames will be shown and/or written, with the given vectors applied to the camera’s position and viewing direction. Afterwards, when controlling the X11 window by keys, the camera vectors’ lengths will be used for the movement and viewport step size, respectively.
Raytracer usage
After building using make
, run the resulting binary on a scene description file:
./crt [-j threads] scene.conf [outdir/]
-j
- Process separate image parts in parallel, e.g. 4 will start a thread for each corner,
1 will disable threading. Rounded down to a power of 2, default 1. Requires the
USE_THREADS
build flag. scene.conf
- The description of the scene to draw, see above for syntax and details.
outdir
- Where to place the output image(s) in PPM format. Optional, as an X window for
interactive tracing will be used if available (requires the
USE_X
build flag).
When tracing the scene as defined by the input file, the result will be:
- written to an image, if an output directory is given
- displayed inside an X-window, if this was enabled at compile time
After the first result, remaining frames are drawn according to the pan
setting. Between each
frame (if more than 1), the camera position and viewport direction will be moved accordingly.
If the output window is available, the camera can then be moved through the scene interactively:
- Arrow Keys: Move left, right, forward, or backwards
- Page Up/Down: Move up or down
- Alt + Arrow Keys: Look left, right, up, or down
- Escape: Exit
The resulting output images can be combined and rendered into a video by using for example
ffmpeg -r 25 -i '%d.ppm' -preset veryslow out.mp4
.
Build options
There are several build-time options available, which can be added or removed as -D
CFLAG
in the
provided Makefile
.
USE_X
-
Enables X11 support for “realtime” rendering with interactive controls.
Disable this for an improved performance during bitmap image output.
Enabled per default. Requires the
-lX11
linker flag, library, and headers. IMAGE_LIN_AVG
- When supersampling, use linear RGB interpolation instead of squared component distance (faster).
IMAGE_INTERPOLATE
- Enable 2D linear interpolation for texture pixels. Otherwise the nearest neighbor is chosen.
TRACE_ALL
- Before tracing, the scene will be preprocessed – objects that won’t be visible or are occluded under certain circumstances are then skipped at runtime. This flag disables this feature.
STATIC_LIGHT
- Enable static lighting. In a (lengthy but parallelized) preprocessing step, every object’s surface gets traversed and lighting (and occlusion) information is collected. Improves runtime performance.
STATIC_LIGHT_RES
-
When using static lighting, use this resolution in scene units. For texture images, their very
own resolution in pixels is taken. The default is
1.0
. LIGHTMAP_INTERPOLATE
- Produce softer shadows by linear lightmap interpolation.
PLIGHT_SQ_DROPOFF
- Squared instead of linear dropoff for pointlights (faster).
NO_CLIP
- When moving due to keyboard input, don’t check for clipping.
NO_OBJ_LRU
- Enables several LRU caches for objects hit or occluded by the last ray or light source.
WRITE_PLAIN_PPM
- Writes PPM images in ASCII instead of in binary format. Will be slower but might be needed for compatibility.
STATS
- Collect timing statistics and counters for debugging purposes. Slows everything down significantly.
USE_SIMD
- Use floating-point SIMD intructions for vertex calculus. Can provide a performance benefit.
USE_THREADS
-
Support multithreaded shadowmaps generation and rendering using the
-j
argument. If disabled, only a single process is used instead. Enabled per default. Requires the-lpthread
linker flag, library, and headers. KEY_REPEAT
- Honor repeated keypresses. Otherwise, only a single keystroke after rendering the current frame gets processed.
NDEBUG
- Disables assertions and additional checks (faster). Enabled per default.