Files
snow_trail/blender/scripts/generate_heightmap.py
2026-02-08 14:06:35 +01:00

136 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import bpy
from pathlib import Path
def bake_heightmap(terrain_obj, resolution=1024, output_path=None):
"""
Bake terrain heightmap using Blender's render/bake system.
Args:
terrain_obj: Terrain mesh object
resolution: Texture resolution (square)
output_path: Path to save EXR file
"""
print(f"Baking heightmap for: {terrain_obj.name}")
print(f"Resolution: {resolution}×{resolution}")
# Ensure object has UV map
if not terrain_obj.data.uv_layers:
print("Adding UV map...")
terrain_obj.data.uv_layers.new(name="UVMap")
# Create new image for baking
bake_image = bpy.data.images.new(
name="Heightmap_Bake",
width=resolution,
height=resolution,
alpha=False,
float_buffer=True,
is_data=True
)
# Setup material for baking
if not terrain_obj.data.materials:
mat = bpy.data.materials.new(name="Heightmap_Material")
terrain_obj.data.materials.append(mat)
else:
mat = terrain_obj.data.materials[0]
mat.use_nodes = True
nodes = mat.node_tree.nodes
nodes.clear()
# Create nodes for height baking
# Geometry node to get position
geo_node = nodes.new(type='ShaderNodeNewGeometry')
# Separate XYZ to get Z (height)
separate_node = nodes.new(type='ShaderNodeSeparateXYZ')
mat.node_tree.links.new(geo_node.outputs['Position'], separate_node.inputs['Vector'])
# Emission shader to output height value
emission_node = nodes.new(type='ShaderNodeEmission')
mat.node_tree.links.new(separate_node.outputs['Z'], emission_node.inputs['Color'])
# Material output
output_node = nodes.new(type='ShaderNodeOutputMaterial')
mat.node_tree.links.new(emission_node.outputs['Emission'], output_node.inputs['Surface'])
# Add image texture node (required for baking target)
image_node = nodes.new(type='ShaderNodeTexImage')
image_node.image = bake_image
image_node.select = True
nodes.active = image_node
# Select object and set mode
bpy.context.view_layer.objects.active = terrain_obj
terrain_obj.select_set(True)
# Setup render settings for baking
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.cycles.samples = 1
bpy.context.scene.cycles.bake_type = 'EMIT'
print("Baking...")
bpy.ops.object.bake(type='EMIT', use_clear=True)
print("Bake complete!")
# Save as EXR
if output_path:
bake_image.filepath_raw = str(output_path)
bake_image.file_format = 'OPEN_EXR'
bake_image.use_half_precision = False
scene = bpy.context.scene
original_color_mode = scene.render.image_settings.color_mode
original_color_depth = scene.render.image_settings.color_depth
scene.render.image_settings.color_mode = 'BW'
scene.render.image_settings.color_depth = '32'
bake_image.save_render(str(output_path), scene=scene)
scene.render.image_settings.color_mode = original_color_mode
scene.render.image_settings.color_depth = original_color_depth
print(f"Saved to: {output_path}")
# Cleanup
bpy.data.images.remove(bake_image)
return True
if __name__ == "__main__":
project_root = Path(bpy.data.filepath).parent.parent
output_path = project_root / "textures" / "terrain_heightmap.exr"
output_path.parent.mkdir(parents=True, exist_ok=True)
# Find terrain object
terrain_obj = bpy.data.objects.get("TerrainPlane")
if not terrain_obj:
print("'TerrainPlane' not found. Searching for terrain mesh...")
for obj in bpy.data.objects:
if obj.type == 'MESH' and ('terrain' in obj.name.lower() or 'plane' in obj.name.lower()):
terrain_obj = obj
print(f"Using: {obj.name}")
break
if not terrain_obj:
raise ValueError("No terrain object found!")
bake_heightmap(
terrain_obj=terrain_obj,
resolution=1000,
output_path=output_path
)
print("\n" + "="*60)
print("Heightmap baking complete!")
print(f"Output: {output_path}")
print("="*60)