- Introduced color_image.py for preparing standalone color images for tests. - Added hdr_image.py to create HDR textures for testing. - Implemented test_image_depth_panorama.py to evaluate panorama topology, UVs, and depth discontinuities. - Created test_shadeless.py to validate the opacity and HDR color endpoints of the Shadeless group. - Developed moge3_export.py for exporting dynamic MoGe-3 backbone and ONNX graphs. - Added material.py to inherit material slots onto generated geometry. - Built image_depth_panorama.py to construct a quad panorama with depth separation and sampling.
449 lines
20 KiB
Python
449 lines
20 KiB
Python
"""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"]
|
|
|
|
|
|
def _math(group, operation, a, b=None):
|
|
node = group.nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
for index, value in enumerate((a, b)):
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, (int, float)):
|
|
node.inputs[index].default_value = value
|
|
else:
|
|
group.links.new(value, node.inputs[index])
|
|
return node.outputs[0]
|
|
|
|
|
|
def _remove_strip_faces(group, geometry, *, triangles=False):
|
|
"""Delete boundary strips from one unchanged topology snapshot."""
|
|
nodes, links = group.nodes, group.links
|
|
|
|
def equals(value, count):
|
|
compare = nodes.new("FunctionNodeCompare")
|
|
compare.data_type, compare.operation = "INT", "EQUAL"
|
|
links.new(value, compare.inputs[2])
|
|
if isinstance(count, int):
|
|
compare.inputs[3].default_value = count
|
|
else:
|
|
links.new(count, compare.inputs[3])
|
|
return compare.outputs["Result"]
|
|
|
|
def combine(operation, a, b):
|
|
node = nodes.new("FunctionNodeIntegerMath")
|
|
node.operation = operation
|
|
links.new(a, node.inputs[0])
|
|
links.new(b, node.inputs[1])
|
|
return node.outputs[0]
|
|
|
|
boundaries = []
|
|
for offset in range(4):
|
|
index = nodes.new("GeometryNodeInputIndex")
|
|
corners = nodes.new("GeometryNodeCornersOfFace")
|
|
links.new(index.outputs[0], corners.inputs["Face Index"])
|
|
corner = nodes.new("GeometryNodeOffsetCornerInFace")
|
|
corner.inputs["Offset"].default_value = offset
|
|
links.new(corners.outputs["Corner Index"], corner.inputs["Corner Index"])
|
|
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
|
links.new(corner.outputs["Corner Index"], edge.inputs["Corner Index"])
|
|
sample = nodes.new("GeometryNodeFieldAtIndex")
|
|
sample.data_type, sample.domain = "INT", "EDGE"
|
|
links.new(edge.outputs["Next Edge Index"], sample.inputs["Index"])
|
|
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
links.new(neighbors.outputs["Face Count"], sample.inputs["Value"])
|
|
boundaries.append(equals(sample.outputs["Value"], 1))
|
|
opposite = combine(
|
|
"MAXIMUM",
|
|
combine("MULTIPLY", boundaries[0], boundaries[2]),
|
|
combine("MULTIPLY", boundaries[1], boundaries[3]),
|
|
)
|
|
face = nodes.new("GeometryNodeInputMeshFaceNeighbors")
|
|
selected = combine("MULTIPLY", equals(face.outputs["Vertex Count"], 4), opposite)
|
|
if triangles:
|
|
def sample(value, index, domain):
|
|
node = nodes.new("GeometryNodeFieldAtIndex")
|
|
node.data_type, node.domain = "INT", domain
|
|
links.new(value, node.inputs["Value"])
|
|
links.new(index, node.inputs["Index"])
|
|
return node.outputs["Value"]
|
|
|
|
def offset_corner(corner, offset):
|
|
node = nodes.new("GeometryNodeOffsetCornerInFace")
|
|
links.new(corner, node.inputs["Corner Index"])
|
|
node.inputs["Offset"].default_value = offset
|
|
return node.outputs["Corner Index"]
|
|
|
|
def vertex(corner):
|
|
node = nodes.new("GeometryNodeVertexOfCorner")
|
|
links.new(corner, node.inputs["Corner Index"])
|
|
return node.outputs["Vertex Index"]
|
|
|
|
def boundary(corner):
|
|
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
|
links.new(corner, edge.inputs["Corner Index"])
|
|
count = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
return equals(sample(count.outputs["Face Count"], edge.outputs["Next Edge Index"], "EDGE"), 1)
|
|
|
|
# Evaluate each internal edge as a symmetric pair of triangles.
|
|
edge_index = nodes.new("GeometryNodeInputIndex")
|
|
sides = []
|
|
for side in (0, 1):
|
|
corners = nodes.new("GeometryNodeCornersOfEdge")
|
|
links.new(edge_index.outputs[0], corners.inputs["Edge Index"])
|
|
corners.inputs["Sort Index"].default_value = side
|
|
corner = corners.outputs["Corner Index"]
|
|
face_index = nodes.new("GeometryNodeFaceOfCorner")
|
|
links.new(corner, face_index.inputs["Corner Index"])
|
|
face_size = nodes.new("GeometryNodeInputMeshFaceNeighbors")
|
|
triangle = equals(sample(face_size.outputs["Vertex Count"], face_index.outputs["Face Index"], "FACE"), 3)
|
|
end, tip = offset_corner(corner, 1), offset_corner(corner, 2)
|
|
sides.append((triangle, vertex(corner), vertex(tip), boundary(end), boundary(tip)))
|
|
a, b = sides
|
|
same_direction = equals(a[1], b[1])
|
|
same_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[3]), combine("MULTIPLY", a[4], b[4]))
|
|
reverse_order = combine("MAXIMUM", combine("MULTIPLY", a[3], b[4]), combine("MULTIPLY", a[4], b[3]))
|
|
pair = combine("MAXIMUM",
|
|
combine("MULTIPLY", same_direction, reverse_order),
|
|
combine("MULTIPLY", equals(same_direction, 0), same_order))
|
|
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
for condition in (equals(neighbors.outputs["Face Count"], 2), a[0], b[0], equals(equals(a[2], b[2]), 0)):
|
|
pair = combine("MULTIPLY", pair, condition)
|
|
|
|
# Explicit edge-to-face sampling marks both members without averaging
|
|
# through neighboring vertices or iterating over newly exposed edges.
|
|
face_index = nodes.new("GeometryNodeInputIndex")
|
|
corners = nodes.new("GeometryNodeCornersOfFace")
|
|
links.new(face_index.outputs[0], corners.inputs["Face Index"])
|
|
boundary_count, paired = None, None
|
|
for offset in range(3):
|
|
corner = offset_corner(corners.outputs["Corner Index"], offset)
|
|
edge = nodes.new("GeometryNodeEdgesOfCorner")
|
|
links.new(corner, edge.inputs["Corner Index"])
|
|
match = sample(pair, edge.outputs["Next Edge Index"], "EDGE")
|
|
paired = match if paired is None else combine("MAXIMUM", paired, match)
|
|
rim = boundary(corner)
|
|
boundary_count = rim if boundary_count is None else combine("ADD", boundary_count, rim)
|
|
triangle_selected = combine("MULTIPLY", equals(corners.outputs["Total"], 3),
|
|
combine("MAXIMUM", paired, equals(boundary_count, 3)))
|
|
selected = combine("MAXIMUM", selected, triangle_selected)
|
|
delete = nodes.new("GeometryNodeDeleteGeometry")
|
|
delete.domain, delete.mode = "FACE", "ALL"
|
|
links.new(geometry, delete.inputs["Geometry"])
|
|
links.new(selected, delete.inputs["Selection"])
|
|
return delete.outputs["Geometry"]
|
|
|
|
|
|
def sample_face_camera(group, image):
|
|
"""Sample camera XYZ at each planar face's UV center."""
|
|
nodes, links = group.nodes, group.links
|
|
uv = nodes.new("GeometryNodeInputNamedAttribute")
|
|
uv.data_type = "FLOAT_VECTOR"
|
|
uv.inputs["Name"].default_value = "UVMap"
|
|
face_uv = nodes.new("GeometryNodeFieldOnDomain")
|
|
face_uv.data_type = "FLOAT_VECTOR"
|
|
face_uv.domain = "FACE"
|
|
links.new(uv.outputs["Attribute"], face_uv.inputs["Value"])
|
|
texture = nodes.new("GeometryNodeImageTexture")
|
|
texture.interpolation = "Linear"
|
|
texture.extension = "EXTEND"
|
|
links.new(image, texture.inputs["Image"])
|
|
links.new(face_uv.outputs["Value"], texture.inputs["Vector"])
|
|
return texture.outputs["Color"]
|
|
|
|
|
|
def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
|
|
face_center=None, triangles=False):
|
|
"""Split adjacent face samples using their Z depth and base center spacing."""
|
|
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)
|
|
axes = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(_read(group, "face_camera"), axes.inputs[0])
|
|
center_field = _read(group, "face_center")
|
|
centers, depths = [], []
|
|
for side in (0, 1):
|
|
corner = nodes.new("GeometryNodeCornersOfEdge")
|
|
corner.inputs["Sort Index"].default_value = side
|
|
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", axes.outputs["Z"], depths),
|
|
):
|
|
sample = nodes.new("GeometryNodeFieldAtIndex")
|
|
sample.data_type = data_type
|
|
sample.domain = "FACE"
|
|
links.new(face.outputs["Face Index"], sample.inputs["Index"])
|
|
links.new(field, sample.inputs["Value"])
|
|
results.append(sample.outputs["Value"])
|
|
distance = nodes.new("ShaderNodeVectorMath")
|
|
distance.operation = "DISTANCE"
|
|
for index, center in enumerate(centers):
|
|
links.new(center, distance.inputs[index])
|
|
delta = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", *depths))
|
|
scaled = _math(group, "MULTIPLY", delta, uniform)
|
|
# Cross multiplication avoids division and stays invariant under uniform scaling.
|
|
selected = _math(
|
|
group,
|
|
"GREATER_THAN",
|
|
_math(group, "MULTIPLY", scaled, strength),
|
|
_math(group, "MULTIPLY", distance.outputs["Value"], 2.5),
|
|
)
|
|
selected = _math(
|
|
group,
|
|
"MULTIPLY",
|
|
selected,
|
|
_math(group, "GREATER_THAN", distance.outputs["Value"], 0.0),
|
|
)
|
|
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
|
|
selected = _math(
|
|
group,
|
|
"MULTIPLY",
|
|
selected,
|
|
_math(group, "COMPARE", neighbors.outputs["Face Count"], 2.0),
|
|
)
|
|
cut_edges = nodes.new("GeometryNodeFieldOnDomain")
|
|
cut_edges.data_type = "FLOAT"
|
|
cut_edges.domain = "EDGE"
|
|
links.new(selected, cut_edges.inputs["Value"])
|
|
cut_vertices = nodes.new("GeometryNodeFieldOnDomain")
|
|
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),
|
|
)
|
|
split = nodes.new("GeometryNodeSplitEdges")
|
|
links.new(corners, split.inputs["Mesh"])
|
|
links.new(selected, split.inputs["Selection"])
|
|
enabled = _math(group, "GREATER_THAN", strength, 0.0)
|
|
choose = nodes.new("GeometryNodeSwitch")
|
|
choose.input_type = "GEOMETRY"
|
|
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"]
|
|
|
|
|
|
def store_surface_camera(group, geometry, original, 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"),
|
|
)
|
|
original_axes = group.nodes.new("ShaderNodeSeparateXYZ")
|
|
separated_axes = group.nodes.new("ShaderNodeSeparateXYZ")
|
|
group.links.new(original, original_axes.inputs[0])
|
|
group.links.new(separated, separated_axes.inputs[0])
|
|
ratio = _math(
|
|
group, "DIVIDE", separated_axes.outputs["Z"], original_axes.outputs["Z"]
|
|
)
|
|
projected = group.nodes.new("ShaderNodeVectorMath")
|
|
projected.operation = "SCALE"
|
|
group.links.new(original, projected.inputs[0])
|
|
group.links.new(ratio, projected.inputs[3])
|
|
choose = group.nodes.new("GeometryNodeSwitch")
|
|
choose.input_type = "VECTOR"
|
|
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")
|
|
|
|
|
|
def thicken_depth_surface(group, geometry, thickness, normal_smooth=None):
|
|
"""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
|
|
flattened = nodes.new("ShaderNodeVectorMath")
|
|
flattened.operation = "MULTIPLY"
|
|
flattened.inputs[1].default_value = (1.0, 1.0, 0.0)
|
|
links.new(position.outputs[0], flattened.inputs[0])
|
|
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
|
|
|
|
# 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.
|
|
identity = nodes.new("ShaderNodeCombineXYZ")
|
|
for operation, axis in (("MODULO", "X"), ("DIVIDE", "Y")):
|
|
part = nodes.new("FunctionNodeIntegerMath")
|
|
part.operation = operation
|
|
part.inputs[1].default_value = 4096
|
|
links.new(_read(group, "source_vertex"), part.inputs[0])
|
|
links.new(part.outputs[0], identity.inputs[axis])
|
|
|
|
extrude = nodes.new("GeometryNodeExtrudeMesh")
|
|
extrude.mode = "FACES"
|
|
extrude.inputs["Individual"].default_value = False
|
|
extrusion_offset = nodes.new("ShaderNodeCombineXYZ")
|
|
extrusion_offset.inputs["Z"].default_value = -1.0
|
|
links.new(extrusion_offset.outputs[0], extrude.inputs["Offset"])
|
|
links.new(saved, extrude.inputs["Mesh"])
|
|
sides = nodes.new("GeometryNodeSeparateGeometry")
|
|
sides.domain = "FACE"
|
|
links.new(extrude.outputs["Mesh"], sides.inputs["Geometry"])
|
|
links.new(extrude.outputs["Side"], sides.inputs["Selection"])
|
|
offset = nodes.new("ShaderNodeCombineXYZ")
|
|
offset.inputs["Z"].default_value = -1.0
|
|
back = nodes.new("GeometryNodeSetPosition")
|
|
links.new(saved, back.inputs["Geometry"])
|
|
links.new(offset.outputs[0], back.inputs["Offset"])
|
|
flip_back = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(back.outputs["Geometry"], flip_back.inputs["Mesh"])
|
|
flip_sides = nodes.new("GeometryNodeFlipFaces")
|
|
links.new(sides.outputs["Selection"], flip_sides.inputs["Mesh"])
|
|
join = nodes.new("GeometryNodeJoinGeometry")
|
|
for value in (
|
|
saved,
|
|
flip_back.outputs["Mesh"],
|
|
flip_sides.outputs["Mesh"],
|
|
):
|
|
links.new(value, join.inputs["Geometry"])
|
|
encoded = nodes.new("GeometryNodeSetPosition")
|
|
links.new(join.outputs["Geometry"], encoded.inputs["Geometry"])
|
|
links.new(identity.outputs[0], encoded.inputs["Position"])
|
|
merge = nodes.new("GeometryNodeMergeByDistance")
|
|
merge.mode = "ALL"
|
|
merge.inputs["Distance"].default_value = 0.1
|
|
links.new(encoded.outputs["Geometry"], merge.inputs["Geometry"])
|
|
displacement = nodes.new("ShaderNodeCombineXYZ")
|
|
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"],
|
|
)
|
|
restored = nodes.new("ShaderNodeVectorMath")
|
|
restored.operation = "ADD"
|
|
links.new(_read(group, "surface_position"), restored.inputs[0])
|
|
links.new(displacement.outputs[0], restored.inputs[1])
|
|
result = nodes.new("GeometryNodeSetPosition")
|
|
links.new(merge.outputs["Geometry"], result.inputs["Geometry"])
|
|
links.new(restored.outputs[0], result.inputs["Position"])
|
|
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"])
|
|
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(normal_offset.outputs[0], restored.inputs[1])
|
|
nodes.remove(displacement)
|
|
return result.outputs["Geometry"]
|
|
|
|
|
|
def _projection_math(nodes, operation):
|
|
node = nodes.new("ShaderNodeMath")
|
|
node.operation = operation
|
|
return node
|
|
|
|
|
|
def project_depth_surface(
|
|
group, geometry, camera_position, uniform_scale, reference_depth, depth_scale
|
|
):
|
|
"""Interpolate the rectangular plane into the model camera coordinates."""
|
|
nodes, links = group.nodes, group.links
|
|
scaled = nodes.new("ShaderNodeVectorMath")
|
|
scaled.operation = "SCALE"
|
|
links.new(camera_position, scaled.inputs[0])
|
|
links.new(uniform_scale, scaled.inputs[3])
|
|
separate = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
|
|
relative_depth = _projection_math(nodes, "SUBTRACT")
|
|
links.new(reference_depth, relative_depth.inputs[0])
|
|
links.new(separate.outputs["Z"], relative_depth.inputs[1])
|
|
front = _projection_math(nodes, "MULTIPLY")
|
|
links.new(relative_depth.outputs[0], front.inputs[0])
|
|
links.new(depth_scale, front.inputs[1])
|
|
|
|
negative_y = _projection_math(nodes, "MULTIPLY")
|
|
negative_y.inputs[1].default_value = -1.0
|
|
links.new(separate.outputs["Y"], negative_y.inputs[0])
|
|
projected = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(separate.outputs["X"], projected.inputs["X"])
|
|
links.new(negative_y.outputs[0], projected.inputs["Y"])
|
|
current_position = nodes.new("GeometryNodeInputPosition")
|
|
blend = nodes.new("ShaderNodeMix")
|
|
blend.data_type = "VECTOR"
|
|
blend.clamp_factor = False
|
|
links.new(depth_scale, blend.inputs[0])
|
|
links.new(current_position.outputs["Position"], blend.inputs[4])
|
|
links.new(projected.outputs["Vector"], blend.inputs[5])
|
|
blended_axes = nodes.new("ShaderNodeSeparateXYZ")
|
|
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
|
|
surface_position = nodes.new("ShaderNodeCombineXYZ")
|
|
links.new(blended_axes.outputs["X"], surface_position.inputs["X"])
|
|
links.new(blended_axes.outputs["Y"], surface_position.inputs["Y"])
|
|
links.new(front.outputs[0], surface_position.inputs["Z"])
|
|
project_geometry = nodes.new("GeometryNodeSetPosition")
|
|
links.new(geometry, project_geometry.inputs["Geometry"])
|
|
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
|
|
|
|
return project_geometry.outputs["Geometry"]
|