Unity shader 中ddx/ddy偏导数的原理和简单应用

转自 https://blog.csdn.net/wylionheart/article/details/78026707
转自 https://blog.csdn.net/pizi0475/article/details/53931954

最近发现一篇对shader中ddx,ddy讲解的比较清楚的一篇文章,这里对其做个简单的翻译和总结。
连接:http://www.aclockworkberry.com/shader-derivative-functions/#footnote_3_1104

偏导函数,分为 HLSL : ddx 和 ddy , GLSL : dFdx 和 dFdy,分别对应 x, y 轴上,在屏幕空间中,像素块中各种变量的变化率。

偏导数的计算:

在数学上:导数就是函数的变化率。几何意义是固定面上一点的斜率。
对X求导,就意味着把X看做一个数,Y是一个函数,求导的时候,X的导数等于1,Y的导数为Y’,通常用这样的办法求出Y’。

看了下面的图就可以更加清晰是怎么一回事了:
这里写图片描述

我们知道在光栅化的时刻,GPUs会在同一时刻并行运行很多Fragment Shader,但是并不是一个pixel一个pixel去执行的,而是将其组织在2x2的一组pixels分块中,去并行执行。而偏导数就正好是计算的这一块像素中的变化率。从上图可以看出来ddx 就是右边的像素块的值减去左边像素块的值,而ddy就是下面像素块的值减去上面像素块的值。其中的x,y代表的是屏幕坐标。

注意:偏导数ddx/y可以计算我们FragmentShader中任意的变量。向量,矩阵等等。

看看几个偏导数的应用:

1. Mipmap:(对UV求偏导的应用)

这里写图片描述

大家应该都知道mipmap 的用处,但是可能并不知道mipmap的核心在选择到底用那一块mipmap的level时,靠的就是偏导数。屏幕空间的贴图UV偏导数过大的时候代表贴图离我们过远,就会选择低等级的mipmap。

2. Flat Shader(对顶点做偏导的应用)

通过对顶点的偏导数,就可以实现简单的顶点作色效果。

而且不用顶点传入法线,也能求出模型的法线,原理如下:

  • VertexShader,将顶点的Pos传入到FragmentShader中;

  • 在FragShader中,我们如果调用ddx(Pos),和ddy(Pos)这个代表求出相邻的2个像素块之间坐标的差值,即下面图中的红色和绿色2个矢量,而这2个矢量都在这个三角形的平面上,那么执行 normalize( cross(ddx(pos),ddy(pos)) ) 就求出的面的法线,但是这里要注意,在HlSL上面,或者Unity上面要写成normalize( cross(ddy(pos),ddx(pos)) ),不然法线是反向的。这个是由于左右手坐标系引起的。

这里写图片描述

这里写图片描述
(demo made by webgl using three.js)

Unity里面显示法线如下效果:

void surf (Input IN, inout SurfaceOutput o) {
    o.Albedo = normalize(cross(ddy(IN.worldPos),ddx(IN.worldPos)));
}

这里写图片描述

3. 贴图勾边锐化(对贴图颜色求偏导的例子)

在Unity里面测试

void surf (Input IN, inout SurfaceOutput o) {
    half4 c = tex2D(_MainTex, IN.uv_MainTex);
    //c += ddx(c)*2 + ddy(c)*2; //这行代码开启和关闭的效果
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}

这里写图片描述这里写图片描述

左边是直接显示图片,右边是在图片上面加上x和y的偏导数。

不知道这个有没有那个大侠做个这方面的应用哈,可以留言,大家一起学习学习:)

在原贴中,还讲解了偏导数在GPU 处理条件语句的情况下的一些情况和2x2分块的一些细节,大家可以原贴去看下哈。

4. ddx/ddy重建法线出现的edge artifacts问题

其中,artifacts 可以翻译成矫饰、伪色、瑕疵,指图像里正常情况下不该出现的信息。

经验证,原来ddx/ddy这两个操作,在forward rendering与deferred rendering中存在着微妙的应用区别。

在forward rendering中,GPU shader会自动地判断其2x2像素区域是否仅有部分落在当前绘制的三角面所覆盖的光栅化interpolate范围内。

而在dr中,当将ddx/ddy操作应用于一个render target(即NDC quad)时,GPU shader这一免费的“合法性校验”操作便失效了。用于计算ddx/ddy的2x2像素区域有可能一部分位于模型的三角面A、而另一部分则位于模型的三角面B。也就是说:参与ddx/ddy运算的像素,有可能超出了模型中同一三角面的插值范围,从而导致ddx/ddy得到错误的结果,进而导致模型edge上的artifacts。这一问题在dr中使用像素world(或view)坐标重建几何法线时(normalize(cross(ddx(posW), ddy(posW)))),尤为突出。

总结:ddx/ddy 与 forward rendering的兼容性更佳。使用ddx/ddy,切记一定要确保其2x2区域位于同一三角面的光栅化范围内,不能跨三角面。在deferred rendering中,GPU shader不会自动地保障上述前提成立,所以没有引入其他额外机制的前提下,宜避免使用ddx/ddy计算几何法线。

  • forward rendering,使用ddx/ddy计算法线,注意到不存在edge artifacts
    这里写图片描述

  • deferred rendering,使用ddx/ddy重建法线,注意到edge上存在artifacts
    这里写图片描述

  • 4
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Unity3D使用C#计算偏导数可以通过数值方法或者符号方法来实现。 数值方法是一种逼近计算的方式,通过在变量的微小变化范围内进行计算来估计偏导数。这种方法比较简单,但精度较低。 符号方法是通过对函数进行符号化的处理来计算偏导数。这种方法可以得到精确的结果,但实现起来比较复杂,需要对函数进行符号化处理。 下面是一个使用数值方法计算二维函数偏导数的示例代码: ```csharp using UnityEngine; public class DerivativeExample : MonoBehaviour { public float Delta = 0.001f; private float Function(float x, float y) { // 二维函数示例:f(x, y) = x^2 + 2*y return x * x + 2 * y; } private float PartialDerivativeX(float x, float y) { float fx1 = Function(x + Delta, y); float fx2 = Function(x - Delta, y); return (fx1 - fx2) / (2 * Delta); } private float PartialDerivativeY(float x, float y) { float fy1 = Function(x, y + Delta); float fy2 = Function(x, y - Delta); return (fy1 - fy2) / (2 * Delta); } private void Start() { float x = 1f; float y = 2f; float dx = PartialDerivativeX(x, y); float dy = PartialDerivativeY(x, y); Debug.Log("Partial derivative with respect to x: " + dx); Debug.Log("Partial derivative with respect to y: " + dy); } } ``` 请注意,这只是一个简单的示例,可以根据需要进行调整和扩展。使用数值方法计算偏导数时,需要选择适当的微小变化范围(Delta),以便在精度和计算效率之间进行平衡。 希望以上信息能对你有所帮助!如有其他问题,请随时提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值