Technical features

Primary rendering

for implementing our rendering pipeline I decided to reuse the core ideas that I explored in the previous project (voxel raymarcher). This meant implemented a raymarcher using an algorithm called DDA (digitial differential analysis). The medium which was traversed was a 3d grid stored in the gpu memory representated as a 3d texture to leverage potential optimisations regarding memory-layout, a simple grid was chosen due to time constraints and for it's ease of implementation in many different fields like physics, collision querying, as well as terraforming.


Raymarching was performed in the fragment/pixel shader stage of the standard rasterization pipeline. This method was chosen as opposed to doing it in compute as this effectively eliminates the need for a upper level acceleration structure at the cost of having to deal with ways to limit overdraw for occluded objects. However, during the course of the project our scope changed so that we would not have to tackle this issue. In hindsight for our case doing the raymarching in compute could have saved us potential overhead that the standard rasterization pipeline might have although this would likely be marginal.

Foliage(Subvoxel) Rendering

To render geometric details smaller than single voxels I implemented a 2 pass traversal. Once the top level traversal has found an intersection with a voxel that has subvoxel detail, ie: bushes, foliage etc. then it initialises a secondary traversal through this voxel. Doing this is not extremely cheap as it involves potentially quite a bit of divergent controlflow per warp, although this is true for the standard DDA algorithm as well. None the less we stayed about our target framerate.

In the final product:

With added swaying. due to performance reasons we decided that shadowcasting for subvoxel objects would not be possible.


Prototypes of the subvoxel rendering:

first I focused on getting the traversal itself functional, with correct normals and occlusion.

after that I focused on seamless integration with out lighting system.

Storage medium for subvoxel details

subvoxel details are stored as instances of loaded volumes. A certain "model" can be imported from magicaVoxel which then also gets stored as a 3d textures on the GPU.while traversing subvoxel volumes we prefetch the appropriate texture through indices which then index into the array of bindless 3d textures, these indices are stored inside the primary voxel grid.

Real time terrain manipulation

looking back this is likely one of the things I would change. Terrain manipulation was implemented through compute, where a simple ray march was done through the volume in front of the camera to determine what block the player is looking at. Later I realised that we need to store the voxel data on the CPU anyway so it's likely more performant to perform this simple raymarch on the CPU instead since this avoids the cost of compute shader dispatching, plus this algorithm is sequential instead of parallel iin nature which is a better fit for the CPU. The original idea was that doing this in compute would allow us to manipulate huge blocks of voxel data at once but this never actually turned out to be a use case due to scope changes.

Here it is working in action: