open-code-review Codex 插件教程:对话式调用评审技能指南
TiXL PrismSDF 节点深度解析:三角/六角棱柱 SDF 距离场的生成原理与实战用法
本文围绕 TiXL 开源实时动态图形软件中 Lib.field.generate.sdf 算子库的 PrismSDF 节点展开,讲解如何用它在着色器图(Shader Graph)中生成棱柱形有符号距离场(SDF),并通过 RaymarchField 渲染、VisualizeFieldDistance 可视化。读完本文,你将掌握 PrismSDF 全部输入参数的实际含义与默认值、棱柱轴对齐机制、三角/六角两种变体的底层 SDF 数学实现,以及将其接入 TiXL 场渲染管线完成三维图形创作的完整路径。
一、PrismSDF 是什么
PrismSDF 是 TiXL Lib.field.generate.sdf(字段生成 → 有符号距离场)算子库中的一员,其职责是生成一个棱柱(Prism)形状的 SDF 距离场,即"n 边圆柱"(n-sided cylinder)——横截面为正多边形、纵向拉伸的柱状体。它输出的不是网格(Mesh),而是一个可供后续着色器图节点消费的 ShaderGraphNode 距离场描述。
在 Lib.field.generate.sdf 算子列表中,它与 SphereSDF、BoxSDF、CylinderSDF、TorusSDF 等同类节点并列,全部用于"以程序化方式描述隐式几何"。棱柱介于圆柱与方柱之间:Sides 选择 6 时得到六棱柱(横截面为正六边形,最接近圆柱的棱柱形态),Sides 选择 3 时得到三棱柱(三角棱柱)。
官方文档对它的定位一句话即可概括:
Generates a prism SDF (i.e. an n-sided cylinder).
其中 "n-sided cylinder" 正是理解该节点建模意图的关键:把棱柱看作"侧面数有限的圆柱"。
二、输入参数详解
PrismSDF 一共暴露 6 个输入参数,对应源码 PrismSDF.cs 中 6 个 InputSlot 定义。下表为官方文档给出的参数说明,并补充了从 PrismSDF.t3 中提取的节点默认值:
| 参数名 | 类型 | 官方说明 | 默认值(来自 .t3 文件) |
|---|---|---|---|
| Center | Vector3 | 平移对象的中心 | (0, 0, 0) |
| Radius | Single | 定义棱柱的尺寸(半径) | 1.0 |
| Length | Single | 定义棱柱的高度 | 1.0 |
| EdgeRadius | Single | 定义棱边的圆滑程度(圆角半径) | 0.05 |
| Sides | Int32 | 在三棱柱与六棱柱之间切换 | 1(对应 6 棱柱) |
| Axis | Int32 | 定义距离场沿哪个轴对齐 | 1(对应 Y 轴) |
各参数在源码层面的细节如下:
- Center:声明类型为
InputSlot<Vector3>,并同时作为ITransformable.TranslationInput暴露(见 PrismSDF.cs),意味着该参数除了直接输入数值外,还能被 TiXL 的 Gizmo(变换手柄) 驱动——在视口中直接拖动即可平移棱柱。 - Radius / Length:均声明为
InputSlot<float>。注意在生成着色器代码时,二者会被乘上0.5(Radius * 0.5、Length * 0.5),即以半宽 / 半长的形式传入 SDF 函数(详见下文第四节),这与大多数 TiXL SDF 节点一致。 - EdgeRadius:仅对六棱柱生效,控制六棱柱棱边的圆角半径(默认
0.05)。官方文档特别用 NOTE 声明了限制,详见第五节。 - Sides:源码中该参数映射到内部枚举
SidesType { _3, _6 }(见 PrismSDF.cs),节点.t3文件默认值1对应_6,即默认生成六棱柱。 - Axis:映射到内部枚举
AxisTypes { X, Y, Z }(见 PrismSDF.cs),默认值1对应 Y 轴。
关于 Sides 切换的"生效"机制
官方文档对 Sides 有一句容易被忽略的提示:
If changing this value has no effect, any other value must be changed to bring the change into effect.
(若修改此值不生效,则必须改动任意其他参数值才能促使修改生效。)
从源码可以找到这一现象的根因。在 Update 方法 中,节点仅在 轴(axis)或边数(sides)发生变化 时才判定 templateChanged 为真,进而调用 ShaderNode.FlagCodeChanged() 触发着色器代码重建:
var axis = Axis.GetEnumValue<AxisTypes>(context);
var sides = Sides.GetEnumValue<SidesType>(context) == SidesType._3 ? 3 : 6;
var templateChanged = axis != _axis || sides != _sides;
if (!templateChanged)
return;
_axis = axis;
_sides = sides;
ShaderNode.FlagCodeChanged();
也就是说,Sides / Axis 驱动的是代码模板切换而非普通的数值参数:改变它们意味着重新选择要注入的 GLSL 函数(三角棱柱用 fTriangularPrism,六角棱柱用 fHexPrism)。当 UI 上的切换因内部脏标记(Dirty Flag)或求值缓存未及时刷新而不触发 FlagCodeChanged 时,改动任意其他参数即可强制整条着色器图重新求值,从而让模板切换生效。这是 TiXL 场系统"代码级参数"特有的行为,实际使用中若发现切换无响应,按文档提示操作即可。
三、输出:着色器图节点(ShaderGraphNode)
| 输出名 | 类型 |
|---|---|
| Result | T3.Core.DataTypes.ShaderGraphNode |
PrismSDF 的唯一输出 Result 类型为 T3.Core.DataTypes.ShaderGraphNode,在源码中由 构造函数 初始化:
public PrismSDF()
{
ShaderNode = new ShaderGraphNode(this);
Result.Value = ShaderNode;
Result.UpdateAction += Update;
}
ShaderGraphNode 是 TiXL 场(Field)渲染体系的核心数据载体:它不是渲染好的像素,而是一段参与着色器图装配(Code Assembly)的代码片段描述。PrismSDF 通过实现 IGraphNodeOp 接口,把自己的 SDF 数学注入到由 RaymarchField 等"渲染器"节点最终拼装出的着色器中。
因此 PrismSDF 不能独立显示任何内容,它必须与下游消费节点连接,典型链路为:
PrismSDF → SdfField → RaymarchField →(输出 DrawCommand 渲染画面)
PrismSDF → SdfField → VisualizeFieldDistance →(以线条框可视化距离场)
其中 VisualizeFieldDistance 接收 SdfField 输入并输出 TransformCallbackSlot<Command>,适合在编辑阶段直观预览棱柱的轮廓与位置;RaymarchField 则提供 Color、AmbientOcclusion、MaxSteps、MinDistance、StepSize 等参数完成最终的光线步进渲染。
四、源码级原理:两种棱柱 SDF 的数学实现
PrismSDF 的着色器代码生成逻辑在 GetPreShaderCode 与 AddDefinitions 中,核心思想是:按 Sides 枚举注入不同的全局 SDF 函数,再按 Axis 枚举选择不同的坐标分量组合来调用它。
1. 三角棱柱:fTriangularPrism
当 Sides = 3 时,节点向着色器全局注入(源码 L52-L58):
float fTriangularPrism(float3 p, float r, float l)
{
float3 q = abs(p);
return max(q.z-l,max(q.x*0.866025+p.y*0.5,-p.y)-r*0.5);
}
数学要点:
q = abs(p)利用对称性只计算第一象限;max(q.x * 0.866025 + p.y * 0.5, -p.y)是用两条斜边直线(斜率对应 ±60°,0.866025 ≈ √3/2 = sin(60°))相交形成三角形截面的有符号距离;q.z - l表示沿 z 轴方向超出半长l的距离;- 外层
max(...)把"横截面内距离"与"纵向越界距离"合并,得到标准的隐式棱柱距离场。
注意该函数没有圆角参数——这正是"三角棱柱不支持圆角"这一限制的源码证据(见第五节)。
2. 六角棱柱:fHexPrism
当 Sides = 6 时,注入的是带圆角参数的版本(源码 L61-L72):
// h is radius and length
float fHexPrism(float3 p, float r, float l, float round)
{
const float3 k = float3(-0.8660254, 0.5, 0.57735);
p = abs(p);
p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;
float2 d = float2(length(p.xy-float2(clamp(p.x,-k.z * r, k.z * r), r))*sign(p.y - r),p.z - l);
return min(max(d.x,d.y),0.0) + length(max(d,0.0))-round;
}
数学要点:
- 常量
k = float3(-0.8660254, 0.5, 0.57735)是正六边形"支持向量"(support vector)的紧凑编码:0.8660254 ≈ √3/2、0.57735 ≈ 1/√3,配合p.xy -= 2.0 * min(dot(k.xy, p.xy), 0.0) * k.xy的反射折叠操作,把任意点反射进正六边形一个 30° 的楔形扇区内,从而只需计算一条边的距离; d.x通过length(...) * sign(p.y - r)同时携带"到六边形棱线的距离"与"在棱线内/外的符号",d.y = p.z - l为纵向距离;- 最后
min(max(d.x,d.y),0.0) + length(max(d,0.0)) - round是 Inigo Quilez 风格的圆角 SDF 惯用组合:min/max合并两轴距离、length(max(d,0))处理角点过渡,整体减去round即把棱边向外扩张出圆角半径。
3. 轴对齐机制:_axisCodes0
Axis 参数不改变 SDF 数学本身,而是通过 _axisCodes0 数组 做坐标分量重排,把"轴向"映射为"横截面所在平面":
private readonly string[] _axisCodes0 =
[
"yzx", // Axis = X:横截面在 YZ 平面
"xzy", // Axis = Y:横截面在 XZ 平面
"xyz", // Axis = Z:横截面在 XY 平面
];
在 GetPreShaderCode 中,_axisCodes0[(int)_axis] 被同时用于取采样点分量与中心分量,例如 6 棱柱沿 Z 轴时生成:
f{c}.w = fHexPrism(p{c}.xyz - Center.xyz, Radius * 0.5, Length * 0.5, EdgeRadius);
而沿 X 轴时则变成 p{c}.yzx - Center.yzx,使棱柱轴向平行于 X 轴。这也解释了为何文档将 Axis 描述为"Defines the axis along which the field is aligned"。
最后一行 f{c}.xyz = p.w < 0.5 ? p{c}.xyz : 1;(源码 L93)用于保存局部空间坐标,供后续光照/纹理等场节点消费,与同库 CylinderSDF 的做法一致。
五、关键限制:三角棱柱不支持圆角
官方文档在介绍后紧跟一条显眼提示:
NOTE: the triangular prism does not support rounding.
这条限制在源码中有直接证据:
- 三棱柱函数
fTriangularPrism(float3 p, float r, float l)只有三个形参,根本没有round参数; - GetPreShaderCode 的分支调用 中,三棱柱分支生成的调用是
fTriangularPrism(p{a} - Center{a}, Radius * 0.5, Length * 0.5),EdgeRadius 完全不参与;只有六棱柱分支的fHexPrism(...)才把EdgeRadius作为第四个实参传入。
因此在实际创作中:
- 若需要尖锐的三角截面,选
Sides = 3,此时 EdgeRadius 参数无效(改变它不会影响三角棱柱外观); - 若需要带圆角的棱柱,必须选
Sides = 6(六棱柱),圆角半径由 EdgeRadius 控制,默认0.05即可获得柔和的棱线。
六、实战:把 PrismSDF 接入渲染与场变换
作为 Lib.field.generate.sdf 家族的节点,PrismSDF 遵循与其他 SDF 节点一致的接入方式(可参考同库文档 CylinderSDF.md 描述的渲染管线):
- 创建节点:在 TiXL 编辑器的算子库(Operator Library)中搜索
PrismSDF,拖入着色器图,路径为Lib.field.generate.sdf; - 连接渲染器:把
Result(ShaderGraphNode)连入RaymarchField的SdfField输入,即可用光线步进渲染出棱柱实体,并通过其Color、AmbientOcclusion、MaxSteps等参数调节着色; - 调试可视化:在连接 RaymarchField 之前,可先连入
VisualizeFieldDistance预览棱柱轮廓与中心位置,直观调整Center、Radius、Length、Sides、Axis; - 组合场变换:PrismSDF 输出的是可组合的距离场,从源码结构看,可以推断它与场空间变换类节点(如
Lib.field.space下的TransformField、RotateAxis、RepeatField、BendField等)配合使用,实现棱柱的复制、弯曲、扭曲等进阶形态; - Gizmo 直接操控:由于 Center 实现了
ITransformable.TranslationInput,选中节点后可在视口中用手柄直接拖拽定位,无需手动改参数。
调试时若遇到"修改 Sides 没反应",记得按本文第二节末尾的机制:改动任意其他参数值强制着色器重新编译即可。
七、与同类 SDF 节点的选型对比
| 需求 | 推荐节点 | 理由 |
|---|---|---|
| 圆形横截面、高度可调 | CylinderSDF | 真正意义的圆柱,自带 Rounding 圆角 |
| 正多边形柱体(棱柱) | PrismSDF | 唯一支持 3/6 面棱柱的节点 |
| 正六边形横截面且需要圆角 | PrismSDF(Sides=6) | 唯一带 EdgeRadius 的棱柱实现 |
| 立方体/圆角立方体 | BoxSDF | 6 面方体 |
| 圆环 | TorusSDF | 环形截面 |
| 胶囊 | CapsuleLineSDF | 两点连线胶囊 |
选型要点:PrismSDF 的定位是"多边形棱柱",介于 CylinderSDF 与 BoxSDF 之间——默认 6 棱柱在视觉上接近圆柱但保留切面特征,适合需要硬朗几何感、又不想用真实网格的场景,例如抽象几何动画、标题图形(Motion Graphics)中的柱体装饰元素。
八、小结
PrismSDF 是 TiXL 场生成体系中一个"小而精"的节点:它通过 Sides 在三角/六角两种棱柱模板间切换、通过 Axis 重排坐标分量实现轴向对齐,并在六棱柱模式下用 EdgeRadius 提供圆角支持。其底层是两个精确的隐式 SDF 函数(fTriangularPrism / fHexPrism),由 PrismSDF.cs 在着色器装配阶段注入。掌握它的参数语义与"代码级参数需触发重编译"的生效机制后,你就能在 TiXL 中快速搭建由棱柱距离场驱动的实时图形效果,并与 RaymarchField、场空间变换算子组成完整的程序化三维场景。
更多推荐


所有评论(0)