VRscosity Dev Blog #3: Mind your loops

In an upcoming DevBlog I’m going to be talking at length about the steps taken to generate the graphical side of VRscosity. It’s relatively boilerplate stuff – Voxel engines have effectively been long solved, so it’s simply a case of implementing something I’ve done a couple of times before.

…Which is why a particular prototyping bug I had drove me up the wall.


The Symptoms

In brief, the final render system works similar to how other such systems do. A chunk is generated by building meshes by vertices and then once the desired size has been reached, a new chunk is started, repeat. Any Voxels that don’t touch an empty space aren’t rendered, essentially culling a lot of the work that the extra meshes would cause the graphics card. Chunking in itself also lowers the amount of draw calls made, resulting in (generally) better performance all around.

The issue I was having was one of bloat. As each chunk was being generated, the amount of vertices required was exploding exponentially. By the time the final chunks were being drawn, they were trying to add too many vertices to the mesh (which, in Unity, is the hard 16 bit limit of 65536) and failing. Some of the chunks were reporting vertex collections close to 100,000 strong, which shouldn’t be anywhere near the case in a system which relies on each side being no more than 1024!

Holes in the mesh? This isn’t right.

The Source

I threw myself at this problem for more hours than I’d honestly like to admit, trying to work out a solution. Ultimately, after relying on console printouts to show me anything at all, I realised it was my own damn stupidity.

One of the issues with building 3D Voxel environments is that you essentially, somewhere, have to rely on six nested loops. Three for each direction (X,Y,Z) of chunk, and three for each direction of Voxel within the chunk. The mechanism is basically this:

//Setup chunkX/Y/Z here, and the expected chunkCount, then:
  while (chunkY < chunkCount) {
    while (chunkX < chunkCount) {
      while (chunkZ < chunkCount) {
        //Setup Voxel vertex factory, then do this:

        int y = Math.Max(chunkY * chunkEdgeSize, 0);
        int x = Math.Max(chunkX * chunkEdgeSize, 0);
        int z = Math.Max(chunkZ * chunkEdgeSize, 0);

        while (y < ((chunkY * chunkEdgeSize) + chunkEdgeSize)) {
          while (x < ((chunkX * chunkEdgeSize) + chunkEdgeSize)) {
            while (z < ((chunkZ * chunkEdgeSize) + chunkEdgeSize)) { 
              //Generate vertex here
            z = 0; //Oops! - Read below
          x = 0; //Oops! - Read below
        y = 0; //Oops! - Read below
        //Update chunk mesh and apply material
      chunkZ = 0;
    chunkX = 0;

This code is a mess, but it works and as a prototype that was the goal. It’s simple enough, but for some reason was causing overdraw and I had no idea why – especially when the internal mechanism for drawing the vertices was working perfectly.

It may be lack of sleep, but eventually it clicked, and I’m a moron.


The Cure

int y = Math.Max(chunkY * chunkEdgeSize, 0);
int x = Math.Max(chunkX * chunkEdgeSize, 0);
int z = Math.Max(chunkZ * chunkEdgeSize, 0);

So this section is designed to ensure that chunks only started drawing vertices at the current chunk location. It’s called at the start of a new chunk with the idea that the first vertex considered to be rendered for the second chunk, assuming chunks are 8 Voxels across, would be for the ninth Voxel.

But in a moment of stupidity that took me far too long to notice, I was zeroing the variables every loop, instead of calling the above! This resulted in a situation wherein the earlier chunks would “work” fine, but every loop the Voxels would be displayed from the very first one, every time, rather than from the chunk location. This resulted in both overdraw (as vertices were being placed in the same location multiple times), and come larger world sizes, massive amounts of vertices.

Basically the moral of the story is twofold: If you have a problem, come back the next day, work on something else in the mean time. And secondly. check your damn loop conditions.

One comment

Leave a Reply

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