Procedurally generated terrain using a quadtree to determine chunk size and mesh resolution. Chunks closer to the camera end up at a higher depth in the quadtree and so will have a smaller size and higher triangle density. Further away chunks are larger and lower resolution to reduce number of chunk updates and increase performance.
Unused terrain chunks objects are stored in an object pool in order to avoid constant memory allocations. In the quadtree, only nodes which are currently leaf nodes represent chunks. After evaluating the entire tree each frame, any node which is no longer a leaf node will return it's chunk to the pool to be reused.
The most computationally expensive part of this project is the mesh generation for each chunk, with a visible increase to the frame time whenever a new mesh needs to be generated. To solve this, mesh caching is used to store generated meshes which allows the chunk to swap its mesh without having to generate the same mesh again.
Naive method, 4 Unity units per vertex
Quadtree method, variable triangle density
At a terrain size of 32768 Unity units squared, the quadtree method is 98.3% faster than the naive approach. Despite this, resolution near the camera is almost identical due to the quadtree based LOD.
Another method to improve the consistency of frame times was to split mesh generation over multiple frames through the use of a mesh queue. Every time the quadtree's evaluation function ran and new meshes were needed, a mesh task is pushed to the back of the mesh queue. Each frame the mesh queue runs for a user specified amount of time, before yielding and continuing the game loop. Any mesh tasks remaining at the end of the frame will be handled in the next frame.
Terrain generation is driven by fractional Brownian motion (fBm) which is calculated by summing multiple octaves of 2D Perlin noise. Low frequency, high amplitude octaves give the overall shape of the terrain, whilst high frequency, low amplitude octaves create the finer details.