Mats M's profile

Volumetric rendering with lighting and CUDA processing

This project is a continuation of the previous volume rendering project. I left off with the generation of a sphere with a bump map much like the one below. As nice as they looked there were some difficulties associated with their generation.

Memory:
To gain satisfying fidelity in the sphere there had to be a lot of voxels. Specifically the spheres in the former project were rendered from a data set consisting of 1400³ voxels, each holding three byte sized values. That amounts to over eight Gigabytes.

Time consumption:
The sheer magnitude of the data set and the resolution of the output images brought with them rendering times exceeding twenty minutes. Despite rendering on 8 threads of a 4 GHz quad core processor at 100% CPU usage.

Therefore i tried to create a GPU implementation. I had a fairly good GTX 970 at my disposal which seemed to provide superior performance.

Now why would such a task be fit for a GPU consisting of tiny general processor units or CUDA cores rather? All my data functions at this time were coordinate based functions that utilized mathematics and if statements to determine the output voxel data. Therefore there was no inherent link from one voxel to it's neighboring elements. Another way to put it is that the order at which the voxels are computed is trivial. This property could be translated to the task of ray tracing the data set as well.

It was my first time programming for the GPU so some studying was required. After a day of tinkering i had however ported the ray tracing algorithm as well as the function for generationg the perlin bumped sphere.
The processing went much faster this time, showing a 60% reduction in rendering time on similar data sets, and roughly similar performance gain in the computation of the voxel values.

There were some GPU related issues that had to be addressed however, mainly regarding memory. The GTX 970 only had 4 GB of on-board memory 3.5 of which were continuous (queue Nvidia memes). This imposed a limitation on the maximum data set dimensions.

Another issue that arose was the fact that my computer was unable to function while the data was rendering, in the sense that the framerate dropped to ~0.1 Hz. I therefore opted out of using the CUDA functions when i wanted to browse the net while rendering. 
After porting the functions to CUDA i had few ideas on what to actually fill the data set with. I therefore looked at what features i might wish to introduce to the volume render. The most obvious candidate was lighting effects. Below are some of my thoughts in relation to developing this feature.

How should the light be processed?
I wanted it to be absorbable and colored. To achieve this i introduced a few additional data points to my voxels, namely opacity and three luminosity values (red, green and blue).

How can it be absorbed differently for each voxel it passes through?
For the light to be absorbable you need to evaluate the light from its source. I originally wanted for the shape to be conical. This could be done by creating a ray from the light source to each of the unit coordinates inside the intersection ellipse on one of the far planes of the data cube, and iterate them all through the data set. This would lead to voxels near the source getting intersected by multiple lines requiring a system to determine if the same light source had already illuminated it (since i was planning to have multiple light sources).

How can i make it fast?
The conical shape would be more general but i opted for a cylindrical shape instead. This would allow for a very effective and simple algorithm with the desired light properties.

1. Determine the unit coordinates within the intersection ellipse in the near plane of the data cube.
2. Scale the direction vector of the light such that the coordinate normal to said plane is 1.
3. Increment each of the coordinates found in 1. by the vector in 2. and perform the lighting dynamics on the voxel surrounding this point.
4. Continue until you exit the data cube. 

This strategy worked pretty well with negligible computation time in relation to other aspects of the process. Some test renderings are posted below.
I wanted to check out how the lighting fared against hollow objects. I set up a very minimalistic castle with hollow walls and corners to test this. The pattern on the upper left image is an artifact created by the fact that the volume renderer takes the distance a ray lies inside the voxel into account when integrating its value. When returning the first non zero value you therefore end up with this pattern. If i removed this feature when trying to display the object you would get no sense of depth as the image would only consist of two different color values.
Now comes the fun part. Actually using the tools i created. I don't really remember all that much of how these were constructed (i never kept a log of the small changes in parameters or how my main function was set up), but there are pretty much two motives with different lighting effects added. The first motive is a sphere with simple layers of perlin noise summed together inside it. The second motive is similar but the layers are now put through an absolute value function before being summed. 
The same object as above but now lit from one side. The absorbtion of the light gives a smoky look. 
The second motive. Here illuminated from different angles by different colors and with a red glow preprogrammed into it.
Lighting from different angles and some with several sources.
Some close-ups.
This is about as far as my volume rendering experience goes. It was a great bit of fun when i actually managed to render 3D objects for the first time. I gained a bit of knowledge about intersection algorithms and light blending as well as memory management, code optimization and CUDA programming (in C). This project is probably the most involved piece of software i've ever created, maybe surpassed by the embedded system i created at school with many low level communication drivers, a display driver with menu system and a snake game. It also communicated over CAN with an arduino which controlled a physical pong game.

Where to go from here?
- Create a working interface to enable quick parameter tweaking and real time rendering output for finding good views.
- Implement support for poygon meshes, in the sense that rays must lie within the mesh to integrate voxel data. 
- Tweak the integration parameters and add support for opaque objects. This would essentially make the program a voxel-art renderer with volumetric capabilities.

Hope you like it :)
Volumetric rendering with lighting and CUDA processing
Published:

Volumetric rendering with lighting and CUDA processing

Published: