- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
104 lines
5.5 KiB
Python
104 lines
5.5 KiB
Python
"""Build O Image Layer."""
|
|
import bpy
|
|
from anyimage.common.material import IMAGE_MATERIAL_NODE_GROUP_NAME
|
|
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
|
|
from ..common.shader import build_material_nodes, connect_material_outputs
|
|
from ..common.normal_map import transform_object_normal_color
|
|
|
|
|
|
def build_image_layer_group():
|
|
group_name = IMAGE_MATERIAL_NODE_GROUP_NAME
|
|
existing = bpy.data.node_groups.get(group_name)
|
|
if existing is not None:
|
|
return existing
|
|
group = bpy.data.node_groups.new(group_name, 'ShaderNodeTree')
|
|
interface = group.interface
|
|
input_sockets = [('Color', 'NodeSocketColor'), ('Alpha', 'NodeSocketFloat'), ('Bump Scale', 'NodeSocketFloat')]
|
|
input_sockets.insert(2, ('Normal', 'NodeSocketColor'))
|
|
input_sockets.insert(3, ('Normal Scale', 'NodeSocketFloat'))
|
|
input_sockets.insert(4, ('Object Space', 'NodeSocketBool'))
|
|
input_sockets.insert(2, ('Alpha Fix', 'NodeSocketFloat'))
|
|
options = interface.new_panel(name='Options', default_closed=True)
|
|
option_names = {'Alpha Fix', 'Normal Scale', 'Object Space'}
|
|
for name, socket_type in input_sockets:
|
|
socket = interface.new_socket(name=name, in_out='INPUT', socket_type=socket_type, parent=options if name in option_names else None)
|
|
if name in {'Alpha', 'Alpha Fix'}:
|
|
socket.default_value = 1.0 if name == 'Alpha' else 0.0
|
|
socket.min_value = 0.0
|
|
socket.max_value = 1.0
|
|
socket.subtype = 'FACTOR'
|
|
elif name == 'Bump Scale':
|
|
socket.description = 'Control bump detail derived from image brightness. Negative values reverse the bumps. Zero disables the effect.'
|
|
socket.default_value = 0.0
|
|
socket.min_value = -10000.0
|
|
socket.max_value = 10000.0
|
|
elif name == 'Normal':
|
|
socket.default_value = (0.5, 0.5, 1.0, 1.0)
|
|
elif name == 'Normal Scale':
|
|
socket.description = 'Control the strength of surface detail from the normal map.'
|
|
socket.default_value = 1.0
|
|
socket.min_value = 0.0
|
|
socket.max_value = 10.0
|
|
elif name == 'Object Space':
|
|
socket.description = 'Interpret the normal map in object space when enabled, or tangent space when disabled.'
|
|
socket.default_value = False
|
|
if name == 'Alpha Fix':
|
|
socket.description = 'Reduce semi-transparent fringes around image edges. Higher values remove more faint edge pixels. Zero keeps the original transparency.'
|
|
output_sockets = [('Color', 'NodeSocketColor'), ('Roughness', 'NodeSocketFloat'), ('Alpha', 'NodeSocketFloat'), ('Normal', 'NodeSocketVector')]
|
|
for name, socket_type in output_sockets:
|
|
interface.new_socket(name=name, in_out='OUTPUT', socket_type=socket_type)
|
|
nodes, links = group.nodes, group.links
|
|
group_input, group_output, roughness_curve, bump = build_material_nodes(group)
|
|
normal_attribute = nodes.new('ShaderNodeAttribute')
|
|
normal_attribute.attribute_type = 'GEOMETRY'
|
|
normal_attribute.attribute_name = NORMAL_REDUCTION_ATTRIBUTE_NAME
|
|
normal_weight = nodes.new('ShaderNodeMath')
|
|
normal_weight.operation = 'SUBTRACT'
|
|
normal_weight.inputs[0].default_value = 1.0
|
|
links.new(normal_attribute.outputs['Fac'], normal_weight.inputs[1])
|
|
weighted_strength = nodes.new('ShaderNodeMath')
|
|
weighted_strength.operation = 'MULTIPLY'
|
|
links.new(group_input.outputs['Normal Scale'], weighted_strength.inputs[0])
|
|
links.new(normal_weight.outputs[0], weighted_strength.inputs[1])
|
|
tangent_normal = nodes.new('ShaderNodeNormalMap')
|
|
tangent_normal.space = 'TANGENT'
|
|
object_normal = nodes.new('ShaderNodeNormalMap')
|
|
object_normal.space = 'OBJECT'
|
|
choose_normal = nodes.new('ShaderNodeMix')
|
|
choose_normal.data_type = 'VECTOR'
|
|
links.new(group_input.outputs['Normal'], tangent_normal.inputs['Color'])
|
|
links.new(weighted_strength.outputs[0], tangent_normal.inputs['Strength'])
|
|
normal_color, normal_strength = transform_object_normal_color(
|
|
group, group_input.outputs['Normal'], weighted_strength.outputs[0],
|
|
)
|
|
links.new(normal_color, object_normal.inputs['Color'])
|
|
links.new(normal_strength, object_normal.inputs['Strength'])
|
|
links.new(group_input.outputs['Object Space'], choose_normal.inputs[0])
|
|
links.new(tangent_normal.outputs['Normal'], choose_normal.inputs[4])
|
|
links.new(object_normal.outputs['Normal'], choose_normal.inputs[5])
|
|
links.new(choose_normal.outputs['Result'], bump.inputs['Normal'])
|
|
processed_alpha = group_input.outputs['Alpha']
|
|
alpha_curve = nodes.new('ShaderNodeFloatCurve')
|
|
alpha_curve.mapping.initialize()
|
|
alpha_curve.mapping.extend = 'HORIZONTAL'
|
|
alpha_curve.mapping.use_clip = True
|
|
alpha_points = alpha_curve.mapping.curves[0].points
|
|
alpha_points[0].location = (0.0, 0.0)
|
|
alpha_points[-1].location = (1.0, 1.0)
|
|
alpha_points.new(0.8, 0.0)
|
|
alpha_points.new(0.925, 0.925)
|
|
for point in alpha_points:
|
|
point.handle_type = 'AUTO_CLAMPED'
|
|
alpha_curve.mapping.update()
|
|
links.new(group_input.outputs['Alpha'], alpha_curve.inputs['Value'])
|
|
links.new(group_input.outputs['Alpha Fix'], alpha_curve.inputs['Factor'])
|
|
processed_alpha = alpha_curve.outputs['Value']
|
|
connect_material_outputs(group, group_input, group_output, roughness_curve, bump, processed_alpha)
|
|
group.color_tag = 'SHADER'
|
|
group.description = f'Prepare {group_name} material outputs.'
|
|
group.use_fake_user = True
|
|
for node in nodes:
|
|
for socket in node.outputs:
|
|
socket.hide = not socket.is_linked
|
|
return group
|