TiXL 自定义 Shader 算子完全指南:从预设到像素、点、粒子、网格与 SDF 的全流程实战
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
本文基于 TiXL 官方高级文档 UsingCustomShaders.md 并结合仓库源码编写。Custom Shader Operators(自定义 Shader 算子)把 TiXL 的预设系统与着色器管线结合在一起,让开发者只需要编写一个 shader 函数函数体,算子就会自动将其包裹进一个完整的 shader、即时编译,并把编译错误直接显示在编辑器里。读完本文,你将掌握六个自定义算子(CustomPixelShader / CustomPointShader / CustomForce / CustomVertexShader / CustomFaceShader / CustomSDF)的输入输出契约、模板机制、内置辅助函数、四元数工具与 Field 采样接口,能直接写出可复用的实时特效。
六个自定义 Shader 算子一览
自 v4.1 起,TiXL 提供了六个模板化(template-based)的自定义 shader 算子,覆盖图像、点、粒子、网格与距离场五大场景:
- [CustomPixelShader]— 为纹理的每一个像素计算颜色(像素着色器)。
- [CustomPointShader]— 移动、旋转、缩放并给点缓冲中的点着色(计算着色器)。
- [CustomForce]— 为粒子系统中的粒子施加自定义力。
- [CustomVertexShader]— 位移并给网格顶点着色。
- [CustomFaceShader]— 操作网格三角形,包括将三角形炸开分解。
- [CustomSDF]— 定义一个距离场(SDF),接入 field / raymarching 管线。
在仓库中,这些算子的实例分别位于 Operators/Lib/Symbols 目录:image/use/CustomPixelShader.t3、point/modify/CustomPointShader.t3、particle/force/CustomForce.t3、mesh/modify/CustomVertexShader.t3、mesh/modify/CustomFaceShader.t3、field/generate/sdf/CustomSDF.t3,每个算子的 C# 入口定义在对应的.cs文件中。
你完全不需要手写完整 shader 就能开始使用:打开Variations(变体)窗口,任选一个预设并调节参数即可。每个算子都带有一组共享参数 ——Offset、A、B、C、D、GainAndBias 和 Gradient—— shader 片段可以直接读取它们。优秀的预设会在代码顶部用注释说明每个参数的含义(例如// A: Falloff B: Size),方便他人复用。
工作原理:模板 + 代码拼接
每个算子都拥有一个模板(template)shader 文件,由TemplateFile参数指定 —— 打开该参数即可看到完整地包裹你代码的 shader 源码。以 CustomPixelShader 为例,它的TemplateFile默认值为Lib:shaders/img/use/CustomImageShader-template.hlsl(实际文件位于 Operators/Lib/Assets/shaders/img/use/CustomImageShader-template.hlsl)。
模板负责声明常量缓冲、纹理与辅助函数,然后把你的ShaderCode粘贴进其主函数的函数体中:
// ...template sets up uv, c, p, v etc. ... { //- METHOD ------------------------------------- /*{method}*/ // ← your ShaderCode lands here //---------------------------------------------- } // ...template writes the expected variables back...你的片段操作的是已准备好的局部变量,并且需要设置或修改指定的输出变量。除此之外的一切 —— 线程分发、缓冲读写 —— 都由模板处理。
AdditionalCode/AdditionalDefines参数会被插入到主函数之上的全局作用域(模板中对应/*{defines}*/标记的位置),用于放置你自己的辅助函数、常量,或声明额外的资源。
从源码可以确认这套"拼接"机制并非魔法:在 CustomPixelShader.t3 内部,它用ReadFile节点读取模板、用两个SearchAndReplace节点分别把/*{defines}*/替换为AdditionalCode、把/*{method}*/替换为ShaderCode,再交给PixelShaderFromSource节点(入口点psMain)编译。而 CustomImageShader-template.hlsl 中可以看到实际的注册绑定:ImageA/ImageB/Gradient分别绑定t0/t1/t2,三个采样器Sampler/ClampedSampler/CustomSampler分别绑定s0/s1/s2,Offset/A/B/C/D/GainAndBias位于b0常量缓冲,目标尺寸在b1,并定义了Biased与SampleGradient辅助函数。
各算子的输入输出契约
下表是六个算子的完整契约 —— 你能拿到什么,需要设置什么:
| Operator | Runs per... | Prepared variables | You set / modify |
|---|---|---|---|
| [CustomPixelShader] | pixel | float2 uv,int2 PixelCoord,int2 TargetSize | float4 c— 输出颜色(默认为白色) |
| [CustomPointShader] | point | Point p,uint idx(别名i),float f= idx 归一化到 0…1 | p— 写入结果缓冲 |
| [CustomForce] | particle | Particle p(只读),float3 vel,float3 pos,float4 col,float age,float2 uv | vel,col,pos— 按Amount混合回去 |
| [CustomVertexShader] | vertex | PbrVertex v,uint vertexIndex | v— 写入结果网格 |
| [CustomFaceShader] | triangle | PbrVertex v1, v2, v3,int3 faceIndices,uint faceIndex,float3 pos1, pos2, pos3 | v1,v2,v3— 作为非共享三角形写入 |
| [CustomSDF] | field sample | float3 p,float3 Offset,float A, B, C | return在p处的有符号距离 |
上述结构体定义:
struct Point struct Particle struct PbrVertex { { { float3 Position; float3 Position; float3 Position; float FX1; float Radius; float3 Normal; float4 Rotation; // quat float4 Rotation; // quat float3 Tangent; float4 Color; float4 Color; float3 Bitangent; float3 Scale; float3 Velocity; float2 TexCoord; float FX2; float BirthTime; float2 TexCoord2; }; }; float Selected; float3 ColorRGB; };这些结构体在模板的 include 头中定义(如 shared/point.hlsl 等)。
CustomPixelShader:逐像素图像处理
该算子对目标纹理运行像素着色器。输入ImageA和ImageB绑定为Texture2D<float4>(并带有别名Image和Image2),Gradient作为一张 1 像素高的查找纹理。有三个采样器可用:Sampler(wrap 环绕)、ClampedSampler(clamp 钳制)和CustomSampler(来自CustomSampler输入)。
除了ShaderCode、AdditionalCode、TemplateFile与共享参数外,它的 C# 入口 CustomPixelShader.cs 还声明了Resolution(渲染分辨率)、TextureFormat(输出格式,默认R16G16B16A16_Float)、GenerateMips、Clear、多输入的ShaderResources(额外 SRV)、ConstantBuffers(额外常量缓冲)与CustomSampler(自定义采样器),并输出TextureOutput与编译后的ShaderCode_。
默认代码是一个简单的暗角(vignette):
float d = 1 - length(uv - 0.5 - Offset * float2(1,-1)); d = ApplyGainAndBias(d, GainAndBias); c = ImageA.Sample(Sampler, uv); c.rgb *= SampleGradient(d).rgb;用一张图位移另一张图(displacement):
// Connect two images and use the Offset parameter float4 cb = ImageB.Sample(Sampler, uv); float d = Biased(cb.r); c = ImageA.Sample(Sampler, uv - d * Offset * float2(1,-1));扩展输入:用 [FloatsToBuffer] / [IntsToBuffer] 提供额外常量缓冲,用 [SrvFromTexture2d] 提供更多纹理,用 [SamplerState] 提供自定义采样器。每个都要在AdditionalCode中声明,例如Texture2D<float4> Image3 : register(t3);(寄存器号从模板已占用之后继续编号)。
CustomPointShader:逐点计算着色器
这是一个对连接缓冲的每个点运行一次的计算着色器。p是源点的拷贝;你在它里面留下的任何内容都会写入输出。f是点在缓冲中的归一化位置(0…1),非常适合沿曲线分布点。Time保存按TimeScale缩放的播放时间,TotalCount是缓冲大小。
默认代码让点沿自身旋转方向移动并用渐变着色:
float t = Biased( frac(f * (B+1) + A) ); float3 pForward = qRotateVec3(float3(0,0,1), p.Rotation); p.Position += pForward * (t - 0.5); p.Position.y += sin(t * 2 * PI); p.Color = SampleGradient(t); p.Scale += t;Wave预设 —— 形成波浪的网格点阵:
int gs = sqrt(TotalCount); float2 gpos = float2(idx % gs, idx / gs) / gs - 0.5; p.Position = float3(gpos.x, 0, gpos.y); float h = length(sin(gpos * 8 + Offset.xz) * 0.1); p.Position.y = h; p.Color = SampleGradient(h * 8);程序化生成点同样可行:设置Count并让Points输入留空,p会从原点处的默认点开始。
CustomForce:粒子力场
它对粒子系统的每个粒子运行一次。你修改局部拷贝vel(速度)、col(颜色)和pos(位置);代码执行后,它们按Amount参数混合回粒子。尽量避免直接写pos,当你能把变化表达成速度时就表达成速度 —— 位置绕过模拟的速度处理(从 CustomForce-Template.hlsl 源码可以看到pos的注释警告:它不会应用SpeedFactor)。
age保存粒子的寿命(秒),uv是它当前的屏幕空间位置(适合把Image输入当作屏幕对齐贴图采样)。注意这里的采样器名称是ClampedSampler和WrappedSampler。
把粒子从连接的 SDF 场中推出去:
float d = GetDistance(pos); float3 n = GetNormal(pos); if (d < 0) vel += n / (d + 1);模板中提供了完整的相机与物体变换矩阵(WorldToClipSpace、ObjectToWorld等,位于b3常量缓冲),可做高级效果;内置的PositionToScreenSpaceUv就是利用WorldToClipSpace把世界坐标转成屏幕 uv 的。
CustomVertexShader:逐顶点网格变形
对连接网格的每个顶点运行一次。修改PbrVertex v—— 通常是v.Position、v.Normal或v.ColorRGB。
SimpleNoise预设 —— 沿法线随机位移:
float4 noise = hash41u(vertexIndex); v.Position += v.Normal * (noise.xyz - 0.5) * Offset * A;按连接的 distance field 着色:
// A: Falloff B: Size float d = GetField(float4(v.Position, 0)).w; float t = Biased(smoothstep(1, 0, (d - A) / B)); v.ColorRGB = SampleGradient(t).rgb;CustomFaceShader:逐三角形分解
它对每个三角形运行一次,并把三个顶点非共享(unshared)写回 —— 每个面拥有自己的顶点。这让共享顶点不可能实现的 faceting 效果成为可能:整体缩小、旋转或平移整个三角形。pos1–pos3保留原始位置,同时你修改v1–v3。
按平均高度给面着色:
float3 avgPos = (v1.Position + v2.Position + v3.Position) / 3; float4 color = SampleGradient((avgPos.y + Offset.y) * A + B); v1.ColorRGB = color.rgb; v2.ColorRGB = color.rgb; v3.ColorRGB = color.rgb;用逐面噪声炸开三角形:
v1.Position += (hash41u(faceIndices.x).xyz - 0.5) * C; v2.Position += (hash41u(faceIndices.y).xyz - 0.5) * C; v3.Position += (hash41u(faceIndices.z).xyz - 0.5) * C;CustomSDF:距离场节点
与上面的算子不同,[CustomSDF]不作为一个独立 shader 运行—— 它在 TiXL 的shader graph中定义一个节点。你编写一个距离函数的函数体,field 管线把它组装进任何采样该场的 shader 中(例如 [RaymarchField],或其他 Custom 算子的 Field 输入):
float dCustom(float3 p, float3 Offset, float A, float B, float C) { // ← your DistanceFunction code; must return the signed distance at p }默认是一个简单球体:
return length(p - Offset) - A;SinBlobs预设 —— 有机的团块:
float scale = A; float thickness = B; float bias = C; p *= scale; return (abs(dot(sin(p*.5 + Offset), cos(p.zxy * 1.23)) - bias) / scale - thickness) * 0.55;一个 GLSL 风格的mod(x, y)宏已预定义,这让把 Shadertoy 或 jbaker.graphics 的分形单行代码移植进来变得直接 —— 许多内置预设(BoxFold、SpiderCave、PipeMaze 等)都来自那里。把辅助函数放进AdditionalDefines。这里没有 Gradient 或 Image—— 距离函数只返回几何;用消费它的 field 算子来着色。
用 [VisualizeFieldDistance] 可视化结果,用 [RaymarchField] 渲染它,并用 [BendField]、[TransformField]、[PolarRepeat] 等修改它。在仓库中对应算子位于field命名空间(如 CustomSDF.t3),更多说明可参考 field 算子文档。
在其它算子中使用 Field
[CustomPointShader]、[CustomForce]、[CustomVertexShader] 和 [CustomFaceShader] 都有Field输入,接受 field 算子 —— 任何来自field命名空间的东西,包括你自己的 [CustomSDF]。模板通过以下函数暴露它们:
| Function | Description |
|---|---|
GetField(float4 p) | 采样p.xyz处的连接场;返回 rgb = 颜色,w = 距离 |
GetDistance(float3 p) | 距离值的简写 |
GetFieldNormal(float3 p) | 归一化的场梯度(在 [CustomForce] 中叫GetNormal) |
这让你可以轻松地对任何你在图中构建的 SDF 进行着色、位移或碰撞。以 CustomForce-Template.hlsl 为证:GetField函数体里是/*{FIELD_CALL}*/占位符,GetDistance取GetField(...).w,而GetNormal用 4 个偏移采样点做中心差分求梯度。
内置辅助函数
以下函数在所有模板类算子中可用([CustomSDF] 除外 —— 它只能看到其 shader-graph 上下文和AdditionalDefines提供的东西):
| Name | Description |
|---|---|
Biased(float f) | 对 f 应用GainAndBias参数重映射 |
SampleGradient(float f) | 在 f(0…1)处采样Gradient参数 |
ApplyGainAndBias(f, gainBias) | 类似Biased,但使用显式的 gain/biasfloat2 |
hash11(f)…hash44(v) | 快速哈希函数:数字 = 输出/输入分量数,例如hash41u(uint)用 uint 种子返回float4 |
在模板源码中可以找到它们的实际实现:例如 CustomImageShader-template.hlsl 中Biased就是调用ApplyGainAndBias(f, GainAndBias),SampleGradient用Gradient.SampleLevel(ClampedSampler, float2(f, 0.5), 0)采样;hash*系列位于 shared/hash-functions.hlsl,gain/bias 位于 shared/bias-functions.hlsl。
噪声函数(cnoise、snoise、curlNoise等)包含在 [CustomVertexShader] 和 [CustomFaceShader] 中。对于其它算子,可以通过AdditionalCode引入。
四元数工具
点的旋转以四元数存储。以下辅助函数在所有算子([CustomPixelShader] 除外)中可用:
| Name | Description | Parameters |
|---|---|---|
qMul | 两个四元数的标准 Hamilton 乘积 | float4 q1,float4 q2 |
qRotateVec3 | 用优化公式将 3D 向量按四元数旋转 | float3 v,float4 q |
qConjugate | 返回四元数的共轭(取反虚部) | float4 q |
qInverse | 计算四元数的逆 | float4 q |
qFromAngleAxis | 创建绕axis旋转angle的四元数 | float angle,float3 axis |
qFromVectors | 创建从v1到v2的旋转四元数 | float3 v1,float3 v2 |
qLookAt | 创建指向forward、以指定up为上的朝向四元数 | float3 forward,float3 up |
qSlerp | 两个四元数之间的球面线性插值 | float4 a,float4 b,float t |
qFromEuler | 将欧拉角(yaw, pitch, roll)转换为四元数 | float yaw,float pitch,float roll |
qToMatrix | 将四元数转换为 4×4 旋转矩阵 | float4 quat |
qFromMatrix3 | 从 3×3 矩阵简化的四元数转换 | float3x3 m |
qFromMatrix3Precise | 从 3×3 矩阵稳健的四元数转换(处理所有情况) | float3x3 m |
这些函数定义在 shared/quat-functions.hlsl,被各模板 include(例如 CustomForce-Template.hlsl 第 3 行)。
示例 —— 让点沿生成的曲线定向(来自Knot预设,Generator函数定义在AdditionalDefines中):
p.Position = Generator(f); float3 up = float3(0,-1,0); float3 fwd = normalize(Generator(f - .01) - Generator(f + .01)); p.Rotation = qLookAt(fwd, up);实战建议
- 打开模板。
TemplateFile参数指向完整的 shader 源码 —— 它是关于"什么包围着你的代码、哪些寄存器被绑定"的权威参考。模板文件与各算子的对应关系可在 Operators/Lib/Assets/shaders 目录中查看(如img/use/CustomImageShader-template.hlsl、particles/CustomForce-Template.hlsl、3d/mesh/modify/CustomVertexShader-template.hlsl、3d/mesh/modify/CustomFaceShader-template.hlsl)。 - 把预设作为起点。它们就是普通的参数快照;保存你自己的预设会连同所有参数一起捕获 shader 代码。
- 给参数写注释。在片段顶部写一行类似
// A: Falloff的注释,能让预设对其他人更可复用。 - 编译错误会显示在算子上 —— 点击它读取 HLSL 编译器消息;行号指向组装后的 shader,所以请对照模板查看上下文。
进一步阅读
- Writing code operators —— 当 shader 片段不够用时,编写代码算子
- Shader development example —— 在完整 shader 文件上配合热重载开发
- 相关算子源码入口:CustomPixelShader.cs、CustomPixelShader.t3、CustomForce.t3、CustomSDF.t3
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考