Help with Perlin Noise

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I want to achieve 3D perlin noise with overhangs as well as the transitioning of different biomes with different perlin noise. Also if it’s possible, I would like to make my structures/flowers also be generated with math…noise so they are the same for everybody’s client side…

  2. What is the issue? Include screenshots / videos if possible!
    I can’t seem to get the 3d perlin noise with the chunk rendering sytem I have. I also cannot find a way to transition from one biome to the other. I also cannot find a way to get a position for the structures/flowers with math.noise.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I have looked on the DevForum for help and I have tried many other people’s solutions however none of them seem to work.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

If possibile I would like to keep using the same rendering system that I am using and hopefully I can find a way to optimize it myself.

local ChunkRenderDistance = 8 local ChunkSize = 16 local ChunkSizeY = 1 local ChunkGrid = {} local CurrentBiome = 4 local SeedValue = workspace:WaitForChild("Seed") repeat task.wait() until SeedValue.Value ~= -2 local Seed = SeedValue.Value local blockSize = 3 local worldHeight = 10 local worldSize = 16 local divisor = 32 local blockSize = 3 local blocks = game.Workspace.Blocks local uis = game:GetService("UserInputService") local Biomes = {	--{'Mountains' ,80, 70},	{'Plains' ,160, 80},	{'Forest' ,160, 80},	{'Desert' ,160, 80},	{'Rivers' ,160, 80}, } game.Players.PlayerAdded:Connect(function(player)	local Character = player.Character or player.CharacterAdded:Wait()	Character:FindFirstChildWhichIsA("Humanoid").WalkSpeed = 12.951 end) local function fractalNoise(x, y, octaves, lacunarity, persistence, scale, seed)	-- The sum of our octaves	local value = 0	-- These coordinates will be scaled the lacunarity	local x1 = x	local y1 = y	-- Determines the effect of each octave on the previous sum	local amplitude = 1	for i = 1, octaves, 1 do	-- Multiply the noise output by the amplitude and add it to our sum	value += math.noise(x1 / scale, y1 / scale, seed) * amplitude	-- Scale up our perlin noise by multiplying the coordinates by lacunarity	y1 *= lacunarity	x1 *= lacunarity	-- Reduce our amplitude by multiplying it by persistence	amplitude *= persistence	end	-- It is possible to have an output value outside of the range [-1,1]	-- For consistency let's clamp it to that range	return math.clamp(value, -1, 1) end function GenerateNewChunk(Position)	--if math.random(1,100) == 1 then CurrentBiome = math.random(1, #Biomes) print('Changed') end	local Frequency = Biomes[CurrentBiome][2] --	local Amplitude = Biomes[CurrentBiome][3] --Height of wave	local ChunkData = {}	for x = 1, ChunkSize do	ChunkData[x] = {}	local LastYPos = 0	for y = 1, ChunkSizeY do	for z = 1, ChunkSize do	local X = ((x * 3) + Position.X + 1.5)	local noiseValue = math.noise((((x * 3) + Position.X + 1.5) / Frequency) + Seed, (((y * 3) + Position.Y + 1.5) / Frequency) + Seed, (((z * 3) + Position.Z + 1.5) / Frequency) + Seed)	local Y = ((math.floor((noiseValue * Amplitude) / 3) * 3) + 1.5)	local Z = ((z * 3) + Position.Z + 1.5)	ChunkData[x][z] = Vector3.new(X, Y, Z)	LastYPos = Y	end	end	end	table.insert(ChunkGrid, {Position ,ChunkData})	return {Position ,ChunkData} end function GetBlockTypes(BlockPos, Chunk)	local YPos = BlockPos.Y	if CurrentBiome == 1 then	if YPos < -4 then return game.ReplicatedStorage.Blocks.GrassBlock:Clone() end	if YPos >= -4 then return game.ReplicatedStorage.Blocks.GrassBlock:Clone() end	elseif CurrentBiome == 2 then	if YPos < -4 then return game.ReplicatedStorage.Blocks.GrassBlock:Clone() end	if YPos >= -4 then return game.ReplicatedStorage.Blocks.GrassBlock:Clone() end	elseif CurrentBiome == 3 then	if YPos < -4 then return game.ReplicatedStorage.Blocks.SandBlock:Clone() end	if YPos >= -4 then return game.ReplicatedStorage.Blocks.SandBlock:Clone() end	elseif CurrentBiome == 4 then	if YPos < 1 then	local WaterBlock = BlockPos	repeat	WaterBlock += Vector3.new(0,3,0)	until WaterBlock.Y + 3 > 2	local NewWaterBlock = game.ReplicatedStorage.WaterBlock:Clone()	NewWaterBlock.Parent = Chunk	NewWaterBlock.Position = WaterBlock + Vector3.new(1,1,1)	end	if YPos < 2 then return game.ReplicatedStorage.Blocks.SandBlock:Clone() end	if YPos >= 2 then return game.ReplicatedStorage.Blocks.GrassBlock:Clone() end	end end local ChunksRendered = 0 local WorldLoaded = false function RenderChunk(ChunkData, ChunkIndex, player)	local ChunkCenter = ChunkData[1]	local BlockData = ChunkData[2]	local ChunkModel = Instance.new('Model')	ChunkModel.Name = 'Chunk '..ChunkIndex	ChunkModel.Parent = workspace.Chunks	local RunService = game:GetService("RunService")	for _, Row in ipairs(BlockData) do	for _, Block in ipairs(Row) do	local NewBlock = GetBlockTypes(Block ,ChunkModel)	NewBlock.Position = Block + Vector3.new(1,0,1)	NewBlock.Parent = ChunkModel	if NewBlock.Name == "GrassBlock" then	--[[local OddsMob = math.random(1,1000)	if OddsMob <= 4 then	local NewMob = game.ReplicatedStorage.Mobs["Pig"]:Clone()	NewMob.PrimaryPart.Position = NewBlock.Position + Vector3.new(0,3.345,0)	NewMob.Parent = game.Workspace.Mobs	NewMob.Name = NewMob.Name.."#"..#game.Workspace.Mobs:GetChildren()	end]]	local OddsPlant = math.random(1, 100)	if OddsPlant <= 5 then	local NewPlant = game.ReplicatedStorage.Blocks.FlowerBlock:Clone()	NewPlant.Position = NewBlock.Position + Vector3.new(0,3,0)	NewPlant.Parent = ChunkModel	end	local OddsStructure = math.random(0, 100)	if CurrentBiome == 2 then	if OddsStructure <= 2 then	local NewStructure = game.ReplicatedStorage.Structures.Tree:Clone()	NewStructure.PrimaryPart.Position = NewBlock.Position + Vector3.new(0,3,0)	NewStructure.Parent = ChunkModel	for _, block in ipairs(NewStructure:GetChildren()) do	block.Parent = ChunkModel	end	NewStructure:Destroy()	end	elseif CurrentBiome == 4 then	if OddsStructure <= 2 then	local NewStructure = game.ReplicatedStorage.Structures.Tree:Clone()	NewStructure.PrimaryPart.Position = NewBlock.Position + Vector3.new(0,3,0)	NewStructure.Parent = ChunkModel	for _, block in ipairs(NewStructure:GetChildren()) do	block.Parent = ChunkModel	end	NewStructure:Destroy()	end	end	elseif NewBlock.Name == "SandBlock" then	local OddsStructure = math.random(0, 100)	if CurrentBiome == 3 then	if OddsStructure <= 2 then	local NewStructure = game.ReplicatedStorage.Structures.Cactus:Clone()	NewStructure.PrimaryPart.Position = NewBlock.Position + Vector3.new(0,3,0)	NewStructure.Parent = ChunkModel	for _, block in ipairs(NewStructure:GetChildren()) do	block.Parent = ChunkModel	end	NewStructure:Destroy()	end	end	end	end	end	print(ChunksRendered)	if ChunksRendered >= 14 then	if WorldLoaded == false then	WorldLoaded = true	game.Workspace.SpawnLocation.CanCollide = false	game.ReplicatedStorage.Events.WorldLoaded:FireServer()	ChunksRendered = 1	else	ChunksRendered = 1	end	else	ChunksRendered = ChunksRendered + 1	end end function RenderChunksNearPlayer(Player)	for x = 1, (ChunkRenderDistance) do	for z = 1, (ChunkRenderDistance) do	local PlayerPos = workspace:WaitForChild(Player.Name):WaitForChild('HumanoidRootPart').Position	local FoundChunk = false	local ChunkX = ((math.floor(PlayerPos.x / (ChunkSize * 3)) * (ChunkSize * 3)) + ((ChunkSize * 3) * x)) - ((ChunkSize * 3) * (ChunkRenderDistance / 1.5))	local ChunkZ = ((math.floor(PlayerPos.z / (ChunkSize * 3)) * (ChunkSize * 3)) + ((ChunkSize * 3) * z)) - ((ChunkSize * 3) * (ChunkRenderDistance / 1.5))	for _, Chunk in ipairs(ChunkGrid) do	if Chunk[1] == Vector3.new(ChunkX, 0, ChunkZ) then	FoundChunk = true	end	end	if not FoundChunk then	GenerateNewChunk(Vector3.new(ChunkX, 0, ChunkZ))	end	end	end	for ChunkIndex, Chunk in ipairs(ChunkGrid) do	if (workspace:WaitForChild(Player.Name):WaitForChild('HumanoidRootPart').Position - Chunk[1]).Magnitude <= (ChunkSize * ChunkRenderDistance) then	if not workspace.Chunks:FindFirstChild('Chunk '..ChunkIndex) then	print('Render')	RenderChunk(Chunk, ChunkIndex, Player)	end	else	if workspace.Chunks:FindFirstChild('Chunk '..ChunkIndex) then	workspace.Chunks:FindFirstChild('Chunk '..ChunkIndex):Destroy()	end	end	end end local Player = game.Players.LocalPlayer while task.wait() do	RenderChunksNearPlayer(Player) end 

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

  1. Deterministic client-side placement of generated items is possible with using Random.new(seed) to create a seeded PRNG that only your code will use.

  2. Biomes regions can be specified by sampling math.noise with one of the 3 arguments set to a constant, as you’re already doing for the height map (you have Y always 0). But you don’t get transitions for free if you just quantize math.noise to an integer per biome. You have to write your own code for how to blur the biome boundaries using some kind of 2D interpolation (like bilinear, bicubic, etc… all the usual suspects) and some custom rules, like using distance of a point from each of the nearest biomes to make “fat” boundaries where the interpolation or transition biomes occur, if you don’t want hard boundaries.

  3. Using the 3rd dimension from Perlin noise won’t give you overhangs for free, or nice cave generation. It will just extend the blobby noise upwards and make unnatural shapes. If you want nice overhanging cliffs and caves, this my friends is an art form to create algorithms for.

1 Like

3D perlin noise with overhang is very tricky. There’s really no clear cut way to approach this. What I recommend is to look online and see what other people done and reverse engineer it. You could also pull up real life photos and study them, then tweak parameters until you get stuff that look similar.


source: https://www.youtube.com/watch?v=TZFv493D7jo


source: 3D Perlin Noise Map Ridges - Graphics and GPU Programming - GameDev.net

As an example. I know what 3D perlin noise looks like (see 1st photo). I look at other map generation systems online. I see photo 2. I would look at this and notice that it looks like a combination of 2d and 3d perlin noise. The bottom half is 2d with a height map and the top looks like 3d. There must be some kind of blending technique going on. Maybe it’s just a simple additive effect? etc. etc.

Same process as above. Look online for inspiration.


source: https://www.bio.miami.edu/dana/330/330F19_9.html

As an example here, I took inspiration from real life. This is a graph of precipitation and temperature and what biome it produces. It looks like all I’d have to do is generate a 2 independent Perlin noise numbers and map it to a 2d graph, which will correspond to the x and y of a biome chart.

You’re working with a lot of internal math calculations (as opposed to interacting with instances & properties). You could benefit from parallel computation and native code generation.

The Parallel Luau tutorial also demonstrates a chunk loading system.

1 Like

Adding on to the other amazing comments here, IMO structures are the most conceptually tricky part.

The trick for structures is that they need to be generated at a larger scale then each chunk, otherwise they get cut off. Two ways of doing this: decide where structures go on about a 16x16 level then flag chunks (works well if you need large structures) or for small structures (limited to 3x3 chunk size) you just search adjacent chunks to see if the code needs to expand out a structure in the adjacent chunk to add stuff that extends into the current chunk (much simpler).

I also see you’re using the third parameter of the noise for the seed, which is a good way for two dimensional terrain. One easy way you can extend this to 3 dimensions is to use the seed as an offset (e.g. noise(x, y, z+seed) should work well, since your height is probably fairly limited).

One easy way to do 3D noise terrain is to have an increasing threshold for “solidity”: as blocks increase in height, they require a larger and larger value to be solid blocks instead of air blocks. You can then tune the solidity requirement function to get results you like. (And even vary it based on other maps to create oceans, mountains, valleys, etc!)

1 Like