Houdini - Unity Terrain Streaming
Procedural terrain workflow that separates designer-controlled geographic layout from automated terrain reconstruction using metadata-driven generation.
real-world map data → Unity editor UX → tile/color/elevation acquisition → metadata → Houdini HDA terrain generation → Unity/Unreal output
Problem
I originally built a Mapbox-style terrain generation workflow similar to the Houdini Labs Mapbox tool, with location selection happening inside Houdini.
That version worked technically, but it created adoption friction: Unity designers using Houdini Engine had to leave Unity, open Houdini, learn a separate UI, choose a location there, then return to Unity to continue. Because a critical step lived outside their day-to-day environment, the workflow felt disconnected and harder for non-Houdini users to trust.
Most real-world terrain workflows are fragmented across map tools, manual downloads, coordinate conversion, and reconstruction steps.
I wanted to reduce that friction by separating:
- High-level intent: where and what region the artist wants.
- Low-level generation: how terrain is reconstructed procedurally.
This created a metadata-driven workflow where geographic selection becomes an abstract procedural input rather than a manual setup process.
Solution
I redesigned the workflow around the user instead of the tool.
I built a custom Unity Editor interface that connects directly to the Mapbox API, so users can select a location and download color + DEM data without leaving Unity. The Houdini tool then reads that exported data to generate terrain procedurally via Houdini Engine.
From the artist perspective, the process became simpler and more controllable: stay in Unity, click once to rebuild, and iterate quickly on new locations.
System design
Acquisition layer (Unity)
A lightweight Unity editor tool was used for:
- Real-world location selection
- Imagery/elevation acquisition
- Metadata standardization
Unity map selector → imagery/elevation download → metadata export (meta.json) → Houdini HDA reconstruction → Unity/Unreal-ready terrain
flowchart LR
A[Unity Map Selector] --> B[Imagery + Elevation Download]
B --> C[Metadata Export<br/>meta.json]
C --> D[Houdini HDA Reconstruction]
D --> E[Unity/Unreal-ready Terrain]- Interactive map navigation: pan, zoom, and click to get precise latitude/longitude + tile IDs.
- Tile intelligence: 3x3 neighbor lookup, cache management, and parent/child tile fallback while streaming.
- Download pipeline: one-click export of satellite color + Terrain-RGB elevation tiles, plus metadata (
meta.json) with scale and geo-reference. - Data handoff: exported raster data is passed into a Houdini HDA workflow that converts height data into clean terrain geometry for downstream use in Unity.
The Unity side intentionally stayed minimal — its role was only to capture spatial intent and export deterministic terrain metadata.


meta.json is the bridge between the map-selection stage and the Houdini reconstruction stage:
This terrain came from this exact location, this exact zoom level, this exact tile, and this exact real-world scale.
Without this file, your Houdini terrain generation becomes partially blind.
Core Technical Decisions
Deterministic metadata bridge
- Exported
lat,lon,zoom,tileX,tileY, and real-world dimensions. - Made terrain reconstruction reproducible and debuggable.
- Exported
Provider-flexible acquisition
- Tested with Mapbox/OpenStreetMap, Esri, and Cesium-style service endpoints.
- Kept the acquisition layer abstract enough for different data backends.
Artist-focused UX
- Enabled location picking and terrain setup from inside Unity Editor.
- Reduced context switching and manual data wrangling.
Example Metadata Payload
{
"sector": "Sector_01",
"timestamp": "2026-03-06 17:20:41",
"geo": {
"lat": 50.7646067735562,
"lon": -125.744293271564,
"zoom": 9
},
"dimensions": {
"widthMeters": 99014.7,
"heightMeters": 99014.7,
"metersPerPixel": 193.3881,
"elevationMultiplier": 0.1
},
"mapping": {
"tileX": 77,
"tileY": 171
}
}Technical Deep Dive
How Zoom Level Changes Output
Zoom level directly controls how much land each tile represents and how dense the resulting terrain detail will be.
- Higher zoom → smaller area, more detail
- Lower zoom → larger area, less detail
It directly affects terrain resolution, meters-per-pixel, and detail density.
metersPerPixel in Practice
metersPerPixel converts downloaded image pixels into real-world scale.
If your image is 512 pixels wide and metersPerPixel = 193.3881:
512 × 193.3881 ≈ 99014.7
That gives a terrain width of about 99 km, which is then used by Houdini for physically meaningful terrain scale.
Tile Mapping and Neighbor Stitching
tileX / tileY identify the exact source tile and make reconstruction deterministic.
Beyond single-tile generation, this supports neighbor-aware workflows like terrain streaming and basic neighbor-tile stitching across adjacent sectors. In this project, 3x3 neighbor lookup and stitching prep are implemented in the acquisition layer, while full runtime world streaming is a future extension.
Reconstruction layer (Houdini)
Houdini became the procedural reconstruction system. Using exported metadata (terrain scale, geographic coordinates, tile mapping, and elevation references), the HDA reproducibly reconstructs terrain and prepares outputs for Unity/Unreal pipelines.
This separated artist intent from technical reconstruction complexity.
image: HDA parameters
image: network 
image: attribute from houdini to Unity
unity_hf_terrainlayer_file
unity_hf_texture_diffuse
unity_hf_tile_size (from Python scale)
These attributes map directly to Unity Terrain/Houdini Engine import fields, so terrain layers, diffuse textures, and tile scale are applied automatically when the HDA cooks in Unity.
Python SOP Reference Snippet
The following pattern (parameter read + path expansion + safety checks) is useful when extending the HDA with metadata-driven logic:
import json
import os
node = hou.pwd()
geo = node.geometry()
raw_path = node.evalParm("json_file")
path = hou.text.expandString(raw_path)
if os.path.exists(path) and os.path.isfile(path):
with open(path, "r") as f:
data = json.load(f)
dims = data.get("dimensions", {})
geo.addAttrib(hou.attribType.Global, "widthMeters", float(dims.get("widthMeters", 0)))
geo.addAttrib(hou.attribType.Global, "metersPerPixel", float(dims.get("metersPerPixel", 0)))
else:
print(f"Warning: JSON file not found at {path}")Tools
- Unity EditorWindow (C#): custom interface, request queueing, tile cache, click-marker UX.
- Mapbox tiles: hybrid map rendering and terrain source acquisition.
- UnityWebRequest: asynchronous tile fetching and concurrent download management.
- Houdini Digital Asset (HDA): converts downloaded terrain maps into production-ready geometry.
- JSON metadata export: preserves geo alignment and scale for deterministic reconstruction.
Impact
- Reduced terrain setup friction.
- Improved reproducibility through metadata-driven workflows.
- Faster iteration between acquisition and reconstruction.
- Demonstrated procedural pipeline/tool development for real-time environments.



