参考
参考博客1
参考博客2
这个问题之前好长时间都没有弄明白,主要原因是没有搞清为什么需要这个方法,今天静下心来仔细看了几篇资料终于想清楚了。这篇博客也主要讲我对透视校正插值前因后果的理解,具体的数学推导可以看上面的参考文章。
在光栅化阶段我们需要对顶点坐标和定义在顶点上的属性(法线、uv等等)进行插值,产生一系列的片元以及定义在片元上的属性。现在有点A(XA,YA,ZA)和点B(XB,YB,ZB),以及定义在A上的属性IA和B上的属性IB。现在AB上有一点C(XC,YC,ZC),需要求得C上的属性IC。线性插值的方法就是通过ABC的坐标求出插值系数s,即C=s*A+(1-s)*B,那么IC=s*IA+(1-s)*IB。这里的I可以是任何需要插值的顶点属性(顶点着色器的输出),如颜色、法线、纹理坐标等等。
这种插值方法显然在线性空间中是正确的,但我们的问题是当需要对顶点属性进行插值时,我们已经进入了光栅化阶段,早就将坐标转化到非线性空间中了,这时如果直接用线性插值就会出问题。
先复习一下管线中的部分坐标变换流程。
model view projection 齐次除法+裁剪 屏幕映射 模型空间 世界空间 相机空间 裁剪空间 裁剪空间 NDC 屏幕空间 光栅化阶段注意projection矩阵在透视投影下是一个非线性变换矩阵,也就是裁剪空间、NDC、屏幕空间中的坐标都是非线性空间下的坐标。如果用非线性空间的坐标来计算插值比s,使用s对顶点属性插值就会得到错误的结果。
常见的例子就是屏幕上一条线段的中点,往往在实际的场景中并非是真正的中点。如下图在投影平面上c是ab的中点,但实际上C在线段AB中的位置较靠近B。设顶点属性IA=0,IB=1,我们希望插值所得的IC大概是0.7左右,但如果我们如果直接用线性插值来计算C的属性就只能得到IC=0.5的错误结果。
透视校正插值使用线性的深度值和非线性空间下的插值系数s来计算出线性空间下的插值系数t,并用t来对顶点属性进行插值。说的明白点,在上图中就相当于我们需要知道点A和点B距离投影平面的距离(线性深度),以及投影面上线段ac和bc的长度比(非线性空间下的插值系数,在上图例子中就是1:1),然后我们就可以经过一系列数学推导得到真实场景中AC和BC长度的比值,通过这个比值我们就可以进行正确的线性插值得到C的属性了。
【下面这段仅供参考,不能保证准确】
在光栅化阶段中我们是如何访问到线性空间下的深度值的呢?注意我们要的是线性的深度值,在光栅化阶段,顶点坐标的Z分量虽然记录了顶点的深度值,但并不是一个线性的深度值,它维持了线性深度的大小关系来保证深度检测正常,但对我们的透视矫正插值并没有帮助。这里我们就要注意顶点坐标的w分量了。我们将相机空间的坐标记为(xview,yview,zview,wview),裁剪空间下的坐标记为(xclip,yclip,zclip,wclip),根据projection矩阵可知:wclip=-zview,相当于裁剪空间坐标的w分量记录了相机空间下顶点的深度值,而相机空间下的深度是线性的。wclip最重要的作用是在齐次除法中担任分母,而在做完齐次除法后这个分量并没有被丢弃,而是保留下来供透视校正插值使用。所以,在光栅化阶段顶点坐标的xyz分量都进入了非线性空间时,W分量仍保留了顶点的线性深度。
我们设点A的深度为Z1,点B的深度为Z2。C为AB上的插值点,投影面上的插值比为s。我们的目标根据Z1、Z2、s来求得线性空间下的插值比t,再根据t对顶点属性进行插值,即存在一个函数f使得It=f(Z1,Z2,s,I1,I2)。
数学推导过程参考这篇博客。最后得出的插值公式如下: