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