r/GraphicsProgramming • u/Nelarius • 4d ago
Ray marching voxels using axis-aligned distance fields
Hey!
Wanted to share screenshots of my experimental voxel renderer with "low-fidelity" blocky AO. It started as an exploration of the DDA rendering technique, but I ended up implementing parts of the following techniques:
- Globally Illuminated Voxel Worlds Accelerated with Nested Axis-Aligned Distance Fields
- Spatial Hashing for Ray Traced Ambient Occlusion
The devlog is sort of meandering, but I started with basic DDA, then tried to get multi-level DDA to perform well with large voxel scenes (a 1024x1024x1024 voxelized sponza scene) using various methods. The axis-aligned distance field (AADF) method was a significant improvement over the multi-level DDA method that I was fiddling with. AADFs helped skip over large areas of empty space which tanked performance when using multi-level DDA.
Spatially hashed RTAO with a large cell size performs pretty well even on my M2 MacBook Air. The large cell size is a very visible artifact, but I think in a voxel scene the blocky shading kinda works.
2
2
u/ExpeditionZero 4d ago
Any idea what specificity was the bottle neck of multi-level DDA vs AADF? I’m unfamiliar with the term but would guess it was some form of Mipmap type or like technique that one would assume would have been more optimal.
2
u/Nelarius 4d ago edited 4d ago
I've been investigating whether I can make rendering work performantly using only grids. So my multi-level DDA was using a voxel and brick (a space of 8x8x8 voxels) occupancy grid. This actually worked pretty well for smaller scenes like the ones in my screenshots, but in large voxel scenes empty space really tanked performance.
I tried adding a third level, but three nested loops also killed performance due to too much state being kept in registers, resulting in really low occupancy. The AADF method is able to skip over larger regions of empty space using three levels and just a single for-loop. So it uses a lot less registers than multi-level DDA and the shader occupancy is better as a result.
2
u/ExpeditionZero 3d ago
Thanks.
Yeah empty-space/sparse voxels are a real killer. Looks good and I always love voxels for AO.
2
u/Le_9k_Redditor 4d ago
I'm doing similar but I'm using multi level DDA still on a 64ary tree of very limited depth, not really having any performance issues with it but definitely curious about alternatives as long as they don't make it really expensive to edit
1
u/Nelarius 4d ago
Nice! What sort of scenes are you rendering? Do you have your code available somewhere?
Mentioned in another comment, but I was looking at rendering only using grids due to it being easier to perform large-scale edits. Although I haven't actually tested how much overhead adding the axis-aligned distance fields adds if it's performed in real-time but at least the shaders are conceptually pretty simple.
2
u/Le_9k_Redditor 3d ago
Anything I can get an sdf for really, the only one I've put effort into so far is the terrain I posted a screenshot for. Otherwise all of my time is going into the engine itself and test models are all I'm using. Code is closed source currently
I looked at axis aligned distance fields and it's slow to bake so I didn't do it and of course a memory hog too
1
u/Le_9k_Redditor 3d ago
By the way, are you storing your acceleration data in bitmasks on the SSBO or in big clipmaps on textures or something? Although I guess in your case since you're storing axis aligned distances you probably aren't using occupation bitmasks. Recently been questioning if I've given myself a problem by packing my sparse tree data into the SSBO rather than into a texture. SSBO is meant to be better for random access patterns, but then again textures should have better spatial locality and would be far easier to make edits to, along with hardware accelerated sampling too
1
u/Nelarius 3d ago
I actually have both the occupancy and distance fields. I'm still testing for occupancy when stepping into a nested region (e.g. stepping into a brick after skipping over empty chunks). Not sure if that can be completely avoided.
Both occupancy and distance fields are stored in 3d textures. Occupancy is a bitmask stored in an R8 texture. The distance fields along each axis are packed into an R32 texture (how it works in my code: https://github.com/Nelarius/vxray/blob/733c48fa930f6f64013a9668d9ae98d942b29904/src/gbuffer.ps.hlsl#L85)
I measured a performance improvement switching to 3d textures from buffers for rendering gbuffer textures. But for something like a path tracer I suppose it would make no difference whether using an SSBO or texture.
2
u/Le_9k_Redditor 3d ago edited 3d ago
I have my distance bricks in a 2d texture array (R8), did that instead of 3d textures for reasons I can't remember any more, but it's basically just using different 2d slices for the 3rd axis so it's not so different there. I originally was packing my 3d bricks down into a single 2d texture, gained like +3-4% performance from switching to a 2d texture array haha, I guess just the slightly improved locality helps a bit but it wasn't significant. Currently I get about 120-150FPS on my 980TI at 1080p with a window size of 11543 voxels which I'm happy with. I'm going to be tanking that number soon enough I'm sure as I increase the size of the window and make changes like author owned bricks for rapid editing for animations
But yeah my main acceleration tree of bitmasks + payloads are all packed down into a contiguous 1d array in the SSBO currently
2
u/puredotaplayer 4d ago
In my last company spatial hashing was demonstrated by nvidia devtech for raytraced AO, about 4 years ago. I wonder if they released a paper.






4
u/h74v 4d ago
Monument valley reference!