Procedural Terrain Toolkit is a beta-stage Unity Package Manager package for streaming chunked terrain in traversal-heavy games without blocking the main thread. The package combines Burst-compiled jobs, optional GPU noise generation through compute shaders and AsyncGPUReadback, dual-noise biome signals, and vertex-color splat weights for lightweight terrain shading workflows.
If you want to contribute, please read CONTRIBUTING.md before opening a Pull Request and review the repository CODE_OF_CONDUCT.md.
- Burst + Jobs mesh generation using
IJobParallelForfor noise sampling, vertices, normals, UVs, and triangle buffers - Optional GPU noise generation via
NoiseGenerator.computeand an async C# dispatcher - Dual-noise biomes with synchronized moisture and temperature maps alongside terrain height
- Procedural splat weights encoded into vertex colors:
- R = grass
- G = rock
- B = sand
- A = snow
- Chunk pooling and async completion designed for high-speed traversal
- GitHub beta release automation for tagged prereleases
ProceduralTerrainToolkit/
├── .github/
│ └── workflows/
│ └── release.yml
├── package.json
├── README.md
└── Runtime/
├── ProceduralTerrainToolkit.asmdef
├── Scripts/
│ ├── ChunkManager.cs
│ ├── GpuNoiseGenerator.cs
│ ├── NoiseGenerator.cs
│ └── TerrainChunk.cs
└── Shaders/
└── NoiseGenerator.compute
ChunkManager is the streaming coordinator. It:
- tracks a viewer transform
- determines which chunk coordinates should be visible
- reuses chunk GameObjects from a pool
- services async chunk pipelines every frame
- rebuilds visible chunks when generation settings change
- chooses between CPU Burst jobs and GPU compute noise generation
TerrainChunk owns the per-chunk generation state. It:
- schedules Burst jobs for CPU terrain sampling and mesh buffer generation
- waits for
AsyncGPUReadbackcompletion without blocking the main thread - schedules follow-up jobs after GPU data becomes available
- applies mesh data only after jobs finish
- delays pool reuse when a chunk is still finishing background work
NoiseGenerator converts authoring settings into runtime noise parameters and exposes Burst-safe sampling helpers. The CPU and GPU paths share the same layered value-noise model so biome data remains consistent regardless of the backend.
GpuNoiseGenerator dispatches the compute shader, allocates the temporary GPU buffer, and converts the async readback into a persistent native array so TerrainChunk can continue the pipeline through jobs.
ChunkManagerrequests a build for a chunk.TerrainChunkschedules a Burst job to sample:- height
- moisture
- temperature
- A second Burst job converts those samples into:
- vertices
- normals
- UVs
- vertex-color splat weights
- A triangle job fills the index buffer in parallel.
- The chunk applies the finished mesh only after
JobHandle.IsCompletedreports ready.
ChunkManagerrequests a build with GPU generation enabled.TerrainChunkasksGpuNoiseGeneratorto dispatchNoiseGenerator.compute.- The compute shader writes height/moisture/temperature samples into a structured buffer.
AsyncGPUReadback.Requestreturns immediately, so the frame continues.- Once Unity reports the readback complete,
TerrainChunkschedules jobs that copy the samples into its persistent buffers and builds the mesh. - If GPU support is unavailable, the system logs a warning and falls back to the Burst CPU path.
The toolkit uses layered, deterministic value noise instead of a Unity Terrain component dependency. Each channel is computed as fractal noise:
channel(x, z) = Σ(amplitude_i * valueNoise(frequency_i * (world + offset)))
Each octave uses:
amplitude_(i+1) = amplitude_i * persistence
frequency_(i+1) = frequency_i * lacunarity
The resulting raw value is normalized against the theoretical maximum amplitude sum, which is why Global normalization is required for the async streaming path. Every chunk agrees on the same range, so seams remain stable while chunks are built independently.
Every sample stores three terrain signals:
- Height: drives mesh displacement
- Moisture: favors grass or sand depending on altitude
- Temperature: favors snow in colder, higher regions
These channels are computed in lockstep so biome classification does not require a second sampling pass on the main thread.
The default surface classifier produces four blend weights:
- Grass: moderate slopes with decent moisture
- Rock: steep slopes
- Sand: low altitude and dry areas
- Snow: high altitude and cold areas
The weights are stored in vertex colors so you can plug them into a custom shader, Shader Graph, URP/HDRP lit shader extension, or material property workflow without needing Unity's terrain splat system.
Using an explicit tag is the safest way to choose between the stable channel and preview builds.
- Open your Unity project.
- Open
Window > Package Manager. - Click the
+menu. - Choose Add package from git URL...
- Enter one of these URLs:
Stable release:
https://github.com/Markgatcha/ProceduralTerrainToolkit.git#v0.1.0
Latest beta prerelease:
https://github.com/Markgatcha/ProceduralTerrainToolkit.git#v0.2.0-beta.1
If you install from the bare repository URL without #tag, Unity follows the repository's default branch, which may contain beta or in-progress work.
Edit your Unity project's Packages/manifest.json:
{
"dependencies": {
"com.proceduralterraintoolkit.core": "file:../ProceduralTerrainToolkit"
}
}The beta package declares:
com.unity.burstcom.unity.collectionscom.unity.mathematics
Target editor version: Unity 2022.3.17 LTS or newer in the 2022.3 line
- Create an empty GameObject such as
Terrain System. - Add
ChunkManager. - Assign:
- a viewer transform
- a terrain material
- optionally
Runtime/Shaders/NoiseGenerator.computewhen using GPU generation
- Configure
TerrainChunkSettingsandNoiseSettings. - Press Play.
Chunk Size:128Vertex Resolution:65Height Scale:32Job Batch Size:64Generate Collider:false
Visible Chunk Radius:4Update Interval Seconds:0.1Viewer Movement Threshold:8
- Backend:
CpuBurstJobsfor universal compatibility - Height scale:
128 - Moisture scale:
192 - Temperature scale:
256 - All channels:
Globalnormalization
The mesh renderer receives vertex colors as splat weights. A typical shader setup is:
- sample 4 terrain textures
- multiply each sample by the corresponding vertex-color channel
- normalize or blend in the fragment stage
This keeps the package renderer-agnostic and works in Built-in, URP, or HDRP with a compatible material setup.
The repository now includes .github/workflows/release.yml.
When you push a tag such as:
v0.1.0
v0.2.0-beta.1
the workflow:
- checks out the repository
- validates that the tag matches
package.json - copies the package contents into a staging directory
- builds
.zipand.tgzarchives - creates a GitHub release and uploads the package archives
Tags containing a prerelease suffix such as -beta.1 are published as prereleases. Plain semantic-version tags such as v0.1.0 are published as regular releases.
- Mesh application still occurs on the main thread because Unity mesh objects are main-thread-owned.
- GPU readback is asynchronous, but the copied data still needs to be consumed by jobs before rendering.
- Global normalization is required for the async terrain path.
- Collider generation is still more expensive than render-only chunks; keep it disabled unless the gameplay layer requires it.
- chunk LODs
- background normal-map baking
- collision-only low-resolution meshes
- ECS/DOTS world streaming wrappers
- shader graph sample assets under
Samples~