Rendering Multiple Volumes

Rendering Multiple Volumes


When I first started writing a volumetric path tracer my end goal was to render beautiful cloudscapes consisting of with tens or maybe hundreds of individual clouds. With the last cloud modelling series I now have the experience to sculpt the necessary cloud elements based on their altitudes but my renderer couldn’t render more than a single cloud until recently.

To be able to create a cloudscape, of course, one needs to be able to render multiple clouds. But filling your scene with individual clouds is not the solution. To save on memory space and the excessive cost that comes with sculpting and exporting many individual volumes, we generally use instances.

Instances are the objects that share a common vdb file but they have their own unique transform matrices. So when we do texture lookups for attributes like extinction or emission we only check if the current instance sits on that ray position and convert the ray position to local space of vdb file and do the lookup.

Instances and multiple volumes has been sitting on the to-do list of VPT code path project. And I recently decided to tackle the problem and give my renderer the ability to render more than one vdb. But this turned out to be rather difficult problem.

Considerations in volume sampling

Now please take a look at the situation above assuming that our ray hit the point p and we want to calculate the amount of density for path segment and we also want to calculate the light contribution from the light source L. Traveling the distance between the point p and L is not a problem but how do we decide if we want to sample volume B. And to make things a little bit more complicated, the volumes can be intersecting, leaving us no choice but test all the volumes if their bounding boxes contain this point. We can either naively sample all the volumes in the scene as the ray travels from p to L, or find a smart way to accelerate the ray traversal.

Constructing a BVH structure

My first thought for an accelerating structure was to create a binary BVH tree to hold all the volumes inside a giant bvh node and travel the scene based on this structure.

BVH Structure for volumes

For BVH construction I’ve used Henrik Dhalberg’s cuda path tracer as the base and modified the code to hold gpu_vdb classes instead of triangles. This structure gave me the acceleration I’ve needed but there was a problem.

When a ray is traversing the BVH structure it decides if it has hit the root node. And if it does it goes on to testing the children of that node if a hit occured. Then finally if the intersected node is a leaf node, the integrator does the calculations of the lighting and finding a new direction etc. But in the case of the volumetric rendering we not only interested if a hit occurred, but we also want to travel inside that region covered by the BVH node and sample all the volumes inside it. But by nature, the bvh leaves only hold one volume object and they are independent of other volumes. So traveling inside the BVH node does not gives us the required volumetric attributes we want.

Octree Structure

Disappointed by the BVH structure I’ve set out to find an alternative acceleration structure. Surfing the internet I’ve remembered that I’ve read something similar a couple years ago and finally found what I’ve wanted in 2017 Production Volume Rendering Siggraph Course.

I immediately created an octree structure that can hold the volumes by creating an octree class that holds several volumetric attributes. First of all, all octree nodes should have a child and parent pointers that point to the hierarchical structure of that node. Secondly they should hold an AABB structure to test if their bounding boxes contains a volume. They also have max and min extinction to be used in ratio tracking during volume sampling. A couple of boolean flags as has_children and depth are also there for good measure. But most importantly they hold a list of volume indices array to access the number of volumes they hold in their bounding boxes. For now the size of this array is at a fixed size because the gpu memory management purposes but I will make it dynamic in the future.

My implementation differs from the paper by a couple points. For example my octree structure is not a unit cube but rather has the dimensions of the bounding box encapsulating the volumes. Secondly I am using a fixed level of depth of 4 with fourth level being the root octree. This gives me an octree structure with 23x23x23 octrees or 512 pointers. Because I’m doing the rendering in gpu, knowing the number of octree pointers at compile time gives me the advantage of memory management.

3x3x3 Octree structure

Traversing Octree

During the rendering of volumes there are couple times we have to sample density from volumes. Some of them are for transmittance estimations or sampling the extinction. To be able to sample an octree structure however you need a smart way of travelling inside the root octree and be able to skip the empty ones (the octrees that don’t have a children). These algorithms are generally called DDA Stepping algorithms and you can find many implementations in literature. However I’m yet to find a satisfying algorithm for a dda algorithm working on gpu with supporting hierarchical octree structures.

For this purpose I’ve tried to implement a couple octree travellers but the fastest one yet was a naive algorithm you can see here for transmittance estimation. What I do basically is while travelling the octree, if the child node is empty I just do a ray->box intersection test and jump to the limit of the box, then test again for children and jump again until I find an octree with volume or just exit the loop.

This may not be the smartest algorithm but it surely is the fastest as of now.

Octree traversal for a simple ray

As you can see in the image above A ray (purple) entering the root octree doesn’t skip the depth3 node because it includes at least one volume. But it skips all the depth 2 nodes but one that has a volume in it. And the same is true for depth 1 (lowest level nodes) too. They are skipped if their bounding box does not coincide with the volume bounding boxes. This new system now gives me the ability to render hundreds of volume instances that you can see an example below.

Final words

This was a short and very specific article but I hope you find it useful if you ever need to implement such a concept to your volumetric renderer. Please remind that I am open for all questions regarding these topics and please don’t hesitate to reach me out.

See you on next article where we will be talking a little look to the usage of VPT and it’s specific points to do lighting for different looks depending on day-time, density of the cloud, source of the cloud etc.