基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十八)深度测试

Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/01%20Depth%20testing/ 关于深度测试的详细知识了解请看原教程,本篇旨在对Vires基于visual studio平台的编程思想与c++代码做纯Qt平台的移植,重在记录自身学习之用)

程序源代码链接:https://pan.baidu.com/s/1IiVLsr5PB7FGpuvVL7HVzg   提取码:soln

编译环境:Qt5.9

编译器:MSVC

IDE:QtCreator

一.啥是个深度测试

  最简单的莫过于对同一个场景做一个

  glEnable(GL_DEPTH_TEST); //深度测试

  开启与关闭的对比。

开启效果:

关闭效果:

差别在于,深度测试开启后,渲染效果有了空间立体感,各物体可以很明显感知到前后左右的位置关系。

关闭后,一切的渲染效果按照各物体的绘制顺序展现。比如淡蓝色的河流流域是最后绘画的,不管从哪个视角看,河流永远在前面。

所以深度测试与glViewport()视口函数相关。需要确定各个物体的片段谁前谁后的关系。需要根据视角观察位置,记录所有片段与视角位置的深度关系。这个记录工具称为深度缓冲。每绘制一个新物体,就将这个物体片段的深度信息"a"与深度缓冲已存储的深度"b"做对比,如果a<b, 说明a在b的前面,则绘制a,并更新深度缓冲。

所以每一次视角观察位置的变化,都要重新清理一次深度缓冲,

为方便起见,每一帧的渲染都对深度缓冲做一个清理重设,用以下函数

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  

二.深度测试函数

  当然我们可以控制深度测试的比较函数,确定什么情况通过测试,只有通过测试的片段才能被渲染。

  我们可以修改这个比较规则,默认规则是当片段的深度值小于深度缓冲中的存储值时,测试通过。即

   glDepthFunc(GL_LESS);

像Vries一样,我们搭一个小场景。两个立方体,一个平面。

选择默认比较函数  

  glDepthFunc(GL_LESS);

场景正常显示。

选择比较函数GL_ALWAYS, 所有片段永远通过深度测试,则展示顺序按程序中片段的绘制顺序来。

  glDepthFunc(GL_ALWAYS);

平面是最后被绘制的,所以永远在前。  

三.深度值精度

  这里解释一下,每个像素的深度缓冲是一个介于0.0到1.0之间的浮点数值,场景中物体每个片段的深度应同样换算至这个范围内,再进行比较。一种简单的换算方法是线性换算

  near与far是投射投影perspective的平截面体中近平面与远平面的位置。

  这样,物体的z值与near=1,far=50平截面体的深度是这样的一种关系。

  但实际中,永远不会出现这样的线性插值!!!因为要保证距离摄像头较近范围的精度问题,所以我们要假设深度与1/z成某种正比关系,距离摄像头近的范围,精度大,远的精度小。简单假设一个深度计算公式:

这是一种深度与z值的非线性关系,当z值在1~10之间时,深度值在0~0.9之间,距离较近的地方,深度计算会更加细致精细。这就给了近处的物体很大的深度精度,因为我们没必要给远处的精度与近处一样。

当然,这就会产生一个问题:

  深度缓冲中的0.5并不代表,这个片段的z值在近远平面之间,而是非常接近近平面!!!

四.深度缓冲的可视化

  片段着色器中的内建变量gl_FragCoord.z包含了该片段的深度值,当然是非线性的深度值,近处精度高,远处精度小。

  因为这个变量的值介于0.0~1.0之间,所以我们可以直接将其输出看效果

  新建箱子cube与平面plane的非线性着色器:

  cube_nonLinear.vert

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main(){
  gl_Position = projection * view * model * vec4(aPos, 1.0f);

}

  cube_nonLinear.frag

#version 330 core
out vec4 FragColor;

void main(){
  FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}

看效果,整个界面基本都是白色的,为什么呢,因为我们假设的perspective的平截面体的near是0.1f, far是100.0f

而非线性深度函数对近平面的精度非常高,且整个场景据视角观察位置非常近,所以绝大多数片段的深度值大于0.9,

而rgb(0.9f, 0.9f, 0.9f)已经非常接近白色了,所以整个场景基本就是白色的。

只有非常接近视角位置的片段深度值才会小,才会接近黑色

  我们在将非线性深度转换为线性深度,观察颜色变化:

cube_linear.vert

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main(){
  gl_Position = projection * view * model * vec4(aPos, 1.0f);

}

cube_linear.frag

#version 330 core
out vec4 FragColor;

uniform float near;
uniform float far;

float LinearizeDepth(float depth){
  float z = depth * 2.0 - 1.0; // back to NDC
  return (2.0 * near * far) / (far + near - z * (far - near));
}

void main(){
  float depth = LinearizeDepth(gl_FragCoord.z) / far; // 为了演示除以 far
  FragColor = vec4(vec3(depth), 1.0);
}

转换公式看这篇文章,我没看懂。。。。。

http://www.songho.ca/opengl/gl_projectionmatrix.html

这次整个场景偏黑色,我们继续分析,整个场景据观察位置较近,就上图所示的观察角度而言,距离大致在0~10之间,而perspective的平截面体是一个近平面为0.1f,远平面为100.0f的几何。所以场景中各片段的深度值在0.0~0.1之间,

而rgb(0.1f, 0.1f, 0.1f)也是一个近黑色的颜色,故整个场景近黑色。

当我们拉远视角,颜色就会变白一些,因为深度值更大了,如下图所示:

五.深度冲突

  在最后,Vries还提了一个非常有趣且常见的现象,当我们进入箱子的内部,会发现有明显的锯齿状波纹若隐若现,这是因为箱子的底面与plane平面处于相同位置,OpenGL不知道要显示谁,造成了冲突。这种冲突也会出现在距离观察视角较远的地方,因为远处的片段深度精度较低,更容易出现深度冲突。

有三种方法可以解决深度冲突问题:

1.不要将各物体摆的太近,就上图所示的冲突,将箱子抬起一点点的高度,比如0.0001f,原则上就可以解决冲突问题。

2.因为越靠近perspective的平截面体近平面的片段,深度精度越高,所以可以把近平面设远一些,但这时会出现距离观察位置较近的一些片段无法显示,这个度需要根据需求自己平衡。

3.选用更高精度的深度缓冲,深度缓冲默认是24位的,可以手动选择32位进行深度存储,当然这会造成空间浪费。

最后,仅针对上图问题,因为箱子地面与plane平面是重合的,不是深度精度不够的问题,所以仅第一种解决方法有效。

猜你喜欢

转载自blog.csdn.net/z136411501/article/details/83184530