 # Using Fractal Noise To Create An Island Map

Hello, I am @MightyPart and this is a tutorial on how to use Fractal Noise to create an island map.

Please tell me below if you get stuck or are confused. English is my native language but is currently 3am.

## What is Perlin Noise?

Perlin noise is a type of visual noise that consists of a smoothly-varying signal with an organic feel than pure random noise.

## What is Fractal Noise?

Fractal noise is multiple layers of perlin noise that are combined together to form a more intricate version of perlin noise.

Summary

# Section 1: Settings Up The Project

Create a new Roblox Studio Project and remove everything from the workspace. Then add a Script called `GenTerrain` into `ServerScriptService`. Now add an empty folder called `Settings` into the newly created `GenTerrain` script.

Inside of the `Settings` folder add 2 IntValue’s called `SIZE_XZ` and `PART_SCALE`. Set the value of `SIZE_XZ` to “150” and set the value of `PART_SCALE` to “5”.

Inside of the `GenTerrain` Script add these variables

``````SIZE_XZ = script.Settings.SIZE_XZ.Value

PART_SCALE = script.Settings.PART_SCALE.Value
NOISE_SCALE = 100
HEIGHT_SCALE = 30

OCTAVES = 4
LACUNARITY = 3
PERSISTENCE = .35
SEED = 69

WATER_HEIGHT = 0
TREES_AMOUNT = 350
``````

# Section 2: Creating The Grid

To start off we need to create a grid of parts. We can achieve this by adding this code to the `GenTerrain` Script:

``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,0,z)
part.Parent = workspace.Map
end
end
``````

When you press `Run`, this grid that consists of many parts should be the result.

# Section 3: Creating The Fractal Noise

Add a new ModuleScript called `FractalNoise` into the `GenTerrain` Script.

Add the code below into the `FractalNoise` ModuleScript:

``````return function(x, y, octaves, lacunarity, persistence, scale, seed)
local value = 0
local x1 = x
local y1 = y
local amplitude = 1
for i = 1, octaves, 1 do
value += math.noise(x1 / scale, y1 / scale, seed) * amplitude
y1 *= lacunarity
x1 *= lacunarity
amplitude *= persistence
end
return math.clamp(value, -1, 1)
end
``````

Now import the FractalNoise ModuleScript function into the `GenTerrain` Script by adding in this line of code (ideally near the top of the Script):

``````FractalNoise = require(script.FractalNoise)
``````

The nested for loops from Section 2 can now be changed to include the Fractal Noise:

``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES, -- Integer that is >1
LACUNARITY, -- Number that is >1
PERSISTENCE, -- Number that is >0 and <1
NOISE_SCALE,
SEED
) * HEIGHT_SCALE
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE -- rounds to nearest 5

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)
part.Parent = workspace.Map
end
end
``````

When you press `Run`, your grid should now be reminiscent of terrain.

# Section 4: Creating The Falloff Map

What is a falloff map?
A falloff map, for the context of this tutorial, is a nested array of positions that raises or lowers a specific part of the terrain. It will be used to lower the terrain close to the edge of the map/island.

Add a new ModuleScript called `FalloffMap` into the `GenTerrain` Script.

Add the code below into the `FalloffMap` ModuleScript:

``````SIZE_XZ = script.Parent.Settings.SIZE_XZ.Value
PART_SCALE = script.Parent.Settings.PART_SCALE.Value

VALUES = {}

for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
VALUES[x] = {}
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do

local valueX = math.abs(((SIZE_XZ*PART_SCALE)/2 - x) / (SIZE_XZ*PART_SCALE))
local valueZ = math.abs(((SIZE_XZ*PART_SCALE)/2 - z) / (SIZE_XZ*PART_SCALE))
local value = math.max(valueX, valueZ)

VALUES[x][z] = value

end
end

return function(x,z)
return VALUES[x][z]
end
``````

import the FalloffMap ModuleScript function into thee `GenTerrain` Script, preferably underneath where you required the Fractal Noise script:

``````FalloffMap = require(script.FalloffMap)
``````

Now lets visualise the Falloff Map on the terrain. To do this alter the nested for loops seen in Section 2 & 3 to this:

``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES, -- Integer that is >1
LACUNARITY, -- Number that is >1
PERSISTENCE, -- Number that is >0 and <1
NOISE_SCALE,
SEED
) * HEIGHT_SCALE
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE -- rounds to nearest 5

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)

local value = FalloffMap(x,z)
part.Color = Color3.new(.5-value, .5-value, .5-value)

part.Parent = workspace.Map
end
end
``````

When you press `Run`, you will see the falloff map visualised:

The blackest parts represents water and the lightest parts represent grass. As you can currently tell there isn’t a very big area for the grass, most of the map is taken up by water. We can fix this issue by making the gradient from the darkest color to the lightest color be a curve instead of a straight line.

To do this create a new function inside of the `FalloffMap` ModuleScript called “evaluate()”:

``````function evaluate(value)
local a = 2.8
local b = 2.2

return math.pow(value, a) / (math.pow(value,a) + math.pow(b-b*value, a))
end
``````

Now change the nested for loop inside of the `FalloffMap` ModuleScript to include the “evaluate()” function:

``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
VALUES[x] = {}
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do

local valueX = math.abs(((SIZE_XZ*PART_SCALE)/2 - x) / (SIZE_XZ*PART_SCALE))
local valueZ = math.abs(((SIZE_XZ*PART_SCALE)/2 - z) / (SIZE_XZ*PART_SCALE))
local value = math.max(valueX, valueZ)

local evalValue = math.clamp(evaluate(value)*10, -.5, .5)

VALUES[x][z] = evalValue

end
end
``````

When you press `Run`, you will now see that there is now more lighter parts than darker parts.

# Section 5: Applying The Falloff Map Onto The Terrain.

Now that we have visualised the falloff map we now need to make the falloff map affected the height of the terrain. To do this we need to edit the nested for loops of the `GenTerrain` Script:

``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE

local value = FalloffMap(x,z)
height = height - (value*(PART_SCALE*20))
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)

part.Color = Color3.new(.5-value,.5-value,.5-value)
part.Parent = workspace.Map

end
end
``````

When you press `Run`, you will see that the darker a part is the lower down it is.

# Section 6: Adding Color To The Terrain

To add color to the terrain add this if statement to the nested for loop inside of the `GenTerrain` Script:

``````if value >= .15 then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
end
``````
The nested for loop should now look like this
``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE

local value = FalloffMap(x,z)
height = height - (value*(PART_SCALE*20))
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)

if value >= .15 then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
end

part.Parent = workspace.Map

end
end
``````

When you press `Run`, Your terrain should look like this:

# Section 7: Refining The Terrain Colors

In the image above (in Section 6) the grass section of the terrain is a square, this is quite unnatural. To fix this we are going to utilise `math.noise` to make the sides of the green square wavy.

To achieve this we need to add a table at the top of the `GenTerrain` Script:

``````conversionTable = {
["-0.5"] = .10,
["-0.25"] = .13,
["0"] = .16,
["0.25"] = .19,
["0.5"] = .22
}
``````

You also need to add this code in just above where you inserted the if statement in Section 6 into the nested for loop inside the `GenTerrain` Script:

``````local noise = math.noise(x/NOISE_SCALE, z/NOISE_SCALE)
noise = Round(noise, .25)
noise = math.clamp(noise, -0.5, .5)
noise = conversionTable[tostring(noise)]
``````

You also need to change the if statement slightly:

``````if value >= noise then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
end
``````
The nested for loop should now look like this
``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE

local value = FalloffMap(x,z)
height = height - (value*(PART_SCALE*20))
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)

local noise = math.noise(x/NOISE_SCALE, z/NOISE_SCALE)
noise = math.floor(noise / .25 + 0.5) * .25
noise = math.clamp(noise, -0.5, .5)
noise = conversionTable[tostring(noise)]

if value >= noise then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
end

part.Parent = workspace.Map

end
end
``````

When you press `Run`, you will see that the green square has wavy sides now, which looks more natural.

As you can probably tell the terrain currently looks very dry, we can remedy this by adding water.

To create the water add this code to the bottom of the `GenTerrain` Script.

``````-- adds water to the terrain
WD_SIZE = (2048)-0.01
XY_POS = (2048/2) - (NOISE_SCALE*PART_SCALE)/2
local water = Instance.new("Part")
water.Anchored = true
water.Size = Vector3.new(WD_SIZE, HEIGHT_SCALE, WD_SIZE)
water.Position = Vector3.new(XY_POS, WATER_HEIGHT, XY_POS)
water.Color = Color3.fromRGB(12, 84, 92)
water.Parent = workspace
water.Transparency = .5
water.CanCollide = false
``````

When you press `Run`, the terrain will have water around it. Now it actually looks like an island.

To give your island a sense of live you could add some trees. First get my tree model here. Add the tree to `ReplicatedStorage`

Add a variable to the top of the `GenTerrain` Script called “validTreePositions”

``````validTreePositions = {}
``````

Add this for loop to the bottom of the `GenTerrain` Script.

``````for count=1,TREES_AMOUNT do
local tree = game.ReplicatedStorage.Tree:Clone()
local pos = validTreePositions[math.random(1, #validTreePositions)]

local cframe = CFrame.new(
pos.X, pos.Y+(NOISE_SCALE/2)+(tree:GetExtentsSize().Y/2)-5, pos.Z
) * CFrame.Angles(0, math.rad(math.random(1, 360)), 0)

tree:SetPrimaryPartCFrame(cframe)
tree.Parent = workspace
end
``````

Now change the if statement in the nested for loops inside of the `GenTerrain` Script:

``````if value >= noise then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
table.insert(validTreePositions, Vector3.new(x,height,z))
end
``````
The nested for loop should now look like this
``````for x=0,SIZE_XZ*PART_SCALE,PART_SCALE do
for z=0,SIZE_XZ*PART_SCALE,PART_SCALE do
local height = FractalNoise(x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE

local value = FalloffMap(x,z)
height = height - (value*(PART_SCALE*20))
height = math.floor(height / PART_SCALE + 0.5) * PART_SCALE

local part = Instance.new("Part")
part.Anchored = true
part.Size = Vector3.new(PART_SCALE,NOISE_SCALE,PART_SCALE)
part.Position = Vector3.new(x,height,z)

local noise = math.noise(x/NOISE_SCALE, z/NOISE_SCALE)
noise = math.floor(noise / .25 + 0.5) * .25
noise = math.clamp(noise, -0.5, .5)
noise = conversionTable[tostring(noise)]

if value >= noise then
part.Color = Color3.fromRGB(143, 126, 95)
part.Material = Enum.Material.Sand
elseif value >= 0 then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
table.insert(validTreePositions, Vector3.new(x,height,z))
end

part.Parent = workspace.Map

end
end
``````

When you press `Run` you should see your completed island.

# Section 9: Finishing Touches

Change the value of `SIZE_NZ` (located in `GenTerrain` > `Settings`) to be “300”.

Your island should now be 3 times as big.

20 Likes

It’s “it’s” This is an excellent tutorial! I’ve been meaning to make my own map generator using Fractal Noise for a while but never got around to doing it.

3 Likes

Bruh this is sick. I’ve heard of people making maps with Perlin noise but never heard of fractal noise. Great job on the tutorial 2 Likes

Good job in this tutorial

a procedural minecraft village or monster is something we need to make it more procedural

lol

1 Like

Woah! This is actually really sick. Nice community tutorial.

1 Like

Minecraft world

Okay, but you should probably greedy mesh too for performance.

1 Like

This part wont work for me, no errors in the output.

could you show me what happens instead?

You should also be greedymeshing the map to drastically reduce the parts count and thus increase performance and the generation speed.

2 Likes

Dumb studio wont let me open the file and give you a image

Okay, the Noise wont work. There are maybe (some) errors in the output, but case seems to be unknown

I see. Look in output and make sure that theres no errors. Also make sure your code matches the code in the tutorial.