A high-performance Unity terrain manipulation system using runtime heightmap projection
Created by evesfect
Dynamic Terrain System enables real-time terrain manipulation in Unity through mesh-based heightmap projection. Instead of using individual meshes for ground geometry (which severely impacts performance), this system projects mesh heights onto Unity's terrain heightmap, leveraging the engine's built-in LOD optimization and culling systems.
The entire terrain update process is handled through a single function call, making it ideal for both editor workflows and runtime applications.
This system is inspired from the Game Developers Conference session of Giant Squid Studios, Creating the Art of ABZU https://www.youtube.com/watch?v=l9NX06mvp2E&t=937s
- Runtime and Editor Terrain Manipulation
dt-1.mp4
- Automatic LOD generation from native Unity Terrain
dt-2.mp4
- Flexible Terrain Sizes - Supports almost any terrain dimensions and height ranges (adjustable via settings)
- Single Function Call -
ApplyRenderTextureToTerrain()handles the entire update process - GPU-Accelerated - Uses GPU texture copy for efficient heightmap updates
- Manual or Automatic - Trigger updates manually / integrate into your own scripts
- No Performance Impact - Heightmap projection done and updated in two frames, only updates terrain data, no extra meshes.
Building ground terrain in Unity traditionally involves either:
- Individual meshes → Poor performance due to excessive draw calls and lack of LOD optimization
- Manual terrain sculpting → Time-consuming and difficult to automate
This system solves both problems by:
- Projecting mesh geometry to heightmap data → Only terrain data is modified, no performance penalty
- Leveraging Unity's terrain system → Automatic LOD, occlusion culling, and optimized rendering
- Single function API → Can be called at any time, in editor or runtime
- Supports runtime manipulation → Ideal for procedural generation, building placement, and ground manipulation
- Procedural terrain generation - Manipulate terrain from code at runtime
- Building placement systems - Automatically flatten or modify terrain when placing structures
- Dynamic environment changes - Modify terrain in response to gameplay events
- Rapid level design - Quickly block out terrain using simple meshes in the editor
- For standard and large terrains, runtime performance is excellent due to Unity's terrain LOD system
- Extremely large maps may experience slower update times during heightmap application
- The terrain itself renders at native Unity terrain performance regardless of update frequency
The system uses a three-stage pipeline to convert mesh geometry into terrain heightmap data:
An orthographic camera positioned above the terrain captures a top-down view:
- Positioned at terrain center, looking down along the Y-axis
- Orthographic size matches terrain dimensions for 1:1 mapping
- Culling mask set to only render the
TerrainMesheslayer - Outputs to a RenderTexture matching the terrain's heightmap resolution
Meshes in the TerrainMeshes layer use a custom URP shader (HeightShader.shader) that:
- Reads the mesh vertex world-space Y position
- Normalizes height between configurable min/max height bounds
- Encodes normalized height as grayscale values (0.0 to 0.5 range)
- Outputs to the camera's RenderTexture
The shader formula: normalizedHeight = saturate((worldY - minHeight) / (maxHeight - minHeight)) * 0.5
The TerrainHeightmapUpdater component:
- Receives the RenderTexture containing encoded height data
- Uses Unity's
CopyActiveRenderTextureToHeightmap()API for efficient GPU-to-terrain transfer - Optionally syncs terrain LOD meshes via
SyncHeightmap() - Applies the heightmap in a single frame
- Render Pipeline: Universal Render Pipeline (URP)
- Required Layer:
TerrainMeshes(Layer 10 by default)
- You can use the DynamicTerrain prefab for ease (found in
Assets/DynamicTerrainSystem/Prefabs) - Attach your terrain to the Dynamic Terrain Manager script (in the prefab)
- You should call Update Terrain and Camera function (available in context menu)
- Assign M_HeightShader material to your terrain meshes
- Set your terrain meshes layer to 10:TerrainMeshes (just edit if necessary)
- Ready to go, you can call
Apply Render Texture to Terrainat editor/runtime Note: I recommend disabling 10:TerrainMeshes layer from the main cameras culling mask, for convenience.
You can see the setup in the TestScene
// Get reference to the manager
DynamicTerrainManager manager = FindObjectOfType<DynamicTerrainManager>();
// Update terrain from current mesh positions
manager.ApplyRenderTextureToTerrain();
// Or update terrain configuration first, then apply
manager.UpdateTerrainAndCamera();
manager.ApplyRenderTextureToTerrain();Modify the TerrainSettings in the DynamicTerrainManager:
terrainSize- XZ dimensions of the terrain (Vector2)minHeight/maxHeight- Y-axis height range for encodingheightmapResolution- Resolution of the heightmap (must match RenderTexture resolution)
Ensure the _MinHeight and _MaxHeight properties on the M_HeightShader material match your terrain settings.
Created by evesfect
Built with Unity 6000.0.34f1 LTS and Universal Render Pipeline.