
Automatische Generierung von Terrain-Layern in Blender mit Python
Ein Python-Modul zur Erstellung gelayerter Terrain-Materials mit automatisch erzeugten Masken über Geometry Nodes
Bei der Entwicklung von 3D-Spielen in Unity ist mir aufgefallen, dass ich 3D-Welten lieber zuerst in Blender aufbaue. Die Werkzeuge dort sind deutlich leistungsfähiger und flexibler als die integrierten Funktionen von Unity, besonders wenn es um die Gestaltung von Gelände geht.
Deshalb wollte ich eine Möglichkeit, schnell mehrere Textur-Ebenen auf ein Terrain-Mesh in Blender anzuwenden. Jede dieser Ebenen sollte eine eigene Maske besitzen, die steuert, in welchen Bereichen die jeweilige Textur sichtbar ist. Das manuelle Malen dieser Masken wäre nicht nur zeitaufwendig, sondern würde auch weniger organisch wirken. Daher sollten die Masken automatisch anhand von Gelände-Eigenschaften wie Höhe oder Neigung erzeugt werden. Außerdem war mir wichtig, den Prozess möglichst modular zu gestalten, um Ebenen flexibel hinzufügen, entfernen oder anpassen zu können.
Die Erzeugung der Masken lässt sich gut über Geometry Nodes realisieren, und die berechneten Masken können anschließend für die Weiterverarbeitung in Unity exportiert werden. Dank der Shader-Nodes in Blender ist es außerdem möglich, das Ergebnis direkt in der Vorschau zu betrachten und die spätere Optik des Terrains zu simulieren.
Da das wiederholte Erstellen dieser Node-Systeme bei vielen Layern jedoch mühsam und repetitiv ist, habe ich ein Python-Modul entwickelt, das diesen Ablauf automatisiert. Über das Blender-Modul bpy lassen sich Objekte und Node-Strukturen programmgesteuert erzeugen und bearbeiten, sowohl für Geometry Nodes als auch für Shader Nodes.
Mit dem Modul lassen sich unter anderem:
Die Terrain-Layer können über ein einfaches Python-Dictionary definiert werden, unterstützt durch Dataclasses für bessere Struktur und Type Checking. Hier ein Beispiel für eine mögliche Konfiguration:
# Two dual noise configs: default and an alternate for "Rock"
dual_default = DualNoiseConfig(scale=6.0, large_scale=1.5, large_mix=0.35, detail=1.0)
dual_alt = DualNoiseConfig(scale=10.0, large_scale=2.2, large_mix=0.55, detail=0.8)
config = TerrainConfig(
geometry_modifier_name="Terrain_Layer_Masks",
layers=[
# Underwater layer: default layer for everything not covered by other layers
# Uses uv warping and anti-tiling to improve texture appearance
Layer(
name="Underwater",
priority=0,
strength=1.0,
ground_material=GroundMaterial(
"Muddy ground with underwater moss",
uv_scale=2.0,
uv_warp=UVWarpConfig(),
uv_anti_tiling=UVAntiTilingConfig(),
),
),
# Beach layer: sandy areas near the shore
# Uses height mask with noise to create natural transitions
Layer(
name="Beach",
priority=10,
strength=1.0,
mask=HeightMask(
min_height=1.0,
max_height=6.5,
ramp_low=0.35,
ramp_high=0.55,
),
mask_noise=MaskNoiseConfig(
dual=dual_default,
amount=2.0,
sharpness=1.6,
bias=0.0,
zone_width=0.35,
zone_softness=1.0,
),
ground_material=GroundMaterial("Sand"),
),
# Grass layer: mid-elevation grassy areas
Layer(
name="Grass",
priority=20,
strength=1.0,
mask=HeightMask(
min_height=3.5,
max_height=8.0,
ramp_low=0.45,
ramp_high=0.65,
),
mask_noise=MaskNoiseConfig(
dual=dual_default,
amount=1.8,
sharpness=1.8,
bias=0.0,
zone_width=0.5,
zone_softness=1.0,
),
ground_material=GroundMaterial("Grass"),
),
# Snow layer: high-elevation snowy areas
Layer(
name="Snow",
priority=25,
strength=1.0,
mask=HeightMask(
min_height=11.0,
max_height=15.0,
ramp_low=0.45,
ramp_high=0.65,
),
mask_noise=MaskNoiseConfig(
dual=dual_default,
amount=1.2,
sharpness=2.2,
bias=0.0,
zone_width=0.3,
zone_softness=1.2,
),
ground_material=GroundMaterial("Snow"),
),
# Rock layer: steep slope areas
Layer(
name="Rock",
priority=27,
strength=1.0,
mask=SlopeMask(
min_angle=25.0,
max_angle=60.0,
ramp_low=0.4,
ramp_high=0.6,
),
mask_noise=MaskNoiseConfig(
dual=dual_alt,
amount=2.2,
sharpness=2.0,
bias=0.0,
zone_width=0.4,
zone_softness=1.0,
),
ground_material=GroundMaterial("Rock"),
),
# Volcanos layer: specific areas defined by a paint mask
Layer(
name="Volcanos",
priority=30,
strength=1.0,
mask=PaintMask(
image_name="IMG_Terrain_VolcanosMask",
),
ground_material=GroundMaterial("04 Vulcanic Rock Surface D"),
),
],
)
Die folgenden Screenshots zeigen die automatisch erzeugten Geometry- und Shader-Nodes für ein Terrain mit mehreren Textur-Ebenen:


Und hier einige Render-Beispiele mit den generierten Terrain-Layern. Die Konfiguration und Ausführung des Skripts dauerten nur wenige Minuten, im Vergleich zu vielen Stunden manueller Arbeit.


Obwohl das Modul bereits jetzt ziemlich hilfreich ist, gibt es noch Erweiterungsmöglichkeiten. Da die Masken vorab berechnet werden, eignen sie sich nicht nur für Texturen, sondern auch für weitere Anwendungsfälle, wie etwa die Platzierung von Vegetation oder gezielte Verformungen des Geländes.
Denkbar wäre außerdem ein neuer Maskentyp für Straßen, bei dem Geometry Nodes automatisch ein Straßen-Mesh entlang eines in Blender definierten Bezier-Pfads erzeugen. Damit könnte das Projekt langfristig zu einer vollständigen Terrain-Generierungs-Pipeline wachsen, statt nur ein Texturierungs-Werkzeug zu sein.
Noch Fragen?