TiXL SDF 布尔运算进阶:StairCombineSDF 算子的楼梯与立柱合并技法
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
StairCombineSDF 是 TiXL(tooll3)开源实时动态图形工具中Lib.field.combine系列的核心算子之一,专门用于把两个或多个 SDF(符号距离场)的交界处改造成**台阶、立柱、木工槽口(Groove)与榫头(Tongue)**等结构化起伏效果。读完本文,你将掌握该算子的全部 9 种合并方法、K与Steps两个关键参数的调参逻辑,并能理解它如何通过IGraphNodeOp接口在 CodeAssembleContext.cs 中动态拼接 HLSL 距离函数——从而在自己的 Shader Graph 场系统中实现"布尔运算 + 工业感细节"的实时造型。
一、算子定位:Lib.field.combine 家族的一员
在 TiXL 的场(Field)系统中,Lib.field.combine目录下的算子负责将多个已生成的 SDF 字段以不同方式合并。根据 combine 目录文档,该家族包含四个成员:
| 算子 | 文档描述 |
|---|---|
| BlendSDFWithSDF | 混合两个 SDF |
| CombineFieldColor | 合并场颜色 |
| CombineSDF | 用指定混合方法合并两个及以上连接的场 |
| StairCombineSDF | 一组可将相交处变成台阶或凸起的 SDF 合并操作 |
StairCombineSDF 的定位与普通布尔合并(min/max)不同:它不再追求平滑的接缝,而是刻意在相交区域生成离散化、阶梯化的结构细节,非常适合齿轮、机械、电路板、折纸、几何装饰等风格的实时渲染。
算子的官方描述定义在其 UI 元数据文件 StairCombineSDF.t3ui 中:
"A set of sdf combine operations that will turn intersections into stairs or bumps."
其算子实现位于 StairCombineSDF.cs,对应实例配置文件为 StairCombineSDF.t3。
二、输入输出参数全解
原文档给出了该算子的完整接口签名,下表在此基础上补充了源码中确认的默认值与类型细节(默认值来自 StairCombineSDF.t3):
输入参数
| 名称 | 类型 | 相关度 | 默认值 | 说明 |
|---|---|---|---|---|
| InputFields | ShaderGraphNode(多输入) | Required 必需 | null | 待合并的场节点,可连接多个;少于 2 个输入时算子直接跳过合并(见下文"代码生成链路") |
| K | Single | — | 0.5 | 合并的半径/影响范围,控制台阶、立柱、槽口的整体尺寸 |
| Steps | Single | — | 3.0 | 台阶或立柱的数量 n,决定离散化密度 |
| CombineMethod | Int32 | — | 3(即UnionStairs) | 合并方法选择,共 9 种,见第三节枚举表 |
输出参数
| 名称 | 类型 |
|---|---|
| Result | T3.Core.DataTypes.ShaderGraphNode |
从 StairCombineSDF.cs 可以看到,输出Result是一个Slot<ShaderGraphNode>,构造时即被初始化为该算子自身的ShaderNode:
[Output(Guid = "cb491f9b-837f-4e73-9ab8-8ce1e8abb46d")] public readonly Slot<ShaderGraphNode> Result = new(); public StairCombineSDF() { ShaderNode = new ShaderGraphNode(this, InputFields); Result.UpdateAction += Update; Result.Value = ShaderNode; }InputFields在源码中声明为MultiInputSlot<ShaderGraphNode>(StairCombineSDF.cs),因此可以同时接入任意多个场节点,合并按连接顺序依次进行。
三、9 种合并方法:枚举与 HLSL 实现
CombineMethod输入在 C# 中是一个int槽,运行时通过CombineMethod.GetEnumValue<CombineMethods>(context)映射为私有枚举(StairCombineSDF.cs):
private enum CombineMethods { UnionColumns, // 0 DifferenceColumns, // 1 IntersectionColumns, // 2 UnionStairs, // 3 ← 默认值 IntersectionStairs, // 4 DifferenceStairs, // 5 Groove, // 6 Tongue, // 7 }枚举按声明顺序映射,因此默认值3对应UnionStairs。下面逐一解析每种方法的 HLSL 实现(全部内嵌在 StairCombineSDF.cs 的AddDefinitions中)。
1. UnionColumns(并集立柱)
在 45 度对角线上放置 n-1 个圆柱形立柱来连接两个物体。代码注释明确指出:
The "Columns" flavour makes n-1 circular columns at a 45 degree angle
float fOpUnionColumns(float a, float b, float r, float n) { if ((a < r) && (b < r)) { float2 p = float2(a, b); float columnradius = r*sqrt(2)/((n-1)*2+sqrt(2)); pR45(p); p.x -= sqrt(2)/2*r; p.x += columnradius*sqrt(2); if (mod(n,2) == 1) { p.y += columnradius; } // At this point, we have turned 45 degrees and moved at a point on the // diagonal that we want to place the columns on. // Now, repeat the domain along this direction and place a circle. pMod1(p.y, columnradius*2); float result = length(p) - columnradius; result = min(result, p.x); result = min(result, a); return min(result, b); } else { return min(a, b); } }其思路是:仅当两个场距离都小于r(进入相交区)时,把 2D 距离坐标旋转 45 度(pR45),沿对角线方向用pMod1做域重复(domain repetition),从而生成一系列等距排列的圆柱;非相交区域退化为普通min(a, b)。n越大,立柱越细越多,columnradius随之变小。
2. DifferenceColumns(差集立柱)与 3. IntersectionColumns(交集立柱)
两者共用一个代码块。DifferenceColumns先对a取反(a = -a)再做类似计算,最终返回-min(result, b);而IntersectionColumns直接复用差集实现:
float fOpIntersectionColumns(float a, float b, float r, float n) { return fOpDifferenceColumns(a,-b,r, n); }这与 SDF 布尔运算的经典等价关系一致:intersection(a,b) = difference(a,-b)。源码注释还指出快速路径会产生"不连续(discontinuity)":
avoid the expensive computation where not needed (produces discontinuity though)
4. UnionStairs(并集台阶)
默认方法,采用 paniq 的"聪明得多"的版本(源码注释原话:"much less stupid version by paniq"):
float fOpUnionStairs(float a, float b, float r, float n) { float s = r/n; float u = b-r; return min(min(a,b), 0.5 * (u + a + abs ((mod (u - a + s, 2 * s)) - s))); }台阶高度为s = r/n,通过模运算把两个场的差折叠成锯齿波形,从而在r范围内生成 n 级台阶。这是一个极其紧凑的"三角波 + min"公式。
5. IntersectionStairs 与 6. DifferenceStairs
与立柱家族类似,台阶家族也通过取反复用:
float fOpIntersectionStairs(float a, float b, float r, float n) { return -fOpUnionStairs(-a, -b, r, n); } float fOpDifferenceStairs(float a, float b, float r, float n) { return -fOpUnionStairs(-a, b, r, n); }7. Groove(槽口)
给第一个物体在交界处"切"出一道木工风格的凹槽:
float fOpGroove(float a, float b, float ra, float rb) { return max(a, min(a + ra, rb - abs(b))); }8. Tongue(榫头)
与 Groove 互补,给第一个物体在交界处"长"出一个凸榫:
float fOpTongue(float a, float b, float ra, float rb) { return min(a, max(a - ra, abs(b) - rb)); }注意:Groove 与 Tongue 的 HLSL 签名中第二个半径参数名为rb,但算子调用时传入的是K和Steps({n}K, {n}Steps),即Steps在槽口/榫头模式下扮演第二个尺寸参数的角色——这与其他方法中"台阶数"的语义不同,调参时需要留意。
方法速查表
| CombineMethod 值 | 方法 | HLSL 函数 | 效果 |
|---|---|---|---|
| 0 | UnionColumns | fOpUnionColumns | 并集,45° 斜向立柱连接 |
| 1 | DifferenceColumns | fOpDifferenceColumns | 差集,立柱镂空 |
| 2 | IntersectionColumns | fOpIntersectionColumns | 交集,保留立柱骨架 |
| 3(默认) | UnionStairs | fOpUnionStairs | 并集,n 级台阶 |
| 4 | IntersectionStairs | fOpIntersectionStairs | 交集台阶 |
| 5 | DifferenceStairs | fOpDifferenceStairs | 差集台阶 |
| 6 | Groove | fOpGroove | 第一物体切出槽口 |
| 7 | Tongue | fOpTongue | 第一物体长出榫头 |
这些算法来源于开源 SDF 库 hg_sdf(Mercury),该算子的 StairCombineSDF.t3ui 的 Links 元数据中明确标注了引用来源,合并相关的辅助函数(pR45、pMod1、mod、sgn)定义在 ShaderGraphIncludes.cs 的CommonHgSdf常量中。
四、核心原理:算子如何变成一段 HLSL 距离函数
StairCombineSDF 之所以能"无代码"地在图编辑器中工作,是因为它实现了IGraphNodeOp接口(IGraphNodeOp.cs)。该接口定义了 Shader Graph 节点参与代码生成的四个钩子方法:AddDefinitions(注册全局函数)、TryBuildCustomCode、GetPreShaderCode、GetPostShaderCode。
1. AddDefinitions:把 C# 字符串注入 HLSL 全局区
IGraphNodeOp.AddDefinitions在遍历连接场之前被调用一次,用于把纯函数注册进CodeAssembleContext.Globals字典(CodeAssembleContext.cs):
void IGraphNodeOp.AddDefinitions(CodeAssembleContext c) { c.Globals["Common"] = ShaderGraphIncludes.Common; c.Globals["CommonHgSdf"] = ShaderGraphIncludes.CommonHgSdf; switch (_combineMethod) { case CombineMethods.UnionColumns: c.Globals["fOpUnionColumns"] = """..."""; ... } }StairCombineSDF 固定引入Common(PI/TAU/mod宏定义)与CommonHgSdf(pR45、pMod1等),随后按当前CombineMethod只注册对应的那个函数——因此切换方法会触发代码重生成。
2. 方法切换与脏标记
在Update方法中,算子会读取CombineMethod枚举值,一旦与上次不同,就调用ShaderNode.FlagCodeChanged()强制重建着色器(StairCombineSDF.cs)。这是 TiXL 运行时"参数变更 → 代码热更新"机制的体现:ShaderGraphNode.Update()会统计结构、代码、参数三类变更标记(见 ShaderGraphNode.cs),供上层决定是否重新编译 HLSL。
3. GetPostShaderCode:按输入顺序逐层合并
真正写入距离函数主体的是GetPostShaderCode(StairCombineSDF.cs),它在每个输入字段求值后回调:
- 输入不足:
InputNodes.Count <= 1时只追加一行注释// skipping combine with single or no input...,直接透传; - 第一个输入(
inputIndex == 0):f{pc} = f{c};保留初始场值; - 后续输入:按当前方法调用对应的
fOp*函数合并到父上下文:c.AppendCall($"f{pc}.w = fOpUnionStairs(f{pc}.w, f{c}.w, {n}K, {n}Steps);"); ... c.AppendCall($"f{pc}.xyz = f{pc}.w < f{c}.w ? f{pc}.xyz : f{c}.xyz;");
这里f.w保存距离值,f.xyz保存颜色,最后一行确保颜色取自距离更小(更靠近表面)的那个场,与min合并的距离语义保持一致。上下文编号取自ContextIdStack(CodeAssembleContext.cs),PushContext会为每个子场生成独立的p{sub}、f{sub}局部变量,从而支持任意深度的嵌套连接。
五、实战调参指南
K(默认 0.5)
- 语义:合并影响半径
r,即台阶/立柱生成的"过渡带"宽度; - 影响:
UnionStairs中台阶总高r,r越大过渡越宽、台阶越明显;UnionColumns中它同时决定立柱粗细(columnradius与r成正比)和存在范围(仅当a<r && b<r时生成立柱); - 建议:先固定
Steps,从小到大扫描K,观察交界处细节的扩张速度。
Steps(默认 3.0)
- 语义:台阶级数 / 立柱个数 n;
- 影响:
UnionStairs中每级台阶高r/n,n 越大台阶越密越细腻;UnionColumns中生成 n-1 个立柱,n 越大柱体越多越细;在 Groove/Tongue 模式下它作为第二半径rb使用; - 建议:
Steps取 2~6 通常视觉最清晰,过大时台阶会趋近于普通布尔运算,过小则只剩 1 级或退化为倒角。
CombineMethod(默认 3 = UnionStairs)
- 想要"齿轮感"立面 →
UnionStairs/IntersectionStairs; - 想要"铆钉/管柱"连接 →
UnionColumns; - 想要"榫卯结构"拼装感 →
Groove+Tongue组合使用(一个物体切槽、另一个出榫,可形成互锁效果); - 注意切换方法会改变着色器结构,同一参数下不同方法的效果差异极大,建议在参数面板上快速轮播预览。
与其他算子协同
- 与同目录的 CombineSDF 对比:
CombineSDF提供平滑/圆角/倒角/管道/雕刻等 13 种光滑过渡合并,而StairCombineSDF专攻硬朗的离散结构;两者可按需在一条链上串联使用; - 输入字段通常来自
field目录下的 Shape SDF 算子(Box、Sphere、RoundBox 等),多路输入按顺序累积合并,顺序会影响差集/交集的结果方向; InputFields的 UI 相关度标记为Required(见 StairCombineSDF.t3ui),未连接任何场时节点输出为空。
六、注意事项与已知限制
- 仅单输入不合并:连接数 ≤ 1 时,
GetPostShaderCode直接跳过,不会报错,但也没有任何合并效果; - 立柱快速路径不连续:源码注释明确提示
fOpDifferenceColumns的快速分支会引入距离场不连续,在需要严格 Lipschitz 连续的场合(如后续再做平滑偏移、法线计算)需谨慎使用; - Steps 语义随方法变化:Groove/Tongue 中
Steps不再是数量而是尺寸,跨方法复用时需重新审视参数; - 颜色取近原则:合并后颜色取自距离更小的一方(
f{pc}.w < f{c}.w ? f{pc}.xyz : f{c}.xyz),若希望两场颜色混合,应改用带GetColorBlendFactor的CombineSDF之类的混合算子; - 代码按需注册:只有当前选中的方法会被注入 HLSL,未使用的方法函数不会进入最终着色器,编译负担很小。
七、参考与延伸阅读
- 算子文档:StairCombineSDF.md、combine 目录 README
- 算子实现:StairCombineSDF.cs、实例配置 StairCombineSDF.t3、UI 配置 StairCombineSDF.t3ui
- 框架机制:IGraphNodeOp.cs、CodeAssembleContext.cs、ShaderGraphIncludes.cs、ShaderGraphNode.cs
- 同类算子:CombineSDF.cs
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考