 # Using Fractal Noise To Create 3D Terrain With Caves

Hello, I am @MightyPart and this is a tutorial on how to use Fractal Noise to create 3D Terrain with caves.

Please tell me below if you get stuck or are confused. English is my native language but its 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.

# Section 1: Setting Up The Project

Create a new Roblox Studio Project and remove everything from the workspace. Then add a Script called `GenTerrain` into `ServerScriptService`.

Add these variables into the `GenTerrain` Script:

``````WIDTH, DEPTH, HEIGHT = 50, 50, 30

PART_SCALE = 5
NOISE_SCALE = 100
HEIGHT_SCALE = 30

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

MAX_TREES = 50

ValidTreePositions = {}

CollectionService = game:GetService("CollectionService")
``````

Add this function near the top of the `GenTerrain` Script:

``````local function Round(num, mult)
return math.floor(num / mult + 0.5) * mult
end
``````

# 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,WIDTH*PART_SCALE,PART_SCALE do
for z=0,DEPTH*PART_SCALE,PART_SCALE do
for y=0,HEIGHT*PART_SCALE,PART_SCALE do

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

part.Parent = workspace

end
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:

``````FractalNoise = {}

FractalNoise["2D"] = 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

return FractalNoise
``````

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,WIDTH*PART_SCALE,PART_SCALE do
for z=0,DEPTH*PART_SCALE,PART_SCALE do

local TwoD_height = FractalNoise["2D"](x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE
TwoD_height = Round(TwoD_height, PART_SCALE)
TwoD_height += (HEIGHT*PART_SCALE) - HEIGHT_SCALE

for y=0,HEIGHT*PART_SCALE,PART_SCALE do

if y > TwoD_height then continue end

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

part.Parent = workspace

end
end
end
``````

When you press `Run`, the top of the grid should now be reminiscent of terrain:

# Section 4: Making The Caves

To make the caves we need to add another function into the `FractalNoise` ModuleScript. This function should be situated above `return FractalNoise`:

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

Now edit the nested for loops inside of the `GenTerrain` Script:

``````for x=0,WIDTH*PART_SCALE,PART_SCALE do
for z=0,DEPTH*PART_SCALE,PART_SCALE do

local TwoD_height = FractalNoise["2D"](x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE
TwoD_height = Round(TwoD_height, PART_SCALE)
TwoD_height += (HEIGHT*PART_SCALE) - HEIGHT_SCALE

for y=0,HEIGHT*PART_SCALE,PART_SCALE do

if y > TwoD_height then continue end

local ThreeD_height = FractalNoise["3D"](x, y, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
20,
SEED
) * HEIGHT_SCALE
ThreeD_height = Round(ThreeD_height, PART_SCALE)

if ThreeD_height < 0 and y ~= TwoD_height then continue end

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

part.Parent = workspace

end
end
end
``````

When you press `Run`, you will see that the terrain now has caves underneath it.

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

``````if y == TwoD_height then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
table.insert(ValidTreePositions, Vector3.new(x,y,z))

elseif y <= TwoD_height-PART_SCALE and y >= (TwoD_height-(PART_SCALE*3)) then
part.Color = Color3.fromRGB(140, 95, 56)
part.Material = Enum.Material.Slate

else
part.Color = Color3.fromRGB(143, 146, 146)
part.Material = Enum.Material.Rock
end
``````
The nested for loop should now look like this
``````for x=0,WIDTH*PART_SCALE,PART_SCALE do
for z=0,DEPTH*PART_SCALE,PART_SCALE do

local TwoD_height = FractalNoise["2D"](x, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
NOISE_SCALE,
SEED
) * HEIGHT_SCALE
TwoD_height = Round(TwoD_height, PART_SCALE)
TwoD_height += (HEIGHT*PART_SCALE) - HEIGHT_SCALE

for y=0,HEIGHT*PART_SCALE,PART_SCALE do

if y > TwoD_height then continue end

local ThreeD_height = FractalNoise["3D"](x, y, z,
OCTAVES,
LACUNARITY,
PERSISTENCE,
20,
SEED
) * HEIGHT_SCALE
ThreeD_height = Round(ThreeD_height, PART_SCALE)

if ThreeD_height < 0 and y ~= TwoD_height then continue end

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

if y == TwoD_height then
part.Color = Color3.fromRGB(115, 142, 112)
part.Material = Enum.Material.Grass
table.insert(ValidTreePositions, Vector3.new(x,y,z))

elseif y <= TwoD_height-PART_SCALE and y >= (TwoD_height-(PART_SCALE*3)) then
part.Color = Color3.fromRGB(140, 95, 56)
part.Material = Enum.Material.Slate

else
part.Color = Color3.fromRGB(143, 146, 146)
part.Material = Enum.Material.Cobblestone
end

part.Parent = workspace

end
end
end
``````

When you press `Run` , Your terrain should now have some color on it.

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 this for loop to the bottom of the `GenTerrain` Script.

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

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

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

When you press `Run`, your terrain will now have Trees on top of it.

# Section 7: Making Blocks Breakable

Add the code below into the nested for loops inside of the `GenTerrain` function right above the `part.Parent = workspace` line:

``````clickDetector = Instance.new("ClickDetector")
clickDetector.Parent = part

``````

Add the for loop below to the end of the `GenTerrain` script:

``````for _,block in pairs(CollectionService:GetTagged("Blocks")) do
block.ClickDetector.MouseClick:Connect(function()
block:Destroy()
end)
end
``````

When you press `Run`, you will now be able to remove blocks.

# Section 8: Finishing Touches

Currently the player can fall through the bottom of the map, to fix this we can add a part at the bottom of the terrain to make sure players can’t fall through holes in the bottom of the map.

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

``````local bottomRock = Instance.new("Part")
bottomRock.Anchored = true
bottomRock.Size = Vector3.new((WIDTH*PART_SCALE)+PART_SCALE, PART_SCALE, (DEPTH*PART_SCALE)+PART_SCALE)
bottomRock.Position = Vector3.new((WIDTH*PART_SCALE)/2, -PART_SCALE, (DEPTH*PART_SCALE)/2)
bottomRock.Parent = workspace
bottomRock.Color = Color3.fromRGB(116, 114, 115)
bottomRock.Material = Enum.Material.Pebble
``````

When you press `Run`, there will now be a layer of rock preventing the player from falling through holes in the bottom of the map.