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
0and100, 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.ppmor0xffffff. 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:
spheretexture texture vertex center unsigned radius:- A sphere with a given position and radius.
planetexture texture vertex point vertex direction vertex direction:- A general plane given by a position and two direction vectors, thus actually being a parallelogram.
axisplanetexture 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.
cylindertexture texture vertex start vertex end unsigned radius:- Creates a hollow cylinder between the two given points and a radius.
globallightpercentage brightness:- Global illumination value. Will be added to all pixels unconditionally, thus creating minimal lighting.
raylightpercentage 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).
pointlightvertex point color color percentage brightness unsigned len:- Creates a single colored light at the given point with a maximum length for dropoff.
arealightvertex 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:
depthblurunsigned mindist unsigned maxdistdepthfogcolor color unsigned start unsigned densityvolumetriclightvertex point percentage brightness unsigned len
General per-scene configuration options are:
set ao_lenfloat- 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_linbool- 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_facfloat-
Ambient occlusion weight offset and factor. Both should add up to 1, so for example each one
0.5. set alight_samplesunsigned-
Number of samples per
arealightdimension. Giving 3 here will thus trace 9 individual lights. set supersampleunsigned- 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:
cameravertex 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.
panunsigned 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_THREADSbuild 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_Xbuild 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
-lX11linker 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
-jargument. If disabled, only a single process is used instead. Enabled per default. Requires the-lpthreadlinker 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.