A multithreaded raytracing library built in C++. Example usages available in the "Sandbox" project.
This project uses Premake to build project files.
The Scene is constructed with a scene graph. Components can be attached to each node, and by default each node carries a Transform component which defines local translation, rotation and scale. Shapes are component too, and so they have to be attached to a node in order to be rendered.
There are 3 basic shapes: Sphere, Plane and TriangleMesh, but the base Shape class can be extended to support more. Anyway the TriangleMesh allows to render almost everything. For an efficient rendering, triangle meshes use a KD-tree to store triangles inside to minimize the number of intersection tests.
Shapes can be assigned a Material which defines the appearance of the shape. Materials inherit from the base class Material which defines the properties of every point in space (color, reflectivity, etc.). The class UniformMaterial can be used to build materials that have the same appearance in every point in space. To build more complex materials, they can be combined using InterpolatedMaterial, which interpolates between 2 materials given a 3D noise function. There are several built-in noise functions (Perlin, Worley, CheckerBoard, Marble), but the base Noise class can be extended to achieve more complex results.
All the examples in the Sandbox project use the predefined noises, which already allow to achieve a lot of interesting results.
The Background base class can be used to specify the appearance of the background. There are 2 implementations:
- the SkyBox class, which renders a sky with a light source (sun/moon).
- the ColorBackground class, which renders a solid color as a background.
If no background is specified, black is used.
The base class Renderer defines a generic renderer for a Scene object: basically the Render method takes a Scene reference as a parameter and returns the rendered image as an array of pixels. The rendering process is supposed to be asynchronous, and that's why the result is stored in a std::promise. AbstractRaycaster inherits from Renderer, and defines the Render method, which uses multiple threads to render the scene by calling the abstract method Raycast. The concrete class Raytracer inherits from AbstractRaycaster and of course implements the Raycast method.
The rendering process splits the screen into vertical lines 1 pixel wide, and then each line is rendered in a separate thread. A pool of N (user-defined) threads is instantiated, and they run concurrently rendering one line at time until all of them have been rendered.