Rendering huge landmasses such as planets is a difficult problem. From handling all the data while staying within memory limits to keeping vertex count and pixel fill-rate low enough to get smooth performance and 60 frames per second.
To solve these problems and prepare the next part of my planet generation series; I decided to take another look at how to implement level of detail.
Level of detail, or LOD, is a term for techniques which involve reducing the complexity of a scene to increase performance. Notable examples are models which are replaced with lower polygon versions as the camera is far away, trees replaced with billboards and terrain.
In the case of terrain LOD usually involves replacing the terrain grid with less tessellated versions as the camera gets further from the object.
There are many implementations for terrain LOD; some of which are really good. In this post I will briefly go over some of the techniques and explain how they may be adapted for the special case of a spherical terrain.
Chunked LOD is a CPU based implementation which splits the full terrain grid into 2n-1×2n-1 grids while n = the number of levels set by the developer.
As the camera goes further away, groups consisting of 2×2 adjacent grids are joined into one grid with half the detail. This means if your grids are 32×32 you essentially scale up by 2 and set the height data of the odd vertices from the previous 4 grids.
A more common way to look at it is by starting with a 32×32 grid which scales across the whole terrain size and replacing it with 4 32×32 grids at half the scale with additional height data where new vertices are added, effectively making it a single, higher detailed 64×64 grid. These 4 grids are structured as children of the lower detailed parent grid.
Chunked LOD is traditionally implemented for a flat grid. As we are using a grid and collapsing it into a sphere it is very easy to adapt to a spherical implementation.
There are two methods to do that. One is by creating a vertex buffer per grid and manipulating the positions on creation. Note that you should only create vertex buffers for active grids, otherwise you will run out of memory very quickly.
The second method which is to maintain a single vertex buffer with positions between [-1, 1] and apply an offset, rotation and scale for rendering each grid. For retrieving the height data I create a texture and use texture fetch in the vertex shader to displace the height. I also apply the cube to sphere equations inside the shader to collapse the cube.
An advantage of the technique is its scalability and that the grids can be efficiently culled and with each grid all of its children are culled too. Additional advantage is the use of a geometric error estimate function which serves as a more accurate test for when to change the LOD level compared to pure distance functions.
A disadvantage is working out the data streaming of the terrain. The technique is better when the whole data pre-exists either in memory or hard drive.
For my needs it means I had to create the height data on a different thread as the camera gets closer to the planet, save it to the hard drive (or SD card in case of mobile devices) and load it on demand. It is also possible to generate the data as you create the vertex buffers/height textures but for complex algorithms it is slower than streaming the data.
As I created a texture for each grid to fetch the height data in the GPU, I could have optimized the technique further by creating a single texture atlas and use geometry instancing to batch all the grids into a single draw call and apply texture coordinate offset.
That way it is possible to reduce to a single draw call and a single texture which would also reduce texture switches and glTexSubImage2D calls.
The original technique is detailed in the paper Rendering Massive Terrains using Chunked Level of Detail Control.
Another technique I experimented with in the past is GPU Clipmaps. The idea here is to use nested regular grids to keep high level of detail close to the camera and lower detail further away from the camera.
The main idea of the technique is to keep the geometry constant and centered around the player but update the height data into textures as the camera moves. These textures are then fetched in the vertex shader and used to displace the height of the vertex.
As this is also grid there was no reason why not to map it to [-1, 1] coordinates and collapse it into a sphere in the shader. As the grid needs to be centered around the camera's position all I needed to do is to create a look at matrix to keep the orientation correctly below the camera.
Because the sphere consists of 6 grids I chose one grid to be the center which implemented the GPU clipmaps technique and applied the same rotation matrix to the other grids to turn them away from the camera, allowing me to keep them with low vertex count and hidden from the user.
The technique is relatively fast and minimizes vertex buffer manipulations, most of the hard work is done on the GPU and during texture preparation. Additionally some of the grids can be culled by a frustum, reducing the texture updates and draw calls.
The disadvantages of the technique shows when the camera is above the terrain. As the size of the transition between level of detail grids is static, the transitions can be easily seen from altitude.
The original technique is detailed in the paper Terrain Rendering Using GPU-Based Geometry Clipmaps.
A final technique which I experimented with is my own idea inspired by GPU Clip Maps, using an Icosahedron with higher tessellation toward a center point which is kept oriented towards the camera.
The results were pretty good, except for the triangular shape which can be identified with the rotation of the camera. It also suffered a similar problem as GPU Clipmaps, with transitions being obvious as the camera was positioned at higher altitude.
Level of detail for terrain is not easy. There are many techniques each with their pros and cons.
Doing LOD for spherical terrain systems such as planets makes it even more difficult and emphasises some of the cons, making them appear more pronounced than they usually would.
My impression is that Chunked LOD gave the best results for visuals and with good enough pre-generation and streaming functions I was able to reach smooth performance on mobile devices. The devices I experimented with are the Nexus 5 phone and Nexus 7 (2013 Gen) tablet.