Weird Bumps in fBm Terrain Generator

I’m currently following Sebastian Lague’s terrain generation series and trying to translate it over to Roblox using EditableMesh, but I have these weird bumps that keep showing up whenever I generate terrain:

I’m not quite sure where the issue is coming from, but I have a suspicion that it might be because I’m layering multiple octaves to the point where it adds fine details that can’t be rendered well, like trying to approximate a smooth curve with just a few points.

Here’s my localscript:

-- SERVICES

local RS = game:GetService("ReplicatedStorage")
local AssetService = game:GetService("AssetService")

-- FUNCTIONS

local function GenerateMap(Length: number, Width: number, InitialAmplitude: number, InitialFrequency: number, Seed: number, Scale: number, Octaves: number, Persistence: number, Lacunarity: number, Offset: Vector2)
	local RNG = Random.new(Seed)
	local Offsets = {}
	local EditableMesh = AssetService:CreateEditableMesh()
	local VerticesStableIDs = {}

	for i = 1, Octaves do
		table.insert(Offsets, Vector2.new(RNG:NextNumber(-100000, 100000) + Offset.X, RNG:NextNumber(-100000, 100000) + Offset.Y))
	end

	local HalfWidth = Width / 2
	local HalfLength = Length / 2

	for X = 1, Width do
		for Z = 1, Length do
			local Amplitude = InitialAmplitude
			local Frequency = InitialFrequency
			local NoiseHeight = 0

			for i = 1, Octaves do
				local SampleX = ((X - HalfWidth) / Scale + Offsets[i].X) * Frequency
				local SampleY = ((Z - HalfLength) / Scale + Offsets[i].Y) * Frequency
				local Noise = math.clamp(math.noise(SampleX, SampleY), -0.5, 0.5) * 2 -- Generated between -0.5 to 0.5 for some reason (w/ exceptions)

				NoiseHeight += Noise * Amplitude

				Amplitude *= Persistence
				Frequency *= Lacunarity
			end

			local VertexID = EditableMesh:AddVertex(Vector3.new(X - HalfWidth, NoiseHeight, Z - HalfLength))

			table.insert(VerticesStableIDs, VertexID)
		end
	end

	local VertexIndex = 1

	for X = 1, Width do 
		for Z = 1, Length do
			if X < Width and Z < Length then
				EditableMesh:AddTriangle(VerticesStableIDs[VertexIndex], VerticesStableIDs[VertexIndex + 1], VerticesStableIDs[VertexIndex + Length + 1])
				EditableMesh:AddTriangle(VerticesStableIDs[VertexIndex + Length + 1], VerticesStableIDs[VertexIndex + Length], VerticesStableIDs[VertexIndex])
			end

			VertexIndex += 1
		end
	end
	
	local MeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(EditableMesh))
		--{CollisionFidelity = Enum.CollisionFidelity.PreciseConvexDecomposition})
	MeshPart.Anchored = true
	MeshPart.CFrame = CFrame.new(0, 0, 0)
	MeshPart.Parent = workspace
end

-- EVENTS

RS.GenerateTerrain.OnClientEvent:Connect(GenerateMap)

And here’s my serverscript:

-- SERVICES

local Players = game:GetService("Players")
local AssetService = game:GetService("AssetService")
local RS = game:GetService("ReplicatedStorage")

-- VARIABLES

local MapWidth = 100
local MapLength = 100
local Seed = 10--os.time()

-- EVENTS

Players.PlayerAdded:Connect(function(Player)
	RS.GenerateTerrain:FireClient(Player, MapLength, MapWidth, 150, 1, Seed, 300, 6, 0.5, 2, Vector2.new(0, 0))
end)

For convenience, these are essentially my main parameters:

  • Map width of 100 (number of vertices)
  • Map length of 100 (number of vertices)
  • Initial amplitude of 150
  • Initial frequency of 1
  • Seed is 10 (I’m keeping it fixed just for debugging purposes right now)
  • Scale is 300
  • The number of octaves is 6
  • The persistence is 0.5
  • The lacunarity is 2

The gap between each vertex is 1 stud. I’m not sure if this is too much or too little.

1 Like

Its probably because the higher frequency octaves have too much amplitude. And the resolution probably needs to be higher so it looks smoother

Wdym like increasing the resolution? This is like my first time messing around with this stuff so I’m not that familiar with the terminology. By like higher resolution, do you mean like creating more vertices in the same space?

Yes i mean if you have more vertices with the same space they get denser and the terrain will look smoother

So, I had to refactor the script a little bit since resolution wasn’t really directly coded into it:

-- SERVICES

local RS = game:GetService("ReplicatedStorage")
local AssetService = game:GetService("AssetService")

-- FUNCTIONS

local function GenerateMap(Length: number, Width: number, InitialAmplitude: number, InitialFrequency: number, Seed: number, Scale: number, Octaves: number, Persistence: number, Lacunarity: number, Offset: Vector2)
	local RNG = Random.new(Seed)
	local Offsets = {}
	local EditableMesh = AssetService:CreateEditableMesh()
	local VerticesStableIDs = {}
	local VerticesPerStud = 2 -- controls resolution

	for i = 1, Octaves do
		table.insert(Offsets, Vector2.new(RNG:NextNumber(-100000, 100000) + Offset.X, RNG:NextNumber(-100000, 100000) + Offset.Y))
	end

	local HalfWidth = Width / 2
	local HalfLength = Length / 2

	for VertexX = 1, Width * VerticesPerStud do
		for VertexZ = 1, Length * VerticesPerStud do
			local Amplitude = InitialAmplitude
			local Frequency = InitialFrequency
			local NoiseHeight = 0

			local X = VertexX / VerticesPerStud
			local Z = VertexZ / VerticesPerStud

			for i = 1, Octaves do
				local SampleX = ((X - HalfWidth) / Scale + Offsets[i].X) * Frequency
				local SampleY = ((Z - HalfLength) / Scale + Offsets[i].Y) * Frequency
				local Noise = math.clamp(math.noise(SampleX, SampleY), -0.5, 0.5) * 2 -- Generated between -0.5 to 0.5 for some reason (w/ exceptions)

				NoiseHeight += Noise * Amplitude

				Amplitude *= Persistence
				Frequency *= Lacunarity
			end

			local VertexID = EditableMesh:AddVertex(Vector3.new(X - HalfWidth, NoiseHeight, Z - HalfLength))

			table.insert(VerticesStableIDs, VertexID)
		end
	end

	print(#VerticesStableIDs)

	local VertexIndex = 1
	
	for VertexX = 1, Width * VerticesPerStud do
		for VertexZ = 1, Length * VerticesPerStud do
			if VertexX < Width * VerticesPerStud and VertexZ < Length * VerticesPerStud then
				EditableMesh:AddTriangle(VerticesStableIDs[VertexIndex], VerticesStableIDs[VertexIndex + 1], VerticesStableIDs[VertexIndex + Length * VerticesPerStud + 1])
				EditableMesh:AddTriangle(VerticesStableIDs[VertexIndex + Length * VerticesPerStud + 1], VerticesStableIDs[VertexIndex + VerticesPerStud * Length], VerticesStableIDs[VertexIndex])
			end
			
			VertexIndex += 1
		end
	end

	print("JOE")
	local MeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(EditableMesh))
	--{CollisionFidelity = Enum.CollisionFidelity.PreciseConvexDecomposition})
	print("yo")
	MeshPart.Anchored = true
	MeshPart.CFrame = CFrame.new(0, 0, 0)
	MeshPart.Parent = workspace
end

-- EVENTS

RS.GenerateTerrain.OnClientEvent:Connect(GenerateMap)

I also had to decrease the map size to 50x50 since I’m now using 2 vertices per pixel (otherwise I would hit the triangle limit for EditableMeshes). The bumps are still there:

I’m wondering if this is because it’s like sort of “linearly interpolating” between each vertex.

I also noticed something weird that happens when I use one octave. I get these weird stripes:

If I enter wireframe mode, this is what it looks like:

I’m not sure if this is because I haven’t added the normals or whatever it’s called that’s responsible for the lighting, or if this is a result of like this linear interpolation issue. I’m thinking that unless I increase the resolution super high, I can’t really perfectly capture the curvature of these tiny details. Do you know what the problem is?

I think the generation is working correctly; it’s just that there are too many octaves for how much detail you can support with the limited number of tris. Try using less octaves. When I did something similar it only looked smooth when i used 1 or 2. Or scale down the amplitude much more for the smaller octaves

I feel like 1 or 2 octaves is not enough. It makes the terrain look unnaturally smooth. Also, when I used one octave, it creates these weird stripes you see in that image above. Is there a way to fix this?