3D Cave Generation
Paul Molnar, May 2022
If you found your way to this page it is because you are interested in learning how to make 3D cave terrain in Unity. There are many different ways to do this, but the way that will be discussed in this wiki page generates procedural terrain using the marching cubes algorithm. This method of terrain generation is pretty old and relies on two components in order to work. A density function and the marching cubes mesh creation algorithm. If you're confused about what that means it is ok, as both options will be described in depth further into this page. A bigger question is why might you want to generate 3D caves for your game? Maybe you want to analyze a certain cave structure in the real world and have a function approximation for it. Maybe it is because you want to generate a cave for an interactive exploration game or app to have a compelling environment. Regardless there are many reasons to want cave terrain and this page will show you a way to create it in real time. Below are some example projects created using this exact algorithm.
This program uses a complex density function to get the features inside such as the icicles
Checkout this link to run this program in VR:
Building Your Density Function:
What is a 3D density function and why is it so important to create quality cave terrain. A density function is in the form: f(x, y, z) = c . This essentially means that for any continuous (x, y, z) point the function should spit out a constant. You then also have some threshold value k. If c > k at that point then that location is solid. If c < k then it means the location is empty. In order to choose a function and threshold for your application there are several factors that you will want to consider. How much of your terrain do you want to be random, and how much of your data do you want to be fixed? For example, the floor and ceiling should mostly be fixed besides the occasional lump, and everything in between should be some kind of random boulder effects. In order to get the random boulder effects as seen in the images above you will need to use a function called Mathf.PerlinNoise(x, y) . This function will take in an x and y value and output a semi random scalar value. The (x, y) pair will always return the same constant so your density function will always be the same unless you apply some kind of random offset to the (x, y) pair. Now you may be thinking how do I extend this to 3D from 2D. One solution is simple, you can just plug in (x, y), (y, z), (x, z) into the noise function and average their values together. Then to get the hard boundaries such as the ceiling and floor you just have to check if floor < y < ceiling . If it is in between floor and ceiling just set the value to be higher than the threshold. You can also apply scaling values to your Perlin Noise if the scale of the randomness is too small or big for your liking.
Marching Cubes Algorithm:
The marching cubes algorithm turns your density function into a mesh by breaking the space up into a voxel grid and building triangles between the solid neighboring points. There is lots of documentation out there about how to go about doing this process, unlike the density function creation, so less time will be spent explaining this. For background check out this video by Sebastian Lague: Marching Cubes Video. The basic idea is to loop through a chunk of voxels say 32 x 32 x 32 each of which is 1 Unity unit in volume. Then get the 8 points at that location for the edges of the voxel and check if they are dense or empty. Once you know the points that are dense, compute the binary number given those points and index into the table (explained in the video linked and inside his repo in the description of the video) with the binary number to get the triangles to add to the mesh. Then simply add those triangles to your working mesh and repeat until each voxel in your 32 x 32 x 32 chunk has added its triangles. You may run into problems with the amount of triangles you can fit in your mesh in which case you can optimize by not adding the triangles if they are at the binary number 0, or the max number in the table. You can also decrease the size of your chunk or increase the memory that you let Unity allocate for meshes. Once you've successfully implemented a density function and the marching cubes algorithm you will have a single chunk representation working.
Possible Extensions and Results in VR:
There are many ways you can extend your single chunk representation. You can generate lots of chunks in a grid such as the way Minecraft does it and then only render the mesh when your character model enters a trigger attached to the chunks. This allows you to render lots of chunks but not all at the same time, which will reduce lag and still give you lots of environment to explore. Another way to extend this is through features like material shaders, depth-of-field shaders, and even more interesting terrain like icicles. These features are all implemented in the ice cave example in the image on the left. Another possible extension is to make your character VR controlled. This will make it feel like you are actually inside of the cave environment and gives players a very interesting way to interact with the chunks you just generated. The right image is a VR game where you can explore underwater environments with friends and search for treasure. Overall this 3D cave generation algorithm works very well with VR and gives great results as long as you implement smooth motion to stop motion sickness.