Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

Sparse Voxel Octrees



Previously I noted that since Voxel global illumination involves calculation of direct lighting, it would actually be possible to do away with shadow maps altogether, and use voxels for direct and global illumination. This can eliminate the problems of image-based shadows like shadow acne and adjusting the shadow map size. I also believe this method will turn out a lot faster than shadow map rendering, and you know how I like fast performance. ;)

The sparse voxel octree node structure consumes 48 bytes and looks like this:

struct SparseVoxelOctreeTreeNode
	uint32_t index, parent, color, emission;
	uint32_t child[2][2][2];

It might be possible to eliminate the index and parent values, but the structure size has to be aligned to 16 bytes in GPU memory anyways, so I don't see it getting any smaller.

In my test scenario, the sparse voxel octree creates 353,345 voxels, which consumes 14% the memory of the uncompressed data, but is only a little bit smaller than compressed volume textures, and I could see the SVO data being bigger than a compressed 3D texture.

Uncompressed, diffuse + emission

256*256*256*4 + 256*256*256*3 = 67108864 + 50331648 = 117440512 bytes = 112 Mb

DXT5 compressed diffuse + DXT1 compressed emission

16777216 + 8388608 = 25165824 bytes = 24 Mb

Sparse Voxel Octree

353345 * 48 = 16.2 Mb

That's just for the voxelized triangles' diffuse color. We still need size textures to store direct lighting, one for each direction on each axis. Since these are rendered to in a shader, I don't see any texture compression happening here:

256 * 256 * 256 * 4 * 6 bytes = 384 Mb

If we store the lit sparse voxels in a 1024x512 RGBA textures (x6), that consumes a lot less memory:

1024 * 512 * 4 * 6 = 12 Mb

So overall, we do see a very significant reduction in memory usage when we make the octree sparse. It's also going to be A LOT more efficient to render to one 512x1024 texture buffer (with six color attachments), instead of rendering 256 separate slices of a volume texture.

Looking up a single value in a sparse voxel octree is more complex than a simple texture sampler, because it has to iterate through all the levels of the tree, down to the terminal node. However, ray traversal should be much faster with the sparse voxel octree, because the algorithm can efficiently skip over large empty spaces. An interesting challenge is the fact that GLSL does not support recursive function calls, so recursive functions have to be written in creative ways. This one isn't too bad, but when you hav branching pathways in a ray traversal, it can get pretty complicated:

bool SVOTGetNodeColor(in uint x, in uint y, in uint z, out vec4 diffuse, out vec3 emission)
    diffuse = vec4(0,0,0,0);
    uint index = 1;
    int maxlevels = 10;
    uint size = 256;
    if (x >= size || y >= size || z >= size) return true;
    uint hsize;
    uint px,py,pz,childindex;

    for (int n = 0; n < maxlevels - 1; n++)
        hsize = size / 2;
        px = uint(x >= hsize);
        py = uint(y >= hsize);
        pz = uint(z >= hsize);
        index = svotnodes[index - 1].child[px * 4 + py * 2 + pz];
        if (index == 0) return false;
        x -= px * hsize;
        y -= py * hsize;
        z -= pz * hsize;
        size = hsize;
    diffuse = SVOTNodeGetDiffuse(index);
    return true;

In this shot, I am rendering the original mesh geometry and doing a texture lookup in the sparse voxel octree to find the color stored for the voxel at each point. There's a few places where the surface appears black, meaning that the point being rendered lies outside the bounds of any voxel saved. Maybe there is a problem with the precision of the triangle voxelization routine, I will have to look into this further.


The important point is that this image is being rendered without the use of a volume texture. You are seeing the sparse voxel octree being successfully sent to and navigated within in the fragment shader.

The next step will be to take the diffuse colors and render direct lighting into a separate texture. That means my clustered forward rendering implementation will not get used, but developing that did prepare me for what I must do next. Instead of placing all the lights into a grid oriented around the camera frustum, I need to place them in a grid in world space, with the camera at the center of the grid. This is actually quite a lot simpler. 

  • Like 3


Recommended Comments

The more empty space there is, the more memory the SVO saves. When I changed the grid resolution from 256 to 512 in the above scene, so the entire building was enclosed in the grid area, the SVO used 42 Mb, but two 512x512x512 RGBA images would require 1 Gb of VRAM.

Link to comment

It just occurred to me that since only the terminal nodes store color data, and have no children, the colors could be stored in the child member, bringing the data structure size down to 32 bytes. So multiply all the SVO memory consumption values by 2/3.

struct SparseVoxelOctreeTreeNode
	uint32_t child[2][2][2];


  • Upvote 1
Link to comment

I found with shadow maps, the shadows must be updated every single frame or you get a flickering effect. I think what I can do is store a small voxel octree for each dynamic object, instead of adding it into the voxel octree of static objects. The lighting shader will transform each ray to local space around the object and calculate a raycast for each object it intersects.

  • Like 1
Link to comment

Thin walls can be a problem, but the same is true with shadow maps. Light leaks can and do happen with the GI calculation, but I've got a good balance of settings that is nearly perfect.

The hardest thing to do is very dark interiors with bright outdoor lights. The buildings from the Zone are actually perfect for testing, because they have a lot of thin walls and interior spaces that see no light.

Link to comment
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...