Notes on ddx/ddy

定义

ddx/ddy用于返回屏幕空间中某一值关于x,y方向上的偏导数。它只可以用于fragment program中,参数必须来自fragment的输入。
HLSL对ddx的定义:Returns the partial derivative of the specified value with respect to the screen-space x-coordinate.
GLSL中对应的函数为dFdx/dFdy

实现原理

在triangle rasterization阶段,GPU会对多个像素实例同时运行fragment shader。在最细的程度上,它将会通过SIMD同时跑32-64个像素(实例)。在这些像素中,又会被划分成2 * 2的小组(又被称为quad-fragments),所以每个组会有四个相邻的像素,这样SIMD指令就能同时处理屏幕上2 * 2 = 4个像素了。
每个像素中的值都来自于vertex program或其他步骤输出之后得到的插值,所以对于这些值来说,在进入到fragment shader之前就完成了计算,这些是可以并行计算得到的。所以在fragment shader中运行ddx(vertexOuput)的时候,就能知道这个插值的变化导数。

fragment shader之所以能够访问到相邻像素的数据(即SIMD的不同lanes/thread,每个lanes/thread都处理一个像素),是因为GPU利用了(quad) swizzle进行了跨lanes的数据访问

对于AMD的GCN,ds_swizzle_b32指令的offset field是留给ds_pattern的。它有两种模式[10]

  • Quad-permute mode (QDMode): Each of the four adjacent lanes can access each other’s data, and the same switch applies to each set of four. The ds_pattern LSBs directly encode the element ID for each lane.
  • Bit-masks mode (BitMode): This mode enables limited data sharing within 32 consecutive lanes. Each lane applies bitwise logical operations with constants to its lane ID to produce the element ID from which to read. Constants are encoded in ds_pattern .


QDMode模式会更清晰一点

应用

1.计算Lod或各向异性(Anisotropy)

因为mipmap level需要计算fragmentInput.uv的导数,即相邻像素之间对应纹素的差距。过程是将屏幕坐标的点映射到uv平面上,然后计算出距离。取出最大的一个距离L,并做$log_2⁡L$。

其含义就是如果uv平面中采样点很稀,相应的du/dx或其他的值就比较大,L也相应的比较大。这种情况说明Minification了,很多纹素就堆在了一个像素里。我们实际上用不到这么多纹素,于是一个很高级别的mipmap(更模糊的)就可以用来节省渲染的负担,也可以避免出现锯齿。

2.计算面法线

在 FragShader 中,调用ddx(i.position), 和ddy(i.position)可以求出相邻的2 个像素之间座标的差值,即两个像素在三角面上采样的点所构成的向量。下面图中的红色和绿色2个向量即为ddx,ddy所返回的向量

而这2 个向量都在这个三角形的平面上,那么
normal = normalize(cross(ddx(pos), ddy(pos)))
就可以求出的面的法线,但是这里要注意,在 HLSL 或者Unity shader里要写成normalize(cross(ddy(pos), ddx(pos))) , 不然法线是反向的。这个是由于左右手座标系引起的。

注意事项

嵌套使用ddx(p)或ddy(p)可能导致undefine的值,如ddx(ddx(p))或ddx(ddy(p))。
同时,程序假设参数p是连续的。对于嵌在flow-control代码段的值,因为不是每一个在quad-fragment中的片段都会执行同样的branch,所以结果也是undefine的
ddx/ddy分为coarse版本和fine版本。如果直接使用ddx,等价于ddx_coarse(可以看做alias).
ddx_coarseddx_fine对于同一组2*2的quad-fragment的四个像素,他们的偏导数都将会是一样。这两个函数需要Shader Model 5的支持[4]

其他相关函数:fwidth(p) = abs(ddx(p)) + abs(ddy(p))

参考

[1] What are screen space derivatives and when would I use them?
[2] An introduction to shader derivative functions | A Clockwork Berry
[3] What does ddx (hlsl) actually do?
[4] A trip through the Graphics Pipeline 2011, part 8
[5] [CMU 15462] Lecture 7: PerspectiveTexture
[6] Flat and Wireframe Shading Derivatives and Geometry
[7] More compute shaders
[8] [GPU Gems 2] 35.2.4 The Swizzle Operator
[9] [Reading Between The Threads: Shader Intrinsics] Fragment Quad Swizzle - Data Exchange and Arithmetic
[10] [AMD GCN Assembly: Cross-Lane Operations] The Swizzle Instruction