136 lines
4.1 KiB
Python
136 lines
4.1 KiB
Python
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)
|