Procedurally Generated Terrain
By Dennis Wiersma
During the first four weeks we had to make a couple assignments. The one I found the most interesting was Procedural Content Generation. There were so many methods you could use for PCG, I wanted to experiment with one of them. At first I wasn’t really sure what I wanted to make, but then I remembered what I originally wanted to make for my Mesh Generation assignment. My idea there was basically create a nice looking mountain range. Then when it came to actually defining my scope, I had some talks with students and a teacher. This helped me a lot in actually deciding what I wanted to build.
So in this report I will show you what kind of steps I took to make a landscape Generator based on Perlin Noise. It will also feature some objects on my landscape to make the Landscape look a bit more alive and realistic.
Table of Contents
- Start my project
- Starting with octaves
- Creating the mesh
- Generating the mountains
- Placing objects
So my idea for the Scope of my landscape generator is a RPG like game. The goal is to make a Landscape which a player could walk trough. Because of this the Landscape needs to be smaller and a bit more detailed. Therefore I want to add some objects to my Landscape, like trees. I also want to have the ground change color depending on the height of the terrain. The ground should be more like grass when you are on the lower part of the terrain. The higher you get the more change in color of the ground you should see. When you are in the mountains, the ground should look more like snow. And the mountains themselves should look like mountains with a more rock like color. For the trees I also want to have some changes depending on where you are in the landscape. The trees should change depending on what type of climate you are in. In the snow mountains there should be more spruce or pine trees whereas on the flat parts of the landscape there should be more like the trees you would see here in the Netherlands.
2. Starting my project
2.1 Creating Terrain
Before starting with my project, I had to decide which procedural generation method I was going to use. From the lecture about procedural generation I had gathered the information that Perlin Noise is a good method to use for creating a natural looking landscape. This is exactly what I wanted to make, so I figured this would be the best method to use for my project. I still did some other research about the possibilities of creating a natural looking method with procedural generation, but I wasn’t able to find much about other methods. All I could find was a source on using Voronoi Diagram for my landscape, but to me it didn’t really look like a natural terrain. There also simply weren’t enough sources for me to get a good idea on how to approach generating terrain with a Voronoi Diagram. After doing some more research I found enough sources that could help me with a landscape by using Perlin Noise, so I figured using Perlin Noise would be the best method for my project.
I wanted to start off by creating the mountains themselves with the GameObject Terrain. I figured since I wanted to make a terrain generator, using a GameObject called Terrain would be perfect for my project. To change the properties of Terrain I had to access terrainData. To change the properties from terrainData I created a new function called GenerateTerrain, which takes in the already existing properties from terrainData. Inside GenerateTerrain I can change these properties to create the new terrain. In this function I set the size of my terrain and I can set the height of the terrain. With SetHeights I can modify my height of the terrain. I then made a two dimensional float array for the width and the height of my terrain. By doing this every point in my terrain now has a float value attached to it. Now I can loop trough each value of the width and the height and give this a new value equal to my Perlin Noise value. Therefore I had to create another method that returns a float, which I would use to set the heights of my terrain. In this method I calculated the coordinates of the x and the y. Then I could use Perlin Noise with these coordinates and change the height of my terrain.
Now I have created a terrain which should look more like a mountain. But as you can see the terrain is pink. I then wanted to give my terrain some colour, but I wasn’t really able to do so. After doing some research I found that the only way to give terrain colour was by using a shader. This wasn’t really what I was after, so I decided to change the way I generated my terrain.
2.2 Creating a mesh
After my generated terrain with the Terrain GameObject had failed, I went back to the drawing board and wanted to start over. Since the my whole project was based on an idea I had for the Mesh Generation assignment, I figured why not create a mesh. So I then decided to make my mountains by using a generated mesh and changing the height with Perlin Noise. The idea is practically the same as with the Terrain GameObject, but now I should be able to change the colour of my Landscape without using a shader.
I started off by getting the number of vertices for my landscape, which is always Size + 1, since there is always one more vertex than the amount of quads in the mesh. To get the total amount you multiply the xSize and zSize with each other. To fill the mesh with quads, you need to loop trough the z-axis and the x-axis and keep on creating triangles. In these for-loops I can create the triangles needed to make a quad. The amount of triangles needed for my mesh is equal to the size of the x-axis multiplied with the size of the z-axis multiplied with 6, since 6 is the number of vertices to create a quad. To create the Mountain like terrain, I then needed to change the y-axis. I did this by changing the value of y while using Perlin Noise. Inside PerlinNoise you multiply the value for x and z with a heightMultiplier. I can then change the heightMultiplier to whatever number I want, so I can create mountains of different heights.
This results then creates a landscape with different heights, to make it look like mountains. But I wasn’t too happy with the end result. To me the mountains looked to pointy, they weren’t really realistic mountains. Each point in a mesh simply had a different height, which resulted in some of these points going straight up, creating more of a triangle like mountain. This still wasn’t really what I was going for.
3. Starting with octaves
3.1 Creating the noise map
At this point I decided I needed to do some more research on how to make my mountains look a bit more realistic. While doing this I found a source which explained that there are 4 parameters that decide how your Perlin Noise would look like. These four are scale, octaves, lacunarity and persistence. Scale determines at what distance you view the Perlin Noise, which basically means you can zoom in and out. Octaves determines the level of detail the Perlin Noise has, this can be used to produce natural looking terrains. Lacunarity determines how much the detail is added at each octave. And persistence determines how much each octave adds to the final version of Perlin Noise. When I read the description of what the octaves do, I knew this was what I needed to use to make my mountains look more realistic. So I started over again. This time I started off by visualizing the Perlin Noise, so I could have a general idea of what my mountains could look like before creating them.
To do so I started off by creating a method which would generate a noise which would give a grid of values between 0 and 1. Since we need to have the width and the height of the map, I create a two dimensional array called noiseMap. This array gets the values of the mapWidth for the x and the mapHeight for the Y. Then I want to loop trough this array, so I created two for loops, one to loop trough the height and one to loop trough to width. Then to determine what the values would be for my noise, I create two float variables. I set these equal to x and y divided by scale. If I would set them equal to just x and y, I would get the same integers every time, so to make sure that doesn’t happen I divide the x and y values by a float called scale. This gives me a float value which I can pass into the PerlinNoise. I ran into one problem here, which is that if scale is 0 you can’t really divide x or y by scale. To prevent his I made sure that if scale is equal or lower than 0, I would set this equal to a number just above 0. Now I can apply my Perlin Noise value to my noiseMap, without it failing.
Then I needed in order to actually see what my Perlin Noise value looks like, I needed to display it on a plane. In order to do so I needed a script which would translate my Perlin Noise values to a texture. I started this script off by creating a Renderer to be able to set the texture of the plane. Then for my method this takes in the noiseMap from the Noise script I created earlier, so I can check what the values are that needed to be translated to a texture. To check these values I create two variables called width and height, which are equal to the width and height of the noiseMap. This is set by getting the Length of noiseMap, 0 for the x of the two dimensional array and 1 to check the y of the two dimensional array. With these values I then create the texture to show the Perlin Noise values. This texture takes in the variables width and height.
In order to actually show the values, I need to create the colors for the values. So I create a Color array which has the size of the width * height. By doing so I have created an array of colors for all of the pixels in the noiseMap. With this array I can then create the map and set all of the pixels with the created colors. To set the colors I first loop trough all of the values of the noiseMap, so again two for loops for the x and the y values. Then in order to get the index for the Color array, I need to multiply y with the width of the map to get index of the current row and then to get the right column of the map I need to add x to this value. To actually show the difference between the different values of the noiseMap, I want to Lerp between the colors black and white depending on the value of the noiseMap. To apply these colors, I simply use SetPixels to the texture, to set the color for each pixel of the map. Then I want to apply this texture to my Renderer. I used textureRenderer.sharedMaterial, because when I used textureRenderer.material I had to go into the game view each time I wanted to see the texture of my noiseMap. By using sharedMaterial I was able to see the my changes to the texture in the scene view. This then creates a nice looking texture for my noise. So I can see how my noise is currently looking.
Now that I got my noise working fine, I needed to figure out how to add octaves to this noise. Because I needed the octaves to create a more realistic looking mountain, otherwise it would still look like the mountains I had already created.
3.2 Adding octaves
Then I went on to create the parameter I talked about before, the octaves. I added this to my GenerateNoiseMap method. Here I wanted to create the level of detail my map has. I added another for loop to loop trough my octaves, within this for loop I then added my code that I had already created for my noiseMap. I did this because octaves was necessary for the level of detail of my noise. If I loop trough the number of octaves and add another layer of Perlin Noise each time, the level of detail will change. Then I changed the way I add the perlinValue. Before I had it so the noiseMap was equal to the perlinValue. Now I made a new float called noiseHeight and I made this equal to the perlinValue. By doing this I made it so that now it keeps track of the noiseHeight for each octave. This then can create different levels of detail. After the for loop of the octaves has ended I then set my noiseMap equal to the noiseHeight, so it takes into account the noiseHeight of each octave when creating the noiseMap.
This then created a new noiseMap. This didn’t really look like what I was going for. After comparing this to the noiseMap I had before implementing the octaves part, I had way more black and grey parts in my noiseMap than I do now. So now I had to figure out how to get this back to normal. After doing some testing I learned that a lot of my values for noiseHeight were outside the range 0 to 1. Since Perlin Noise is used for a range of 0 to 1, I had to make sure that my noiseHeight values wouldn’t go outside of the 0 to 1 range.
I started off by creating two variables called minNoiseHeight and maxNoiseHeight. I set the maxNoiseHeight equal to float.MinValue and minNoiseHeight equal to float.MaxValue. Then I checked to see if my noiseHeight was greater than the maxNoiseHeight and if it was I would set the maxNoiseHeight equal to the noiseHeight. I did the same for minNoiseHeight except I checked if noiseHeight was smaller than the minNoiseHeight. By doing this I could check what the range was what my noiseHeight values would be between. By making maxNoiseHeight and minNoiseHeight equal to noiseHeight, I can now for sure what the maximum and minimum values of noiseHeight are.
I then was able to use these values in two new for loops. These loop trough the mapWidth and mapHeight again to check for each value in the noiseMap. Within these for loops I set the noiseMap equal to InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x,y]). Since InverseLerp returns a value between a 0 and 1, this means that if the current value of noiseMap is equal to minNoiseHeight, InverseLerp would return a 0 and if it is equal to maxNoiseHeight it would return a 1. So now noiseMap would always return a value between 0 and 1. This then creates a noiseMap which looks a lot more like my original noiseMap. Now it is looking a lot more grey and black instead of a lot of white on my noiseMap.
3.3 Creating a random seed
Then I noticed that my noiseMap was the same each time I generated it. Each time it would start looking the exact same as the previous time I generated it. So I wanted to implement a system with multiple options of my noiseMap. So I wanted to create a seeding system, where I could choose a different seed of the noiseMap if I didn’t like the current one, so each noiseMap would be a unique one.
To do so I created an object called randomSeed which would generate a random number. In order to make each noiseMap look unique, I needed to make sure my octaves all started in a different location. For this I created a Vector2 array with a length of the total number of octaves. Then I created a for loop which would loop trough all of the octaves. In this for loop I then create 2 floats which would eventually be used to set each Vector2 in my array. These two floats, offSetX and offSetY, get a random number between -100000 and 100000. I made these as big as I could, so there would be a whole lot of different possibilities for my noiseMap. Then I just needed to add the octaveOffSetX and octaveOffSetY to my sampleX and sampleY, so my perlinValue would actually take the created offset in consideration to make it look like the noiseMap is unique, depending on the seed that is given.
4. Creating the mesh
Now that I got my Perlin Noise working fine, it is time for me to start actually creating the mountains. For this I wanted to generate another mesh just like I did before, but then I wanted to implement my newly generated Perlin Noise with it. For my mesh I need two arrays, one Vector3 array for the vertices and one int array for the triangles. These arrays are the same length as I explained before when I created my first mesh. Then I have two for loops for the width and the height of the mesh. Inside these for loops I set the position of each vertex. I then made the triangles inside these for loops. I made an if statement which checks if the edge of the map is reached, which basically means that if the edge is reached there shouldn’t be any more triangles created. For the first triangle of a quad, it starts at the most top left position, which is vertexIndex in this case. From there we go down 1 vertex and move to the right, to get the vertex bottom right of the quad across from the vertex top left. This is vertexIndex + width + 1. I add the width here since I am now a whole row down and I add one since it is the second vertex in the row. For the bottom left vertex to complete the triangle, I move one row down from vertexIndex. For the second triangle I basically do the same, but then we start at the bottom right position. From there we go to the top left, which is the start point of vertexIndex. And from there we move right one spot, so vertexIndex + 1. To add a texture to the mesh, I have created an array for the uv, which has the same length as the vertices.
Then I made an DrawGizmos method to be able to show the vertices for the screenshot. This method only consisted of a for loop which would loop trough the vertices. And for each of the vertices it would draw a small sphere. I only created this to show that I have created my mesh.
5. Generating the mountains
5.1 Adding height to the mesh
Now that I have created my mesh, I could use this mesh to create the mountains for my terrain. To change the height of my mesh, I added an argument into my GenerateLandscapeMesh method from before. In here I made a float called heightMultiplier. This would be used inside the Vector3 for the vertices. In here I multiply the heightMap with the heightMultiplier. By doing so the height of the map changes with the amount I gave the heightMultiplier. This would give a result with a terrain with mountains. My problem here was that it didn’t really show any form of open space, which I could use as a ground or water in the future. It basically made my terrain full of mountains. There really wasn’t any open field. So to overcome this issue I simply changed the height of the Noise, so the value would be a lot lower, which would result in me having a lot more open space in my terrain.
5.2 Adding color to the mountains
Now that I got my mountains ready, I wanted to add color to my terrain. So I could make it look a lot more realistic. To do so I create a Struct called,, which takes in 3 variables, name, height and color. Name is there to give some meaning to the color, height is there to actually set till what value of height the color has to be shown and color is for the actual color on the map. With this Struct I can then make an array call landscapeType. This array is used for the number of different types of color I want on my map.
To actually set the colors, I first want to loop trough the map again. So I create two for loops for the width and the height of the map. Inside these for loops, I create a variable called currentHeight and set this equal to the value of the noiseMap. I do this so I can actually set the height at a certain height. With the currentHeight I can then check if this is equal or lower to the height I created in my Struct LanscapeType. If it is equal or lower, I want to set the color of the current index from landscapeType at the currentHeight. I place this if statement in a for loop again, because I want to keep doing this for each index of my array landscapeType.
6. Placing objects
6.1 Adding trees
With the color added, it was time to try and add in some objects in my scene. I wanted to add some trees in my scene, which would change depending on the height of the map. At first I wasn’t really show how to approach this. I struggled a lot, since I simply wasn’t able to figure out how to get my trees to spawn at the height that I wanted. I knew how to spawn my trees, but I needed them to spawn at the vertices which were higher then the value at which I wanted the trees to spawn. I just wasn’t able to figure out how to approach this. So after asking for some tips from my Guild, I was able to figure out where to start. I got the tip to try and get the y value of each of my vertices and then place the trees on these vertices. To do this I started of by creating a for loop which would loop trough all of my vertices. Then I created an if statement which would check if the y value of each of the vertices would be higher than the value I had given. If these vertices were higher, then I would Instantiate a tree on this position. This would create quite a lot of trees.
6.2 Make the forest look natural
Since I wanted the trees to look a bit more natural I tried to figure out how to add a bit of space between the trees. At first I was thinking about a system which would use Perlin Noise again, because I got this as a tip from Rosa to try to do this. But in the end I went with another solution. I created an array called randomNumber. This array would get a length of the amount of vertices in my generated mesh. Each vertex in the mesh would be assigned a random number. After checking what the values would somewhat be for each number, I figured out a value which would spawn a decent amount of trees, which would also look a bit more realistic. This value would be 0.25f. If the randomNumber would be lower than this value, I would check the if statement again for the height of the vertices and then spawn all the trees.
6.3 Using AnimationCurve and changing the colors
After I had made my trees look a bit more natural, I went back to change the way I handeld the way I made the open fields within my terrain. At first I simply subtracted a value from the noiseHeight, which would then create the open fields in my terrain. But this also made the mountains themselves a lot more round and flat. This didn’t look as realistic as I wanted it to be. So in order to change this I added an argument for an AnimationCurve to my GenerateLandscapeMesh method called heightCurve.
What AnimationCurve does is it gives me the possibility to change the height values of my map. The values on the x-axis in AnimationCurve represent the height of my map, and the values on the y-axis in AnimationCurve represent how I want to present them on the map. So I have set that all the height values from 0 to roughly 0.5, should basically all be flat. Starting from 0.5 the values start to increase a bit, which represents the mountains in my terrain. All I had to do to make this work, was change the value of the y-axis of my vertices. Instead of giving the heightMap value, I know had to give the heightCurve for the y-axis. This heightCurve would then evaluate the heightMap, which would then create the more realistic looking mountains.
My terrain still has some flat spots, but now the mountains look more realistic. They don’t look that round or flat anymore. I also decided that I wanted to implement some different colors in my map. Since I wanted my map to have some water in it, I added the colors for water and sand. So now I have 7 different colors in my map. Depending on the height of the map, the colors should represent water, sand, grass, mud, rocks and snow.
6.4 Finishing placing all the trees
So now all that was left for me to do was to add in all of the trees on my map. I had downloaded a package from the Unity Asset Store, which had 4 tree assets. I wanted to implement all of them. Depending on the height the trees should be different. High up in the mountains there should be Pine Trees. Around the beach there should be Palm Trees. On the grass I went for a mixture of two different trees. I created a integer called randomValue, which would be set to a random number from 0 to 10. Depending on the value of this randomValue, I would spawn a different tree.
The last thing I did was add a type of rock to my palmTrees. I got another package from the Unity Asset Store, this time with some rock assets. I wanted to implement some more, but I didn’t like the look of the other rocks. So in the end I only went with the rocks I have now put along side my palm trees on the beach.
Now that I have finished the project, I can say I am fairly pleased with the work I have done. Looking back I wish I hadn’t wasted my time at the begin trying to get my Terrain Object, just like my Mesh. If I had done enough research at the start, I would have figured out that to give my Perlin Noise some more detail, I would need to add the octaves to my project. Not doing enough research before starting basically cost me a lot of time, which I could have used to do something more valuable. I could have made more than I had originally planned to do. But in the end I managed to get my project to work exactly like I had planned with my scope, so I did what I wanted to do. I simply could have done more.
There are still multiple ways to try to improve my project. I briefly mentioned in my report, that there are 4 parameters for Perlin Noise, Scale, Octaves, Lacunarity and Persistence. In my current project I only have 2 of the 4 parameters added. So I could still fine tune my Perlin Noise some more. I also mentioned briefly about the spawning of my trees and how I could maybe have used Perlin Noise here to get my trees to spawn randomly. This would be a good place to try and improve my project, since it would add another layer of Procedural Generated Content to my project. I also would like to try to do something on the performance of my project. Currently all of the trees are spawned as clones, so if there are a whole lot of trees, the response time of my project goes down a lot.
Oliveira, R. (23 Juni 2019). Complete Guide to Procedural Level Generation in Unity – Part 1. GameDev Acedamy. Retrieved on 24 September 2020, from https://gamedevacademy.org/complete-guide-to-procedural-level-generation-in-unity-part-1/
Lague, S. (6 February 2016). Procedural Landmass Generation (E02: Noise Map). Retrieved on 24 September 2020, from https://www.youtube.com/watch?v=WP-Bm65Q-1Y
ItsKristinsSrsly. (20 April 2020). Procedurallly Generated Low-Poly Terrains in Unity. Retrieved on 24 September 2020, from https://www.youtube.com/watch?v=sRn8TL3EKDU
Squeaky Spacebar. (12 June 2017). Procedural Terrain Generations With Vonoroi Diagrams. Retrieved on 24 September 2020, from https://squeakyspacebar.github.io/2017/07/12/Procedural-Map-Generation-With-Voronoi-Diagrams.html
Lague, S. (23 November 2018). [Unity] Procedural Object Placement (E01: poisson disc sampling). Retrieved on 25 September 2020, from https://www.youtube.com/watch?v=7WcmyxyFO7o
Jorna, D. (14 May 2019). Procedural Terrain Generation. Retrieved on 25 September 2020, from https://davidjorna.com/Terrain1.html
Brackeys. (24 May 2017). GENERATING TERRAIN in Unity – Procedural Generation Tutorial. Retrieved on 28 September 2020, from https://www.youtube.com/watch?v=vFvwyu_ZKfU
Brackeys. (4 November 2018). PROCEDURAL TERRAIN in Unity! – Mesh Generation. Retrieved on 28 September 2020, from https://www.youtube.com/watch?v=64NblGkAabk
Scher, Y. ( 27 November 2017). Playing with Perlin Noise: Generating Realistic Archipelagos. Retrieved on 3 October 2020, from https://medium.com/@yvanscher/playing-with-perlin-noise-generating-realistic-archipelagos-b59f004d8401
Lague, S. (11 February 2016). Procedural Landmass Generation (E03: Octaves). Retrieved on 3 October 2020, from https://www.youtube.com/watch?v=MRNFcywkUSA
Lague, S. (13 February 2016). Procedural Landmass Generation (E04: Colours). Retrieved on 7 October 2020, from https://www.youtube.com/watch?v=RDQK1_SWFuc