关于透视插值 Vertex -> Frag

Posted by FlowingCrescent on 2021-08-11
Estimated Reading Time 2 Minutes
Words 628 In Total
Viewed Times

问题起因

最近我有一位朋友研究Colin的Screen Space Decal时,对其中的一个部分表示非常不解:

1
2
3
4
5
6
7
//in vertex shader :
o.viewRayOS.w = viewRay.z;
o.viewRayOS.xyz = mul((float3x3)ViewToObjectMatrix, viewRay);
.....

//in fragment shader :
i.viewRayOS /= i.viewRayOS.w;

他很想知道为什么i.viewRayOS /= i.viewRayOS.w这一步不能在vertex shader中做,如果改在vertex中做就会出错。

如果在Vertex Shader中除去w值,则会与期望情况产生误差,在此我将误差可视化

1
2
3
4
5
6
7
8
9
10
//in vertex shader :
//错误的做法
o.viewRayOSDiv = float4(o.viewRayOS.xyz/o.viewRayOS.w, 1);


//in fragment shader :
//正确的做法
i.viewRayOS /= i.viewRayOS.w;
float errorValue = length(i.viewRayOS.xyz - i.viewRayOSDiv.xyz) * 10;
return float4(errorValue, errorValue, errorValue, 1);

结果如图:
image.png
可见当frag在顶点位置时计算结果是一致的,但越远离顶点,误差可能越大。

诶,那我如果不是除w,而是除以一个定值呢?

1
2
3
4
5
6
7
8
//in vertex shader :
o.viewRayOSDiv = float4(o.viewRayOS.xyz/10, 1);


//in fragment shader :
i.viewRayOS /= 10;
float errorValue = length(i.viewRayOS.xyz - i.viewRayOSDiv.xyz) * 10;
return float4(errorValue, errorValue, errorValue, 1);

image.png
嗯,没有误差。

那么,为什么除以w不行,而除以一个定值就没问题呢?

透视校正插值

一个值从顶点着色器到片元着色器之间经历了什么?没错,就是光栅化,而光栅化会让一个值经过透视校正插值
具体可参考此文:https://zhuanlan.zhihu.com/p/144331875
image.png

直接使用三角形屏幕空间坐标进行重心坐标插值会出现错误

image.png
很显然,这个变换是非线性的,那么先做除法还是后做除法会导致结果不一样……但这样解释大概还是有些难懂。

那么我们将计算简化后,自己代入值计算看看:
假设X,Y是向量值,ZA,ZB为两个顶点的深度,w则与代码中含义相同。
image.png
左边为在frag中进行除法获得的值,而右边则是在vertex中进行除法获得的值,
很显然两个值并不会相等。

而为什么接近顶点时误差会小也很好解释,
当片元相当接近顶点,则α、β、γ中有一个值为极为接近1,其余两个值则接近0,此时不等式两边会长得极为相似(但只有特殊情况是真正相等,其余情况只是相差较小),读者不妨自己算算看。

而除以一个定值为什么无所谓,也相当好理解了:
image.png
这怎么想都是相等的。


感谢您阅读完本文,若您认为本文对您有所帮助,可以将其分享给其他人;若您发现文章对您的利益产生侵害,请联系作者进行删除。