Skip to content

Instantly share code, notes, and snippets.

@unwave
Last active March 22, 2021 23:40
Show Gist options
  • Select an option

  • Save unwave/5434baf1ac8a5b7ec6fdf28d1c4936fe to your computer and use it in GitHub Desktop.

Select an option

Save unwave/5434baf1ac8a5b7ec6fdf28d1c4936fe to your computer and use it in GitHub Desktop.
import bpy
#import os
#os.system('cls')
class Position_Material_Override:
def __init__(self, objects):
self.objects = objects
def __enter__(self):
position_material = bpy.data.materials.new(name="__position__")
position_material.use_nodes = True
self.position_material = position_material
nodes = position_material.node_tree.nodes
links = position_material.node_tree.links
output = nodes["Material Output"]
emission = nodes.new(type='ShaderNodeEmission')
geometry = nodes.new(type='ShaderNodeNewGeometry')
links.new(geometry.outputs['Position'], emission.inputs['Color'])
links.new(emission.outputs['Emission'], output.inputs['Surface'])
prev_materials = {}
self.prev_materials = prev_materials
for object in self.objects:
slots = object.material_slots
if not slots:
prev_materials[object] = None
object.data.materials.append(position_material)
else:
prev_materials[object] = []
for slot in slots:
if slot.material:
prev_materials[object].append(slot.material)
else:
prev_materials[object].append(None)
slot.material = position_material
def __exit__(self, type, value, traceback):
prev_materials = self.prev_materials
for object in self.objects:
materials = prev_materials[object]
if prev_materials[object] == None:
object.data.materials.clear()
else:
slots = object.material_slots
for index, material in enumerate(materials):
slots[index].material = material
bpy.data.materials.remove(self.position_material)
class Height_Baking:
def __init__(self, object):
self.object = object
def __enter__(self):
object = self.object
self.prev_cycles_samples = bpy.context.scene.cycles.samples
bpy.context.scene.cycles.samples = 1
baking_material = bpy.data.materials.new(name="__height_baking__")
baking_material.use_nodes = True
self.baking_material = baking_material
self.prev_materials = None
slots = self.object.material_slots
if not slots:
object.data.materials.append(position_material)
else:
self.prev_materials = []
for slot in slots:
if slot.material:
self.prev_materials.append(slot.material)
else:
self.prev_materials.append(None)
slot.material = baking_material
nodes = baking_material.node_tree.nodes
self.nodes = nodes
links = baking_material.node_tree.links
self.links = links
position = nodes.new(type='ShaderNodeTexImage')
self.position_node = position
output = nodes["Material Output"]
geometry = nodes.new(type='ShaderNodeNewGeometry')
emission = nodes.new(type='ShaderNodeEmission')
subtract = nodes.new(type='ShaderNodeMixRGB')
subtract.blend_type = 'SUBTRACT'
add = nodes.new(type='ShaderNodeMath')
add.operation = 'ADD'
dot_product = nodes.new(type='ShaderNodeVectorMath')
dot_product.operation = 'DOT_PRODUCT'
map_range = nodes.new(type='ShaderNodeMapRange')
self.map_range = map_range
map_range.clamp = False
links.new(position.outputs['Alpha'], subtract.inputs['Fac'])
links.new(position.outputs['Color'], subtract.inputs['Color1'])
links.new(geometry.outputs['Position'], subtract.inputs['Color2'])
links.new(geometry.outputs['Normal'], dot_product.inputs[0])
links.new(subtract.outputs['Color'], dot_product.inputs[1])
links.new(dot_product.outputs['Value'], add.inputs[0])
links.new(add.outputs['Value'], map_range.inputs['Value'])
links.new(map_range.outputs['Result'], emission.inputs['Color'])
links.new(emission.outputs['Emission'], output.inputs['Surface'])
return self
def __exit__(self, type, value, traceback):
bpy.data.images.remove(self.position_image)
bpy.data.materials.remove(self.baking_material)
bpy.context.scene.cycles.samples = self.prev_cycles_samples
if self.prev_materials == None:
self.object.data.materials.clear()
else:
slots = self.object.material_slots
for index, material in enumerate(self.prev_materials):
slots[index].material = material
def bake(self, width, height, cage_extrusion = 0.5, float_buffer = False, margin=0):
# for GPU?
#self.prev_render_tile_x = bpy.context.scene.render.tile_x
#self.prev_render_tile_y = bpy.context.scene.render.tile_y
#bpy.context.scene.render.tile_x = 256
#bpy.context.scene.render.tile_y = 256
use_persistent_data = bpy.context.scene.render.use_persistent_data
bpy.context.scene.render.use_persistent_data = False # otherwise will fail
self.position_image = bpy.data.images.new("__baked_position__", width=width, height=height, float_buffer=True, is_data=True)
self.position_node.image = self.position_image
self.position_node.select = True
self.nodes.active = self.position_node
bpy.ops.object.bake(type='EMIT', cage_extrusion=cage_extrusion, use_selected_to_active=True, margin=margin)
height_image = bpy.data.images.new(self.object.name + "_height", width=width, height=height, float_buffer=float_buffer, is_data=True)
height = self.nodes.new( type='ShaderNodeTexImage')
height.image = height_image
height.select = True
self.nodes.active = height
cage_extrusion = cage_extrusion/2
self.map_range.inputs['From Min'].default_value = -(cage_extrusion - 0.5)
self.map_range.inputs['From Max'].default_value = cage_extrusion + 0.5
bpy.ops.object.bake({'selected_objects': [self.object]}, use_selected_to_active=False, type='EMIT', margin=margin)
#bpy.context.scene.render.tile_x = self.prev_render_tile_x
#bpy.context.scene.render.tile_y = self.prev_render_tile_y
bpy.context.scene.render.use_persistent_data = use_persistent_data
return height_image
selected_objects = bpy.context.selected_objects
assert len(selected_objects) > 1, "Select at least two objects."
active = bpy.context.object
selected = selected_objects.copy()
selected.remove(active)
from timeit import default_timer as timer
with Position_Material_Override(selected), Height_Baking(active) as baking:
start = timer()
cage_extrusion = 3
image = baking.bake(1024, 1024, cage_extrusion = cage_extrusion, float_buffer = False, margin=0)
print("Baking time:", timer()-start)
nodes = bpy.data.materials["TEST"].node_tree.nodes
nodes["Image Texture"].image = image
nodes["Displacement"].inputs['Scale'].default_value = cage_extrusion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment