# 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.

3 Likes