Let’s see how to use noises to create 2D heightmaps and apply them to Unity terrains!
This article is also available on Medium.
This tutorial is available either in video format or in text format – see below 🙂
The power of procedural generation
When you develop games, you usually have two ways of designing your levels: you either do everything by hand and apply your game designer skills to this specific context; or you write up a machine, or a set of rules, that can be fed some initial start values and then automatically create a (valid) level for you.
This automated approach is called procedural generation: it relies on various algorithms and/or static data that are combined into a chain of building stages. For example, you can generate the 3d landscape, the assets in the scene, the NPCs, the quests, or even animations… Basically: all the content you can do by hand could theoretically be programmed – but in truth, some parts are easier to automate than others 😉
Also, those stages can be more or less deterministic: transitioning from Step 1 to Step 2 can either go always the same (fully deterministic), be completely random (fully stochastic) or have an in-between logic. Having a deterministic engine is usually very beneficial because it reduces the amount of data you have to store: if your logic always run the same, then you only have to save the initial values (the “seeds“) and those can be later re-used to completely reconstruct the level.
Of course, procedurally generated content is interesting in terms of content quantity: it’s easier to ask a computer to generate a thousand assets than an artist… and it will be way quicker! But, on the other hand, this content might be more generic, it might not be as high quality and it still requires to first code up the generator properly. This can be difficult; however, over the years, programmers have devised lots of tools and algorithms to cope with plenty of common procedural generation-related situations.
The big picture
Today, we’re specifically going to focus on mathematical noise functions, or “math noises”, and how they can be used to generate heightmaps procedurally.
Heightmaps are 2D greyscale textures that can be mapped to a 3d object and determine a vertical displacement (generally speaking, dark pixels are matched to lower areas and lighter pixels are matched to higher areas); here is an example with a simple ring:
Note that the smoothness of the heightmap is directly linked to how steep the slope of your “mountains” and “valleys” is!
In Unity, those heightmaps are particularly interesting when you work with 3d terrains, as we’re going to see in this tutorial…
Math noises are a bunch of well-known algorithms that people have come up with to get “high-level” random generators. The idea is usually to pass in an input vector (that can be 1D, 2D, 3D, nD…), optionally a seed to set the “context” of the generator and eventually return a float value in a predefined range (for example, [0, 1]).
If you want to learn more about using noises for random generation, I really recommend you watch this amazing talk by Squirrel Eiserloh from the GDC 2017 conference on noise-based RNGs:
So – are you ready? Then, let’s dive in! 🙂
Step 1: Displaying a 2D float array as a 2D texture
In this project, we’re going to compute noises and store them in 2d float arrays. But we also want to display those noises so we can actually see what they look like and how they can be used as heightmaps!
To begin with, let’s see how to create a 2d texture from an array of floats and apply this texture on a UI (raw) image to display it on-screen.
First things first: create a new Raw Image by going to the Game Object > UI > Raw Image menu. Make sure to take the “Raw Image” UI element and not the “Image” (because we won’t be using sprites but direct 2d textures):
You can put the image where you want on the screen – I myself moved it to the bottom-left corner, with a little margin. I also replaced the skybox in my Main Camera with a basic solid colour:
Now, we’re ready to start coding our
NoiseManager, the main manager for our scene. In this C# script, we are going to have a reference to our Raw Image, fixed values for the
height of the 2d texture to generate (stored as integers) and a method called
_SetNoiseTexture() that transforms a (2d) float array into a 2d texture and applies it to our Raw Image.
When we create a new 2d texture (with a given
height), we need to fill all of its pixels by assigning them the proper colour. This can be done using the
SetPixels() function but that method asks for a 1d array of
Color-type. So we need to perform a conversion from our 2d float array to a 1d Color array.
This conversion is actually pretty straight-forward:
- to go from a 2d array to a 1d array, we can use a common trick: the cell
(x, y)is matched to the cell
(x + width * y), i.e. you stack all of your rows one after the other in the 1d array
- we want our colours to be tints of gray, going from black to white: Unity has the built-in
Color.Lerp()method that allows us to pass in the “low” value (black), the “high” value (white) and the current “ratio” of colour lerping (the value of our noise at an
All in all, this gives us the following script:
Note that we need the
UnityEngine.UI package to be able to have a
Of course, this float array will just make for a black image – but this already validates that, when we run the game, we indeed create a 2d texture and apply it to our Raw Image:
Step 2: Generating a basic Perlin noise
Alright – it’s time to actually dive into math noises and implement our first simple noise: the Perlin noise!
This noise is quite interesting because it is very customisable, it makes smooth transitions between areas and it can be seeded (so your noise generator will consistently give out the same result if you have the exact same inputs). On the other hand, a slight change in those inputs translates into widely different noises. All of this makes the Perlin noise quite an intuitive-to-use and powerful procedural texture primitive.
A 2d Perlin noise typically looks like that:
Also, the Perlin noise is actually built-in in Unity! You can get a 2d Perlin noise value at a given
(x, y) position by calling the
Mathf.PerlinNoise() function. This method basically picks a 2d point on an infinite plane and returns its float value. Since this plane is infinite, you can go as far as you like and always get a value. So it’s really easy to compute for our project – that’s great 🙂
But! To anticipate the fact that we’ll eventually have a list of noises you can pick from, we’re going to create an abstract
Noise class for all of our noises to inherit from. C# abstract classes cannot be instantiated: only the derived classes that actually implement the logic can be instantiated; but they are useful for sharing data or method prototypes between all the child classes and enforcing a given interface.
For now, this parent class will only have one prototype: the
GetNoiseMap() function that returns a noise value for a 2d
(x, y) coordinate:
You see that we also pass in a
scale parameter: this is usually necessary for the noise to be adapted to the size of your image and actually have variations. It’s of course different from one noise algorithm to another; but for example, for the Unity built-in Perlin noise, if you don’t use the
scale, you’ll always get a constant value of ~0.4621!
Once again, remember that we can’t create a
Noise class instance directly: we need to create a derived class that inherits from it for our Perlin noise.
So let’s create a new C# script,
PerlinNoise.cs, with the following content:
We need to multiply the
(x, y) coordinate by the scale to avoid the aforementioned issues but other than that, we’re simply using the built-in method.
PerlinNoise class inherits from the
Noise and it implements the
GetNoiseMap() abstract function with an override; so here we have a logic that is specific to the Perlin noise, but has the same interface as all the other noises. This means that we’ll be able to easily instantiate and use our
PerlinNoise in the
NoiseManager instead of our mockup float array 😉
To do this, we just need to:
- create a new
_noisevariable (that will contain our current noise algorithm): it is of
Noisetype because, thanks to polymorphism, this type can be automatically casted to any derived-class type
- initialise this variable in the
Awake()method by creating a new instance of our
- use the
GetNoiseMap()function to get values for all the
(x, y)cells in our grid: we’ll need to have two nested for-loops that iterate through our
heightand fill in a 2d float array. Also, I’ll start with a hardcoded value for the scale (here: 0.15) but we’ll eventually store this in a
- pass this float array to the
_SetNoiseTexture()function so the generated noise is displayed in our Raw Image
We will create a new method,
_RecomputeNoise(), that takes care of creating the 2d float array based on the current
And tadaa! If we start the game again, we now have a nice 2d Perlin noise in our Raw Image:
Step 3: Applying the texture as a heightmap on a terrain
Terrains are a really neat feature of Unity – they’re such a specific topic that they have their own dedicated section in the docs 🙂
The basic idea of terrains is to provide you with a more “evolved” mesh that can be sculpted to create elevations and holes, painted to mix various ground and soil textures, populated with trees or rocks and even added some dynamics with wind zones.
The entire thing relies on brush painting which makes it pretty user-friendly and fun to learn. Also, it’s been tuned and optimised to be quite efficient, even if you have several texture layers and lots of forests everywhere. In particular, it takes advantage of the LOD (level of detail) concept to show more or less-detailed meshes depending on the distance between the objects and the camera; this way, you avoid having too much unnecessary subtle details on your trees if you’re looking at them from far away!
To create a new terrain, go to the Game Object > 3D Object > Terrain menu:
Terrain are probably larger than the objects you’re used to working with. If you zoom out, you’ll see that it’s quite big. But here, we want it to be scaled to fit with our 256×256 heightmap. So we need to go to the terrain settings and change some parameters in the Mesh Resolution and Texture Resolutions sections:
Now, we can add a reference to our terrain asset in the
NoiseManager and access its data whenever we set the noise texture to assign it both to our 2d Raw Image (already taken care of) and to our 3d terrain (we need to add that).
But there is a tricky thing with Unity terrains and heightmaps: when you pass in a 2d float array to your terrain to use as altitudes, it will actually consider it with a (y, x)-coordinate system instead of a common (x, y)-coordinate system. Meaning that if you pass it the
(x, y) coordinate, it will in truth put it in the
(y, x) cell!
If we keep our code as is, we’re going to have some inconsistencies between the 2d texture and the 3d terrain: the heightmap will appear to be “flipped” on the terrain. To avoid that, we have to change our logic a bit.
- when we create our 2d float array, we’ll fill in the
(y, x)cell so it is valid for the terrain heightmap system
- and then when we convert these float values to colours (in the
_SetNoiseTexture()method), we’ll make sure to re-assign the
(y, x)coordinate to our usual
This just translates in updating our indexers in both our for-loops – and eventually we assign the generated
noise array to the terrain using its
If you run the game again, you’ll see that the terrain now has some deformations, it has varying altitudes throughout its surface that match our 2d heightmap texture 😉
You can move and rotate your camera a bit to see both your 2d Raw Image and your terrain at the same time while in playing mode; here, I’m also using the camera orthographic mode:
Step 4: Adding some UI to tweak our scale parameter
It would be nice to see how the scale parameter affects the generated noise. For now, we have a hardcoded value of 0.15, but it would be better to have a variable that we can adjust thanks to a little slider somewhere on the screen.
To quickly setup some basic UI, you can take advantage of Unity’s old UI system, also known as the Immediate Mode GUI (or IMGUI), that relies on the
OnGUI() built-in entry point. This method is called every frame and “repaints” all the UI elements that should be shown on the screen (above the camera render).
If you want to learn more about the different UI components you can create (labels, buttons, groups…), you can check the Unity docs. Here, we’ll create a
HorizontalSlider in the top-left corner with values between 0.01 and 0.3 (remember before we had 0.15 and it was already quite big, because our 2d image is pretty small). This slider returns the updated value of the variable it controls, so you can re-assign it easily to get its new value:
Of course, we need to initialise this scale and make sure that, whenever the value changes, we recompute our noise.
But be careful: when checking for changes with the IMGUI, you might get false positives – for example if you press the handle on your slider and release it immediately (without changing the value), the UI will have “changed” (because you clicked something) but not the value.
To avoid recomputing the noise too often, we’ll store the previous value for our scale and add a little intermediary function that checks if the value has indeed change; otherwise, it will simply cancel the update:
If we rerun the game, we now have a little slider in the top-left corner and, whenever we change its value, the noise is recomputed with a different scale. We directly see how this impacts the distance between the “peaks” in our noise (both in our Raw Image and on the 3d terrain):
Step 5: Seeding our noise
As I’ve mentioned before, another key feature of mathematical noises is that they can be seeded. This seed is usually an integer that specifies the “context” of the noise function and can drastically change the values it returns. However, this change is deterministic, so reverting back to a seed you already had before will give you the exact same values for a given
(x, y) cell.
This is very powerful and not that hard to implement!
What we’ll do to add seeds in our project is:
- add this
seedparameter to our abstract
Noiseclass (so that all noises expose this variable)
- use it in our
PerlinNoiseto change the values properly
- finally, add another slider in our UI to make it easy to change and set the
seedof our current
_noiseinstance before asking for new values
Alright, let’s get to work! 🙂
Noise class, adding the
seed variable is straight-forward: we simply add a public integer:
Now, to seed our
PerlinNoise, we are actually going to “move around” this infinite plane I talked about earlier – this way, by having an offset, we’ll grab a completely different chunk of noise and get significantly values. The trick is to move enough so that you don’t “see” the shift: if you simply add your seed, you will see the infinite plane translate before your eyes and you won’t get much of a “random feeling”. To avoid this, it’s better to multiply the offset by a large constant, for example 1000. All of this is done when you first prepare your
The last thing we need to do is add our UI slider and use the
seed parameter in our
NoiseManager. It’s pretty similar to what we’re doing with the
_scale parameter, except that we want integers and therefore we have to cast the return value of the
HorizontalSlider (which is a float by default):
If you try this again, you’ll see that you have a second slider that also modifies the noise. But this time, it doesn’t change the overall shape of the noise – your heightmap still looks similar. Instead, it changes the position of the “peaks” on your map.
And as expected: going from one seed to another completely transforms the noise, but going back to a seed you saw previously will give you back the exact same map! 🙂
Step 6: Automating noise listing (+ adding a white noise)
There are a lot more things we could discuss about math noises and procedural generation. But I’ll end this tutorial by showing you how to automatically display and switch between several noises – and we’ll add a second noise type (a basic random white noise) to check that it works.
Our system will work as follows: we’ll make a list of valid noise types as a dictionary (keyed by the noise name and where the value is the matching
Noise-derived class); then, we’ll create a UI
SelectionGrid to easily pick one from the list; and whenever we change the value, we’ll dynamically get the proper class and create a new instance of it to assign to our
The list of noises is stored in the
Noise class; it is static: that way, it is not copied on each
Noise (derived) instance, but shared between them. The values are of type
System.Type because we want to store the class type itself so that we can instantiate it later on.
At the moment, we only have the Perlin noise so it’s the only item we can add to our list – but we’ll soon have a second one 🙂
Let’s go back to our
NoiseManager and improve our UI a bit. We want to add a
SelectionGrid that lists all of the possible noise types (by name). This UI element works a bit like the
HorizontalSlider: it returns the updated index of the currently selected item in the grid. We’ll pass it the list of noise names to display (that we’ll cache once at the beginning in a
_noiseTypes list for efficiency) and the number of items per row (in our case, only one).
The subtle part is with the dynamic instantiation. The idea here is to extract the
Noise-derived class type from the reference dictionary and then use the C#
System.Activator tool to create our new instance dynamically at run time. Once we’ve casted it to the
Noise type, we’ll be able to use it just as before!
Procedural generation is a game-changer for the CG industry: with ever more powerful computers and advanced algorithms, we are now at an age where a team of devoted professionals can make incredible generative tools and significantly the time (and thus money) required to create 3d scenes. For example, you might know of the famous Gaia Unity plugin that is just an amazing terrain/landscape procedural generator:
Of course, our little noise engine is far from being that powerful, but it already introduced important concepts for procedural generation: visualising the same data in different ways (via a 2d texture or a 3d heightmap on a terrain), comparing different generators to see which one best fits your use case, seeding the values to get deterministic results…
If you want to take this further, make sure to check out this complement bundle that I made for the tutorial: they contain additional noise functions (namely the Simplex, Worley and Value noises), more parameters and even a little shader to show the level curves on the terrain!
I hope you enjoyed this quick Unity tutorial and the dual video/text versions. Feel free to react in the comments and tell me if you like this new format — and of course, go ahead and share your ideas for future topics you’d like me to make Unity tutorials on! 🙂