refactor(渲染): 重构深度和法向渲染逻辑以提高可维护性
- 将深度渲染从依赖外部合成节点改为程序化材质生成 - 提取通用渲染逻辑到 `_render_with_material` 函数 - 改进转换对象追踪方式,使用场景属性存储名称 - 清理函数中移除临时深度材质和场景属性
This commit is contained in:
202
utils.py
202
utils.py
@@ -177,35 +177,105 @@ def _add_rendered_image(scene, name, filepath):
|
|||||||
scene.sna_rendered_images_index = len(scene.sna_rendered_images) - 1
|
scene.sna_rendered_images_index = len(scene.sna_rendered_images) - 1
|
||||||
|
|
||||||
|
|
||||||
def _render_depth(operator):
|
def _create_depth_material(near, far):
|
||||||
"""Render depth map using Depth_Mat compositor NodeTree."""
|
"""Create a material that renders depth as grayscale (near=white, far=black)."""
|
||||||
|
mat_name = '__ComfyDepthMat__'
|
||||||
|
if mat_name in bpy.data.materials:
|
||||||
|
bpy.data.materials.remove(bpy.data.materials[mat_name])
|
||||||
|
|
||||||
|
mat = bpy.data.materials.new(name=mat_name)
|
||||||
|
mat.use_nodes = True
|
||||||
|
nodes = mat.node_tree.nodes
|
||||||
|
links = mat.node_tree.links
|
||||||
|
nodes.clear()
|
||||||
|
|
||||||
|
# Camera Data → View Z Depth
|
||||||
|
cam_data = nodes.new('ShaderNodeCameraData')
|
||||||
|
cam_data.location = (0, 0)
|
||||||
|
|
||||||
|
# Map Range: [near..far] → [1..0] (near=white, far=black)
|
||||||
|
map_range = nodes.new('ShaderNodeMapRange')
|
||||||
|
map_range.location = (200, 0)
|
||||||
|
map_range.inputs['From Min'].default_value = near
|
||||||
|
map_range.inputs['From Max'].default_value = far
|
||||||
|
map_range.inputs['To Min'].default_value = 1.0
|
||||||
|
map_range.inputs['To Max'].default_value = 0.0
|
||||||
|
map_range.clamp = True
|
||||||
|
|
||||||
|
# Emission shader (unlit)
|
||||||
|
emission = nodes.new('ShaderNodeEmission')
|
||||||
|
emission.location = (400, 0)
|
||||||
|
|
||||||
|
# Output
|
||||||
|
output = nodes.new('ShaderNodeOutputMaterial')
|
||||||
|
output.location = (600, 0)
|
||||||
|
|
||||||
|
links.new(cam_data.outputs['View Z Depth'], map_range.inputs['Value'])
|
||||||
|
links.new(map_range.outputs['Result'], emission.inputs['Color'])
|
||||||
|
links.new(emission.outputs['Emission'], output.inputs['Surface'])
|
||||||
|
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
def _get_converted_obj(scene, operator):
|
||||||
|
"""Find the converted copy object using saved name."""
|
||||||
|
converted_name = scene.get('_comfy_converted_name', '')
|
||||||
|
converted_obj = bpy.data.objects.get(converted_name)
|
||||||
|
if converted_obj is None or converted_obj.type != 'MESH':
|
||||||
|
operator.report({'ERROR'}, message='Cannot find converted copy: ' + converted_name)
|
||||||
|
return None
|
||||||
|
return converted_obj
|
||||||
|
|
||||||
|
|
||||||
|
def _get_depth_range(scene, converted_obj):
|
||||||
|
"""Calculate the min/max view-Z depth from the camera to the object bounding box."""
|
||||||
|
cam = scene.camera
|
||||||
|
if cam is None:
|
||||||
|
return 0.1, 10.0
|
||||||
|
cam_inv = cam.matrix_world.inverted()
|
||||||
|
min_z = float('inf')
|
||||||
|
max_z = float('-inf')
|
||||||
|
for corner in converted_obj.bound_box:
|
||||||
|
world_pt = converted_obj.matrix_world @ Vector(corner)
|
||||||
|
local_pt = cam_inv @ world_pt
|
||||||
|
z = -local_pt.z # View Z Depth is positive distance along camera -Z
|
||||||
|
min_z = min(min_z, z)
|
||||||
|
max_z = max(max_z, z)
|
||||||
|
# Add padding
|
||||||
|
padding = max(0.01, (max_z - min_z) * 0.1)
|
||||||
|
return max(0.001, min_z - padding), max_z + padding
|
||||||
|
|
||||||
|
|
||||||
|
def _render_with_material(operator, mat, out_name, label):
|
||||||
|
"""Generic render: apply material to converted copy, render, restore."""
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
output_dir = _ensure_output_dir()
|
output_dir = _ensure_output_dir()
|
||||||
|
|
||||||
# Force fresh Depth_Mat by removing any cached version
|
converted_obj = _get_converted_obj(scene, operator)
|
||||||
if 'Depth_Mat' in bpy.data.node_groups:
|
if converted_obj is None:
|
||||||
bpy.data.node_groups.remove(bpy.data.node_groups['Depth_Mat'])
|
|
||||||
|
|
||||||
# Append fresh Depth_Mat from Materials.blend
|
|
||||||
depth_tree = _append_from_materials_blend('NodeTree', 'Depth_Mat')
|
|
||||||
if depth_tree is None:
|
|
||||||
operator.report({'ERROR'}, message='Failed to load Depth_Mat from Materials.blend')
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Save original render settings
|
# Save original state
|
||||||
|
orig_materials = [slot.material for slot in converted_obj.material_slots]
|
||||||
orig_use_nodes = scene.use_nodes
|
orig_use_nodes = scene.use_nodes
|
||||||
orig_comp_group = scene.compositing_node_group
|
orig_use_compositing = scene.render.use_compositing
|
||||||
orig_filepath = scene.render.filepath
|
orig_filepath = scene.render.filepath
|
||||||
orig_format = scene.render.image_settings.file_format
|
orig_format = scene.render.image_settings.file_format
|
||||||
orig_color_mode = scene.render.image_settings.color_mode
|
orig_color_mode = scene.render.image_settings.color_mode
|
||||||
|
|
||||||
# Enable compositor and assign Depth_Mat as the compositing node group
|
# Replace ALL material slots with the render material
|
||||||
scene.use_nodes = True
|
if len(converted_obj.material_slots) == 0:
|
||||||
scene.compositing_node_group = depth_tree
|
converted_obj.data.materials.append(mat)
|
||||||
scene.view_layers["ViewLayer"].use_pass_z = True
|
else:
|
||||||
|
for i in range(len(converted_obj.material_slots)):
|
||||||
|
converted_obj.material_slots[i].material = mat
|
||||||
|
|
||||||
|
# Fully disable compositor (both flags needed in Blender 5.0)
|
||||||
|
scene.use_nodes = False
|
||||||
|
scene.render.use_compositing = False
|
||||||
|
|
||||||
# Setup output
|
# Setup output
|
||||||
out_path = os.path.join(output_dir, 'depth.png')
|
out_path = os.path.join(output_dir, out_name)
|
||||||
scene.render.filepath = out_path
|
scene.render.filepath = out_path
|
||||||
scene.render.image_settings.file_format = 'PNG'
|
scene.render.image_settings.file_format = 'PNG'
|
||||||
scene.render.image_settings.color_mode = 'RGB'
|
scene.render.image_settings.color_mode = 'RGB'
|
||||||
@@ -213,73 +283,42 @@ def _render_depth(operator):
|
|||||||
# Render
|
# Render
|
||||||
bpy.ops.render.render(write_still=True)
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
# Restore all settings
|
# Restore original materials per slot
|
||||||
scene.compositing_node_group = orig_comp_group
|
if len(orig_materials) == 0:
|
||||||
|
converted_obj.data.materials.clear()
|
||||||
|
else:
|
||||||
|
for i, mat_orig in enumerate(orig_materials):
|
||||||
|
if i < len(converted_obj.material_slots):
|
||||||
|
converted_obj.material_slots[i].material = mat_orig
|
||||||
scene.use_nodes = orig_use_nodes
|
scene.use_nodes = orig_use_nodes
|
||||||
|
scene.render.use_compositing = orig_use_compositing
|
||||||
scene.render.filepath = orig_filepath
|
scene.render.filepath = orig_filepath
|
||||||
scene.render.image_settings.file_format = orig_format
|
scene.render.image_settings.file_format = orig_format
|
||||||
scene.render.image_settings.color_mode = orig_color_mode
|
scene.render.image_settings.color_mode = orig_color_mode
|
||||||
|
|
||||||
# Add to rendered images list
|
_add_rendered_image(scene, label, out_path)
|
||||||
_add_rendered_image(scene, 'depth', out_path)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _render_normal(operator):
|
def _render_depth(operator):
|
||||||
"""Render normal map by applying Normal_Mat material to active object."""
|
"""Render depth map using a programmatic depth material."""
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
obj = scene.sna_activeobject
|
converted_obj = _get_converted_obj(scene, operator)
|
||||||
output_dir = _ensure_output_dir()
|
if converted_obj is None:
|
||||||
|
return False
|
||||||
|
|
||||||
# Append the Normal_Mat material
|
near, far = _get_depth_range(scene, converted_obj)
|
||||||
|
depth_mat = _create_depth_material(near, far)
|
||||||
|
return _render_with_material(operator, depth_mat, 'depth.png', 'depth')
|
||||||
|
|
||||||
|
|
||||||
|
def _render_normal(operator):
|
||||||
|
"""Render normal map using Normal_Mat material."""
|
||||||
normal_mat = _append_from_materials_blend('Material', 'Normal_Mat')
|
normal_mat = _append_from_materials_blend('Material', 'Normal_Mat')
|
||||||
if normal_mat is None:
|
if normal_mat is None:
|
||||||
operator.report({'ERROR'}, message='Failed to load Normal_Mat from Materials.blend')
|
operator.report({'ERROR'}, message='Failed to load Normal_Mat from Materials.blend')
|
||||||
return False
|
return False
|
||||||
|
return _render_with_material(operator, normal_mat, 'normal.png', '法向')
|
||||||
# Find the converted object (the active object after convert)
|
|
||||||
converted_obj = bpy.context.view_layer.objects.active
|
|
||||||
if converted_obj is None or converted_obj.type != 'MESH':
|
|
||||||
converted_obj = obj
|
|
||||||
|
|
||||||
# Save original materials and render settings
|
|
||||||
orig_materials = [slot.material for slot in converted_obj.material_slots]
|
|
||||||
orig_use_nodes = scene.use_nodes
|
|
||||||
orig_filepath = scene.render.filepath
|
|
||||||
orig_format = scene.render.image_settings.file_format
|
|
||||||
orig_color_mode = scene.render.image_settings.color_mode
|
|
||||||
|
|
||||||
# Clear and assign Normal_Mat
|
|
||||||
converted_obj.data.materials.clear()
|
|
||||||
converted_obj.data.materials.append(normal_mat)
|
|
||||||
|
|
||||||
# Disable compositor for normal pass (direct render)
|
|
||||||
scene.use_nodes = False
|
|
||||||
|
|
||||||
# Setup output
|
|
||||||
out_path = os.path.join(output_dir, 'normal.png')
|
|
||||||
scene.render.filepath = out_path
|
|
||||||
scene.render.image_settings.file_format = 'PNG'
|
|
||||||
scene.render.image_settings.color_mode = 'RGB'
|
|
||||||
|
|
||||||
# Render
|
|
||||||
bpy.ops.render.render(write_still=True)
|
|
||||||
|
|
||||||
# Restore original materials and settings
|
|
||||||
converted_obj.data.materials.clear()
|
|
||||||
for mat in orig_materials:
|
|
||||||
converted_obj.data.materials.append(mat)
|
|
||||||
|
|
||||||
scene.use_nodes = orig_use_nodes
|
|
||||||
scene.render.filepath = orig_filepath
|
|
||||||
scene.render.image_settings.file_format = orig_format
|
|
||||||
scene.render.image_settings.color_mode = orig_color_mode
|
|
||||||
|
|
||||||
# Add to rendered images list
|
|
||||||
_add_rendered_image(scene, 'normal', out_path)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
||||||
@@ -332,6 +371,8 @@ class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
|||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
bpy.context.scene.sna_activeobject.modifiers['MV_Nodes'].node_group = node_group
|
bpy.context.scene.sna_activeobject.modifiers['MV_Nodes'].node_group = node_group
|
||||||
bpy.ops.object.convert(target='MESH', keep_original=True)
|
bpy.ops.object.convert(target='MESH', keep_original=True)
|
||||||
|
# Save the converted copy name immediately (active object after convert)
|
||||||
|
bpy.context.scene['_comfy_converted_name'] = bpy.context.active_object.name
|
||||||
bpy.data.objects[bpy.context.scene.sna_activeobject_name].hide_render = True
|
bpy.data.objects[bpy.context.scene.sna_activeobject_name].hide_render = True
|
||||||
bpy.ops.comfyui.aovbake_ad980('INVOKE_DEFAULT', )
|
bpy.ops.comfyui.aovbake_ad980('INVOKE_DEFAULT', )
|
||||||
bpy.data.objects[bpy.context.scene.sna_activeobject_name].modifiers.clear()
|
bpy.data.objects[bpy.context.scene.sna_activeobject_name].modifiers.clear()
|
||||||
@@ -393,13 +434,10 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
|
|
||||||
# Delete the converted copy (active object that is NOT the original)
|
# Delete the converted copy using saved name
|
||||||
orig_name = scene.sna_activeobject_name
|
converted_name = scene.get('_comfy_converted_name', '')
|
||||||
if orig_name:
|
if converted_name and converted_name in bpy.data.objects:
|
||||||
# The converted copy is typically named with .001 suffix
|
bpy.data.objects.remove(bpy.data.objects[converted_name], do_unlink=True)
|
||||||
for obj in list(bpy.data.objects):
|
|
||||||
if obj.name != orig_name and obj.name.startswith(orig_name) and obj.type == 'MESH':
|
|
||||||
bpy.data.objects.remove(obj, do_unlink=True)
|
|
||||||
|
|
||||||
# Delete the bake camera
|
# Delete the bake camera
|
||||||
bake_cam = scene.sna_bakecam
|
bake_cam = scene.sna_bakecam
|
||||||
@@ -412,11 +450,11 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator):
|
|||||||
bpy.data.objects.remove(bpy.data.objects['ObjectCamera'], do_unlink=True)
|
bpy.data.objects.remove(bpy.data.objects['ObjectCamera'], do_unlink=True)
|
||||||
|
|
||||||
# Restore original object visibility
|
# Restore original object visibility
|
||||||
|
orig_name = scene.sna_activeobject_name
|
||||||
if orig_name and orig_name in bpy.data.objects:
|
if orig_name and orig_name in bpy.data.objects:
|
||||||
orig_obj = bpy.data.objects[orig_name]
|
orig_obj = bpy.data.objects[orig_name]
|
||||||
orig_obj.hide_render = False
|
orig_obj.hide_render = False
|
||||||
orig_obj.hide_set(False)
|
orig_obj.hide_set(False)
|
||||||
# Make it active and selected
|
|
||||||
bpy.context.view_layer.objects.active = orig_obj
|
bpy.context.view_layer.objects.active = orig_obj
|
||||||
orig_obj.select_set(True)
|
orig_obj.select_set(True)
|
||||||
|
|
||||||
@@ -428,6 +466,14 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator):
|
|||||||
pcoll = _get_preview_collection()
|
pcoll = _get_preview_collection()
|
||||||
pcoll.clear()
|
pcoll.clear()
|
||||||
|
|
||||||
|
# Remove temp depth material
|
||||||
|
if '__ComfyDepthMat__' in bpy.data.materials:
|
||||||
|
bpy.data.materials.remove(bpy.data.materials['__ComfyDepthMat__'])
|
||||||
|
|
||||||
|
# Clear converted name
|
||||||
|
if '_comfy_converted_name' in scene:
|
||||||
|
del scene['_comfy_converted_name']
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user