CPU raytracer
A simple CPU PBR raytracer with an experimental triangle hit test.
A collection of renderer projects, exploring the techniques necessary for PBR rendering, alongside my own individual and unconventional experiments.
A simple CPU PBR raytracer with an experimental triangle hit test.
A PBR rasterizer supporting both DirectX and CPU rendering + intra-process line generatioin & post-process stylization.
A work-in-progress PBR Vulkan renderer exploring low-level graphics programming and future rendering experiments.
A CPU raytracer handeling both implicit shapes and explicit geometry as well as PBR materials.
Project includes quaternion camera and an experimental triangle hit test.
The renderer uses a ray per pixel and checks against the scene objects to determine the closest hit, using subsequent material info as well as secondary rays towards every light source to determine how to shade the pixel, achieving cast shadows and pbr materials.
I'm personally not a fan of gimbal lock. And I had never really done quaternions before, so I made my camera quaternion based, resulting in unrestricted rotation, including barrel rolls! Every subsequent project makes use of this type of camera.
While I made the conventional triangle hit tests (ex: barycentric coordinate), I aslo
saw an alternative way.
By using two sides of the triangle as basis vectors, any point inside the triangle can
be expressed as:
A = O + a · x + b · y
With x & y in [0,1], and x + y ≤ 1
The CPU side of the dual rasterizer also makes use of this technique.
Solving for x and y introduces a potential divide-by-zero
case. When this occurs, the resulting intersection can produce visual glitches.
A simple assert can be used in debug mode to detect the problematic geometry.
In the renderer itself, checking the denominator and rejecting invalid cases also prevents the
visual glitches.
With everything else the same, my experimental method is consistently faster than the
traditional barycentric method. with
an aproximate range of 1.9-2.7 FPS compared to 1.4-2.0 FPS, while producing the same visual result
after handling the divide-by-zero case. (screenshots taken from rotating scene)
my experiment - (glitch included for illustrative purposes)
traditional barycentric ordinates
A rendering project combining a DirectX rasterizer with a CPU rasterizer, allowing the same scene to be rendered through two different implementations.
The systems share a vertex buffer, materials, and their render passes (opaque and transparent). Both systems can also switch cull modes. DirectXcan additionally switch texture sample modes, while the CPU version can display various intermediate results such as depth and bounding boxes.
Having direct access to the rasterization
process and calculations also gave me the
oportunity to experiment with effects that
would normally be applied after rendering.
One of my goals is to eventually build a dedicated stylised renderer.
Thus, I wanted to see if I could already do some stylization while rendering the scene. More
specifically, I wanted to see if I could generate sketch-like lines, something that can be tricky to
do without adapting the mesh or textures beforehand.
my aproach:
While rasterizing a triangle, for each pixel, it samples
its top(T) and left(L) neighbors.
The top and left neighbours are chosen because they have
already been processed when the current pixel is reached.
This allows the renderer not just to check against the
current suroundings, but also to detect if the triangle
itself has a slope great enough to generate a line.
If the current pixel's depth differs from its sampled neighbours by a predetermined
amount, the current pixel is darkened.
Because the lines are generated during rasterization,
later geometry can overwrite them.
This naturally limits the effect,
creating a more selective, sketch-like result.
This approach removes the need for a separate post-processing pass for this effect.
The effect can also be adjusted by changing the pixel sample offset or the depth range.
Regardless, the lines stay consistent with the geometry
but are also variable as the scene transforms without distracting jitter.
However, in this form, the lines still follow the geometry exactly as is,
while sketch lines often stray from the exact shape.
To make them less rigid, the vertices can store an offset
(similar to vertex colour) which can be interpolated across the triangle and used to offset
the line placement.
This can then be paired with other stylization methods such as
colour fading and hatching (although as these are not context dependent it may be better to
apply them in post) to achieve a more complete
stylised look.
My current rendering project is a Vulkan-based rasterizer focused on physically based rendering and low-level GPU programming. It includes a multi-pass rendering pipeline, dynamic lighting and HDR-based global illumination.
This is still a work in progress, but the core rendering pipeline is already in place. My current focus is on expanding its lighting capabilities and using the project as a foundation for further rendering experiments.
In line with best practices, the renderer separates geometry, lighting and final image processing into distinct passes.
Renders scene depth before the main g-buffer pass,
using the geometry and texture masks.
Stores surface information used by the lighting pass,
separating material and geometry data from lighting.
Uses the stored surface data to evaluate PBR lighting and the scene's dynamic lights.
converts the raw HDR lighting result and applies tonemapping to get to the final output.
The renderer uses a PBR material workflow with a lighting pass
that can account for changing lights in the scene using a storage buffer.
Aditionaly, before rendering the scene, a light probe is created from an hdr image to give the scene global illumination.
An HDR is rendered onto a cube texture, and resampled across diferent mip levels to achieve a soft light probe that represents the original image.
Enabeling the equivalent of dynamic arrays for shader descriptor arrays. Further steps towards bindless rendering are also still underway.
Doing away with pre defined renderpasses, allowing the steps to be altered frame by frame through the command buffer.
giving greater control over pipeline stages and access flags.
I still intend to finish the core renderer and build on top of it. Shadow mapping is a major planned addition, followed by further improvemens on the system. Once that's done, I want to experiment with compute shaders.
Add dynamic shadows for directional and point lights.
PlannedTo allow for greater flexibility and to gain deeper insight. ( I learn best by doing )
PlannedExplore what they're capable of, and if they can be used to achieve specific visual effects that would traditionaly be done is post.
Planned
Vulkan has been an arduous process. It's pushing me to understand
modern graphics API's beyond the conceptual.
I'm hoping DirectX 12 will be kinder when I eventually get to it.