DDY 节点是 Unity URP Shader Graph 中一个重要的高级功能节点,它提供了在像素着色器阶段计算屏幕空间 Y 方向偏导数的能力。这个节点基于 GPU 的导数计算硬件,能够高效地获取相邻像素间的数值变化率,在计算机图形学中有着广泛的应用场景。
偏导数的概念源自微积分,在图形学上下文中,它表示某个值在屏幕空间相邻像素间的变化率。DDY 节点专门计算 Y 方向(垂直方向)的变化率,而与之对应的 DDX 节点则计算 X 方向(水平方向)的变化率。这两个节点共同构成了现代 GPU 并行架构中导数计算的核心功能。
在 Shader Graph 中使用 DDY 节点时,理解其工作原理和限制条件至关重要。由于该节点依赖于像素着色器中的片段并行处理特性,它只能在特定的渲染阶段使用,并且对硬件有一定的要求。掌握 DDY 节点的正确使用方法,能够为着色器开发带来更多可能性,实现各种高级视觉效果。
描述
DDY 节点的核心功能是计算输入值在屏幕空间 Y 坐标方向上的偏导数。从数学角度理解,偏导数描述了多变量函数沿某一坐标轴方向的变化率。在着色器编程的语境中,这意味着 DDY 节点能够测量某个着色器属性或计算值在垂直相邻像素之间的差异。
屏幕空间偏导数的计算基于 GPU 的硬件特性。现代 GPU 通常以 2x2 像素块为单位并行执行像素着色器,这种架构被称为"像素四边形"(Pixel Quad)。在这种结构中,DDY 节点通过比较当前像素与同一像素四边形中下方像素的数值差异来计算偏导数。这种硬件级的并行计算使得导数计算非常高效,不需要额外的复杂数学运算。
DDY 节点的一个重要限制是它只能在像素着色器阶段使用。这是因为导数计算依赖于片段着色器中的像素级并行处理。如果尝试在顶点着色器或其他渲染阶段使用 DDY 节点,将会导致编译错误或未定义的行为。在 Shader Graph 中,当将 DDY 节点连接到非像素着色器阶段的节点时,系统通常会发出警告或错误提示。
在实际应用中,DDY 节点最常见的用途包括:
- 计算法线贴图的表面法线
- 实现基于导数的边缘检测
- 创建各向异性高光效果
- 优化纹理采样和 mipmap 级别选择
- 实现屏幕空间的环境光遮蔽
理解 DDY 节点的数学原理对于正确使用它至关重要。偏导数的计算可以近似表示为:ddy(p) ≈ p(x, y+1) - p(x, y),其中 p 是输入值,(x, y) 是当前像素的屏幕坐标。这个近似计算由 GPU 硬件在像素四边形级别自动完成,为着色器程序员提供了高效的导数访问方式。
端口

DDY 节点的端口设计遵循 Shader Graph 的标准约定,提供了清晰的输入输出接口,使得节点能够灵活地集成到各种着色器网络中。
输入端口
输入端口标记为 "In",是节点的唯一输入通道,接受动态矢量类型的数据。动态矢量意味着该端口可以接受各种维度的向量输入,包括:
- float(标量值)
- float2(二维向量)
- float3(三维向量)
- float4(四维向量)
这种灵活性使得 DDY 节点能够处理各种类型的数据,从简单的灰度值到完整的颜色信息。当输入多维向量时,DDY 节点会独立计算每个分量的偏导数,返回一个与输入维度相同的输出向量。
输入值的内容可以是任何在像素着色器中有效的表达式或节点输出,包括:
- 纹理采样结果
- 数学运算输出
- 时间变量
- 顶点数据插值
- 其他自定义计算的结果
输出端口
输出端口标记为 "Out",提供计算得到的偏导数结果。与输入端口类似,输出也是动态矢量类型,其维度与输入保持一致。输出值的每个分量对应于输入向量相应分量的偏导数。
输出值的范围和特性取决于输入内容:
- 当输入是连续平滑变化的值时,输出通常较小且变化平缓
- 当输入在相邻像素间有剧烈变化时,输出值会相应增大
- 在边缘或高对比度区域,输出值可能显著增加
- 在平坦或均匀区域,输出值接近零
理解输出值的这些特性对于正确解释和使用 DDY 节点的结果至关重要。在实际应用中,通常需要对输出值进行适当的缩放、钳制或后续处理,以适应特定的视觉效果需求。
端口连接实践
在 Shader Graph 中连接 DDY 节点时,需要考虑数据类型和精度的匹配。虽然动态矢量端口提供了很大的灵活性,但最佳实践包括:
- 确保输入数据的范围合理,避免极端值导致导数计算不稳定
- 注意数据精度,在移动平台或性能受限环境下考虑使用半精度浮点数
- 合理组织节点网络,避免不必要的复杂连接影响可读性
- 使用适当的注释和分组,使包含 DDY 节点的复杂网络更易于理解和维护
生成的代码示例
DDY 节点在背后生成的代码揭示了其在底层着色器语言中的实现方式。理解这些生成的代码有助于深入掌握节点的行为特性,并在需要时进行自定义扩展或优化。
HLSL 代码实现
在大多数情况下,DDY 节点会生成类似于以下示例的 HLSL 代码:
scss
void Unity_DDY_float4(float4 In, out float4 Out)
{
Out = ddy(In);
}
这个简单的函数封装了 HLSL 内置的 ddy() 函数,该函数是 DirectX 着色器语言中用于计算屏幕空间 Y 方向偏导数的原生指令。函数接受一个 float4 类型的输入参数,并输出相应的偏导数结果。
对于不同维度的输入,生成的函数签名会相应调整:
- 对于 float 输入:
Unity_DDY_float(float In, out float Out) - 对于 float2 输入:
Unity_DDY_float2(float2 In, out float2 Out) - 对于 float3 输入:
Unity_DDY_float3(float3 In, out float3 Out)
底层硬件实现
虽然从代码层面看,DDY 节点的实现很简单,但它在硬件层面的执行却涉及 GPU 的并行架构特性。当 GPU 执行包含 ddy() 调用的像素着色器时:
- 着色器单元以 2x2 像素块(像素四边形)为单位调度执行
- 在每个像素四边形中,四个片段并行处理
- 硬件自动比较同一四边形中垂直相邻像素的寄存器值
- 计算得到的导数值用于所有四个像素的着色计算
这种实现方式意味着导数计算基本上没有额外的性能开销,因为 GPU 本来就需要并行处理像素四边形中的多个片段。这也是为什么导数计算只能在像素着色器中工作的原因------其他着色器阶段没有这种并行处理架构。
精度和性能考虑
在使用 DDY 节点时,了解其精度特性和性能影响很重要:
- 导数计算基于实际执行的像素值,因此结果完全准确
- 在几何边缘或遮挡边界处,导数可能不太可靠,因为相邻像素可能属于不同物体
- 性能开销通常很小,但在低端移动设备上,复杂的导数计算网络仍可能影响性能
- 在某些情况下,使用近似计算方法可能比直接使用 DDY 节点更高效
自定义扩展和应用
通过理解生成的代码模式,开发者可以创建自定义的导数计算函数,扩展 DDY 节点的功能:
csharp
// 自定义带缩放的导数计算
void Custom_DDY_Scaled(float4 In, float Scale, out float4 Out)
{
Out = ddy(In) * Scale;
}
// 带钳制的导数计算,避免过大的导数值
void Custom_DDY_Clamped(float4 In, float MaxDerivative, out float4 Out)
{
Out = clamp(ddy(In), -MaxDerivative, MaxDerivative);
}
// 计算导数的大小,用于边缘检测等应用
void Custom_DDY_Length(float4 In, out float Out)
{
Out = length(ddy(In));
}
这些自定义函数可以在 Shader Graph 中通过 Custom Function 节点实现,为特定的应用场景提供更专门的导数计算功能。
实际应用案例
DDY 节点在着色器开发中有着广泛的应用,以下是一些典型的实际应用案例,展示了如何充分利用这个节点的特性。
法线贴图处理
在基于物理的渲染中,法线贴图是增强表面细节的关键技术。DDY 节点可以用于计算法线贴图的正确 mipmap 级别,或者在需要时重建世界空间法线:
scss
// 使用 DDY 计算法线贴图的适当 LOD 级别
float CalculateNormalMapLOD(float2 uv)
{
float2 deriv = float2(ddx(uv.x), ddy(uv.y));
float lod = 0.5 * log2(max(dot(deriv, deriv), 1.0));
return lod;
}
// 结合 DDX 和 DDY 重建世界空间法线
float3 ReconstructWorldNormal(float2 uv, float3 normalTS, float3x3 TBN)
{
float3 ddx_normal = ddx(normalTS);
float3 ddy_normal = ddy(normalTS);
// 应用复杂的法线重建算法
// ...
}
边缘检测效果
DDY 节点在屏幕后处理中常用于边缘检测,通过分析颜色或深度的变化来识别图像中的边缘:
scss
// 基于颜色导数的简单边缘检测
float EdgeDetectionColor(float2 uv, sampler2D colorTexture)
{
float3 color = tex2D(colorTexture, uv).rgb;
float3 deriv_x = ddx(color);
float3 deriv_y = ddy(color);
float edge = length(deriv_x) + length(deriv_y);
return saturate(edge * 10.0); // 调整灵敏度
}
// 结合深度和颜色的高级边缘检测
float AdvancedEdgeDetection(float2 uv, sampler2D colorTexture, sampler2D depthTexture)
{
float depth = tex2D(depthTexture, uv).r;
float3 color = tex2D(colorTexture, uv).rgb;
float depth_edge = abs(ddy(depth)) * 100.0; // 深度边缘
float color_edge = length(ddy(color)) * 10.0; // 颜色边缘
return saturate(max(depth_edge, color_edge));
}
各向异性高光
各向异性高光效果模拟表面在特定方向反射光线的特性,如拉丝金属或头发材质。DDY 节点可以帮助确定高光的方向和强度:
scss
// 简单的各向异性高光计算
float AnisotropicSpecular(float3 worldNormal, float3 viewDir, float2 uv)
{
// 使用 UV 导数确定各向异性方向
float2 deriv = float2(ddx(uv.x), ddy(uv.y));
float anisotropy = length(deriv);
// 基于导数方向调整高光
float3 anisotropicDir = normalize(float3(deriv.x, deriv.y, 0));
// 进一步的高光计算...
return specular;
}
纹理采样优化
通过分析纹理坐标的导数,可以优化纹理采样,选择适当的 mipmap 级别,平衡质量和性能:
scss
// 基于导数的自适应纹理采样
float4 AdaptiveTextureSample(sampler2D tex, float2 uv)
{
// 计算纹理坐标的导数
float2 duv_dx = ddx(uv);
float2 duv_dy = ddy(uv);
// 计算适当的 LOD 级别
float lod = 0.5 * log2(max(dot(duv_dx, duv_dx), dot(duv_dy, duv_dy)));
// 使用计算出的 LOD 进行采样
return tex2Dlod(tex, float4(uv, 0, lod));
}
最佳实践和注意事项
为了确保 DDY 节点的正确使用和最佳性能,遵循一些最佳实践和注意事项非常重要。
平台兼容性
DDY 节点在不同平台和图形 API 上的支持程度可能有所差异:
- 在所有现代桌面 GPU(DirectX 11+、Vulkan、Metal)上完全支持
- 在移动平台上,需要 OpenGL ES 3.0+ 或 Vulkan 支持
- 在较旧的硬件或图形 API 上可能有限制或性能问题
- 在 WebGL 中,支持程度取决于浏览器和硬件能力
为了确保跨平台兼容性,建议:
- 在图形设置中配置适当的回退方案
- 使用 Shader Graph 的条件编译功能处理平台差异
- 在移动平台上测试导数计算的性能影响
性能优化
虽然 DDY 节点本身很高效,但在复杂着色器中仍需注意性能:
- 避免在循环或复杂控制流中过度使用 DDY 节点
- 考虑复用导数计算结果,而不是重复计算
- 对于简单的应用,考虑使用近似的分析方法代替精确的导数计算
- 在性能敏感的平台,评估使用 DDY 节点的实际性能影响
数学精度考虑
导数计算对数值精度很敏感,特别是在 HDR 或高动态范围场景中:
- 注意输入值的范围,过大的值可能导致导数计算不稳定
- 在需要高精度的应用中,考虑使用更高精度的浮点数格式
- 注意导数计算在 discontinuities(不连续点)处的行为可能不符合预期
调试和可视化
调试包含 DDY 节点的着色器可能具有挑战性,以下技巧可以帮助:
- 使用 Color 节点将导数值可视化,检查其范围和分布
- 创建调试视图,单独显示导数计算的结果
- 使用适当的缩放和偏移,使导数值在可视范围内
- 在简单测试案例中验证导数计算的行为
与其他节点的结合
DDY 节点通常与其他数学和工具节点结合使用,创建复杂的视觉效果:
- 结合 DDX 节点获取完整的梯度信息
- 使用数学节点对导数结果进行后处理
- 与条件节点结合,创建基于导数阈值的效果
- 在子图中封装复杂的导数计算逻辑,提高可重用性
【Unity Shader Graph 使用与特效实现】专栏-直达 (欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)