Perlin Noise and Brownian Motion Explained

Hello everyone, my name is Paswa.

This is a small tutorial contained Perlin Noise terrain generation and Brownian Motion

Perlin Noise:

Quick Guide: Perlin Noise

What is Perlin Noise and what can we use it for?
Perlin noise is a Gradient Noise created by Ken Perlin. Perlin noise is an extremely powerful algorithm used to generate random graphs of “noise” which are smoothed out, hence why it’s a gradient noise.

Gradient Noise is a specific type of noise that uses gradients of colors from a grayscale. These gradients are mixed together to create graphs also known as noises. Perlin noise is mainly used for Procedural Generation because of the un-machine-like images it generates. Other noises such as Value Noise are also used to create images like or identical to Perlin Noise depending on how it is adjusted.

How do I use Perlin Noise and where do I Begin?

Perlin noise is a built-in math function that roblox provides. math.noise.
math.noise takes 3 inputs: X,Y, and Z (Optional).
These inputs pick a specific point within a generated image that math.noise creates.

We can actually navigate this image by using a scale factor, offsets, and frequency.

  • Scale Factor is how much we zoom into the noise. This in terms increases how big or small our values will be. If we use a scale factor of 1 we would probably get the following numbers: -1, -0.5, 0, 0.5, 1. Reason being is because we are so zoomed out of the image rather than zooming in and gaining more precise and smoothed data.

  • Frequency: This pretty much zooms out of the image to gain more frequent values.

  • Offset: This is mostly used to randomize the position of our Perlin image by moving the image in different directions.

How do we use this in Roblox though? (We are using voxels or blocks in this example)

First we need to create an area, or base.

local size = 200 --  200x200 blocks
local block_size = 1 -- This will be used later to position and size our blocks correctly.

for x=0, size do
	for z=0, size do
		-- We will add our noise here.
	end
end

Running this won’t do anything, we need to add our blocks into the equation.

local size = 200
local block_size = 1

for x=0, size do
	task.wait()
	for z=0, size do
		local block = Instance.new("Part")
		block.Parent = game.Workspace
		block.Size = Vector3.new(block_size, block_size, block_size)
		block.Position = Vector3.new(x * block_size, 4 * block_size, z * block_size)
		block.Anchored = true
	end
end

Upon running this Roblox will create a 100x100 grid of squares.

Now we need to apply our Perlin into this.

local size = 200
local block_size = 1

local frequency = 10
local scale = 200

local xOffset = math.random(0, 10000)
local zOffset = math.random(0, 10000)

for x=0, size do
	task.wait()
	for z=0, size do
		local noise = math.noise(((x + xOffset) / scale) * frequency, ((z + zOffset)  / scale) * frequency)
		noise = math.clamp(noise, -1, 1)
		local block = Instance.new("Part")
		block.Parent = game.Workspace
		block.Size = Vector3.new(block_size, block_size, block_size)
		block.Position = Vector3.new(x * block_size, math.floor(noise + 10) * block_size, z * block_size)
		block.Anchored = true
	end
end

Why do we use math.floor in this? Well it’s because we need rounded positions in order for the blocks to be placed correctly. If we took math.floor out of the equation then the blocks would not be positioned correctly.

I ran this but the terrain doesn’t alternate?

We need to apply an amplitude to our noise. This basically increases our values.

If you’ve ever learned waves and how they work, each wave has a frequency and amplitude to it. Amplitude is how high the wave is, from top to bottom. Frequency is how big the gap is between each wave.

Now we need to apply this into our noise

local size = 200
local block_size = 1

local frequency = 10
local amplitude = 12
local scale = 200

local xOffset = math.random(0, 10000)
local zOffset = math.random(0, 10000)

for x=0, size do
	task.wait()
	for z=0, size do
		local noise = math.noise(((x + xOffset) / scale) * frequency, ((z + zOffset)  / scale) * frequency)
		noise = math.clamp(noise, -1, 1)
		noise *= amplitude
		local block = Instance.new("Part")
		block.Parent = game.Workspace
		block.Size = Vector3.new(block_size, block_size, block_size)
		block.Position = Vector3.new(x * block_size, math.floor(noise + 10) * block_size, z * block_size)
		block.Anchored = true
	end
end

We multiplied our noise by an amplitude to increase how big each value is, which creates more detail and definition to our landscape.

Now that you understand how to use Perlin Noise we need to discuss Fractional Brownian Motion

Fractional Brownian Motion is also known as layered Perlin Noise.

In this method of layered noise we used 2 more terms.

  • Gain/Persistence : How much our amplitude changes per octave
  • Lacunarity : How much our frequency changes per octave

Next is how FBM is applied and used.

  • Each layer we apply is called an Octave.
  • For every layer, we decrease our amplitude and increase our frequency to gain more detailed terrain.
  • Once the layer is completed we add it onto a value that holds all our perlin noise values summed up.
  • We also need to normalize our total by dividing it by our total amplitude used. This can be done by adding the amplitude the same way as our total into a value called Normalizer
  • Once the layers are all completed we divide our total by the Normal (Normalizer). This will keep our values consistent and between -1,1.
  • Since we put our values back between -1,1 we need to apply another amplitude to our noise. I like to call this ampltiude_fbm.

Alright let’s apply this.

local size = 200
local block_size = 1

local ampltiude_fbm = 60
------
local gain = 0.5
local lacunarity = 2
local scale = 200
------
local octaves = 8
------
local xOffset = math.random(0, 10000)
local zOffset = math.random(0, 10000)

for x=0, size do
	task.wait()
	for z=0, size do
		local amplitude = 1
		local frequency = 1
		local total = 0
		local normalizer = 0
		--------
		for octave=1, octaves do
			local noise = math.noise(((x + xOffset) / scale) * frequency, ((z + zOffset)  / scale) * frequency)
			noise = math.clamp(noise, -1, 1)
			total += noise * amplitude
			amplitude *= gain
			frequency *= lacunarity
			normalizer += amplitude
		end
		total /= normalizer
		total *= ampltiude_fbm
		---------
		local block = Instance.new("Part")
		block.Parent = game.Workspace
		block.Size = Vector3.new(block_size, block_size, block_size)
		block.Position = Vector3.new(x * block_size, math.floor(total + 10) * block_size, z * block_size)
		block.Anchored = true
	end
end

Perlin Noise Visual

FBM Visual

Thank you for reading my tutorial! I hope the future of Procedural Generation sparks and continues to grow on Roblox!

Paswa

22 Likes

Sorry for this looking so wonky. I whipped this up to go along with a video, but i wanted to explain it further.

To explain this snippet.

local noise = math.noise(((x + xOffset) / scale) * frequency, ((z + zOffset)  / scale) * frequency)

We basically add our offset onto our coordinate, we then decrease our value further by dividing it. Which is what i called “Zooming In”. We then zoom out with our frequency to get more frequent waves/values.

This is a really nice explanation, along with some great examples. Good work.
(also just a really small thing, but you should parent the block after setting all the properties, not at the start)

Oh that makes sense! Thank you for telling me about that.

Great tutorial! May I ask how you could substitute the blocks for actual terrain?

game.Workspace.Terrian:FillWedge(CFrame, size, material)

The Perlin noise value, in our case, returns a Y cordinate.

2 Likes

Nice! That worked! Only issue I have though is that for some reason there are random holes on the terrain:

Ahh, thats odd. Maybe increase the size of each terrian/wedge?

That made it worse tbh. I also tried making them smaller and that didn’t really help either. It made it have more sharper edges.

I made it where after generating the terrain it makes a second layer under it where each voxel is 4 studs instead of 1 and that seemed to help. It still has some holes and sharp edges near the water line though:

Great tutorial! I’ve got a few questions:

  • Is “Fractional Brownian Motion” the same as what this tutorial calls “Fractal Noise”? There are a few differences in the code as far as I can see:

    • No normalizer
    • No “amplitude_fbm” (these differences should sort of cancel each other out?)

    Here are a few of the less important differences, I don’t think they change anything:

    • The term persistence is used instead of gain you did mention that one, I couldn’t see that
    • Instead of multiplying the axes’ multiplier (the frequency), the axes’ values are stored in a variable and these are multiplied

    Are these still the same thing?

  • Doesn’t the frequency only make sense in FBM? It’s a constant in the perlin noise example so scale could be modified.

  • Why are normalizer and amplitude_fbm necessary?

Also, there is a typo:

You defined it as ampltiude

Terms: FBM (Fractorial Brownian Motion)

Ah thank you for pointing out the slight error.

He is talking about FBM in his tutorial. The sound he shows is the same as the sound I show.

His tutorial and mine do pretty much operate the same way. Though he does somethings in variously different ways.

Normalizer and Amplitude_fbm are not essential nor apart of fractorial brownian motion. These are just added values to simply produce the landscape differently and at different heights without changing our FBM values.

By the way. FBM defaults are the following:
Gain : 0.5
Lacunarity: 2

Hopefully this answers your questions!

2 Likes