Voxel Engine Part2 – Height map and Perlin Noise

So let’s look at the GamePlan from Part 1 :

  1. Setup a basic environment
  2. Create and texture a cube
  3. Display a bunch of cube
  4. Create a 3D array containing the information of all cube
  5. Fill the 3D array and display it
  6. Use a Perlin Noise algorithm to generate a coherent random world

We’ve got a couple of things done, but we still have a lot to do!! We finished Part 1 with a prism of cube displayed on the screen. However, we don’t know which cube is where and how to interact with them, they are just being displayed on the screen. To achieve this, we’ll make a 3D array that contain the blocktype of each cube, and we will draw them.

4.Create a 3D array containing the information of all cube

For this, I decided to create a new Class called Chunk. A chunk in minecraft, the most popular Voxel Engine, is a block of 16x64x16 cubes. When you move in the game, you keep loading/saving chunks and exchange them between your hard disk and your RAM. Right now, we will only work with 1 chunk and it will hold the information of it’s size itself, something that might eventually changes. Create a new C# script, call it Chunk.cs and add the following code to it :

using UnityEngine;
using System.Collections;
using System.Collections.Generic; // For List class;

public class Chunk
{

  // Size of the Chunk
  public int sizeX = 25;
  public int sizeY = 15;
  public int sizeZ = 25;

  // Declare the 3D block Array
  public int[,,] blocksArray;

  // Initialize the block array
  public void Initialize()
  {
    blocksArray = new int[sizeX, sizeY, sizeZ];
  }
}

I know I could use List to have resizable array. However, the size won’t change after the chunk is created and List seems to bring a small overhead that I’d like to avoid since we’ll work with lots of cube.

5.Fill the 3D array and display it

In CubeGeneration.cs, let’s initialize a Chunk, fill it with random cubes and display it. I got rid of the CreatePrism function because we won’t need it anymore. Here is the up-to-date version of cubeGeneration.cs :


using UnityEngine;
using System.Collections;

public class CubeGeneration : MonoBehaviour {

public GameObject CubeGrass;
public GameObject CubeRock;
public GameObject CubeSand;

private Chunk _CurChunk;

// Use this for initialization
 void Start ()
 {
// Create a Chunk
_CurChunk = new Chunk(); // Instantiate a new Chunk
_CurChunk.Initialize(); // Initialize its blockArray
   FillChunk(); // Fill the _CurChunk with blocktype
   AddChunk(); // Add all the cube to the current scene
 }

// Randomly fill the chunk
 void FillChunk()
 {
  int maxHeight = _CurChunk.sizeY;

  int _curHeight;

  for(int i = 0; i < _CurChunk.sizeX; i++)
  {
    for(int k = 0; k < _CurChunk.sizeZ; k++)
    {
      _curHeight = Random.Range (0,_CurChunk.sizeY+1); // I use _CurChunk.sizeY + 1 since it is exclusive

      // Fill blocksArray with different value according to the height of the block
      for(int j = 0; j < _curHeight; j++)
      {
        if(j == _curHeight - 1)
          _CurChunk.blocksArray[i,j,k] = 1; // Grass
        else if(j == _curHeight - 2)
          _CurChunk.blocksArray[i,j,k] = 3; // Sand
        else
          _CurChunk.blocksArray[i,j,k] = 2; // Rock
      }
    }
  }
}

// Create a cube of the right type for each entry of the blocksArray
 void AddChunk()
 {
   for(int i = 0; i < _CurChunk.sizeX; i++)
   {
     for(int j = 0; j < _CurChunk.sizeY; j++)
     {
       for(int k = 0; k < _CurChunk.sizeZ; k++)
       {
         if(_CurChunk.blocksArray[i,j,k] != 0)
         {
           CreateCube(i,j,k, _CurChunk.blocksArray[i,j,k]);
         }
       }
     }
   }
 }

// Create a Cube at the inputted position with a texture matching the blockType
 void CreateCube(int _posX, int _posY, int _posZ, int _blockType)
 {
   GameObject _CurCube = CubeRock; //Local variable that only remember the good prefab to be displayed

   // _blockType is inputted as an integer. 1 = Grass, 2 = Rock, 3 = Sand
   if(_blockType == 1)
     _CurCube = CubeGrass;
   else if(_blockType == 2)
     _CurCube = CubeRock;
   else if(_blockType == 3)
     _CurCube = CubeSand;

  // Instantiate a new cube at the inputted position, facing identity
   GameObject.Instantiate(_CurCube, new Vector3(_posX, _posY, _posZ), Quaternion.identity);
   }
 }

If you hit play, you should get something like this : 6 - RandomChunk

Fig1 – Displayed cube from a randomly generated Heightmap

6.Use a Perlin Noise algorithm generate a coherent random world

Having all those cube randomly generated is pretty cool, but it won’t make for a nice level. This is where Perlin noise come to the rescue! Perlin noise give you a random coherent array. To make a short story, perlin noise stack different noise together. The low frequency noise will give mountain and valleys  and the high frequency noise will give it some kind of roughness to the terrain. Look at this link for a technical definition.[Edit: As pointed by Carlos in the comments, this link is for Value Noise but give a good idea of perlin noise] If you’re interested in going deeper with Perlin noise, look at Simplex noise which is an updated version of Perlin noise.

I thought about implementing it myself at first. However,  a quick google search gave me a nice and ready C# implementation so I’d figure it’d be quicker to use it. Here is a link to the the page explaining the implementation. Here is another link that helped while debugging it. I cleaned the code a bit and implemented it in a C# script called PerlinNoise.cs. 

using System;
using System.Collections.Generic;
using System.Text;

public class PerlinNoise
  {
  static Random random = new Random();

  // Tools
  public static T[][] GetEmptyArray(int width, int height)
  {
    T[][] image = new T[width][];

    for (int i = 0; i < width; i++)
    {
      image[i] = new T[height];
    }
    return image;
 }

 public static float Interpolate(float x0, float x1, float alpha)
   {
     return x0 * (1 - alpha) + alpha * x1;
   }

// Perlin Noise Generation
 public static float[][] GenerateWhiteNoise(int width, int height)
 {
   float[][] noise = GetEmptyArray(width, height);

    for (int i = 0; i < width; i++)
    {
      for (int j = 0; j < height; j++)
      {
        noise[i][j] = (float)random.NextDouble() % 1;
      }
    }
 return noise;
 }

public static float[][] GenerateSmoothNoise(float[][] baseNoise, int octave)
 {
   int width = baseNoise.Length;
   int height = baseNoise[0].Length;

   float[][] smoothNoise = GetEmptyArray(width, height);
   int samplePeriod = 1 << octave; // calculates 2 ^ k
   float sampleFrequency = 1.0f / samplePeriod;

for (int i = 0; i < width; i++)
  {
  //calculate the horizontal sampling indices
  int sample_i0 = (i / samplePeriod) * samplePeriod;
  int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
  float horizontal_blend = (i - sample_i0) * sampleFrequency;

  for (int j = 0; j < height; j++)
  {
    //calculate the vertical sampling indices
    int sample_j0 = (j / samplePeriod) * samplePeriod;
    int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
    float vertical_blend = (j - sample_j0) * sampleFrequency;

    //blend the top two corners
    float top = Interpolate(baseNoise[sample_i0][sample_j0],
    baseNoise[sample_i1][sample_j0], horizontal_blend);

    //blend the bottom two corners
    float bottom = Interpolate(baseNoise[sample_i0][sample_j1],
    baseNoise[sample_i1][sample_j1], horizontal_blend);

    //final blend
    smoothNoise[i][j] = Interpolate(top, bottom, vertical_blend);
  }
 }

return smoothNoise;
}

public static float[][] GeneratePerlinNoise(float[][] baseNoise, int octaveCount)
 {
   int width = baseNoise.Length;
   int height = baseNoise[0].Length;

   float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing

   float persistance = 0.45f;

   //generate smooth noise
   for (int i = 0; i < octaveCount; i++)    {      smoothNoise[i] = GenerateSmoothNoise(baseNoise, i);    }  float[][] perlinNoise = GetEmptyArray(width, height); //an array of floats initialised to 0  float amplitude = 1.00f;  float totalAmplitude = 0.0f;  //blend noise together  for (int octave = octaveCount - 1; octave >= 0; octave--)
 {
   amplitude *= persistance;
   totalAmplitude += amplitude;

   for (int i = 0; i < width; i++)
   {
     for (int j = 0; j < height; j++)
     {
       perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;;
     }
   }
 }

 return perlinNoise;
}

public static float[][] GeneratePerlinNoise(int width, int height, int octaveCount)
  {
  float[][] baseNoise = GenerateWhiteNoise(width, height);

  return GeneratePerlinNoise(baseNoise, octaveCount);
  }
}

Now that the perlinNoise is implemented, all we need to do is change how the chunk is filled in CubeGeneration.cs. Update the FillChunk() function with this :

void FillChunk()
  {
  int maxHeight = _CurChunk.sizeY;
  int octaveCount = 5;
  int _curHeight;

  float[][] perlinNoiseArray = new float[_CurChunk.sizeX][];

  // Calculate the perlinNoiseArray
  perlinNoiseArray = PerlinNoise.GeneratePerlinNoise(_CurChunk.sizeX,_CurChunk.sizeZ,octaveCount);

  for(int i = 0; i < _CurChunk.sizeX; i++)
  {
    for(int k = 0; k < _CurChunk.sizeZ; k++)
    {
      _curHeight = (int)(maxHeight*Mathf.Clamp01(perlinNoiseArray[i][k]));

     // Specify the type of block according to its height
     for(int j = 0; j < _curHeight; j++)      {        if(j == _curHeight - 1)          _CurChunk.blocksArray[i,j,k] = 1; // Grass        else if(j == _curHeight - 2)          _CurChunk.blocksArray[i,j,k] = 3; // Sand        else          _CurChunk.blocksArray[i,j,k] = 2; // Rock        if(j > 8)
         _CurChunk.blocksArray[i,j,k] = 2; // Rock
      }
    }
  }
}

You can see that _curHeight is now calculated the perlinFunction. Mathf.Clamp01 make sure that it is between 0 and 1. It is then multiplied by the maxHeight to fill the chunk accordingly! I also added rock to cube above level 8 to give a bit of life.

As it is right now, the PerlinNoise is hard to customize. You need to go in the PerlinNoise.cs file to change amplitude and persistance. I will change it later or implement my own version of perlin noise. However, I can say that I get the best result with 5-7 octave, an amplitude of 1.0f and a persistance of 0.5f. Anyway, try it yourself. 7 - PerlinNoiseChunk

Fig2 – Displayed cube from a Perlin Noise generated Heightmap

Yeah! It works! Well, I hope it does if you try it at home!!. You can draw something like 75x15x75 and it should 3-20sec depending of your hardware. However, you’ll quickly notice that the performance drop quickly. There is too much cube being drawn on the screen. We will need to do some Culling. Unity Pro has an Occlusion Culling feature. However, I don’t have Unity Pro and I’m interested in learning it myself.

In Part 3, we will add a small culling feature that will save a lot of draw, because we currently are drawing everything even if it’s hidden. We will also add a small GUI to see the result of the culling. I will give the full code-source. Stay tuned!

Advertisements

15 thoughts on “Voxel Engine Part2 – Height map and Perlin Noise

  1. Pingback: Minecraft-Clone – Part 1 | LearningGeek Blog

  2. Hi Carlos,

    Thanks for pointing that out. I wasn’t aware of the distinction. I will make sure to update the link in the post to correct this.

    Reading the comment on the site of the implementation I used (devmag.org), it seems it is also a Value Noise and not a Perlin Noise. I’ll make sure to update the post accordingly.

    • Hi mac,

      Looking at your code, it seems you added “using System.Linq;”.

      I couldn’t replicate your bug, but it seems you’re not alone to face it. Did this fix your problem? Hope it worked for you!

  3. I am trying to follow along with this, but I am getting errors due to the

    public static T[][] GetEmptyArray(int width, int height)
    {
    T[][] image = new T[width][];

    for (int i = 0; i < width; i++)
    {
    image[i] = new T[height];
    }
    return image;
    }

    it is saying The type or namespace name 'T' could not be found. Any help would be appreciated. This is good stuff though, just wishing I could work through this part but I am stumped.

    • Hi,

      I never had this issue on my end. Here are two things that might help :

      1 – Add using System.Linq; It seems to have worked for the user mac who had the same issue.
      2 – Change the target runtime to Mono /.NET 4.0 by going to Project > Project Options > Build

      I’d like to know if any of this fixed the problem. Good luck!

      • Hi,

        I’m sorry then, I can’t help you. Without being able to replicate the error it’s really hard for me and I can’t figure why you get the error while I don’t. Are you using Unity 4+ with Windows 7? This is what I use.

        Hope you liked the tutorial even if you can’t get pass this bug :\

  4. Hi,
    the problem is that the class T doesn’t exist. The error is at PerlinNoise class.
    i’m using unity 4.3 , windows 7 64 bit and visual studio 2013.

  5. Hey guys who had the T errors. I faced the same issue, and that was with copy/paste. I know it’s been awhile, just wanted to say that it appears that the issue is with T, but you can replace it with float to make it drop the errors. I could not find T listed anywhere as a MD Array, so I’m not sure what it represents

  6. I manage to fix it adding to GetEmptyArray method and looks like

    public static T[][] GetEmptyArray(int width, int height)

    Also need to add the type when calling GetEmptyArray that are in 3 parts just add this

    GetEmptyArray(0, 0);

    And it will work, also I used net 4.0 but I’m using mac don’t know it mono behaves different in this aspect with ms net 4.0

    Regards

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s