Screenshots with realistic sized trees on same terrain, and fixed some links. :3
I noticed r/proceduralgeneration has a lot activity lately, so I thought I would do quick post on how I’m generating my current test islands with only 4 functions. This is what they typically look like:
Terrain generated by simple density functions
I basically use both 2d and 3d simplex noise to add and subtract a global density function, which is described per terrain cube. You can stack cubes like octrees, or more typically just a simple XZ grid. This density function is used to produce voxels, which are then in turn converted to meshes for physics and rendering. I don’t use GPU rendering of the voxels as I need that GPU time for VR. :3
All of this is done in realtime via the clever use of worker threads with the option to serialize them out as models. Each cube is typically 256 meters. Yes, this means if you want really tall mountains you need to stack another layer of cubes on top of your XZ grid.
This listing shows how I build the density function to pass into a now obsolete KaMeshComponent, which will then in turn start deferred creation of the meshes for physics and rendering. The above island is solely defined as as the global density function:
local density = KaDensity.Create() KaDensity.Seed( density, 2312.0, 0.0, 561.972 ) KaDensity.AddFractalNoise2d( density, 6, 32.0, 8.0, 0.0, 32.0 ) KaDensity.AddFractalNoise2d( density, 10, 8.0, 8.0, 0.0, 8.0 ) KaDensity.SubFractalNoise2d( density, 10, 64.0, 8.0, 0.0, 6.0 ) KaDensity.AddFractalNoise3d( density, 3, 32.0, 4.5 )
Notice there is little use of 3d, as that is more for making hills, caves, rivers, and arches. Also you could introduce local density for features, but I don’t typically do any manual level editing at this point in project. This is one of my small islands used for testing materials.
The terrain is composed from a simple triplanar shader. World height, world slope, and a per pixel Fractal Brownian Motion ( FBM ) to break up the monotony of ground foliage. There are typically beach sand, dirt, grass ( eg green to brown / height ), and stone generated from this step. Since you can modify shaders at runtime in kagura this can be fun to improve when you have a few extra minutes in the day.
Once terrain geometry is instanced it will be filled with large vegetation and deco such as bushes and trees to rocks and statues. For every XY terrain sector I generate a SpawnTracer. The spawner will ‘ping’ for the worker threads to be done by ray casting for terrain to appear in its given sector. Once this is done it issues the given spawn callback. In this case the callback is a per sector foliage trace driven again by a grid pattern filtered by a tillable FBM ( see old blog post ), slope, height, max per tile count of an object type, and a few other attributes. This pass handles the case where a house is placed on terrain for example, so it would not put trees and grass on top of it. It is important to note these happen out of order, and the tiling pattern provided by the FBM is the trick making smooth transitions between tiles possible.
Closer look at the same scene
That’s basically how the system works, and it’s a little more complex than that but not much. I’d be happy to answer any questions or take comments. Here’s a script function that creates the entire island above:
function VoxelTerrainComponent.OnCreate( self ) local sz = self.width local hsz = self.height local scale = 256.0 local density = KaDensity.Create() KaDensity.Seed( density, 2312.0, 0.0, 561.972 ) KaDensity.AddFractalNoise2d( density, 6, 32.0, 8.0, 0.0, 32.0 ) KaDensity.AddFractalNoise2d( density, 10, 8.0, 8.0, 0.0, 8.0 ) KaDensity.SubFractalNoise2d( density, 10, 64.0, 8.0, 0.0, 6.0 ) KaDensity.AddFractalNoise3d( density, 3, 32.0, 4.5 ) for x = -sz, sz do for y = 0, hsz do for z = -sz, sz do local name = "VoxelMesh_" .. x .. "_" .. y .. "_" .. z local dump = 'Cache/' .. name .. '.asset' local sector = KaGameObject.New( x * scale, y * scale, z * scale ) KaGameObject.SetName( sector, name ) KaMeshComponent.CreateFromVoxels(sector, density, dump, self.material, x,y,z) KaGameObject.Finalize( sector ) KaGameObject.Hide( sector ) KaGameObject.AddChild( self._GameObject, sector ) if self.foliage and y == 0.0 then -- Place spawners and mappers in the middle of the voxel sector. local offset = scale * 0.5 SpawnByTracePrefabChild( "FoliageSpawner", self._GameObject, x * scale + offset, y * scale + scale * 0.75, z * scale + offset, -- VoxelSectorFoliageCb ) VoxelSectorFoliageFbmCb ) end end end end end
And here’s what you can do with unfiltered 3d noise:
Terrain Generated from Unfiltered 3d Noise