I wrote previously about my dalliance with making an octree-oriented voxel engine in Unity. I recursively generated an octree hierarchy of voxels, with each voxel being a single GameObject (a cube mesh).
My main findings from this were as follows:
- Instantiating GameObjects is slow.
- This cannot be avoided with the use of threading, as they have to be created on the main thread.
- The option of voxel sparsity is not as useful as you’d think.
- Before an octree’s children can be collapsed into a single node, each one must be sampled to determine if they are all homoegenous. Therefore while you may save on memory, you don’t save on the initial cost.
- Naturally non-homogenous regions such as ores in rock cannot be collapsed. Likewise for regions with subterranean caverns.
- The homogenous region of voxels must happen to fall into a single octree’s region.
- A simple cube mesh representation is suboptimal – having eight vertices per voxel is very inefficient.
I first decided to tackle the use of poygonal meshes. Having recently written a raytracer, I decided to apply that knowledge to create a ray-marching shader. I first explored using a SDF (Signed Distance Function) representation, loosely following the tutorials here.
The general idea is that you apply a shader to a simple bounding volume, such as a cube. Every pixel of the cube that is drawn is then produced by tracing a ray from the camera through the surface into the inner volume, which can be defined mathematically as implicit volumes (or the combination of several). I therefore created a 3D texture which represented the signed distance field of the voxels.
However while SDF raymarching is great for arbitrary implicit volumes, I struggled to get accurate voxel rendering at decent performance. There were too many corner cases where rays would just miss the edge of a voxel, causing visible distortions.
I therefore instead turned to the algorithm I was using for voxel picking with the mouse: Fast Voxel Traversal (FVT). It’s a very simple algorithm, similar to Breselham’s line drawing, and easily extended to 3D. I then combined this with some of Unity’s built-in global lighting to produce the following:
The interesting thing about this over polygonal mesh representations is that in theory, the render latency scales with output resolution rather than the number of voxels. Although naturally it is quite intensive on video memory, and there is an initial cost to pay in uploading the 3D textures to the GPU.
I was still quite keen to explore the use of octrees though. So I decided to use them to generate LOD (Level Of Detail) meshes. The idea being that every node of the octree can host a model, rather than just the leaf nodes. Entire subtrees can thus be replaced with a single (simpler) model as appropriate.
An example of this, with 3 levels of detail, is shown below. There’s a very subtle (cough) fading effect between LODs as the distance to the camera changes.
Unfortunately it turns out that having multiple LODs like this is actually a hindrance to performance in all my testing. I suspect this is simply because more meshes have to be drawn in the liminal case. It should be the case that with a sufficiently calibrated LOD bias the performance is much better, so it may still be useful for drawing very distance voxels (e.g. for a first-person perspective). In any case it was fun to implement.