Voxel Performance

Updated on October 8, 2016
charles griffiths profile image

Charles is a software engineer and college professor interested in technology, medicine, economics, and nutrition.

Displaying in the geometry shader, triangle meshes, and Unity Terrain.
Displaying in the geometry shader, triangle meshes, and Unity Terrain. | Source

Voxel Problem

This project began when I wanted to make creatures, items, and landscapes out of voxels. I liked the idea of voxels because they can be used to build up and break down objects in an intuitive way. The problems were speed, memory use, and lack of standardization.

I knew I'd have to write my own voxel package, so I did a brief survey of some free and paid Assets for Unity. Most of them dodged any responsibility for performance, and were suited to using a few tens of thousands voxels in a game scene the way you might use particle effects. I wanted to use voxels as a core game mechanic.

Even with bloated classes used for individual voxels, modern PCs have so much RAM that it's almost not a problem. The biggest problem was speed.

Analysis

Profiling showed that the conversion of a voxel chunk into a mesh of triangles took up over 90% of runtime. Creating large voxel maps quickly was a simple matter of reusing calls to the Perlin noise function, and once converted to meshes a voxel volume performs much as any other object.

Converting chunks can easily be done in parallel, but with eight threads performance was still less than satisfactory. Using a geometry shader to display voxels more directly was no help and began to lag seriously with volumes of only a few million voxels. Sending only surface voxels to the shader handed the problem back to CPU threads. CPU performance is adequate for updates but not for the initial massive volumes required for random map generation.

Solution

Using a series of Compute Shader kernels to produce maps and compress them into an array of visible voxels turned out to be a satisfactory solution. A Geometry Shader can accept a GPU buffer that contains the array to avoid any need to copy to and from main system RAM before the initial display.

In the Unity Asset I wrote to implement and test this process, I settled upon cube voxel chunks 256 on each side that use one byte per voxel. Sending a chunk to or from the GPU would take less than a second on a newer PCIe bus but still be far too slow. With map generation and initial display wholly on the graphics card, the CPU is freed for other scene setup and raw voxel data can be transferred as needed (if at all).

Performance

The speed of the Compute and Geometry Shaders will depend on hardware, but an onboard GPU takes about a third of a second per chunk. Converting to a mesh takes a little longer and uses more memory. Converting to Unity TerrainData is very fast, but only stores a height map.

Sixteen million voxels (256 x 256 x 256) in a chunk take up 16 MB. Surface voxels are typically 512 KB (128K x 4 bytes) for each chunk ready for display.

Level Five Menger Sponge

Displaying 3.2 million voxels.
Displaying 3.2 million voxels. | Source

Stress Testing

This is a fractal that is sometimes used for stress testing voxel libraries because it has no hidden voxels. That is, every voxel has at least one visible face from some angle. It's 14.3 million (243 x 243 x 243) in total volume, with 3.2 million solid voxels.

Using an onboard GPU the FPS varies from 5 to 9 as the camera moves around and through the object.

Flying Through Menger Sponge

Flying Around Terrain

Demonstrations

The terrain demonstration video shows a grid of 16 chunks with all data generated at runtime and near the end you can see the entire 1024x1024 map. Even one 256x256 voxel area could be a game scene but with this asset much larger scenes can be generated and kept active with minimal impact on CPU and main RAM.

Implementation - Geometry Shader

A Geometry Shader has some overhead. One reason is that it can produce a variable amount of output that will need to be moved into a contiguous memory block. My solution is to produce the same amount of output for each geometry function call, and to minimize branching and copying as much as possible.

Each cube face requires four vertices, and the camera can see at most three faces depending on its position relative to the voxel. This makes for three branches that draw very similar faces, except for a shift along one of the axes. The shift variable is assigned to minimize the effect of the branch and used to adjust the face position when the vertices are created.

Geometry Shader

// For each voxel that is visible from some angle, paint the
// three sides that the given camera might see.
[maxvertexcount(12)]
void geom( point inputGS p[1], inout TriangleStream<input> triStream )
{
float4 pos = p[0].pos * float4( _Size, _Size, _Size, 1 );
float4 shift;
float4 voxelPosition = pos + _chunkPosition;
float halfS = _Size * 0.5;  // x, y, z is the center of the voxel,
                            // paint sides offset by half of Size
input pIn1, pIn2, pIn3, pIn4;

  pIn1._color = p[0]._color;
  pIn1.uv = float2( 0.0f, 0.0f );

  pIn2._color = p[0]._color;
  pIn2.uv = float2( 0.0f, 1.0f );

  pIn3._color = p[0]._color;
  pIn3.uv = float2( 1.0f, 0.0f );

  pIn4._color = p[0]._color;
  pIn4.uv = float2( 1.0f, 1.0f );


  shift = (_cameraPosition.x < voxelPosition.x)
        ? float4( 1, 1, 1, 1 ) : float4( -1, 1, -1, 1 );

  pIn1.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, -halfS, halfS, 0 ) ));
  triStream.Append( pIn1 );

  pIn2.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, halfS, halfS, 0 ) ));
  triStream.Append( pIn2 );

  pIn3.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, -halfS, -halfS, 0 )));
  triStream.Append( pIn3 );

  pIn4.pos = mul( UNITY_MATRIX_VP, mul( _worldMatrixTransform,
                     pos + shift*float4( -halfS, halfS, -halfS, 0 ) ));
  triStream.Append( pIn4 );

  triStream.RestartStrip();

...

Future Directions

I created this asset for my own use, packaging it into what I hope is a simple, performant, and reusable form. I have ideas for later versions, but I would also love to hear yours if you're willing to share them, or let me know how you're using this asset to conquer the world of voxels.

One idea I've been toying with lately is changing the pre-display optimization step to return several GPU buffers instead of just one. This would allow more specialized shaders to handle different voxel information, such as support for clouds, liquids, or a graphics change for different voxel faces.

Let me know what you think!

See Also

The video tutorial shown below is by Charles Humphrey who is a scholar and a gentleman for showing how to pass a GPU buffer from a Compute Shader to a Geometry Shader, a technique only recently available in Unity so it remains somewhat of a black art. The tutorial also shows how to achieve some weather effects in Unity using billboard mode so snowflakes are always facing the camera. It's a bunch of neat shader tricks that I think he'd like to pursue further and put into one of his assets on the Unity store but he's being pulled in ten different directions at once.

Related work with weather, and inspiration for this asset

Comments

    0 of 8192 characters used
    Post Comment

    No comments yet.

    working

    This website uses cookies

    As a user in the EEA, your approval is needed on a few things. To provide a better website experience, turbofuture.com uses cookies (and other similar technologies) and may collect, process, and share personal data. Please choose which areas of our service you consent to our doing so.

    For more information on managing or withdrawing consents and how we handle data, visit our Privacy Policy at: https://turbofuture.com/privacy-policy#gdpr

    Show Details
    Necessary
    HubPages Device IDThis is used to identify particular browsers or devices when the access the service, and is used for security reasons.
    LoginThis is necessary to sign in to the HubPages Service.
    Google RecaptchaThis is used to prevent bots and spam. (Privacy Policy)
    AkismetThis is used to detect comment spam. (Privacy Policy)
    HubPages Google AnalyticsThis is used to provide data on traffic to our website, all personally identifyable data is anonymized. (Privacy Policy)
    HubPages Traffic PixelThis is used to collect data on traffic to articles and other pages on our site. Unless you are signed in to a HubPages account, all personally identifiable information is anonymized.
    Amazon Web ServicesThis is a cloud services platform that we used to host our service. (Privacy Policy)
    CloudflareThis is a cloud CDN service that we use to efficiently deliver files required for our service to operate such as javascript, cascading style sheets, images, and videos. (Privacy Policy)
    Google Hosted LibrariesJavascript software libraries such as jQuery are loaded at endpoints on the googleapis.com or gstatic.com domains, for performance and efficiency reasons. (Privacy Policy)
    Features
    Google Custom SearchThis is feature allows you to search the site. (Privacy Policy)
    Google MapsSome articles have Google Maps embedded in them. (Privacy Policy)
    Google ChartsThis is used to display charts and graphs on articles and the author center. (Privacy Policy)
    Google AdSense Host APIThis service allows you to sign up for or associate a Google AdSense account with HubPages, so that you can earn money from ads on your articles. No data is shared unless you engage with this feature. (Privacy Policy)
    Google YouTubeSome articles have YouTube videos embedded in them. (Privacy Policy)
    VimeoSome articles have Vimeo videos embedded in them. (Privacy Policy)
    PaypalThis is used for a registered author who enrolls in the HubPages Earnings program and requests to be paid via PayPal. No data is shared with Paypal unless you engage with this feature. (Privacy Policy)
    Facebook LoginYou can use this to streamline signing up for, or signing in to your Hubpages account. No data is shared with Facebook unless you engage with this feature. (Privacy Policy)
    MavenThis supports the Maven widget and search functionality. (Privacy Policy)
    Marketing
    Google AdSenseThis is an ad network. (Privacy Policy)
    Google DoubleClickGoogle provides ad serving technology and runs an ad network. (Privacy Policy)
    Index ExchangeThis is an ad network. (Privacy Policy)
    SovrnThis is an ad network. (Privacy Policy)
    Facebook AdsThis is an ad network. (Privacy Policy)
    Amazon Unified Ad MarketplaceThis is an ad network. (Privacy Policy)
    AppNexusThis is an ad network. (Privacy Policy)
    OpenxThis is an ad network. (Privacy Policy)
    Rubicon ProjectThis is an ad network. (Privacy Policy)
    TripleLiftThis is an ad network. (Privacy Policy)
    Say MediaWe partner with Say Media to deliver ad campaigns on our sites. (Privacy Policy)
    Remarketing PixelsWe may use remarketing pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to advertise the HubPages Service to people that have visited our sites.
    Conversion Tracking PixelsWe may use conversion tracking pixels from advertising networks such as Google AdWords, Bing Ads, and Facebook in order to identify when an advertisement has successfully resulted in the desired action, such as signing up for the HubPages Service or publishing an article on the HubPages Service.
    Statistics
    Author Google AnalyticsThis is used to provide traffic data and reports to the authors of articles on the HubPages Service. (Privacy Policy)
    ComscoreComScore is a media measurement and analytics company providing marketing data and analytics to enterprises, media and advertising agencies, and publishers. Non-consent will result in ComScore only processing obfuscated personal data. (Privacy Policy)
    Amazon Tracking PixelSome articles display amazon products as part of the Amazon Affiliate program, this pixel provides traffic statistics for those products (Privacy Policy)