BRG Terrain Registerer
Registers a Unity Terrain's trees and details for GPU-instanced rendering through BRG Instanced Renderer. Trees are extracted once at startup. Details stream dynamically around the camera using C# Jobs and Burst Compiler.
For a step-by-step setup guide, see Getting Started: Terrain.
Note
Most terrain options are controlled in the global Config (streaming budgets, motion vectors, detail distance culling, etc.). The settings on this component are limited to per-terrain options.
Settings
| Property | Type | Default | Description |
|---|---|---|---|
| Disable Built-In Rendering | bool | true | Disables the terrain's native drawTreesAndFoliage, so BRG Instanced Renderer takes over all tree and detail rendering. |
| Enable Debug Logging | bool | false | Outputs detailed logging for tree extraction, detail streaming, and patch loading (Development builds only). |
Trees
| Property | Type | Default | Description |
|---|---|---|---|
| Cull Distance | float | — | Read-only. Shows the terrain's Tree Distance setting. Adjust this in Unity's Terrain settings. |
| Chunk Size | float | 256m | World-space size of each spatial chunk for trees. Smaller values give better culling precision but more overhead. |
| Show Chunk Gizmos | bool | false | Draw wireframe boxes in the Scene View for each tree chunk, color-coded by fill percentage. |
| Skip BRG Upload | bool | false | Debug: skip GPU upload for trees. |
The inspector also shows a status line with tree prototype count, total instance count, and chunk count when registered.
Details
Streaming
| Property | Type | Default | Description |
|---|---|---|---|
| Preserve Prototype Layers | bool | false | When enabled, detail instances keep their prefab's layer. When disabled, details use the terrain GameObject's layer (matches Unity's tree behavior). |
| Cull Distance | float | — | Read-only. Shows the terrain's Detail Object Distance setting. Adjust this in Unity's Terrain settings. |
| Detail Chunk Size | float | 32m | World-space size of detail streaming cells. Determines how finely details are streamed in and out around the camera. Snapped to terrain patch boundaries. |
The inspector shows computed read-only fields for Actual Chunk Size (snapped to patch boundaries) and Patch Size when a terrain is assigned.
| Property | Type | Default | Description |
|---|---|---|---|
| Use Streaming | bool | true | When enabled, patches are loaded and unloaded based on camera distance. When disabled, all patches are loaded at startup. |
Global streaming budget settings (upload throttle, unload throttle, concurrent jobs, hysteresis) are configured on the Config asset.
Transform Generation
| Property | Type | Default | Description |
|---|---|---|---|
| Use Unity Transforms | bool | false | Use Unity's ComputeDetailInstanceTransforms API instead of the custom extraction algorithm. Slower and allocates GC, but produces a 1:1 match with Unity's built-in detail rendering. |
Extracted Data
| Property | Type | Default | Description |
|---|---|---|---|
| Pre-Baked Terrain Data | BRGExtractedTerrainData | None | Optional asset holding terrain data extracted ahead of time. When present and matching this terrain, detail layers, trees, holes, and the heightmap are loaded from it instead of read back from TerrainData. See Pre-Extracted Terrain Data. |
The section also exposes Create & Assign, Refresh Registerer, and Clear Editor Cache buttons, plus a payload status readout. See Setup.
Animated Crossfade
| Property | Type | Default | Description |
|---|---|---|---|
| Snap Crossfade On Start | bool | true | Snap animated crossfade to the target LOD for patches already in range when the component initializes. Prevents a mass fade-in on scene load. |
Debug
| Property | Type | Default | Description |
|---|---|---|---|
| Show Chunk Gizmos | bool | false | Draw wireframe boxes in the Scene View for each detail chunk, color-coded by fill percentage. |
| Skip BRG Upload | bool | false | Debug: skip GPU upload for details. |
The inspector also shows a status line with detail prototype count, patch grid size, loaded instance/patch/chunk counts, and pending patches.
Detail Prefab Overrides
To substitute a terrain detail prototype with a different prefab (e.g. an LOD Group variant with multiple LODs), add a BRG Terrain Detail Override component to the detail prototype prefab and assign the replacement prefab to the Override Prefab field.
The terrain registerer resolves overrides at editor time and uses the override prefab for all rendering. The resolved prefab list is shown read-only in the inspector. Override resolution has no runtime cost.
Inspector Actions
| Button | Description |
|---|---|
| Refresh All | Reloads all tree and detail data from the terrain. Call after modifying terrain data outside the paint tools. |
| Clear Caches | Clears cached extraction data and performs a full reload. |
Public Methods
GetTreeInstanceLinks
public InstanceLink[] GetTreeInstanceLinks()
Returns InstanceLink handles for all registered tree instances, or null if trees aren't registered yet. Use the returned handles with the Stage* API (or *Unsafe direct API) to modify individual trees at runtime (e.g. set per-tree color).
GetChunkOwnedTrees
public NativeArray<int> GetChunkOwnedTrees(int chunkId)
Returns the terrain tree indices owned by a chunk, in the same order as that chunk's InstanceLinks. This lets you map a BRG instance back to its entry in TerrainData.treeInstances — useful for spawning colliders, reading original tree data, or building your own index.
| Returns | Description |
|---|---|
| NativeArray<int> | Indices into CachedTreeInstanceData / TerrainData.treeInstances. Returns default (an uncreated array) if trees aren't registered or the chunk is unknown — always check .IsCreated. |
Important
The returned array is a read-only view into internal storage, not a copy. Do not dispose it. It is only valid until the next tree rebuild (a Refresh, terrain edit, or re-registration invalidates it) — don't cache it across frames.
var indices = registerer.GetChunkOwnedTrees(chunkId);
if (!indices.IsCreated) return;
for (int i = 0; i < indices.Length; i++)
{
var link = registerer.Registration.GetInstanceLink(new ChunkLink(chunkId), i);
if (!link.IsValid) continue;
TreeInstance src = registerer.CachedTreeInstanceData[indices[i]];
// ... use src, or store (indices[i] -> link) in your own map
}
ClearExtractionCache
public static void ClearExtractionCache(Terrain terrain)
public static void ClearAllExtractionCaches()
Clear cached tree and detail extraction data for a specific terrain or all terrains. The component caches extraction data to avoid redundant work across enable/disable cycles. Call these when terrain data has changed externally and you need a clean re-extraction.
Tree Data Properties
| Member | Type | Description |
|---|---|---|
| TreeChunkIds | ICollection<int> | Chunk IDs currently holding registered trees. Iterate these to walk every tree chunk. |
| CachedTreeInstanceData | TreeInstance[] | The terrain's cached TreeInstance array. Index into it using values from GetChunkOwnedTrees. |
OnTreeChunkWritten Event
public event Action<ChunkLink, NativeArray<int>> OnTreeChunkWritten
Fires once per tree chunk after its GPU write completes, passing the chunk and its owned terrain tree indices. This is the safe place to resolve InstanceLinks for tree chunks — calling GetInstanceLink right after registration isn't guaranteed, since the write may be deferred.
Subscribe on enable, and seed from already-registered chunks so you don't miss any that were written before you subscribed:
void OnEnable()
{
foreach (int chunkId in _registerer.TreeChunkIds)
HandleChunk(new ChunkLink(chunkId), _registerer.GetChunkOwnedTrees(chunkId));
_registerer.OnTreeChunkWritten += HandleChunk;
}
void OnDisable()
{
if (_registerer != null)
_registerer.OnTreeChunkWritten -= HandleChunk;
}
void HandleChunk(ChunkLink chunk, NativeArray<int> terrainTreeIndices)
{
if (!terrainTreeIndices.IsCreated) return;
for (int i = 0; i < terrainTreeIndices.Length; i++)
{
var link = _registerer.Registration.GetInstanceLink(chunk, i);
if (!link.IsValid) continue;
// ... modify the tree, or map terrainTreeIndices[i] -> link
}
}
The
NativeArray<int>passed to the handler follows the same rules as GetChunkOwnedTrees — it's a non-owning view, don't dispose it, and don't cache it past the current callback.
For working implementations, see Examples/Scripts/TerrainTreeRandomColor.cs and Examples/Scripts/TerrainTreeChopper.cs.
Runtime Distance Overrides
Two static properties let you override tree and detail render distances at runtime across all active terrain registerers. Useful for graphics-quality scaling (e.g. push trees and grass farther on high-end hardware, pull them in on low-end).
public static float TerrainBRGRegisterer.TreeRenderDistance // default: float.MaxValue
public static float TerrainBRGRegisterer.DetailRenderDistance // default: float.MaxValue
The effective render distance is min(terrain setting, override) — so the override can only reduce the distance, never exceed what the terrain itself was configured for. Setting either back to float.MaxValue removes the override.
Assigning to these properties immediately re-applies the new distance to every registered chunk on every active terrain.
// pull tree render distance in to 200m globally
TerrainBRGRegisterer.TreeRenderDistance = 200f;
// pull detail render distance in to 80m globally
TerrainBRGRegisterer.DetailRenderDistance = 80f;
// remove the override
TerrainBRGRegisterer.TreeRenderDistance = float.MaxValue;
How Trees Work
Tree instances are read from the terrain data when the component initializes. They are:
- Spatially batched into chunks for hierarchical culling
- Uploaded to the GPU as permanent instances
- Matched to the prefab assigned in the terrain's tree prototype settings
In the editor, tree changes made with Unity's terrain paint tools are detected automatically. The registerer performs an incremental update without needing a manual refresh.
How Details Work
Terrain details (grass, flowers, small vegetation) are streamed based on camera proximity:
- The terrain is divided into a grid based on Detail Chunk Size
- Patches near the camera are loaded using Burst-compiled extraction jobs
- Patches are unloaded as the camera moves away
- Extraction runs on worker threads to avoid frame hitches
This allows millions of detail instances without loading the entire terrain upfront. See Terrain Detail Spawning for more on the custom extraction algorithm.
Unity's default detail prototypes are single-mesh prefabs with no LOD support. To use an LOD Group prefab as a detail instead, see BRG Terrain Detail Override.
Editor Behavior
- Tree and detail changes from Unity's terrain paint tools are detected and updated automatically
- The component works outside of play mode for editor preview
- Individual terrain trees can be selected and edited directly in the Scene View using the terrain tree selector tool
Notes
- One BRG Terrain Registerer per Terrain GameObject.
- The component caches extraction data per terrain. Cached data survives enable/disable cycles to avoid redundant extraction.
- Per-prefab settings (shadows, density, crossfade) are controlled via BRG Prototype Extra Data on the tree/detail prefabs.
- Use Clear Caches if you modify terrain data through scripts, and the automatic change detection does not detect it.