Join Michael House for an in-depth discussion in this video Procedural voxel terrain, part of Advanced Unity 3D Game Programming.
In this video, I'm going to show you how you can create procedural terrain with voxels, and then you'll render it using iso services. Iso services have also been called metaballs, and you may have have heard of marching cubes and algorithms that generate iso services. ISO services are a strategy for displaying 3D data. In this case we have a three dimensional cube lattice, where each cube has a floating point value to define intensity. We're essentially going to wrap that lettuce in a mesh, so we can see it's shape. Let's look at a 2D example to make things clear.
Here we have a 2D array of floating point values. For each square we're going to find a crossover point. Where the density goes from solid to not solid. In this case, that point is at zero. So anywhere that a data point crosses over at zero, will be where an ISO line is rendered. Theses are all of the edges where data goes from positive to negative. Identifying all of these edges is the first step. Next, we need to find the point at which they are all zero. We can use interpolation to find where a point equals zero. For example, between one and negative one it's directly in the middle.
Between negative one and 0.2 it's much closer to 0.2 than negative 1. Calculating that interpolated position is the next step. Then, we will draw lines between each of the points, creating a surface, where the inside is non-solid, the outside is solid. We're going to do this with a 3D array of data. Let's check out some code. Our procedural terrain class is going to require a mesh filter and a mesh renderer. It's going to contain a 3D array of data, and our service cross value is going to be zero. This is going to mark the point at which terrain becomes solid, or not solid.
When creating this object, we are going to start by creating a mesh, filling our data array with floats, and then applying that data to the mesh. Let's check out filling the data. Filling data is going to happen inside three nested for loops so that we touch each cube inside this 3D lattice. We want to make the outside edges solid by wrapping the entire thing in non-solid. This will create a surface around the entire outside and give us something to look at. Additionally, for all the data inside, we want to calculate a PerlinNoise value, generating some coherent noise.
Next, we want to modify that data with a gradient value so that we're more likely to have air at the top and ground at the bottom. Now that we have data in our array, let's check out how we're going to generate a mesh. We're going to use our terrain mesh generator to fill the mesh, using the data at the specified size with the specified cross over value. The FillMesh algorithm is going to go through each cube in our lattice. It's going to get the data from all eight corners of each cube, and it's going to find the cross over values for each cube. Using edge lookup tables and triangle lookup tables, we're going to create triangles for each cube.
These triangles will represent the surface in that cube. Some cubes may not have no service at all, while others will have various shapes. All of these shapes combined will produce a surface that we can look at. Finally, we're going to add some empty texture coordinates. And then, apply our vertices, triangles, and coordinates, recalculate our normals and bounds, and return the mesh. Let's take a look at this in Unity. Here, we see our data has generated some kind of hill with a flat top. All of our edges have been closed off, so we can see.
And we can actually use the WASD keys to move through our terrain and look at different data points. Finally we can use the Q and E keys to change our service crossover value, this changes the crossover value between solid and not solid. So going very low will make everything very solid, going very high will make everything not solid. I suggest you play with these values to understand better what they do. Finally, to solidify our understanding of the filling data, let's add some data ourselves manually. This will add a chunk of data that is solid into the world at these coordinates.
So we can see at those coordinates there's now a solid chunk of data. And moving through the world we can see that chunk remains. It'll change size slightly just because of the data surrounding it is going to be larger or smaller, increasing or decreasing the density of this value. In this video, I've shown how you can generate a mesh given a point cloud. The data in this lattice is defined as density. We've wrapped the lattice in the mesh everywhere that the density equals zero. This mesh becomes the surface of the volume defined by the 3D array of data.
There's no proven or standard method for generating the data for terrain. And the majority of games will use Pearl in Noise, simplex noise, or some similar algorithm for generating coherent noise. The coherent noise is then transformed further, like we did with our gradient function. Remember that just because it's code, doesn't mean it isn't art. The math and algorithms that make the terrain are just as beautiful as the terrain they produce. It takes knowledge and practice to create an algorithm that produces nice results and fits the requirements of your game. The data behind your terrain is important, but so is the method of displaying it.
ISO services are robust and accurate ways to display such data. This technique allows you more control over the shape and details of your terrain.
NOTE: This course requires Unity 4.5.5. The newer versions of Unity have done away with the GUI system used in this course, so the interfaces included for many of the scenes will not work with 4.6 and higher. You can download Unity 4.5.5 at http://unity3d.com/get-unity/download/archive.
- Enabling/disabling with scripts
- Translating, rotating, and scaling objects with scripts
- Working with mouse and keyboard input
- Creating custom GUI controls like progress bars
- Loading assets with scripts
- Saving games
- Creating prefabs dynamically
- Making remote procedure calls
- Synchronizing object transforms
- Finding slow code
- Optimizing data access
- Extending the editor