Hello. i would like to make infinite terrain generation with biomes and all of that, except I don’t know how to use 3d perlin noise. I tried giving a little bit of depth into the generation by making it generate 25 times on the y axis, but I don’t know how to do caves or biomes or structures.
I also don’t know how to do a chunk system to make the world infinite, I looked it up on YouTube, but I just couldn’t find anything. Any help is appreciated.
Here’s the progress so far, I managed to make it generate stone higher up and a little bit of stone below, aswell as grass and bedrock at the bottom. Nothing much. Please note that each cube’s size is 1 by 1.
The code
--// Customisable settings
local fogScreenSize = 100
local heightSize = 25
local resolution = 150
local frequency = 2
local amplitude = 50
local seed = math.random(1,1000)
--// The code itself
local folder = workspace:WaitForChild("PerlinNoise")
function GetHeightValue(x,z)
local noise = math.noise(x/resolution*frequency*seed/resolution,z/resolution*frequency*seed/resolution)
--noise = math.clamp(noise,-0.5,0.5)+0.5
return noise
end
for x = 0,fogScreenSize do
for z = 0, fogScreenSize do
for y = 0,heightSize do
local part = Instance.new("Part",folder)
part.Anchored = true
part.Size = Vector3.new(1,1,1)
local height = GetHeightValue(x,z)
part.Position = Vector3.new(x,height*amplitude-y,z)
if y == heightSize or y == heightSize - 1 or y == heightSize - 2 or y == heightSize - math.random(3,5) then
part.BrickColor = BrickColor.new("Black")
part.Material = Enum.Material.Rock
part.Name = "Bedrock"
elseif part.Position.Y >= math.random(10,20) then
part.BrickColor = BrickColor.new("Dark stone grey")
part.Name = "Stone"
elseif part.Position.Y <= math.random(-heightSize-5,-heightSize-2) then
part.BrickColor = BrickColor.new("Dark stone grey")
part.Name = "Stone"
elseif y == 0 then
part.BrickColor = BrickColor.new("Sea green")
part.Name = "Grass"
else
part.BrickColor = BrickColor.new("Brown")
part.Name = "Dirt"
end
end
end
game:GetService("RunService").Heartbeat:Wait()
end
Here’s an image too (the black blocks at the bottom are bedrock):
When I first started learning about perlin noise and terrain generation I followed this tutorial. It’s a quick four part series and the video I linked covers the chunking aspect of it. I haven’t dabbled into 3D perlin noise yet which I plan on doing so soon, so that’s the best I can really provide you with. Also on the second to last line just save a reference to RunService, although it saves a singular line of code, repeatedly calling the GetService function can be less performant than just saving a reference.
Hello, I just wanted to say, thank you for the tutorial! It was very helpful! There’s one thing I need help with though, and it’s how do I generate a terrain with mountains and plains, and not just mountains or plains, I know that I don’t have the seed value in the right spot, as I placed it in the math.noise function, causing worlds that are either filled with mountains or straight up plain… the thing is, I don’t know where I am supposed to place it. (also, sorry for my late response)
You can do this trick to merge noise values that are mountains or plains using a third noise function. In total you will have three noise functions, 1 for mountains, 1 for plains, 1 for deciding to use which one of the two to use.
I did a bit of code tweaking and I am afraid I have absolutely no clue what I’m doing
Here’s the part I modified:
function GetHeightValue(x,z)
local noise = math.noise(
(fogScreenSize/resolution*frequency*(seed/resolution)*XPos) + (x/resolution*frequency*(seed/resolution)),
(fogScreenSize/resolution*frequency*(seed/resolution)*ZPos) + (z/resolution*frequency*(seed/resolution))
) * 5
--noise = math.clamp(noise,-0.5,0.5)+0.5
return noise
end
function getPlainsHeightValue(x,z)
local noisePlain = math.noise((fogScreenSize/resolution*frequency*(seed/(resolution*2))*XPos) + (x/resolution*frequency*(seed/(resolution*2))),
(fogScreenSize/resolution*frequency*(seed/(resolution*2))*ZPos) + (z/resolution*frequency*(seed/(resolution*2)))
)
return noisePlain
end
for x = 1,fogScreenSize do
for z = 1, fogScreenSize do
for y = 0,heightSize do
local part = Instance.new("Part",folder)
part.Anchored = true
part.Size = Vector3.new(1,1,1)
local height = math.noise(GetHeightValue(x,z),getPlainsHeightValue(x,z)) * 3
height = math.clamp(height,getPlainsHeightValue(x,z) + GetHeightValue(x,z),math.random(50,100)) + .5
The keyword you are searching for is “Voxel game”, not infinite terrain (I specialise in voxel games). And I must advise you not use roblox if you really need any voxel game. It takes 200ms to generate 512 + 64 perlin noise values on roblox with 5 octaves. And roblox has very bad and currently broken mesh support, no shader support (Which is needed for voxel games since the only decent texturing method is by using triplanar shading), and no Compute Shaders.
The thing is that I wanted to make a game similar to Minecraft, but where each cube is only a 1 by 1 stud to make more detailed terrain, and also wanted to give the player the ability to dig and build, this is why I chose voxel for that
Assuming you want a minecraft style, you want Boxels (blocks) instead of something organic like Marching cubes / something that replicates the voxel grid (Dual Contouring) / something organic that replicated the voxel grid perfectly (Dual Marching Cubes)
You need to populate a 3D array with each voxel, and based on these, make a mesh.
To make 3D perlin noise, you use math.noise(x, y, z) to fetch a noise value, and then you do an if statement where if the noise value is more than or equal to zero, you generate a block at that coordinate, otherwise you don’t. This won’t generate any type of “realistic” terrain, and it will look kinda like the nether from minecraft, or like some type of cheese / cave-world. What you need to do is subtract the y axis from the noise value, but you probably want to divide this y axis value by a certain amount (The higher this value the more overhangs there will be). Here is some example code for this:
local blockSize = 2
local worldSize = 32
local divisor = 32 -- This makes the terrain more wild / crazy, and the higher it is the more overhangs there will be, and the less this is the less overhangs there will be.
for x = 1, worldSize do
for y = 1, worldSize do
for z = 1, worldSize do
local noiseValue = math.noise(x / 16, y / 16, z / 16) - ((y - (worldSize / 2)) / divisor) -- Subtract the world size divided by two because without it, the terrain will have alot of holes in the ground.
if noiseValue >= 0 then
local block = Instance.new("Part")
block.Parent = workspace
block.Size = Vector3.new(blockSize, blockSize, blockSize)
block.Anchored = true
block.CFrame = CFrame.new(x*blockSize, y*blockSize, z*blockSize)
end
end
end
task.wait()
end
Edit: @OP To do a seed, you can add a number to each axis variable / value.
So, on the line of code where we calculate the math dot noise, we can add a seed to each axis.
local noiseValue = math.noise((x + xSeed) / 16, (y + ySeed) / 16, (z + zSeed) / 16) - ((y - (worldSize / 2)) / divisor) -- Subtract the world size divided by two because without it, the terrain will have alot of holes in the ground.