背景
深度测试 - LearnOpenGL CN 中提到将片段深度值从非线性的变换为线性的。
其核心思路是只反转深度值的投影变换。
步骤
1、我们需要首先将深度值从[0, 1]范围重新变换到[-1, 1]范围的标准化设备坐标(裁剪空间)。
2、接下来我们需要像投影矩阵那样反转这个非线性方程(方程2),并将这个反转的方程应用到最终的深度值上。最终的结果就是一个线性的深度值了。听起来是可行的,对吧?问题
问题
如何理解反转深度值的投影变换?
为什么第一步要变换为标准化设备坐标?
第二步的非线性方程指的是什么?
解决
回答上述问题前,需要先知道
深度值和z值
深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。
观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。
那么就涉及到观察空间的z值和深度值变换。(观察空间的z值:[n,f]->深度值depth:[0.0,1.0])
有两种变换方式:
-
线性变换
-
非线性变换
这个(从观察者的视角)变换z值的非线性方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了。
因为这个非线性变换嵌在投影矩阵中,接下来我们只关注投影变换中z分量的变化。
如果你想深度了解投影矩阵究竟做了什么,我建议阅读这篇文章。
投影变换做了什么
首先,它将所有的视口坐标转(Xe,Ye,Ze)换至裁剪坐标(Xc,Yc,Zc)。
然后这些裁剪坐标还会通过除以裁剪坐标的w分量来转换成标准设备坐标(Xn,Yn,Zn)。
总而言之就是:视口坐标->裁剪坐标->标准设备坐标,Ze->Zc->Zn。
Ze、Zc、Zn的关系
我们直接看透视投影矩阵
得到公式(5)Ze->Zn
如何获取深度值
我们从gl_FragCoord.z里获取的是深度值
The z component is the depth value that would be used for the fragment's depth if no shader contained any writes to gl_FragDepth.
z组件是深度值,将用于片段的深度,如果没有着色器包含任何写入gl_FragDepth。
标准化设备坐标的取值范围是[-1.0,1.0],可是深度值取值范围是[0.0,1.0]。从Zn变成深度值depth时,opengl做了不为我们所感知的变换后直接提供depth给我们。
如何将非线性的转变成线性的
从上述得知,我们在进行观察空间的z值和深度值的非线性变换过程是这样的:Ze->Zc->Zn->depth
-
depth :经过非线性变换的深度值
-
depth':经过线性变换的深度值
如何将这个过程变成 Ze->depth’ ?答案是反转深度值的投影变换,depth->Zn->Zc->Ze->depth’。
完成这个过程,我们需要
-
知道Ze、Zc、Zn的关系(√)
-
获取深度值depth(√)
开始depth->Zn->Zc->Ze->depth’:
depth->Zn
Zn->Ze
- 从公式(5)Ze->Zn,可以得到 Zn->Ze
通过看完整的透视投影矩阵,得知A、B
Ze->depth‘
代码
#version 330 core
out vec4 FragColor;
float near = 0.1;
float far = 100.0;
float LinearizeDepth(float depth)
{
//depth->Zn
float z = depth * 2.0 - 1.0; // back to NDC
//Zn->Ze
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
//Ze->depth'
float depth = (LinearizeDepth(gl_FragCoord.z) -near) / (far-near);
//learnopengl里是这样的,near的值很小,其实减不减near影响也不大,效果上也相差不大
//float depth = LinearizeDepth(gl_FragCoord.z) / far;
FragColor = vec4(vec3(depth), 1.0);
}