如果在OpenGL管道中的光栅化阶段发生线性插值,并且顶点已经转换为屏幕空间,那么用于透视正确插值的深度信息来自何处?
任何人都可以详细描述OpenGL如何从屏幕空间原语转换为具有正确插值的片段吗?
顶点着色器的输出是四分量矢量 vec4 gl_Position . 从第13.6节核心GL 4.4规范的坐标转换:
vec4 gl_Position
来自着色器执行的顶点结果的剪辑坐标,其产生顶点坐标gl_Position . 剪辑坐标上的透视分割产生标准化的设备坐标,然后是视口变换(参见第13.6.1节),将这些坐标转换为窗口坐标 .
考虑具有±1的左,上,右,下和近剪裁平面的典型投影矩阵 . 看起来像:
[ 1 0 0 0 ] [ 0 1 0 0 ] P = [ * * * * ] [ 0 0 -1 0 ]
我们忽略第三行,因为它仅用于计算z缓冲区的深度值,并且与其余讨论无关 .
给定世界空间中的顶点(x,y,z,1),顶点着色器将传递该值
gl_Position = P * vec4(x, y, z, 1) = vec4(x, y, *, -z)
进入光栅化阶段 . 这是透视信息的来源:它位于 gl_Position 的最后一个组件中!请注意,顶点着色器不负责进行透视分割 . 如果这样做,您将得到不正确的插值 .
gl_Position
让我们考虑通过给定三角形的平面 . 我们给它一个参数化(s,t):
x = s*x0 + t*x1 + x2 y = s*y0 + t*y1 + y2 z = s*z0 + t*z1 + z2
设 u = -x/z , v = -y/z 为设备坐标 . 替换x,y,z的表达式并求解(s,t) . 表示 w = -1/z 并替换z,s和t . 如果我们都没有犯错误,你应该得到
u = -x/z
v = -y/z
w = -1/z
(y0*z1 - z0*y1)*u + (z0*x1 - x0*z1)*v + (x0*y1 - y0*x1) w = -1 * -------------------------------------------------------------- . (y0*z1 - z0*y1)*x2 + (z0*x1 - x0*z1)*y2 + (x0*y1 - y0*x1)*z2
我们得到的逆深度(w)与设备坐标(u,v)相关 . 因此,我们可以在三角形的顶点计算w,并在内部线性插值 .
接下来,我们想要插入一些任意属性 . 显然,计算每个片段的(s,t)参数化就足够了 .
求解(s,t)得到(s,t)得到:
(x - x2)*y1 - x1*(y - y2) s = --------------------------- x0*y1 - x1*y0 x0*(y - y2) - (x - x2)*y0 t = --------------------------- x0*y1 - x1*y0
其中 (x,y) = (u,v)/w 来自定义 . 因此,我们将(s,t)表示为(u,v)的函数,每个片段有一个除法,并且有许多muls和add .
(x,y) = (u,v)/w
请注意,这是理论部分 . 现实生活中的硬件实现可能会做很多近似,技巧和其他魔术 .
为简单起见,假设视口转换是一个标识,因此窗口坐标与标准化设备坐标一致 .
让我们考虑单个三角形的光栅化,顶点着色器返回位置P0,P1,P2(这是它们的 gl_Position )和一些我们必须正确插值的属性A0,A1,A2 .
表示 Qi = (Pi.x, Pi.y, -Pi.w) .
Qi = (Pi.x, Pi.y, -Pi.w)
计算
(x0, y0, z0) = Q0 - Q2 (x1, y1, z1) = Q1 - Q2 (x2, y2, z2) = Q2 .
(每个三角形执行一次 . )
Ui = Pi.x / Pi.w Vi = Pi.y / Pi.w
这使它在屏幕上的位置 .
使用vetices(U0,V0),(U1,V1),(U2,V2)栅格化二维三角形,生成一组位于三角形内的片段位置(u,v) .
对于上述光栅化生成的每个片段(u,v),执行以下操作:
在上一节的公式中替换(u,v),给出片段的(s,t) .
(s,t)在世界空间中线性变化(P2处为(0,0),P0处为(1,0),P1处为(0,1)),并且在屏幕空间中透视正确 .
A = s*(A0 - A2) + t*(A1 - A2) + A2
您将在GL specification中找到的公式(请参见第427页;链接是当前的4.4规范,但一直是这样)对于三角形中属性值的透视校正插值是:
a * f_a / w_a + b * f_b / w_b + c * f_c / w_c f=----------------------------------------------------- a / w_a + b / w_b + c / w_c
其中 a,b,c 表示三角形中点的重心坐标,我们为( a,b,c >=0, a+b+c = 1 ), f_i 顶点 i 处的属性值插值, w_i 顶点 i 的剪辑空间 w 坐标 . 注意,重心坐标仅针对三角形的窗口空间坐标的2D投影计算(因此z被忽略) .
a,b,c
a,b,c >=0, a+b+c = 1
f_i
i
w_i
w
这就是ybungalowbill在他的精细答案中给出的公式,在一般情况下,归结为具有任意投影轴 . 实际上,投影矩阵的最后一行仅定义了图像平面将与之正交的投影轴,并且剪辑空间 w 分量只是顶点坐标与该轴之间的点积 .
在典型的情况下,投影矩阵具有(0,0,-1,0)作为最后一行,因此它转换为 w_clip = -z_eye ,这就是ybungalowbill所使用的 . 但是,因为 w 是我们实际将要做的除法(这是唯一的整个转换链中的非线性步骤),这适用于任何投影轴 . 它也适用于正交投影的平凡情况,其中 w 始终为1(或至少是常数) .
w_clip = -z_eye
注意一些有效实施的事情 . 可以根据每个顶点预先计算反转 1/w_i (让我们在下面将它们称为 q_i ),不必为每个片段重新计算它 . 它完全免费,因为无论如何我们除了 w 进入NDC空间,所以我们可以保存这个值 . GL规范从未描述过如何在内部实现某个特征,但是屏幕空间坐标可以在 glFragCoord.xyz 中访问,并且 gl_FragCoord.w 保证给出(lineariliy interpolated) 1/w 剪辑空间坐标这一事实在这里很有启发性 . 每个片段 1_w 值实际上是上面给出的公式的分母 .
1/w_i
q_i
glFragCoord.xyz
gl_FragCoord.w
1/w
1_w
因子 a/w_a , b/w_b 和 c/w_c 在公式中分别使用了两次 . 对于任何属性值,这些也是常量,现在要插入多少属性 . 所以,每个片段,你可以计算 a'=q_a * a , b'=q_b * b 和 c'=q_c 并得到
a/w_a
b/w_b
c/w_c
a'=q_a * a
b'=q_b * b
c'=q_c
a' * f_a + b' * f_b + c' * f_c f=------------------------------ a' + b' + c'
所以透视插值归结为
3次额外乘法,
2个额外的补充,和
1个额外师
每个片段 .
2 回答
顶点着色器的输出是四分量矢量
vec4 gl_Position
. 从第13.6节核心GL 4.4规范的坐标转换:考虑具有±1的左,上,右,下和近剪裁平面的典型投影矩阵 . 看起来像:
我们忽略第三行,因为它仅用于计算z缓冲区的深度值,并且与其余讨论无关 .
给定世界空间中的顶点(x,y,z,1),顶点着色器将传递该值
进入光栅化阶段 . 这是透视信息的来源:它位于
gl_Position
的最后一个组件中!请注意,顶点着色器不负责进行透视分割 . 如果这样做,您将得到不正确的插值 .如何计算正确的透视插值?
让我们考虑通过给定三角形的平面 . 我们给它一个参数化(s,t):
设
u = -x/z
,v = -y/z
为设备坐标 . 替换x,y,z的表达式并求解(s,t) . 表示w = -1/z
并替换z,s和t . 如果我们都没有犯错误,你应该得到我们得到的逆深度(w)与设备坐标(u,v)相关 . 因此,我们可以在三角形的顶点计算w,并在内部线性插值 .
接下来,我们想要插入一些任意属性 . 显然,计算每个片段的(s,t)参数化就足够了 .
求解(s,t)得到(s,t)得到:
其中
(x,y) = (u,v)/w
来自定义 . 因此,我们将(s,t)表示为(u,v)的函数,每个片段有一个除法,并且有许多muls和add .请注意,这是理论部分 . 现实生活中的硬件实现可能会做很多近似,技巧和其他魔术 .
把它们放在一起
为简单起见,假设视口转换是一个标识,因此窗口坐标与标准化设备坐标一致 .
让我们考虑单个三角形的光栅化,顶点着色器返回位置P0,P1,P2(这是它们的
gl_Position
)和一些我们必须正确插值的属性A0,A1,A2 .表示
Qi = (Pi.x, Pi.y, -Pi.w)
.计算
(每个三角形执行一次 . )
这使它在屏幕上的位置 .
使用vetices(U0,V0),(U1,V1),(U2,V2)栅格化二维三角形,生成一组位于三角形内的片段位置(u,v) .
对于上述光栅化生成的每个片段(u,v),执行以下操作:
在上一节的公式中替换(u,v),给出片段的(s,t) .
(s,t)在世界空间中线性变化(P2处为(0,0),P0处为(1,0),P1处为(0,1)),并且在屏幕空间中透视正确 .
计算
您将在GL specification中找到的公式(请参见第427页;链接是当前的4.4规范,但一直是这样)对于三角形中属性值的透视校正插值是:
其中
a,b,c
表示三角形中点的重心坐标,我们为(a,b,c >=0, a+b+c = 1
),f_i
顶点i
处的属性值插值,w_i
顶点i
的剪辑空间w
坐标 . 注意,重心坐标仅针对三角形的窗口空间坐标的2D投影计算(因此z被忽略) .这就是ybungalowbill在他的精细答案中给出的公式,在一般情况下,归结为具有任意投影轴 . 实际上,投影矩阵的最后一行仅定义了图像平面将与之正交的投影轴,并且剪辑空间
w
分量只是顶点坐标与该轴之间的点积 .在典型的情况下,投影矩阵具有(0,0,-1,0)作为最后一行,因此它转换为
w_clip = -z_eye
,这就是ybungalowbill所使用的 . 但是,因为w
是我们实际将要做的除法(这是唯一的整个转换链中的非线性步骤),这适用于任何投影轴 . 它也适用于正交投影的平凡情况,其中w
始终为1(或至少是常数) .注意一些有效实施的事情 . 可以根据每个顶点预先计算反转
1/w_i
(让我们在下面将它们称为q_i
),不必为每个片段重新计算它 . 它完全免费,因为无论如何我们除了w
进入NDC空间,所以我们可以保存这个值 . GL规范从未描述过如何在内部实现某个特征,但是屏幕空间坐标可以在glFragCoord.xyz
中访问,并且gl_FragCoord.w
保证给出(lineariliy interpolated)1/w
剪辑空间坐标这一事实在这里很有启发性 . 每个片段1_w
值实际上是上面给出的公式的分母 .因子
a/w_a
,b/w_b
和c/w_c
在公式中分别使用了两次 . 对于任何属性值,这些也是常量,现在要插入多少属性 . 所以,每个片段,你可以计算a'=q_a * a
,b'=q_b * b
和c'=q_c
并得到所以透视插值归结为
3次额外乘法,
2个额外的补充,和
1个额外师
每个片段 .