§ How to · with Picoo
How to generate terrain and maps in Roblox
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
Almost every procedural Roblox terrain that comes out flat and blocky was built with the wrong API.
Terrain is voxels on a 4-stud grid, and generating good land is three decisions: the right write call, enough noise octaves, and a region that lines up.
Voxel-based generation
WriteVoxels with a real heightmap, not stacked FillBlock calls.
Layered noise
Multiple octaves of math.noise so hills have both large shape and small detail.
Grid-aligned regions
Every Region3 expanded to the 4-stud voxel grid before it is written.
Material by height and slope
Rock on cliffs, grass on flats, sand at the waterline — derived, not painted.
Chunked writes
Large maps generated in pieces so a single call does not hang the server.
Seeded and repeatable
The same seed produces the same world, so a map can be regenerated exactly.
Files Picoo ships for this prompt
2 files · 185 lines · ~35s · 1 credit
TerrainGenerator
Heightmap, octave noise, voxel writing, chunking, material rules.
145 lines
TerrainConfig
Seed, size, frequency, octaves, height range, biome thresholds.
40 lines
Sample output: ServerScriptService.TerrainGenerator
-- THE mistake: building terrain out of FillBlock / FillBall calls. Each one
-- fills an axis-aligned box with a single material, so you get stair-stepped
-- blocks no matter how many you stack. WriteVoxels writes the occupancy grid
-- directly, which is what produces smooth land.
local RES = 4 -- Terrain voxels are 4 studs. Every region must fit this grid.
-- One octave of noise is a rolling hill and nothing else. Layering octaves --
-- each at double the frequency and half the amplitude -- is what gives you a
-- large shape WITH small detail. Four to six is the useful range; one or two
-- looks synthetic, ten is expensive and invisible.
local function height(x: number, z: number): number
local value, freq, amp, norm = 0, BASE_FREQ, 1, 0
for _ = 1, OCTAVES do
value += math.noise(x / freq + SEED, z / freq + SEED) * amp
norm += amp
freq /= 2 -- higher frequency = finer detail
amp /= 2
end
return (value / norm) * HEIGHT_RANGE -- math.noise returns about -0.5..0.5
end
local function writeChunk(origin: Vector3, size: Vector3)
-- ExpandToGrid(4) is NOT optional. A Region3 that does not line up with the
-- voxel grid is rejected, and the error does not say "your region is 2 studs
-- off" -- it just fails.
local region = Region3.new(origin, origin + size):ExpandToGrid(RES)
local mat, occ = buildVoxelArrays(region)
workspace.Terrain:WriteVoxels(region, RES, mat, occ)
endHow to generate terrain and maps in Roblox
Procedural terrain in Roblox is one of those tasks where the difference between a good result and an obviously fake one comes down to two API choices made in the first ten minutes.
The first is which call you write with. FillBlock and FillBall are the discoverable ones — they take a position, a size and a material, and they work immediately. They also fill axis-aligned volumes with a single material, which means terrain built out of them is stair-stepped no matter how small you make each step. WriteVoxels is the one you want: it writes the occupancy grid directly, and partial occupancy is precisely what lets a voxel be half-full and produce a smooth slope.
The second is how much noise you layer. A single call to math.noise gives you a smooth rolling surface that reads as artificial the moment you look at it, because real landscape has structure at several scales at once. Layering octaves — each at double the frequency and half the amplitude — gives you the large shape and the small detail from the same function. Four to six octaves is the useful range; beyond that the variation is finer than a voxel and you are paying for detail that cannot be rendered.
One detail will cost you an hour if nobody tells you: terrain voxels are four studs, and every Region3 you read or write has to line up with that grid. Call ExpandToGrid(4) before you use a region. When it is misaligned the operation simply fails, and the error does not mention alignment, so it looks like the API is broken.
Finally, generate in chunks. One WriteVoxels call spanning a large map blocks the server thread until it completes — players disconnect, and it looks like a crash. Breaking the work into chunks with a yield between them turns the same operation into a world visibly building itself, which is both safer and better to watch.
And know where terrain stops. It is the right tool for hills, caves, water and cliffs; it is the wrong tool for buildings and roads. Maps that try to model architecture in voxels end up looking melted, and no amount of noise tuning fixes that.
See more on the Luau generator, the game builder, or browse the full blog.
Frequently asked
Why does my generated terrain look blocky?+
It was built with FillBlock or FillBall. Those fill an axis-aligned volume with one material, so stacking them gives you stair steps regardless of how small you make each block. WriteVoxels writes the occupancy grid itself, and partial occupancy is what produces smooth slopes.
Why does my Region3 error?+
Terrain voxels are 4 studs, and a region has to line up with that grid. Call ExpandToGrid(4) on the region before reading or writing. The failure is not descriptive — it does not tell you the region is misaligned — so it is worth doing unconditionally.
How many noise octaves should I use?+
Four to six. One octave gives you smooth rolling hills with no detail, which reads as artificial immediately. Each additional octave doubles the frequency and halves the amplitude, adding finer variation. Past six the detail is smaller than a voxel and you are paying for something nobody sees.
Is math.noise random?+
No — it is deterministic Perlin noise. The same coordinates and the same offset always return the same value, which is exactly what you want: store the seed and the map regenerates identically. If you want a different world, change the offset you add to the coordinates, not the algorithm.
How do I stop terrain generation freezing the server?+
Generate in chunks and yield between them. A single WriteVoxels call covering a large map blocks the whole server until it finishes, which disconnects players. Splitting into chunks with a task.wait() between them turns a freeze into a visible build.
Terrain or parts for a map?+
Terrain for natural landscape — hills, caves, water, cliffs. Parts for anything built: buildings, roads, platforms. Terrain is cheaper for large organic volumes and worse for precise shapes, and trying to make terrain do architecture is how maps end up looking melted.
Related Picoo prompts
How to set up lighting and atmosphere in Roblox
It is also full of settings that silently cancel each other out — an Atmosphere that makes your fog values do nothing, a Technology mode where your torches cast no shadow.
weather system in Roblox
Weather is server-owned state rendered locally, with the precipitation following the camera instead of covering the world.
Roblox obby
The whole course lands in Explorer in EDIT mode — visible, movable, saveable before you ever press Play — and every jump is checked against what a default Roblox character can actually clear.