Procedurally Generated Terrain is not Smooth

  1. I would like to generate random terrain via a server script and Perlin noise. I already have a system in place, but it is rather steppy- or blocky-looking.

  2. My issue is that I cannot get the terrain to look smooth when generated. This image shows the steppy nature of the terrain. I believe this is caused by the terrain being locked to a 4x4x4 grid.

  1. I’ve tried a few methods to try to smooth out the terrain:
    a) I originally used the Terrain:FillBlock method
    b) I added the Terrain:FillBall method on top of the FillBlock column; didn’t really change anything.
    c) I then went through a more complicated approach by using the Terrain:WriteVoxels method so that I could manipulate the “occupancy” parameter. The thought was that by using the same noise value that decided the Y position, I could calculate the occupancy of that particular 4x4x4 grid section of terrain, with the hopes that it would smooth these edges out. It actually made it worse somehow.
    Terrain:WriteVoxels()

If there are better ways to generate smooth terrain, I’d love to hear how. Any help would be greatly appreciated!

Here is my entire code, condensed as best as possible.

-- Look how many lines thise code is. Nice. ;)
local mapSizeX = 100
local mapSizeY = 25
local mapSizeZ = 100

local cellSize = 4
local noiseScale = 50
local noiseHeightScale = 100
local terrainGenerationMethod = 2 -- 1 is FillBlock; 2 is WriteVoxels


local function generateNewMap ()
	
	for mapX=1, mapSizeX do

		for mapZ=1, mapSizeZ do
			
			-- Uses noise to calculate a cohesively random Y value
			local noiseValue = math.noise(mapX/noiseScale, mapZ/noiseScale)
			local yPos = noiseValue * noiseHeightScale
			local cellCFrame = CFrame.new(mapX*cellSize, yPos, mapZ*cellSize)

			-- Terrain - FillBlock method
			if terrainGenerationMethod == 1 then

				game.Workspace.Terrain:FillBlock(cellCFrame, Vector3.new(cellSize, mapSizeY, cellSize), Enum.Material.Grass)
				game.Workspace.Terrain:FillBall(Vector3.new(cellCFrame.Position.X, cellCFrame.Position.Y +
					cellSize/2, cellCFrame.Position.Z), cellSize/2, Enum.Material.Grass)

			-- Terrain - WriteVoxels method
			elseif terrainGenerationMethod == 2 then

				local region = Region3.new(Vector3.new(cellCFrame.Position.X - 4, cellCFrame.Position.Y - mapSizeY, cellCFrame.Position.Z - 4),
					Vector3.new(cellCFrame.Position.X, cellCFrame.Position.Y, cellCFrame.Position.Z)):ExpandToGrid(4)

				local materials = {}
				local occupancies = {}

				for x=1, 1 do
					materials[x] = {}
					occupancies[x] = {}
					
					for y=1, region.Size.Y/4 do
						materials[x][y] = {}
						occupancies[x][y] = {}
						
						for z=1, 1 do
							-- materials
							materials[x][y][z] = Enum.Material.Grass

							-- occupancies
							if y < region.Size.Y/4 then -- below top layer
								occupancies[x][y][z] = 1
							else -- top layer
								local occupancy = (noiseValue + 1)/2 -- shifts noise range of (-1 to 1) to occupancy range of (0 to 1)
								occupancies[x][y][z] = occupancy
							end
						end
					end
				end

				game.Workspace.Terrain:WriteVoxels(region, 4, materials, occupancies)

			end
		end
	end
end

generateNewMap()
1 Like

I had this issue when I was doing something similar. Mine has become too complex for me to find the specific math that fixed it, but I think I would suggest first removing the top layer check to see if that fixes it.

I’d also have to see how noise value was calculated, but mine ended up being multiplied by amp a great deal. Dividing or multiplying the occupancy might help, sometimes weird stuff happens with the grid but it’s too hard for me looking at this to know if that’s the case.

Regardless, finding a way to check some specific locations and what occupancy it is selecting might help push you in the right direction. There is also a general amount of terracing like the image that can’t be prevented, but i think it can be smoother looking than that for sure.

Apologies if this was too vague but I thought I’d chime in because I faced a similar issue that, frankly, I wasn’t 100% sure how I resolved. Even changing the division and multiplication to, say, 2.0 instead of 2 might fix it. I forget if lua converts things to ints or not.

edit: I want to make it clear that I think the voxel grid is the best way to do this, if not for smoothness, it is much more optimized from what I have found

I suppose it may be possible to implement the noise so that it changes occupancies throughout one Region3 rather than changing the Y position of each 4x4 cell… Is this what you meant when you said the voxel grid is the best and most optimized way to achieve this?

No I really just meant as opposed to using fillblock. I did implement the noise in the region3 like that, making chunks out of large 400x400 areas with the regions.

Well whether that’s what you meant or not, I tried it and it worked beautifully! I created material and occupancy tables for the entire map and then looped through them, using Perlin noise to set the occupancies, and used WriteVoxels for the entire map. The partial occupancy actually worked this time, and made it nearly perfectly smooth!

Here’s the results, after some material-setting based on height :slight_smile:
(my HDR washes out the image a bit; sorry :P)

Even though you didn’t have an exact solution, your general tips are what gave me the idea to do it this way. Thank you sooo much!

1 Like