Marching Cubes Part 4: Level of detail with marching cubes
0. Introduction
This is the fourth part of a series on marching cubes. This time we will make it possible to pick between the level of detail (or resolution) of a mesh while sticking to a fixed physical size for the mesh.
1. Level of detail
The level of detail determines the complexity of a mesh. When a mesh is very far away from us, there is no need for the terrain mesh to be highly detailed, so we replace it with a less detailed mesh that still represents the same outlines as the higher detailed one.
Detailed here indicates the amount of vertices and triangles in a mesh.
1.1 Cleaning up
Before starting with a level of detail system, we should make it possible to regenerate a chunk whenever we want. Currently we only generate a chunk at start.
In our chunk.cs, rename our start method to “Create”. Call this new create method from start.
To allow for chunks and noise to be regenerated, we remove our Awake and OnDestroy method, and instead we create and release the buffers at the start and end of the create method.
The same goes for the EditWeights method.
Lastly, we can do the same in the noise generator.
Next up, we will simplify the dispatching of compute shaders. As you might have noticed, currently we repeat the line
quite often. We could however reduce this. In GridMetrics create a new constant variable for this.
Now, we have to replace every instance of the PointsPerChunk / NumThreads with ThreadGroups.
1.2 Multiple chunk sizes
The chunk size currently represents both the amount of points along one dimension (width, depth and height) as well as the physical size.
We will start with being able to increase the amount of points and than later make sure these points stay within a fixed physical size.
For this we will predefine a list of possible chunk sizes.
Remember, to avoid the GPU doing to much work, we should stick to chunk size that are divisible by 8.
Of course, now our ThreadGroups and PointsPerChunk don’t interact with the LODs, to fix this, we can make them into a static function.
Now we have to tell the chunk what level of detail it is on.
And of course after all these changes we have many errors. Currently our code still asumes we are working with a variable called PointsPerChunk, but this is now a function. So we have to give the function its parameter, the LOD.
At this point we can remove the OnDrawGizmos method if you haven’t already, as we will no longer use it.
The remaining errors are in the noise generator. The generator is a however a singleton (in other words, there only exist one, even if there would be many chunks). To solve this, simply give our LOD as a parameter.
Just as before, use the parameter to get the appropriate values.
The create buffers also requires the lod value, so provide a parameter for it as well.
Back in the Chunk script we now have to provide the GetNoise call with the LOD.
Before going into game, add an OnValidate function to the chunk. The OnValidate method is a unity built-in method that gets called whenever a value in our script changes via the editor. In other words, when we change the LOD slider in the editor the OnValidate method is called.
1.3 Fixed size
While we now have the ability to increase the size of a chunk via a slider, this is not actually level of detail. Level of detail means we cover a fixed physical chunk size with a variable amount of points. To facilitate a fixed size, create a new constant in our metrics, call it Scale. This scale represents the physical size. So if we have a scale of 20, our mesh always spans 20 meters, regardless of the amount of points along a dimension (represented by the chunk size in our case).
We now have to pass this variable to our MarchingShader in the chunk script.
Similarly, add a _Scale function to the MetricsCompute.compute.
All that is left to do is to scale down (or up) our points when adding them to a triangle inside the MarchingCubesCompute.
The formula is as follows:
In the first step we take an unscaledPoint and scale it into the range of [0, 1] by dividing the unscaled point by the ChunkSize - 1 (note: Our points range from 0 to ChunkSize - 1, not from 1 to ChunkSize).
We then scale this [0, 1] range to a [0, Scale] range by multiplying with the Scale variable.
While the marching cubes is now scaled, the noise is not.
The formula can be reused in the NoiseCompute as well.
Note that it is not the id that should be scaled, just like with the triangles we first apply all variables like we did before and only once that is done we scale the point.
Both the noise and mesh are scaled appropriatly to fill a fixed size using a variable amount of points. However there is one problem with the way we generate noise. Take a close look at the video above and then at this line of the noise compute.
You might notice that when the LOD increases, so does the ground level. This is because the ChunkSize variable increases.
Assume we have a GroundPercent of 0.5. This means that our ground level at LOD 0 (ChunkSize = 8) is equal to 4. When we use LOD 4 (ChunkSize = 40), the ground level is equal to 20. When in a later tutorial we want to implement an infinite terrain system, we of course want the chunks to match up and so the noise requires a stable ground level.
The fix is simple, the ground level should not be relative to the chunk size.
Then in the noise compute.
For ease, I decided on using a ground level relative to the scale. This can of course can be relative to any value or not relative to anything at all.
1.4 Interactivity
At this point when painting our terrain, we can get some unexpected behaviours, this is because we dit not yet down- or upscale any positions in the painting process.
Note: Currently I will not be going into painting a chunk and keeping track of its changes in other LOD’s, this will be covered later when we start working with multiple chunks.
Before using the formula
We need to make sure that one of the variables of the formula is a float, otherwise we will not be able to get floating point numbers. I decided on casting the id to a float3.
Note: In the March method this float3 value is achieved by the call to the interp method.
That concludes the level of detail system for this tutorial.
2. Possible problems
While at this point you should have a working LOD system, there seems to exist a bug in the Unity engine that makes it so that the NoiseCompute and the MarchingCompute seem unable to find the _Scale variable defined in the MetricsCompute.
There can be two solutions:
1) You made a typo somewhere in the shader and the problem is not actually related to the _Scale.
2) Try removing the line(s) with the _Scale variable and putting it back afterwards.