Write voxels keeps making ridges on terrain

So I have been trying to make procedural terrain generation using perlin noise and write voxels, but unfortunately, these ridges keep popping up. Would anyone know what im doing wrong?


local function GenerateNodes(Chunk, DoWait, AllowedTime)
	local terrain = workspace.Terrain
	local chunkX = Chunk.Position.X
	local chunkZ = Chunk.Position.Z
	local MaxHeightVoxels = math.ceil(Amplitude / 4) + 1
	local Materials = {}
	local occupancies = {}

	
	for x = 1, VoxelResolution do
		Materials[x] = {}
		occupancies[x] = {}
		for y = 1, MaxHeightVoxels do
			Materials[x][y] = {}
			occupancies[x][y] = {}

			for z = 1, VoxelResolution do
				local worldX = chunkX + (x - 1) * 4
				local worldZ = chunkZ + (y - 1) * 4
				local height = (math.noise(worldX / Scale * frequency + xOffset, worldZ / Scale * frequency + zOffset, SEED) + 1) / 2
				height = height * Amplitude
				Materials[x][y][z] = Enum.Material.Grass
				local occ = 0
				local voxelBottom = (y - 1) * VoxelResolution
				local voxelTop = y * VoxelResolution
				if height > voxelTop then
					occ = 1
				elseif height > voxelBottom then
					occ = (height - voxelBottom) / VoxelResolution
				else
					occ = 0
				end
				occupancies[x][y][z] = occ
			end
		end
	end
	

	local min = Vector3.new(chunkX,0, chunkZ) 
	local max = Vector3.new(chunkSize + chunkX, MaxHeightVoxels * 4 ,chunkSize + chunkZ) 



	local terrainRegion = Region3.new(min, max):ExpandToGrid(4)


	terrain:WriteVoxels(terrainRegion, 4, Materials, occupancies)

end

Values are (some aren’t important)

local RenderDistance = 500
local MapSize = 1232
local chunkSize = 16
local Scale = 50
local frequency = 0.1
local Amplitude = 100
local random = Random.new(5364)
local xOffset = random:NextNumber(-100000, 100000)
local zOffset = random:NextNumber(-100000, 100000)
local SEED = 876442

try this for “elseif height > voxelBottom then” case

local hN = heights[x][z - 1]
local hS = heights[x][z + 1]
local hW = heights[x - 1][z]
local hE = heights[x + 1][z]

local smoothH = (h * 4 + hN + hS + hW + hE) / 8
smoothH = math.clamp(smoothH, voxelBottom, voxelTop)

occ = (smoothH - voxelBottom) / 4
1 Like

I solved this by using Terrain:FillBall() on the terrain surface with higher-resolution Perlin noise and a minimum radius.

Problem is, fillball and fillblock are STUPIDLY inefficent compared to writevoxels with big, procedurally generated maps. This same map takes a second or two to generate with writevoxels, while it consistently takes 40 seconds with fillblock or ball. I could maybe do some black magic to optimize it to 30 or so but it just isn’t worth it

Hmmm, thanks, but I dont think thats quite it. The terrain generation stayed identical when I integrated it
(height generation with my old code)
image

(with yours)
image

Although, I did change some stuff with your code, so maybe I did it wrong?

local function GenerateNodes(Chunk, DoWait, AllowedTime)
	local terrain = workspace.Terrain
	local chunkX = Chunk.Position.X
	local chunkZ = Chunk.Position.Z
	local MaxHeightVoxels = math.ceil(Amplitude / 4) + 1
	local Materials = {}
	local occupancies = {}
	local heights = {} --I wasn't really sure what heights was referring to

	
	for x = 1, VoxelResolution do
		Materials[x] = {}
		occupancies[x] = {}
		heights[x] = {}
		for y = 1, MaxHeightVoxels do
			Materials[x][y] = {}
			occupancies[x][y] = {}

			for z = 1, VoxelResolution do
				heights[z] = {}
				local worldX = chunkX + (x - 1) * 4
				local worldZ = chunkZ + (y - 1) * 4
				local height = (math.noise(worldX / Scale * frequency + xOffset, worldZ / Scale * frequency + zOffset, SEED) + 1) / 2
				height = height * Amplitude
				heights[x][z] = height
				Materials[x][y][z] = Enum.Material.Grass
				local occ = 0
				local voxelBottom = (y - 1) * VoxelResolution
				local voxelTop = y * VoxelResolution
				if height > voxelTop then
					occ = 1
				elseif height > voxelBottom then --the section that got changed
					local hN = heights[x][z]
					local hS = heights[x][z]
					local hW = heights[x][z]
					local hE = heights[x][z]
					if heights[x][z - 1] then --i added these checks because it kept saying attempt to index nil with number
						hN = heights[x][z - 1]
					end
					if heights[x][z + 1] then
						hS = heights[x][z + 1]
					end
					if x > 1 and heights[x - 1][z] then
						hW = heights[x - 1][z]
					end
					if heights[x + 1] and heights[x + 1][z] then
						hE = heights[x + 1][z]
					end
	

					local smoothH = (height * 4 + hN + hS + hW + hE) / 8
					smoothH = math.clamp(smoothH, voxelBottom, voxelTop)
					task.wait(1)

					occ = (smoothH - voxelBottom) / 4
					print(occ)
				else
					occ = 0
				end
				occupancies[x][y][z] = occ
			end
		end
	end
	

	local min = Vector3.new(chunkX,0, chunkZ) 
	local max = Vector3.new(chunkSize + chunkX, MaxHeightVoxels * 4 ,chunkSize + chunkZ) 



	local terrainRegion = Region3.new(min, max):ExpandToGrid(4)


	terrain:WriteVoxels(terrainRegion, 4, Materials, occupancies)

end

I don’t know if this will help, as its a LOT of code, but when I was working with generating terrain, I had lots of issues like that, but eventually I worked it out, but its an abandoned project I havent touched in a few years, so not sure what I did, but here is the function that generates a chunk of terrain. If you can follow it, maybe you can spot something that helps you out.

function RenderChunk(chunk)
	if chunk then
		if math.abs(chunk.ChunkX-MyX) <= ViewRadius and math.abs(chunk.ChunkZ - MyZ) <= ViewRadius then
			local mat = Enum.Material.Grass
			 
--			if chunk.ChunkX%2 == 0 then
--				if chunk.ChunkZ%2 == 0 then mat = Enum.Material.Snow end
--			elseif chunk.ChunkZ%2 ~= 0 then
--				mat = Enum.Material.Snow
--			end
			--if show then print("Chunk Draw ",chunk.Key) end
			

			local opaGrid = {}
			local matGrid = {}
			for x = 1,ChunkSize.X+2 do
				matGrid[x] = {}
				opaGrid[x] = {}
				for y = 1,ChunkSize.Y do
					matGrid[x][y] = {}
					opaGrid[x][y] = {}
					for z = 1,ChunkSize.Z+2 do
						matGrid[x][y][z] = Enum.Material.Air
						opaGrid[x][y][z] = 1
					end
				end
			end

			local cx = chunk.ChunkX
			local cz = chunk.ChunkZ
			for z = 1,ChunkSize.Z+2 do 
				cz = (chunk.ChunkZ * ChunkSize.Z) + (z-1)
				for x = 1,ChunkSize.X+2 do
					cx = (chunk.ChunkX * ChunkSize.X) + (x-1)
					

					local surface,value,noise = PerlinModule.FindSurface(cx,cz)
					--if value  > 0 then value = 1 end
					--if chunk.ChunkX == -1 and chunk.ChunkZ == 0 then
						--print(cx,cz,surface,value)
					--end
					if noise+1 > noiseHigh then
						--print(noise+1)
						noiseHigh = noise+1
					end
					
					local random = Random.new(math.abs(cx*cz*noise))
					
					if (random:NextInteger(0,300)==1) and surface < 70 then
						local rock = Rock:Clone()
						local scale = random:NextNumber()*2+.5
						rock:ScaleTo(scale) -- .Size = rock.Size * scale
						rock:PivotTo(CFrame.new(cx*4,surface*4,cz*4)*CFrame.Angles(0,math.rad(math.random(0,359)),0)) --.CFrame = CFrame.new(cx*4,surface*4,cz*4)*CFrame.Angles(0,math.rad(math.random(0,359)),0)
						rock.Parent = GetSceneryFolder(chunk.Key,true)
					end
					
					if (random:NextInteger(0,200)==21) and surface > waterMaxY and surface < 70 then
						local bush = Bush:Clone()
						local scale = random:NextNumber()*2+.5
						bush:ScaleTo(scale) --.Size = bush.Size * scale

						bush:PivotTo(CFrame.new(cx*4,(surface)*4,cz*4)*CFrame.Angles(0,math.rad(math.random(0,359)),0))
						bush.Parent = GetSceneryFolder(chunk.Key,true)
					end
					
					if (random:NextInteger(0,200)==1) and surface < (waterMaxY-20) then
						local seaweed = Seaweed:Clone()
						local scale = random:NextNumber()*1+.5
						seaweed:ScaleTo(scale) -- .Size = rock.Size * scale
						seaweed:PivotTo(CFrame.new(cx*4,surface*4,cz*4)*CFrame.Angles(0,math.rad(math.random(0,359)),0)) --.CFrame = CFrame.new(cx*4,surface*4,cz*4)*CFrame.Angles(0,math.rad(math.random(0,359)),0)
						seaweed.Parent = GetSceneryFolder(chunk.Key,true)
					end

					--if surface < 2 then
					--	print(cx,",",cz," : ",surface,",",value,",",noise)
					--end

					--print(surface)
					--surface = 5

					local soil = Enum.Material.Ground
					local top = Enum.Material.Grass
					local altTop = Enum.Material.LeafyGrass
					local water = Enum.Material.Water
					
					local terrain = PerlinModule.Perlin(cx,0,cz,1,.03,1)
					local alt = false
					
					if math.abs(terrain)<.3 and math.abs(terrain)>.2 then 
						alt = true 
					end
					
					
					if surface < waterMaxY then
						soil = Enum.Material.Limestone
						top = Enum.Material.Sand
						altTop = Enum.Material.Limestone
					end
					
					if alt then top = altTop end
					
					for y = 1,ChunkSize.Y do
						if y < surface-2 then
							matGrid[x][y][z] = soil
						elseif y<= surface then
							matGrid[x][y][z] = top
						elseif y==surface+1 then
							matGrid[x][y][z] = top
							local i = math.floor(value*100)/100
							if i==0 then i = .01 end
							--print(i)
							opaGrid[x][y][z] =  i
							--if value < 1 and y > 1 then
							--	matGrid[x][y-1][z] = mat
							--end
							--break 
						elseif y<=waterMaxY then
								matGrid[x][y][z] = water
						else
							break
						end
					end
				end 
			end
			local region = chunk.Region
			--print(region)
			
			--print("region(",chunk.Key,") Region size(",region.Size,") pos(",region.CFrame.Position,")")
			--print(matGrid)
			--print(opaGrid)
			--wait()
			workspace.Terrain:WriteVoxels(region,4,matGrid,opaGrid) 
			return true
		end 
	end
end

So, I’m pretty sure the fix lies here, since that’s where the smooth stuff is generated

							matGrid[x][y][z] = top
							local i = math.floor(value*100)/100
							if i==0 then i = .01 end
							--print(i)
							opaGrid[x][y][z] =  i
							--if value < 1 and y > 1 then
							--	matGrid[x][y-1][z] = mat
							--end
							--break

Thing is, I’m not too sure what this “value” and “surface” is. Would you mind telling me? You can even just drop the PerlinModule thing and let me reverse engineer what you did, since I only use “noise”
local surface,value,noise = PerlinModule.FindSurface(cx,cz)

Okay, scratch that. Turns out my terrain generation logic was COMPLETELY fine aside from one little thing

for z = 1, VoxelResolution do
				local worldX = chunkX + (x - 1) * 4
				local worldZ = chunkZ + (y - 1) * 4 --right here
				local height = (math.noise(worldX / Scale * frequency + xOffset, worldZ / Scale * frequency + zOffset, SEED) + 1) / 2

For some reason I didnt notice that I was using y in worldZ. Once I fixed this the ridges basically disappeared and it generated like normal. This probably wont help anyone in the future but I’ll mark it down as a solution anyway. Thanks for your help!

1 Like

oh, sorry, value is the decimal (or partial) solid part
so the return might be 37.4
I believe it returns 37 and value is .4 somehow I separate the two.

Glad you solved it, funny thing, I was just trying to update my code last night to include the new shorelines water, and for my water oppacity table, I did the same thing, was using z on my world y

Best of luck to you.

1 Like

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