OpenGL如何准确地透视校正线性插值?

OpenGL如何准确地透视校正线性插值?,opengl,graphics,projection,fragment-shader,linear-interpolation,Opengl,Graphics,Projection,Fragment Shader,Linear Interpolation,如果在OpenGL管道中的光栅化阶段发生线性插值,并且顶点已经转换到屏幕空间,那么用于透视正确插值的深度信息来自哪里 有人能详细描述OpenGL如何从屏幕空间原语到具有正确插值的片段吗?顶点着色器的输出是一个四分量向量,vec4 gl\u Position。根据核心GL 4.4规范第13.6节坐标变换: 顶点的剪辑坐标是着色器执行的结果,这将生成顶点坐标gl\u位置 剪辑坐标上的透视分割产生标准化设备坐标,然后进行视口转换(见第13.6.1节),将这些坐标转换为窗口坐标 OpenGL将透视图划分

如果在OpenGL管道中的光栅化阶段发生线性插值,并且顶点已经转换到屏幕空间,那么用于透视正确插值的深度信息来自哪里


有人能详细描述OpenGL如何从屏幕空间原语到具有正确插值的片段吗?

顶点着色器的输出是一个四分量向量,
vec4 gl\u Position
。根据核心GL 4.4规范第13.6节坐标变换:

顶点的剪辑坐标是着色器执行的结果,这将生成顶点坐标
gl\u位置

剪辑坐标上的透视分割产生标准化设备坐标,然后进行视口转换(见第13.6.1节),将这些坐标转换为窗口坐标

OpenGL将透视图划分为

device.xyz = gl_Position.xyz / gl_Position.w
然后将
1/gl\u位置.w
保留为
gl\u FragCoord
的最后一个组件:

gl_FragCoord.xyz = device.xyz scaled to viewport
gl_FragCoord.w = 1 / gl_Position.w
此转换是双射的,因此不会丢失深度信息。事实上,正如我们在下面看到的,
1/gl\u位置.w
对于透视正确插值至关重要


重心坐标简介 给定一个三角形(P0、P1、P2),可以通过顶点的线性组合来参数化三角形内的所有点:

P(b0,b1,b2) = P0*b0 + P1*b1 + P2*b2
其中b0+b1+b2=1和b0≥ 0,b1≥ 0,b2≥ 0

给定三角形内的点p,满足上述方程的系数(b0、b1、b2)称为该点的重心坐标。对于非退化三角形,它们是唯一的,可以计算为以下三角形面积的商:

b0(P) = area(P, P1, P2) / area(P0, P1, P2)
b1(P) = area(P0, P, P2) / area(P0, P1, P2)
b2(P) = area(P0, P1, P) / area(P0, P1, P2)
每个bi都可以被认为是“必须混合多少Pi”。所以b=(1,0,0),(0,1,0)和(0,0,1)是三角形的顶点,(1/3,1/3,1/3)是重心,依此类推

给定三角形顶点上的属性(f0、f1、f2),我们现在可以在内部对其进行插值:

f(P) = f0*b0(P) + f1*b1(P) + f2*b2(P)
这是p的线性函数,因此它是给定三角形上唯一的线性插值。数学也适用于二维或三维

透视校正插值 假设我们在屏幕上填充一个投影的2D三角形。对于每个碎片,我们都有它的窗口坐标。首先,我们通过反转
P(b0,b1,b2)
函数来计算其重心坐标,这是窗口坐标中的线性函数。这为我们提供了2D三角形投影上碎片的重心坐标

属性的透视正确插值将在剪辑坐标(以及延伸到世界坐标)中线性变化。为此,我们需要得到片段在片段空间中的重心坐标

正如所发生的那样(请参见和),碎片的深度在窗口坐标中不是线性的,但深度反转(
1/gl_Position.w
)是线性的。因此,属性和剪辑空间重心坐标在通过深度逆加权时在窗口坐标中线性变化

因此,我们通过以下公式计算透视校正重心:

     ( b0 / gl_Position[0].w, b1 / gl_Position[1].w, b2 / gl_Position[2].w )
B = -------------------------------------------------------------------------
      b0 / gl_Position[0].w + b1 / gl_Position[1].w + b2 / gl_Position[2].w
然后使用它从顶点插值属性

注意:通过
gl_-BaryCoordNoPerspNV
显示设备线性重心坐标,并通过
gl_-barycoordv
校正透视图

实施

这是一个C++代码,它以类似OpenGL的方式在CPU上光栅化和阴影三角形。我鼓励您将其与下面列出的着色器进行比较:

struct Renderbuffer{
int w,h,ys;
作废*数据;
};
结构垂直{
vec4f位置;
vec4f texcoord;
vec4f颜色;
};
结构变化{
vec4f texcoord;
vec4f颜色;
};
void vertex_着色器(const Vert&in、vec4f&gl_位置、变化和输出)
{
out.texcoord=in.texcoord;
out.color=in.color;
gl_Position={in.Position[0],in.Position[1],-2*in.Position[2]-2*in.Position[3],-in.Position[2]};
}
void fragment_着色器(vec4f和gl_FragCoord、常数变化和输入、vec4f和输出)
{
out=in.color;
vec2f wrapped=vec2f(in.texcoord-地板(in.texcoord));
bool-brighter=(包裹的[0]<0.5)!=(包裹的[1]<0.5);
如果(!更亮)
(vec3f&)out=0.5f*(vec3f&)out;
}
无效存储颜色(渲染缓冲区和缓冲区、整数x、整数y、常量向量4f&c)
{
//你能在这里做阿尔法合成吗
uint8_t*p=(uint8_t*)buf.data+buf.ys*(buf.h-y-1)+4*x;
p[0]=线性的μ到μsrgb8(c[0]);
p[1]=线性的μ到μsrgb8(c[1]);
p[2]=线性的μ到μsrgb8(c[2]);
p[3]=lrint(c[3]*255);
}
空心绘制三角形(渲染缓冲和颜色附件、常量框2F和视口、常量顶点*顶点)
{
变异性变态[3];
vec4f gl_位置[3];
box2f aabbf={viewport.hi,viewport.lo};
对于(int i=0;i<3;++i)
{
//调用顶点着色器
顶点着色器(顶点[i]、gl_位置[i]、perVertex[i]);
//通过透视分割转换为设备坐标
gl_位置[i][3]=1/gl_位置[i][3];
gl_位置[i][0]*=gl_位置[i][3];
gl_位置[i][1]*=gl_位置[i][3];
gl_位置[i][2]*=gl_位置[i][3];
//转换为窗口坐标
自动位置2=(vec2f&)gl_位置[i];
pos2=混合(viewport.lo,viewport.hi,0.5f*(pos2+vec2f(1));
aabbf=连接(aabbf,(const vec2f&)gl_位置[i]);
}
//预计算碎片坐标到重心坐标的仿射变换
常量浮点数=1/((gl_位置[0][0]-gl_位置[2][0])*(gl_位置[1][1]-gl_位置[0][1])-(gl_位置[0][0]-gl_位置[1][0])*(gl_位置[2][1]-gl_位置[0][1]);
常量向量3f重心向量d0=denom*vec3f(gl_位置[1][1]-gl_位置[2][1],gl_位置[2][1]-gl_位置[0][1],gl_位置[0][1]-gl_位置[1]
   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' * f_a + b' * f_b + c' * f_c
f=------------------------------
           a' + b' + c'