feat(渲染): 新增线稿渲染功能
添加线稿渲染功能,用于生成 ControlNet Canny 所需的黑白线稿图。实现通过 Freestyle 渲染引擎,在白色背景上生成黑色线条,并临时替换材质与世界设置以确保输出符合要求。同时更新了清理函数以移除新增的临时白色材质。
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name" : "ComfyProjector",
|
"name" : "ComfyProjector",
|
||||||
"author" : "Blender Procedural",
|
"author" : "小煜のChannel",
|
||||||
"description" : "Projector for ComfyUI",
|
"description" : "Projector for ComfyUI",
|
||||||
"blender" : (5, 0, 0),
|
"blender" : (5, 0, 0),
|
||||||
"version" : (1, 0, 0),
|
"version" : (1, 0, 0),
|
||||||
|
|||||||
149
utils.py
149
utils.py
@@ -321,6 +321,145 @@ def _render_normal(operator):
|
|||||||
return _render_with_material(operator, normal_mat, 'normal.png', '法向')
|
return _render_with_material(operator, normal_mat, 'normal.png', '法向')
|
||||||
|
|
||||||
|
|
||||||
|
def _create_white_material():
|
||||||
|
"""Create a flat white emission material for line art background."""
|
||||||
|
mat_name = '__ComfyWhiteMat__'
|
||||||
|
if mat_name in bpy.data.materials:
|
||||||
|
return 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()
|
||||||
|
|
||||||
|
emission = nodes.new('ShaderNodeEmission')
|
||||||
|
emission.location = (0, 0)
|
||||||
|
emission.inputs['Color'].default_value = (1, 1, 1, 1)
|
||||||
|
emission.inputs['Strength'].default_value = 1.0
|
||||||
|
|
||||||
|
output = nodes.new('ShaderNodeOutputMaterial')
|
||||||
|
output.location = (200, 0)
|
||||||
|
|
||||||
|
links.new(emission.outputs['Emission'], output.inputs['Surface'])
|
||||||
|
return mat
|
||||||
|
|
||||||
|
|
||||||
|
def _render_lineart(operator):
|
||||||
|
"""Render line art using Freestyle (black lines on white) for ControlNet Canny."""
|
||||||
|
scene = bpy.context.scene
|
||||||
|
output_dir = _ensure_output_dir()
|
||||||
|
|
||||||
|
converted_obj = _get_converted_obj(scene, operator)
|
||||||
|
if converted_obj is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Save original state
|
||||||
|
vl = scene.view_layers["ViewLayer"]
|
||||||
|
orig_materials = [slot.material for slot in converted_obj.material_slots]
|
||||||
|
orig_use_freestyle = scene.render.use_freestyle
|
||||||
|
orig_vl_use_freestyle = vl.use_freestyle
|
||||||
|
orig_line_thickness = scene.render.line_thickness
|
||||||
|
orig_use_nodes = scene.use_nodes
|
||||||
|
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
|
||||||
|
orig_film_transparent = scene.render.film_transparent
|
||||||
|
orig_world = scene.world
|
||||||
|
|
||||||
|
# Apply white emission material to all slots
|
||||||
|
white_mat = _create_white_material()
|
||||||
|
if len(converted_obj.material_slots) == 0:
|
||||||
|
converted_obj.data.materials.append(white_mat)
|
||||||
|
else:
|
||||||
|
for i in range(len(converted_obj.material_slots)):
|
||||||
|
converted_obj.material_slots[i].material = white_mat
|
||||||
|
|
||||||
|
# Enable Freestyle
|
||||||
|
scene.render.use_freestyle = True
|
||||||
|
vl.use_freestyle = True
|
||||||
|
scene.render.line_thickness = 1.0
|
||||||
|
|
||||||
|
# Configure lineset: black lines, all relevant edge types
|
||||||
|
fs = vl.freestyle_settings
|
||||||
|
# Save original linesets state
|
||||||
|
orig_lineset_settings = []
|
||||||
|
for ls in fs.linesets:
|
||||||
|
orig_lineset_settings.append({
|
||||||
|
'color': tuple(ls.linestyle.color),
|
||||||
|
'thickness': ls.linestyle.thickness,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(fs.linesets) == 0:
|
||||||
|
ls = fs.linesets.new('LineSet')
|
||||||
|
else:
|
||||||
|
ls = fs.linesets[0]
|
||||||
|
|
||||||
|
ls.linestyle.color = (0, 0, 0) # Black lines
|
||||||
|
ls.linestyle.thickness = 1.5
|
||||||
|
ls.select_silhouette = True
|
||||||
|
ls.select_border = True
|
||||||
|
ls.select_crease = True
|
||||||
|
ls.select_edge_mark = True
|
||||||
|
|
||||||
|
# White world background
|
||||||
|
white_world = bpy.data.worlds.new('__ComfyWhiteWorld__')
|
||||||
|
white_world.use_nodes = True
|
||||||
|
bg_node = white_world.node_tree.nodes.get('Background')
|
||||||
|
if bg_node:
|
||||||
|
bg_node.inputs['Color'].default_value = (1, 1, 1, 1)
|
||||||
|
bg_node.inputs['Strength'].default_value = 1.0
|
||||||
|
scene.world = white_world
|
||||||
|
scene.render.film_transparent = False
|
||||||
|
|
||||||
|
# Disable compositor
|
||||||
|
scene.use_nodes = False
|
||||||
|
scene.render.use_compositing = False
|
||||||
|
|
||||||
|
# Setup output
|
||||||
|
out_path = os.path.join(output_dir, 'lineart.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 materials
|
||||||
|
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
|
||||||
|
|
||||||
|
# Restore lineset settings
|
||||||
|
for i, ls_data in enumerate(orig_lineset_settings):
|
||||||
|
if i < len(fs.linesets):
|
||||||
|
fs.linesets[i].linestyle.color = ls_data['color']
|
||||||
|
fs.linesets[i].linestyle.thickness = ls_data['thickness']
|
||||||
|
|
||||||
|
# Restore all settings
|
||||||
|
scene.render.use_freestyle = orig_use_freestyle
|
||||||
|
vl.use_freestyle = orig_vl_use_freestyle
|
||||||
|
scene.render.line_thickness = orig_line_thickness
|
||||||
|
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
|
||||||
|
scene.render.film_transparent = orig_film_transparent
|
||||||
|
scene.world = orig_world
|
||||||
|
|
||||||
|
# Remove temporary world
|
||||||
|
if '__ComfyWhiteWorld__' in bpy.data.worlds:
|
||||||
|
bpy.data.worlds.remove(bpy.data.worlds['__ComfyWhiteWorld__'])
|
||||||
|
|
||||||
|
_add_rendered_image(scene, '线稿', out_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
||||||
bl_idname = "comfyui.mvgen_b6229"
|
bl_idname = "comfyui.mvgen_b6229"
|
||||||
bl_label = "生成多视角视图"
|
bl_label = "生成多视角视图"
|
||||||
@@ -384,6 +523,9 @@ class Comfy_OT_Mvgen_B6229(bpy.types.Operator):
|
|||||||
# --- Render Normal Map ---
|
# --- Render Normal Map ---
|
||||||
_render_normal(self)
|
_render_normal(self)
|
||||||
|
|
||||||
|
# --- Render Line Art ---
|
||||||
|
_render_lineart(self)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
def invoke(self, context, _event):
|
def invoke(self, context, _event):
|
||||||
@@ -466,9 +608,10 @@ class Comfy_OT_CleanupMvgen(bpy.types.Operator):
|
|||||||
pcoll = _get_preview_collection()
|
pcoll = _get_preview_collection()
|
||||||
pcoll.clear()
|
pcoll.clear()
|
||||||
|
|
||||||
# Remove temp depth material
|
# Remove temp materials
|
||||||
if '__ComfyDepthMat__' in bpy.data.materials:
|
for mat_name in ('__ComfyDepthMat__', '__ComfyWhiteMat__'):
|
||||||
bpy.data.materials.remove(bpy.data.materials['__ComfyDepthMat__'])
|
if mat_name in bpy.data.materials:
|
||||||
|
bpy.data.materials.remove(bpy.data.materials[mat_name])
|
||||||
|
|
||||||
# Clear converted name
|
# Clear converted name
|
||||||
if '_comfy_converted_name' in scene:
|
if '_comfy_converted_name' in scene:
|
||||||
|
|||||||
Reference in New Issue
Block a user