Refactor geometry node groups for image depth processing

- Introduced new utility functions in common nodes for evaluating fields and capturing attributes.
- Simplified volume field calculations in image depth cutout and panorama groups by utilizing new utility functions.
- Enhanced readability and maintainability of the image depth cutout and panorama node groups by removing redundant code and improving function usage.
- Adjusted default values for thickness and depth scale in image relief plane group for better user experience.
- Improved handling of geometry attributes and connections in image depth plane group to streamline processing.
This commit is contained in:
2026-09-10 16:46:19 +08:00
parent 489003c2cc
commit 8ae3c1d5b7
20 changed files with 553 additions and 473 deletions

View File

@@ -1,40 +1,6 @@
"""Face separation and connected thickness for Depth Surface assets."""
TEMPORARY_ATTRIBUTES = {
"face_center": ("FLOAT_VECTOR", "FACE"),
"face_camera": ("FLOAT_VECTOR", "FACE"),
"corner_camera": ("FLOAT_VECTOR", "CORNER"),
"cut_vertex": ("BOOLEAN", "CORNER"),
"camera": ("FLOAT_VECTOR", "POINT"),
"surface_position": ("FLOAT_VECTOR", "POINT"),
"source_vertex": ("INT", "POINT"),
"surface_normal": ("FLOAT_VECTOR", "POINT"),
}
def _store(group, geometry, name, value):
node = group.nodes.new("GeometryNodeStoreNamedAttribute")
node.data_type, node.domain = TEMPORARY_ATTRIBUTES[name]
node.inputs["Name"].default_value = "o_depth_" + name
group.links.new(geometry, node.inputs["Geometry"])
group.links.new(value, node.inputs["Value"])
return node.outputs["Geometry"]
def _read(group, name):
node = group.nodes.new("GeometryNodeInputNamedAttribute")
node.data_type = TEMPORARY_ATTRIBUTES[name][0]
node.inputs["Name"].default_value = "o_depth_" + name
return node.outputs["Attribute"]
def remove_surface_attributes(group, geometry):
"""Clear the reserved temporary namespace with one wildcard operation."""
node = group.nodes.new("GeometryNodeRemoveAttribute")
node.pattern_mode = "WILDCARD"
node.inputs["Name"].default_value = "o_depth_*"
group.links.new(geometry, node.inputs["Geometry"])
return node.outputs["Geometry"]
from .nodes import capture_fields, evaluate_field, store_float_attribute
def _math(group, operation, a, b=None):
@@ -192,11 +158,9 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
nodes, links = group.nodes, group.links
if face_center is None:
face_center = nodes.new("GeometryNodeInputPosition").outputs[0]
faces = _store(group, geometry, "face_center", face_center)
faces = _store(group, faces, "face_camera", face_camera)
face_camera = evaluate_field(group, face_camera, "FLOAT_VECTOR", "FACE")
axes = nodes.new("ShaderNodeSeparateXYZ")
links.new(_read(group, "face_camera"), axes.inputs[0])
center_field = _read(group, "face_center")
links.new(face_camera, axes.inputs[0])
centers, depths = [], []
for side in (0, 1):
corner = nodes.new("GeometryNodeCornersOfEdge")
@@ -204,7 +168,7 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
face = nodes.new("GeometryNodeFaceOfCorner")
links.new(corner.outputs["Corner Index"], face.inputs["Corner Index"])
for data_type, field, results in (
("FLOAT_VECTOR", center_field, centers),
("FLOAT_VECTOR", face_center, centers),
("FLOAT", axes.outputs["Z"], depths),
):
sample = nodes.new("GeometryNodeFieldAtIndex")
@@ -247,12 +211,10 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
cut_vertices.data_type = "FLOAT"
cut_vertices.domain = "POINT"
links.new(cut_edges.outputs["Value"], cut_vertices.inputs["Value"])
corners = _store(group, faces, "corner_camera", _read(group, "face_camera"))
corners = _store(
group,
corners,
"cut_vertex",
_math(group, "GREATER_THAN", cut_vertices.outputs["Value"], 0.0),
corners, captured = capture_fields(
group, geometry, "CORNER",
Camera=("VECTOR", face_camera),
Cut=("BOOLEAN", _math(group, "GREATER_THAN", cut_vertices.outputs["Value"], 0.0)),
)
split = nodes.new("GeometryNodeSplitEdges")
links.new(corners, split.inputs["Mesh"])
@@ -263,17 +225,16 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
links.new(enabled, choose.inputs["Switch"])
links.new(geometry, choose.inputs["False"])
links.new(_remove_strip_faces(group, split.outputs["Mesh"], triangles=triangles), choose.inputs["True"])
return choose.outputs["Output"]
return choose.outputs["Output"], captured["Camera"], captured["Cut"]
def store_surface_camera(group, geometry, original, strength):
def build_surface_camera(group, original, separated, cut_vertex, strength):
"""Change cut depth along the original ray, preserving every other vertex."""
separated = _read(group, "corner_camera")
enabled = _math(
group,
"MULTIPLY",
_math(group, "GREATER_THAN", strength, 0.0),
_read(group, "cut_vertex"),
cut_vertex,
)
original_axes = group.nodes.new("ShaderNodeSeparateXYZ")
separated_axes = group.nodes.new("ShaderNodeSeparateXYZ")
@@ -291,17 +252,27 @@ def store_surface_camera(group, geometry, original, strength):
group.links.new(enabled, choose.inputs["Switch"])
group.links.new(original, choose.inputs["False"])
group.links.new(projected.outputs[0], choose.inputs["True"])
geometry = _store(group, geometry, "camera", choose.outputs["Output"])
return geometry, _read(group, "camera")
return evaluate_field(group, choose.outputs["Output"], "FLOAT_VECTOR", "POINT")
def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
back_mask=None, weld_zero_thickness=False):
"""Connect caps with single quads by source vertex and cap identity."""
nodes, links = group.nodes, group.links
front_geometry = geometry
position = nodes.new("GeometryNodeInputPosition")
geometry = _store(group, geometry, "surface_position", position.outputs[0])
surface_store = geometry.node
index = nodes.new("GeometryNodeInputIndex")
fields = {"Position": ("VECTOR", position.outputs[0]), "Vertex": ("INT", index.outputs[0])}
if normal_smooth is not None:
normal = nodes.new("GeometryNodeInputNormal")
blur = nodes.new("GeometryNodeBlurAttribute")
blur.data_type = "FLOAT_VECTOR"
links.new(normal_smooth, blur.inputs["Iterations"])
links.new(normal.outputs[0], blur.inputs["Value"])
normalize = nodes.new("ShaderNodeVectorMath")
normalize.operation = "NORMALIZE"
links.new(blur.outputs[0], normalize.inputs[0])
fields["Normal"] = ("VECTOR", normalize.outputs[0])
geometry, captured = capture_fields(group, geometry, "POINT", **fields)
flattened = nodes.new("ShaderNodeVectorMath")
flattened.operation = "MULTIPLY"
flattened.inputs[1].default_value = (1.0, 1.0, 0.0)
@@ -309,11 +280,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
sheet = nodes.new("GeometryNodeSetPosition")
links.new(geometry, sheet.inputs["Geometry"])
links.new(flattened.outputs[0], sheet.inputs["Position"])
index = nodes.new("GeometryNodeInputIndex")
geometry = _store(
group, sheet.outputs["Geometry"], "source_vertex", index.outputs[0]
)
saved = geometry
saved = sheet.outputs["Geometry"]
# Weld in an integer identity grid, never in scene coordinates. Nearby cuts
# retain distinct IDs; copies of one rim vertex share the same ID and layer.
@@ -322,7 +289,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
part = nodes.new("FunctionNodeIntegerMath")
part.operation = operation
part.inputs[1].default_value = 4096
links.new(_read(group, "source_vertex"), part.inputs[0])
links.new(captured["Vertex"], part.inputs[0])
links.new(part.outputs[0], identity.inputs[axis])
extrude = nodes.new("GeometryNodeExtrudeMesh")
@@ -346,10 +313,15 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
flip_sides = nodes.new("GeometryNodeFlipFaces")
links.new(sides.outputs["Selection"], flip_sides.inputs["Mesh"])
join = nodes.new("GeometryNodeJoinGeometry")
rear = flip_back.outputs["Mesh"]
rim = flip_sides.outputs["Mesh"]
if back_mask is not None:
rear = store_float_attribute(group, rear, back_mask, 0.0, domain="FACE")
rim = store_float_attribute(group, rim, back_mask, 0.0, domain="FACE")
for value in (
saved,
flip_back.outputs["Mesh"],
flip_sides.outputs["Mesh"],
rear,
rim,
):
links.new(value, join.inputs["Geometry"])
encoded = nodes.new("GeometryNodeSetPosition")
@@ -363,13 +335,11 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
layer_position = nodes.new("GeometryNodeInputPosition")
layer_axes = nodes.new("ShaderNodeSeparateXYZ")
links.new(layer_position.outputs[0], layer_axes.inputs[0])
links.new(
_math(group, "MULTIPLY", layer_axes.outputs["Z"], thickness),
displacement.inputs["Z"],
)
distance = _math(group, "MULTIPLY", layer_axes.outputs["Z"], thickness)
links.new(distance, displacement.inputs["Z"])
restored = nodes.new("ShaderNodeVectorMath")
restored.operation = "ADD"
links.new(_read(group, "surface_position"), restored.inputs[0])
links.new(captured["Position"], restored.inputs[0])
links.new(displacement.outputs[0], restored.inputs[1])
result = nodes.new("GeometryNodeSetPosition")
links.new(merge.outputs["Geometry"], result.inputs["Geometry"])
@@ -377,25 +347,24 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
local_position = nodes.new("GeometryNodeInputPosition")
local_axes = nodes.new("ShaderNodeSeparateXYZ")
links.new(local_position.outputs[0], local_axes.inputs[0])
links.new(local_axes.outputs["Z"], identity.inputs["Z"])
layer = local_axes.outputs["Z"]
if weld_zero_thickness:
positive = _math(group, "GREATER_THAN", thickness, 1e-12)
layer = _math(group, "MULTIPLY", positive, layer)
links.new(layer, identity.inputs["Z"])
if normal_smooth is not None:
normal = nodes.new("GeometryNodeInputNormal")
blur = nodes.new("GeometryNodeBlurAttribute")
blur.data_type = "FLOAT_VECTOR"
links.new(normal_smooth, blur.inputs["Iterations"])
links.new(normal.outputs[0], blur.inputs["Value"])
normalize = nodes.new("ShaderNodeVectorMath")
normalize.operation = "NORMALIZE"
links.new(blur.outputs[0], normalize.inputs[0])
front_geometry = _store(group, front_geometry, "surface_normal", normalize.outputs[0])
links.new(front_geometry, surface_store.inputs["Geometry"])
normal_offset = nodes.new("ShaderNodeVectorMath")
normal_offset.operation = "SCALE"
links.new(_read(group, "surface_normal"), normal_offset.inputs[0])
links.new(displacement.inputs["Z"].links[0].from_socket, normal_offset.inputs[3])
links.new(captured["Normal"], normal_offset.inputs[0])
links.new(distance, normal_offset.inputs[3])
links.new(normal_offset.outputs[0], restored.inputs[1])
nodes.remove(displacement)
return result.outputs["Geometry"]
return {
"geometry": result.outputs["Geometry"],
"front": captured["Position"],
"position": restored.outputs[0],
"position_input": result.inputs["Position"],
}
def _projection_math(nodes, operation):