How do I go from 2d to 3d perlin noise?

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

image

3 Likes

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.

1 Like

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

For 3d and 4d noise (4D if you want time or seed) You can use this library (Perlin Noise library Module! (4D and octaves!)). It also has octave support.

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

You are doing voxel terrain wrong.

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.
3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.