My First Attempt At Procedural Terrain Generation

Hello! Here is my first test for procedural terrain generation using math.noise().
Map is approx. 3,200 x 3,200 studs!
(Islands were an accident, but worked out quite well!)



Any tips? Is there an easy-ish way to replicate this with actual Voxel terrain? Is there a way to make mountains instead of random hills, without making things too complex? Let me know any suggestions!

(My code if anyone wants to improve it or use it):

local voxelSize = 16
local chunkSize = 64
local edgeX = chunkSize/voxelSize
local edgeZ = chunkSize/voxelSize
local biomeSize = 16

local heightFactor = 1.5

local threshold = 16

local baseHeight = 25 + (heightFactor)^2
local centerPoint = Vector3.new(0,baseHeight,0)

local vs = voxelSize

local materials = {
	Sand = {Color=Color3.fromRGB(234,184,146),Material=Enum.Material.Sand,HeightOffset=1},
	Grass = {Color=Color3.fromRGB(84,120,46),Material=Enum.Material.Grass,HeightOffset=0},
	Dirt = {Color=Color3.fromRGB(108,88,75),Material=Enum.Material.Slate,HeightOffset=0},
	Stone = {Color=Color3.fromRGB(91,93,105),Material=Enum.Material.Concrete,HeightOffset=4},
	Water = {Color=Color3.fromRGB(82,124,174),Material=Enum.Material.Foil,HeightOffset=-4},
}

local biomes = {
	Plain={Weight=8,HeightFactor=1,Material=materials.Grass},
	Desert={Weight=5,HeightFactor=1,Material=materials.Sand},
	Volcano={Weight=1,HeightFactor=1.25,Material=materials.Stone},
	Ocean={Weight=4,HeightFactor=.75,Material=materials.Water},
}

function adjust(part)
	local y1 = part.Position.Y
	part.Size = Vector3.new(vs,y1,vs)
	part.Position = part.Position + Vector3.new(0,-y1/2,0)
end

function makePart(chunk,x,y,z,material)
	local part = Instance.new("Part")
	part.TopSurface = Enum.SurfaceType.Smooth; part.BottomSurface = Enum.SurfaceType.Smooth
	part.Anchored = true
	part.Parent = chunk
	part.Position = Vector3.new(x*vs,y*vs*heightFactor,z*vs) + centerPoint - Vector3.new(voxelSize/2,0,voxelSize/2)
	part.Size = Vector3.new(vs,vs,vs)
	adjust(part)
	part.Color = material.Color; part.Material = material.Material
	part.Position = part.Position + Vector3.new(0,material.HeightOffset,0)
end

function makeChunk(biomeType,chunkX,chunkZ)
	local chunk = Instance.new("Folder")
	chunk.Parent = workspace
	chunk.Name = "Chunk"..chunkX.."/"..chunkZ
	centerPoint = Vector3.new(chunkX*chunkSize,baseHeight,chunkZ*chunkSize)
	local baseMaterial = biomes[biomeType].Material
	local check1, check2 = false, false
	coroutine.wrap(function()
		for x = 1-edgeX/2,edgeX/2 do
			for z = 1-edgeZ/2,0 do
				local trueX, trueZ = x+centerPoint.X/voxelSize, z+centerPoint.Z/voxelSize
				makePart(chunk,x,math.noise(trueX/5,(trueX+trueZ)/10,trueZ/5),z,baseMaterial)
			end
			if x%threshold==0 then wait() end
		end
		check1 = true
	end)()
	coroutine.wrap(function()
		for x = edgeX/2,1-edgeX/2,-1 do
			for z = edgeZ/2,1,-1 do
				local trueX, trueZ = x+centerPoint.X/voxelSize, z+centerPoint.Z/voxelSize
				makePart(chunk,x,math.noise(trueX/5,(trueX+trueZ)/10,trueZ/5),z,baseMaterial)
			end
			if x%threshold==0 then wait() end
		end
		check2 = true
	end)()
	repeat wait() until check1 and check2
end

local currentBiomeName = "Plain"

function setBiome(name)
	heightFactor = biomes[name].HeightFactor
	currentBiomeName = name
end

function getTotalWeight()
	local val = 0
	for i,v in pairs(biomes) do
		val = val + v.Weight
	end
	local count = 0
	for i,v in pairs(biomes) do
		v.Threshold = count + v.Weight/val
		count = v.Threshold
	end
	return val
end

local totalBiomeWeight = getTotalWeight()

function getWeightedBiome(noise)
	noise = math.clamp(noise,0,1)
	--print("NOISE:"..noise)
	local biome = "Plain"
	for i,v in pairs(biomes) do
		if noise<v.Threshold then
			biome=i
			--print("BIOME SET:"..biome)
			break
		end
	end
	return biome
end

function getBiome(x,z)
	local noise = math.noise(x*math.sqrt(2),z*math.sqrt(2)) + .5
	return getWeightedBiome(noise)
end

function round(input,digits)
	return (input*10^digits)/10^digits
end

for x = -15,15 do
	for z = -15,15 do
		setBiome(getBiome(x/biomeSize,z/biomeSize))
		makeChunk(currentBiomeName,x,z)
	end
end
6 Likes

To answer your question about “Can I turn this into Voxel terrain?”, the answer is it depends.
if you wan to use the same format with the parts, first its going to cost lots of lag and second the parts Have to be a 4 x 4 brick and positioned to the “Voxel Grid”

On the other hand, yes using the Noise function Roblox supplies you can make more complex terrain.

and this link will help you get started

3 Likes

If you sum up multiple noise functions with differing amplitude (heightFactor in your code) and differing frequencies (trueX/5 where 5 is the frequency of the noise), you can create something called octave noise.

Octave noise gives more realistic terrain, and is one of the noise variations that Minecraft uses to generate its terrain.

2 Likes

I have implemented something similar into the newest version.