Rendering HUGE Amounts of Voxels II

One year ago I have written a post with the same header. Now I am back to the project Monolith and the 20ms frame time I got before was just unbearable. The observation that there are components consisting of the same voxel patterns again and again helped a lot to implement a new hybrid approach: Geometry-Shader-spawned cubes with ray marched volume textures.

Ray Marching Voxel Textures

As before I use optimized geometry shader instancing for boxes. But now I draw boxes down to the component level only (a component is a functional unit in monolith e.g. a thruster or a laser). Below that the geometry is static and it is affordable to store dense information for that, i.e. volume textures.

The laser weapons in the front demonstrate the effect of neighbor-sensitve sampling

The laser weapons in the front demonstrate the effect of neighbor-sensitve sampling

Visualizing volume textures with ray marching is common sense and straight forward. Since I wanted to have a blocky look the ray can do steps from plane to plane in the uniform grid. The traversal can stop if any non-zero entry is found in the texture. It is possible to march through the texture without maintaining a floating point ray position. Therefore the initial texel position is determined and then in each iteration one of three dimensions is increased or decreased depending on the sign of the ray direction. The right dimension to modify is that which is closest to the next grid-plane in ray direction. Despite changing the texture coordinate, the plane distances must be updated which is a simple subtraction of the distance of the step. The chosen dimension in which the step was done has 0 distance then, so it is reset to the full projected distance between two planes.

The following fragment shader performs the ray marching. Additional to the explained ray marching  voxelMask  is introduced. This mask changes the appearance of the component dependent on the neighborhood. It is a code with a bit for each side and an additional one for non side dependent voxels. The texture contains a mask of the same kind too. Hence, a simple logic AND can decide between visible voxel or not. Additionally the geometry shader computes a LOD (mipmap) dependent on the view distance. Doing this in the fragment shader would cause artifacts, because a LOD-seem can run through a single component which would create little holes.

 

Results - Incredible!

The gain is much larger than hoped. The new hybrid approach takes only a few milliseconds (3-4) on the notbook GPU GTX850M. Before this took over 20ms. The new bottlenecks seem to be driver overhead (the GUI in main menu also takes 3ms) and bad scheduling. Well, I immediately increased the component resolution from 8 to 16 cubic which ended in a bit more than 4ms for really many voxels. Unfortunately the new approach is more sensitive to screen resolution but even so it is on the good side of 16ms. The image shows 62663 components (stone and water) with 16x16x16 voxels each. So the total theoretical amount of voxels in the image is around 256,667,000 (a quarter gigavoxel) whereby the effective number is much smaller (there are only 1M pixels). However the old render approach would have scheduled approximating 4 million voxels for the same image which is much more than 62 thousand.

Approximating 250,000,000 voxels in 4.45ms on a notbook GTX850M

Approximating virtually 250,000,000 voxels (not all visible) in 4.45ms on a notbook GTX850M

2 thoughts on “Rendering HUGE Amounts of Voxels II

  1. Stefan

    Hey there,
    even though this post is a couple years old by now, it is very interesting.
    I implemented this myself following your example.
    https://youtu.be/vg0kKLbeLoU

    A few things i've been struggleing with is performance issues with overdraw. When you have "components" that do not fill up the entire block
    and hence force you to draw all sub surface voxels, it really becomes unfeasible. Or rather i have not found the right solution yet.

    Ither way, thanks alot for this.

    Reply
    1. Johannes Post author

      Hey,
      great to see that someone found this post and actually used it!

      I handled this overdraw problem by carefully designing the volume textures. The neighborhood handling mentioned above makes it possible to always generate full occlusion. I.e. if their is an adjacent voxel I made sure that the entire side has solid voxels. I only used gaps at the outer boundaries.
      This is especially visible in the ship image above. The stone blocks have always 8x8x2 solid connections.

      This might no be the solution you search for a general engine, where texture design is up to the user. Maybe you could simply stop after traversing one or two components and using a fallback texture for the block. This texture could be auto-generated from the existing colors in the volume texture. If this happens far enough inside, nobody will truly notice this random obfuscation.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *