Procedural 3D Terrain Generation
![]() | ![]() | ![]() |
|---|
Project Name: Procedural 3D Terrain Generation using Perlin Noise
Technology: Unity Engine, C# Scripting
Type: Solo Project
Description:
In this Unity app, I procedurally generated a 3D low-poly world with two distinct Biomes. The terrain is fully generated at compile time.
-
A landscape - with a mountain range including moving water, sand, greenery, and ice tops.
-
An island - which is covered by water on all sides and has moving water, sand, greenery, and ice tops.
Sequence of the procedural generation events that happens at compile time:
-
2D Noise map is generated using the Perlin Noise function
-
Octaves are generated on the same 2D noise map
-
The 2D Data is mapped to 3D coordinates of a new empty mesh
-
The vertices are corrected according to some of the variables like “Mesh Height Curve”
-
The mesh is divided into smaller triangles for LOD and optimization
-
Depending on the biome selected, the mesh is regenerated again
-
Colors as textures are applied to the mesh depending on the color map
-
Dynamic water is rendered around the mesh
Two main algorithms used:
-
Algorithm 1 for generating octaves:
For each octave we want to generate, we collect a random number between the range -100000 to 100000 and then generate noise from that seed. This random number is generated based on the system clock, making it unpredictable. Additionally, for each octave, a new seed is generated by offsetting the same seed from the previously generated octave. This whole random generation is applied to both X-axis and Y-axis of the current octave. The following is the main logic where octave calculation happens:
System.Random Rng_No = new System.Random(seed);
Vector2[] octaveOffsets = new Vector2[Total_Octaves];
for(int i=0;i<Total_Octaves;i++)
{
float offset_X = Rng_No.Next(-100000,100000)+ offset.x;
float offset_Y = Rng_No.Next(-100000,100000)+ offset.y;
octaveOffsets[i] = new Vector2(offset_X, offset_Y);
}
for (int i = 0; i < Total_Octaves; i++)
{
float sampleX = (x-halfWidth) / scale * frequency + octaveOffsets[i].x;
float sampleY = (y-halfHeight) / scale * frequency + octaveOffsets[i].y;
}
-
Algorithm 2 for generating island biome:
Creating this Biome was an interesting challenge. I had a 2D noise map filled with octaves and random noise. I was thinking of making a separate noise function to make the noise look like an islandic style noise (sort of a heatmap). However, I found out that masking a 2D gradient map with the current noise map can do the trick too. So I wrote a mathematical function to generate a 2D gradient map where all the pixels in the middle were dark(have full opacity), and the rest of the pixels had no opacity. The following is the mathematical formula:

Above function gives curve like this:

After implementing the above function in code:
Mathf.Pow(value, a) / (Mathf.Pow(value, a) + Mathf.Pow(b - b * value, a));
Above line of code generated this 2D noise map:

The Black color value in the pixel denotes its opacity. Totally dark pixels are opaque, and pixels shifts towards becoming transparent the more white color they have. In the end, I masked this noise map with the original biome barren terrain, which gave off the feel of an island. The white pixels(fully transparent ones) are overlapped by water.


