Stu Kettenring's profile

C++ Procedural Terrain Generator

Procedural Terrain Generation

One of my favorite types of game is open-world. My favorite part of these games is the fact that I have an infinitely generated random world to explore. So, I decided to create a procedural terrain generator in Unreal Engine 4 using C++!




A very important aspect of any video game is the world in which it is set. Although I enjoy creating scenes in Unreal Engine 4, I quickly grew tired of sculpting terrain by hand - it just took too long. I moved to importing heightmaps from tools such as World Machine, but it was limited in that I couldn't create terrain that would allow me to walk in one direction infinitely, forever.

There's several great tools out there that offer infinitely generated worlds, but as a hobbyist, I couldn't justify the price tag.


Third Party Libraries
There are many available noise libraries, and I needed something lightweight and fast. I settled on Jordan Peck's implementation because it is fast and super easy to use.


The basic theory behind the terrain generator, is at any point (x,y,z) in 3D space, grab the noise value for that (x,y) coordinate, apply some offset or modifier, and you have your height at that coordinate.

Now I have a grid from (0, 0) to (n, n) with pseudo randomized heights for each coordinate. Great! But what do I do with it?

Unreal Engine's UProceduralMeshComponent
I can't create UE4s built in terrain very easily with these randomized values (which is super unfortunate, because that would make applying landscape materials much easer - more on that later), but luckily Epic has provided exactly what I need. The Procedural Mesh Component (PMC).

The PMC enables me to create meshes at runtime. All I have to do, is create an array of all the points which should make up the PMC, and assign those points to triangles.

Once the points are calculated and triangles created, I can spawn the PMC:

Initial terrain with no normals or UVs for triangles:


Progress! It looks like terrain (kind of), but the textures look awful. To Fix this, I need to add some normals and UVs to the PMC.


Terrain with normals and UVs, and slope based landscape material:


Now I'm getting somewhere! Because I've added normals and UVs to the PMC, I can now apply a custom UE4 material that applies a different texture based on the slope of the mesh it is applied to. It's looking a lot better than it was before, but it's still too sharp​​​​​​​
To mute the "sharpness" of the materials and break up the gross tiled texture look, I borrowed a neat trick from Epic, called "Macro-Texture Variation".


Macro-Texture Variation:


The result:




Great! Now I have a randomly generated heightmap, and a pretty cool material to apply to the world. Make the material a material instance with the textures as parameters, and I can easily create new material instances with different textures on the fly.

I want to take a brief second to send you over to Aleksandr Ivanov's page, as the textures and other content he's created are absolutely beautiful. Not only that, but I reached out to him with a question I had about using his textures in a landscape material and he was kind enough to provide some examples and content that helped tremendously. Check it out, his stuff is great.

So now we have some terrain with a material that I like. What's next? 

Runtime generation
The next thing I wanted to do was generate terrain in real time as I run around the world. Because it takes some effort and time to calculate all the values that go into the PMC, I decided to create a class ChunkWorker that utilizes multi-threading in order to calculate subsequent "chunks" of terrain in real time.

To accomplish this, I leveraged UE4's Tick method. I created a new actor class ATerrainGenerator which is then placed in the world. On begin play, the actor will generate an initial chunk (or chunks depending on the user's input), and spawn the player character in the center of the spawned terrain.

Then, I check chunks surrounding the player to see if they've been spawned, and if they haven't, store them in a list to calculate the terrain height. When the ChunkWorker is available to process new chunks, I select the chunk from this list of chunks to process that is closest to the player. the I can determine if any given point belongs in a chunk by the (x,y) coordinates. The first chunk begins at the origin (0,0) and ends at (0 + offset, 0 + offset) and the next chunk begins at (0 + offset, 0) and goes to (0 + offset + offset, 0 + offset) and so on and so forth.

Once I've calculated the values needed for the new chunk, I then spawn it in the world (hopefully before the player has been able to get to that chunk).


Chunk generation during run time:



Foliage
Now that I have a heightmap with a terrain material that will spawn additional chunks in front of the player, it's time for foliage!

Because I am using the PMC I can't add foliage like I would if I were using the built in terrain. What I ended up doing was allow the user to add in their own foliage assets. These assets must be actor components with a UHierarchicalInstancedStaticMeshComponent. This component allows me to instance the foliage, which is essential for performance.

The user can tweak additional values like foliage density, which directly affects the placement of the foliage.


Terrain with randomized foliage added:


Atmosphere
To add some atmosphere, I decided to add some multi-colored fog to enhance the environment, as well as mask spawning chunks in the distance. I used a great tutorial from Jack B's YouTube channel underscore to accomplish this. Additionally, I added an additional PMC to each chunk at a preset water level, and now I've got atmosphere and water!

I think I'm pretty happy with the result!

Addition of multi colored fog to enhance atmosphere:


Biomes
I'm very happy with the terrain generator, but it really only creates terrain of one type of biome. I am currently working on adding multiple biomes, by using Cellular noise to split up segments similar to a voronoi diagram. Because this noise gives me a constant value for each segment, I can easily determine which biome any given coordinate belongs to. I then use a second Cellular noise function which does not have constant values, to calculate the "weight" of each biome. The values of this noise goes from -1 to 1, so if I normalize those values between 0 and 1 (0 closer to the edges and 1 at the center) I can multiply the specified biome's height noise amplitude and height modifier by this value, meaning that all biomes meet gradually and there are theoretically no sudden changes between biomes.

I still have a lot of work to do on biomes before I can share too much, but I'm excited to come back and update this project when I've made more progress!



Two biomes with drastically different amplitudes and height modifiers together:



Thanks for reading, and have a great day!


C++ Procedural Terrain Generator
Published:

C++ Procedural Terrain Generator

Published: