TiXL PrismSDF 节点深度解析:三角/六角棱柱 SDF 距离场的生成原理与实战用法

【免费下载链接】t3 TiXL is an open source software to create realtime motion graphics. 【免费下载链接】t3 项目地址: https://gitcode.com/GitHub_Trending/t3/t3

本文围绕 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 算子列表中,它与 SphereSDFBoxSDFCylinderSDFTorusSDF 等同类节点并列,全部用于"以程序化方式描述隐式几何"。棱柱介于圆柱与方柱之间: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 文件)
CenterVector3平移对象的中心(0, 0, 0)
RadiusSingle定义棱柱的尺寸(半径)1.0
LengthSingle定义棱柱的高度1.0
EdgeRadiusSingle定义棱边的圆滑程度(圆角半径)0.05
SidesInt32在三棱柱与六棱柱之间切换1(对应 6 棱柱)
AxisInt32定义距离场沿哪个轴对齐1(对应 Y 轴)

各参数在源码层面的细节如下:

  • Center:声明类型为 InputSlot<Vector3>,并同时作为 ITransformable.TranslationInput 暴露(见 PrismSDF.cs),意味着该参数除了直接输入数值外,还能被 TiXL 的 Gizmo(变换手柄) 驱动——在视口中直接拖动即可平移棱柱。
  • Radius / Length:均声明为 InputSlot<float>。注意在生成着色器代码时,二者会被乘上 0.5Radius * 0.5Length * 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)

输出名类型
ResultT3.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 则提供 ColorAmbientOcclusionMaxStepsMinDistanceStepSize 等参数完成最终的光线步进渲染。

四、源码级原理:两种棱柱 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/20.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.

这条限制在源码中有直接证据:

  1. 三棱柱函数 fTriangularPrism(float3 p, float r, float l) 只有三个形参,根本没有 round 参数
  2. 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 描述的渲染管线):

  1. 创建节点:在 TiXL 编辑器的算子库(Operator Library)中搜索 PrismSDF,拖入着色器图,路径为 Lib.field.generate.sdf
  2. 连接渲染器:把 Result(ShaderGraphNode)连入 RaymarchFieldSdfField 输入,即可用光线步进渲染出棱柱实体,并通过其 ColorAmbientOcclusionMaxSteps 等参数调节着色;
  3. 调试可视化:在连接 RaymarchField 之前,可先连入 VisualizeFieldDistance 预览棱柱轮廓与中心位置,直观调整 CenterRadiusLengthSidesAxis
  4. 组合场变换:PrismSDF 输出的是可组合的距离场,从源码结构看,可以推断它与场空间变换类节点(如 Lib.field.space 下的 TransformFieldRotateAxisRepeatFieldBendField 等)配合使用,实现棱柱的复制、弯曲、扭曲等进阶形态;
  5. Gizmo 直接操控:由于 Center 实现了 ITransformable.TranslationInput,选中节点后可在视口中用手柄直接拖拽定位,无需手动改参数。

调试时若遇到"修改 Sides 没反应",记得按本文第二节末尾的机制:改动任意其他参数值强制着色器重新编译即可。

七、与同类 SDF 节点的选型对比

需求推荐节点理由
圆形横截面、高度可调CylinderSDF真正意义的圆柱,自带 Rounding 圆角
正多边形柱体(棱柱)PrismSDF唯一支持 3/6 面棱柱的节点
正六边形横截面且需要圆角PrismSDF(Sides=6)唯一带 EdgeRadius 的棱柱实现
立方体/圆角立方体BoxSDF6 面方体
圆环TorusSDF环形截面
胶囊CapsuleLineSDF两点连线胶囊

选型要点:PrismSDF 的定位是"多边形棱柱",介于 CylinderSDF 与 BoxSDF 之间——默认 6 棱柱在视觉上接近圆柱但保留切面特征,适合需要硬朗几何感、又不想用真实网格的场景,例如抽象几何动画、标题图形(Motion Graphics)中的柱体装饰元素。

八、小结

PrismSDF 是 TiXL 场生成体系中一个"小而精"的节点:它通过 Sides 在三角/六角两种棱柱模板间切换、通过 Axis 重排坐标分量实现轴向对齐,并在六棱柱模式下用 EdgeRadius 提供圆角支持。其底层是两个精确的隐式 SDF 函数(fTriangularPrism / fHexPrism),由 PrismSDF.cs 在着色器装配阶段注入。掌握它的参数语义与"代码级参数需触发重编译"的生效机制后,你就能在 TiXL 中快速搭建由棱柱距离场驱动的实时图形效果,并与 RaymarchField、场空间变换算子组成完整的程序化三维场景。

【免费下载链接】t3 TiXL is an open source software to create realtime motion graphics. 【免费下载链接】t3 项目地址: https://gitcode.com/GitHub_Trending/t3/t3

更多推荐