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
|
||||
|
||||
|
||||
def _render_depth(operator):
|
||||
"""Render depth map using Depth_Mat compositor NodeTree."""
|
||||
def _create_depth_material(near, far):
|
||||
"""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
|
||||
output_dir = _ensure_output_dir()
|
||||
|
||||
# Force fresh Depth_Mat by removing any cached version
|
||||
if 'Depth_Mat' in bpy.data.node_groups:
|
||||
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')
|
||||
converted_obj = _get_converted_obj(scene, operator)
|
||||
if converted_obj is None:
|
||||
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_comp_group = scene.compositing_node_group
|
||||
orig_use_compositing = scene.render.use_compositing
|
||||
orig_filepath = scene.render.filepath
|
||||
orig_format = scene.render.image_settings.file_format
|
||||
orig_color_mode = scene.render.image_settings.color_mode
|
||||
|
||||
# Enable compositor and assign Depth_Mat as the compositing node group
|
||||
scene.use_nodes = True
|
||||
scene.compositing_node_group = depth_tree
|
||||
scene.view_layers["ViewLayer"].use_pass_z = True
|
||||
# Replace ALL material slots with the render material
|
||||
if len(converted_obj.material_slots) == 0:
|
||||
converted_obj.data.materials.append(mat)
|
||||
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
|
||||
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.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_mode = 'RGB'
|
||||
@@ -213,73 +283,42 @@ def _render_depth(operator):
|
||||
# Render
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
# Restore all settings
|
||||
scene.compositing_node_group = orig_comp_group
|
||||
# Restore original materials per slot
|
||||
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.render.use_compositing = orig_use_compositing
|
||||
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, 'depth', out_path)
|
||||
|
||||
_add_rendered_image(scene, label, out_path)
|
||||
return True
|
||||
|
||||
|
||||
def _render_normal(operator):
|
||||
"""Render normal map by applying Normal_Mat material to active object."""
|
||||
def _render_depth(operator):
|
||||
"""Render depth map using a programmatic depth material."""
|
||||
scene = bpy.context.scene
|
||||
obj = scene.sna_activeobject
|
||||
output_dir = _ensure_output_dir()
|
||||
converted_obj = _get_converted_obj(scene, operator)
|
||||
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')
|
||||
if normal_mat is None:
|
||||
operator.report({'ERROR'}, message='Failed to load Normal_Mat from Materials.blend')
|
||||
return False
|
||||
|
||||
# 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
|
||||
return _render_with_material(operator, normal_mat, 'normal.png', '法向')
|
||||
|
||||
|
||||
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
||||
@@ -332,6 +371,8 @@ class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
bpy.context.scene.sna_activeobject.modifiers['MV_Nodes'].node_group = node_group
|
||||
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.ops.comfyui.aovbake_ad980('INVOKE_DEFAULT', )
|
||||
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):
|
||||
scene = context.scene
|
||||
|
||||
# Delete the converted copy (active object that is NOT the original)
|
||||
orig_name = scene.sna_activeobject_name
|
||||
if orig_name:
|
||||
# The converted copy is typically named with .001 suffix
|
||||
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 converted copy using saved name
|
||||
converted_name = scene.get('_comfy_converted_name', '')
|
||||
if converted_name and converted_name in bpy.data.objects:
|
||||
bpy.data.objects.remove(bpy.data.objects[converted_name], do_unlink=True)
|
||||
|
||||
# Delete the bake camera
|
||||
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)
|
||||
|
||||
# Restore original object visibility
|
||||
orig_name = scene.sna_activeobject_name
|
||||
if orig_name and orig_name in bpy.data.objects:
|
||||
orig_obj = bpy.data.objects[orig_name]
|
||||
orig_obj.hide_render = False
|
||||
orig_obj.hide_set(False)
|
||||
# Make it active and selected
|
||||
bpy.context.view_layer.objects.active = orig_obj
|
||||
orig_obj.select_set(True)
|
||||
|
||||
@@ -428,6 +466,14 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator):
|
||||
pcoll = _get_preview_collection()
|
||||
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'}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user