Terrain Editor Tool
As part of the education at The Game Assembly we have a specialization course that allows us to indiviually choose and work on a project within an area that interests us for 5 weeks half time.
Since I have really found an interest in tools programming as of late and because of my contributions to the editor part of our engine, I knew I wanted to create some kind of tool that could be integrated into the engine.
I looked at a couple of different options but ultimatly decided that a terrain tool seemed fun to work on and would bring the most to the engine. However, I didn't want it to be height map-based, instead I looked into other techniques that would allow for more dynamic terrain with overhangs and caves and such.
Enter Marching Cubes, an algorithm that I have known about for a while and found interesting but have never thought about actually using for anything and this seemed like the perfect time to do so.
Marching cubes is an algorithm used for constructing a mesh based on a 3D grid of points which contains a value that determines if the point is inside or outside the mesh. These points are then used to calculate how the final triangles should be placed.
I thought this would be well suited for the tool since in theory, all you need to do is manipulate the values of the points within a radius of where you click on the mesh and it would just work. With that simple base I also thought there would be space and time to explore some more intereresting features as well.
The Terrain Tool
With a nice way to build the mesh I of course also needed to make a tool to manipulate it as well!
I built the tool with the idea of different brushes in mind, and to create a very general base structure to which it would be easy to add new functionality in the form of new brushes.
The brush base class has some virtual functions to provide each brush the ability to define their own behaviours for manipulating a list of points and have their own settings in the imGui window.
I only ended up having time to create one functioning brush to manipulate the terrain, the sculpt brush, which has settings for intensity and radius (all brushes have this). It simply increases, or decreases, the values of the points in its radius with a dropoff applied so that points closer to the mouse position gets influenced more.
I also attempted two other brushes that I had ideas for how they could be implemented, the flatten brush and the smooth brush. None of them worked as I had planned however and I didn't feel like they were of high priority so I put them on the shelf, maybe I'll revisit them at some point in the future.
The flatten, or "Stair" brush
The smooth, or "Scramble" brush
I knew right from the start that I would be required to implement some kind of chunk system to seperate the mesh into smaller pieces, since manipulating and updating the entire mesh would very quickly get extremely slow.
Each chunk holds a seperate model and its own grid of points, and the system works tightly together with the brush system.
When updating the brush, a ray is sent out from the camera at the mouse position and the ray is then checked against all of the chunks bounding boxes to find all of the hit chunks, the list is also sorted with the closest one to the camera being at the beginning of the list.
I then iterate through the list of chunks and check the same ray against its mesh until one is hit. If a mesh was hit by the ray the active brush updates its position and draws its sphere in the world.
Then, if the mouse is held down, I gather all of the chunks that are overlapping the brush's bounding sphere and call the brush's manipulate function for every affected chunk. The brush then accesses the points that are within its range of influence and in some way manipulates the points.
Each chunk that was influenced by the brush then updates its mesh.
12*4*12 chunks with 32*32*32 points in each one
1 chunk (effectively 0 chunks) with same amount of points
Painting the Terrain
One big feature I wanted to implement in the tool was the ability to paint different textures on the terrain.
The first thing I had to figure out was regarding UVs since generating actual UV coordinates for the vertices would not really work.
I looked around for other ways to map a texture to a mesh and found a technique called Triplanar mapping/Triplanar projection. What it essentially means is that you sample the texture 3 times from the different axises and take the vertexs world position as the UV (xz-position for the y-projection, xy for the z-projection and yz for the x-projection).
Then these samples are blended based on some weights which are constructed from the normal of the vertex, a normal of (1,0,0) means the vertex is pointing straight to the right and thus will purely take the color from the yz-projection while a normal of (0,0.7,0.7) means it will blend 50/50 between the xy- and xz-projections.
Combined with a tiling texture this already looked way better than the hardcoded UVs I had initially, I even tried a common application of the technique where a separate texture is used for the y-projection to automatically have a grass texture on top with rock textures on the sides.
That was not really something I felt was needed for the tool though, so I moved on to the next problem, which was to paint and store different textures in the mesh. I solved it by keeping weights for the different textures in the points themselves, then when I interpolate the position of a created vertex between two points, I also interpolate between the texture weights of the points and write that value in the vertex colors of the new vertex.
In the engine we have support for up to 4 sets of vertex colors so in theory I could fit up to 16 different textures to be blended between, but I decided to limit it to 4 so it could fit in a single vertex color. The main reason for this is that since I already sample 3 times for each pixel from the projections, the amount of required samples would grow really big with larger amounts of textures.
The paint brush itself is very simple, it has a setting for which texture is the selected one and increases the value of the selected texture and decreases the values of the other textures, and then makes sure all of the values still add up to one.
Another thing I knew I wanted to do if the terrain would actually be usable in a game was to create collision for the mesh, a terrain you can't walk on is kind of useless after all.
Fortunatly the engine already had PhysX implemented and with it a way to bake a mesh collider so it was really easy to add support for baking to the tool. I made it so that each chunk remembers when it is modified so that only the chunks that have been updated actually bakes a new collider when the button is pressed.
Prior to starting this project I had never touched Compute shaders and this seemed like a good time to try them out.
Essentially, a compute shader is a shader that lives outside of the graphics pipeline and instead allows the user to utilise the GPUs incredible ability to do parralleled work to speed up calculations that are done in huge amounts and that are not dependent on eachother.
Marching cubes seemed like a great fit for learning compute shaders due to its nature of looking at indiviual sets of points and doing calculations on them.
I struggled quite a bit getting the shader to work properly and by the end when it did finally work, it unfortunatly did not give nearly as big of a performance boost as I was expecting and still carried a few bugs so I did not end up using it in the final product. I am still happy that I choose to do it though as I got to learn about a new technique that I can use in the future!
Overall I am very happy with the result of this project, I managed to finish all of the main goals and I hope that it will be a useful tool to us in the final project.
There is mainly one thing that I did not have time to implement, and which I did not expect to have time for either, and that is dynamic tesselation of the terrain so that the mesh is more detailed closer to the camera.
I really want to look at this at some point, or else the terrain is probably not going to be super useful and have to be very restricted in its scale.
I would also like to play a bit more with different brushes and settings for them such as different types of fall-off.