第十四章 opengl之高级OpenGL(深度测试)

news2024/11/15 10:10:18

OpenGL

  • 深度测试
  • 深度测试函数
  • 深度值精度
  • 深度缓冲的可视化
  • 深度冲突
    • 防止深度冲突

深度测试

前面我们渲染一个3D图片中运用了深度缓冲:防止被阻挡的面渲染到其他面的前面。
深度缓冲就像颜色缓冲(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。
当深度测试(Depth Testing)被启用的时候**,OpenGL会将一个片段的深度值与深度缓冲的内容进行对比**。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃。
深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后)在屏幕空间中运行的。屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。

补充:
现在大部分的GPU都提供一个叫做提前深度测试 (Early Depth Testing)的 硬件特性提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段
片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们。当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。OpenGL不能提前知道深度值。
深度测试默认是禁用的,所以如果要启用深度测试的话,我们需要用GL_DEPTH_TEST选项来启用它:

glEnable(GL_DEPTH_TEST);

当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则你会仍在使用上一次渲染迭代中的写入的深度值:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲。基本上来说,你在使用一个只读的(Read-only)深度缓冲。OpenGL允许我们禁用深度缓冲的写入,只需要设置它的深度掩码(Depth Mask)设置为GL_FALSE就可以了:

glDepthMask(GL_FALSE);

注:深度测试被启用的时候才有效。

深度测试函数

OpenGL允许我们修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段,什么时候去更新深度缓冲。我们可以调用glDepthFunc函数来设置比较运算符(或者说深度函数(Depth Function)):

glDepthFunc(GL_LESS);

如下:
分为:函数和描述
GL_ALWAYS——永远通过深度测试
GL_NEVER——永远不通过深度测试
GL_LESS——在片段深度值小于缓冲的深度值时通过测试
GL_EQUAL——在片段深度值等于缓冲区的深度值时通过测试
GL_LEQUAL——在片段深度值小于等于缓冲区的深度值时通过测试
GL_GREATER——在片段深度值大于缓冲区的深度值时通过测试
GL_NOTEQUAL——在片段深度值不等于缓冲区的深度值时通过测试
GL_GEQUAL——在片段深度值大于等于缓冲区的深度值时通过测试

默认情况下使用的深度函数是GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
如果在深度函数中使用GL_ALWAYS:

glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);

那么会深度测试永远通过,不会抛弃任何片段,则最后绘制的片段总是会渲染 在之前绘制片段的上面。

深度值精度

深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。下面这个(线性)方程将z值变换到了0.0到1.0之间的深度值:
在这里插入图片描述
公式中的near和far是之前提供给投影矩阵设置可视平截头体的值。该方程需要平截头体的z值,并将其转化为[0,1]范围中。
在这里插入图片描述
注:所有的方程都会将非常近的物体的深度值设置为接近0.0的值,而当物体非常接近远平面的时候,它的深度值会非常接近1.0。
当然,实践中一般不会使用到线性深度缓冲。想要正确的投影性质,需要一个非线性的深度方程,它与1/z成正比。目的是:在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。

由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占2%的float精度,这正是我们所需要的。这样的一个考虑了远近距离的方程是这样的:
在这里插入图片描述
深度缓冲中的值在屏幕空间中不是线性的(在透视矩阵应用之前在观察空间中是线性的)。深度缓冲中0.5的值并不代表着物体的z值是位于平截头体的中间了,这个顶点的z值实际上非常接近近平面。你可以在下图中看到z值和最终的深度缓冲值之间的非线性关系:
在这里插入图片描述
可以看到深度值很大一部分是由很小的z值决定的,给了近处的物体很大的深度精度。

深度缓冲的可视化

片段着色器中,内建gl_FragCoord向量的z值包含了那个特定片段的深度值。如果我们将这个深度值输出为颜色,我们可以显示场景中所有片段的深度值。我们可以根据片段的深度值返回一个颜色向量来完成这一工作:

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

运行后可以看到所有东西都是白色的,就好像得到了所有的深度值都是最大的1,即没有近物体,全是最远的。没有0一样,是因为:片段的深度值会随着距离迅速增加,所有的顶点的深度值都接近于1。如果慢慢靠近物体,可能会注意到颜色逐渐变暗,表示z值逐渐变小。
可以得到结论:近处的物体比起远处的物体对深度值有着更大的影响。只需要移动几厘米就能让颜色从暗完全变白。

也可以让片段非线性的深度值变换为线性的。如果要实现这个,只需要反转深度值的投影变换。意味着我们首先需要将深度值从[0,1]重新变为[-1,1]的范围 标准化设备坐标(裁剪空间)。接着需要像投影矩阵那样反转非线性方程,并将反转的方程应用到最终的深度值上。
首先我们将深度值变换为NDC,不是非常困难:
补充:NDC 归一化/规范化设备坐标系–normalized device coordinate

float z = depth * 2.0 - 1.0;

使用获取到的z值,应用逆变换来获取线性的深度值:

float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));

这个方程是用投影矩阵推导得出的。返回一个near与far之间的深度值
将屏幕空间中非线性的深度值变换到线性深度值的完整片段着色器如下:

#version 330 core
out vec4 FragColor;

float near = 0.1; 
float far  = 100.0; 

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);
}

线性化的深度值处于near和far之间,它的大部分值都会大于1,并显示为完全的白色。通过在main函数中将线性深度值除以far,就近似把线性深度值转换到了[0,1]范围之间。可以看到:一个片段越接近投影平截头体的远平面,它就会越亮。

深度冲突

深度缓冲没有足够的精度来决定两个形状哪个在前面。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做深度冲突(Z-fighting),因为它看起来像是这两个形状在争夺(Fight)谁该处于顶端。
深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突。

防止深度冲突

  1. 不要把多个物体摆的太靠近,以至于某些三角形会重叠。(可以在两个物体之间设置一个用户不注意的偏移值,避免两个物体的深度冲突)
  2. 尽可能将近平面设置远一些。因为,精度靠近平面时非常高,如果将近平面远离观察者,将会对整个平截头体有着更大的精度。但是将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合你的场景的近平面距离。
  3. 牺牲一些性能,使用更高精度的深度缓冲。大部分深度缓冲的精度是24位,但是大部分的显卡都支持32位的深度缓冲,这将极大地提高精度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/400942.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA开发(JAVA中的异常)

在java开发与代码运行过程中,我们经常会遇到需要处理异常的时候。有时候是在用编辑器写代码,点击保存的时候,编辑器就提示我们某块代码有异常,强制需要处理。有时候是我们启动,运行JAVA代码的时候的,日志里…

案例06-没有复用思想的接口和sql--mybatis,spring

目录一、背景二、思路&方案问题1优化问题2优化三、总结四、升华一、背景 写这篇文章的目的是通过对没有复用思想接口的代码例子优化告诉大家,没有复用思想的代码不要写,用这种思维方式和习惯来指导我们写代码。 项目中有两处没有复用思想代码&#…

R语言基础(三):运算

接前文 R语言基础(一):注释、变量 R语言基础(二):常用函数 4.运算 4.1 数学运算 R语言中支持加减乘除四则运算、乘方运算、求余数(取模)运算: 符号含义示例加法11 结果是2-减法2-1 结果是1*乘法4*5 结果是20/除法4/5 结果是0.8%/%整除(只要…

MySQL 事务隔离

MySQL 事务隔离事务隔离实现事务的启动ACID : 原子(Atomicity)、一致(Consistency)、隔离(Isolation)、永久(Durability) 多个事务可能出现问题 : 脏读 (dirty read) , 不可重复读 (non-repeatable read) , 幻读 (phantom read) 事务隔离级别 : 读未提交 (read uncommitted)…

一篇学习ES

文章目录ES简介1.什么是ElasticSearch2.ElasticSearch的使用案例3.ElasticSearch对比SolrElasticSearch环境搭建1. 下载ES压缩包2. 安装ES服务3. 启动ES服务3. 安装ES的图形化界面插件ES术语1.概述2.索引 index3.类型 type4.字段Field5.映射 mapping6.文档 document7. 接近实时…

制造业数字化转型要注重哪些方面?

近年来,制造业企业数字化转型的话题一直处于行业高热位置。中央经济工作会议作出“大力发展数字经济”的部署,工信部提出要深化产业数字化转型,建设一批全球领先的智能工厂、智慧供应链,并向中小企业场景化、标准化复制推广。 随…

监控体系划分

按采集类型划分 1.基于 Metrics 的监控 基于 Metrics 的监控,背后对应的是度量(指标监控)系统,监控机器在某段时间内的 CPU 使用率、系统负载; HTTP 请求访问量等。 1.Skywalking 开源地址 (既能做调用链监…

Spring-AOP简介案例

Spring-AOP简介&案例 1,AOP简介 Spring有两个核心的概念,一个是IOC/DI,一个是AOP。 对于AOP,我们前面提过一句话是:AOP是在不改原有代码的前提下对其进行增强。 1.1 什么是AOP? AOP(Aspect Oriented Programming)面向切面编程&…

java Spring5 xml配置文件方式实现声明式事务

在java Spring5通过声明式事务(注解方式)完成一个简单的事务操作中 我们通过注解方式完成了一个事务操作 那么 下面 我还是讲一下 基于xml实现声明式事务的操作 其实在开发过程中 大家肯定都喜欢用注解 因为他方便 这篇文章中的xml方式 大家做个了解就好 还是 我们的这张表 记…

ECharts数据可视化--常用图表类型

目录 一.柱状图 1.基本柱状图 1.1最简单的柱状图 ​编辑 1.2多系列柱状图 1.3柱状图的样式 (1)柱条样式 (2)柱条的宽度和高度 (3)柱条间距 (4)为柱条添加背景颜色 ​编辑 2.堆…

SpringBoot创建和使用

目录 什么是SpringBoot SpringBoot的优点 SpringBoot项目的创建 1、使用idea创建 2、项目目录介绍和运行 Spring Boot配置文件 1、配置文件 2、配置文件的格式 3、properties 3.1、properties基本语法 3.2、读取配置文件 3.3、缺点 4、yml 4.1、优点 4.2、yml基本…

虚拟机下Linux系统磁盘扩容

在VM虚拟机中,我们经常会选择默认磁盘大小20G,用着用着才发现20G不够用,服务启动不了,就很尴尬,让我们今天一起来学习下,如何在虚拟机给磁盘扩容。一:关闭虚拟机,添加硬盘背景&#…

mysql Docker容器的安装(centos版)以及修改docker默认端口、解决1251问题

文章目录一、Docker的安装以及在Docker下进行mysql的安装1、安装Docker2、上传安装包并进行安装并启动docker3、 配置Docker的镜像加速器,这里使用阿里云的镜像4、刷新守护进程,并重启docker,检验镜像是否配置成功5、搜索并下载mysql镜像6、导…

超分扩散模型 SR3 可以做图像去雨、去雾等恢复任务吗?

文章目录前言代码及原文链接主要的点如何进行图像恢复前言 关于扩散模型以及条件扩散模型的介绍,大家可以前往我的上一篇博客:扩散模型diffusion model用于图像恢复任务详细原理 (去雨,去雾等皆可),附实现代码。 SR3是利用扩散模…

优化Facebook广告ROI的数据驱动方法:从投放到运营

“投放广告并不是最终的目的,关键在于如何最大程度地利用数据驱动的方法来提高广告投放的回报率(ROI)”Facebook广告是现代数字营销中最为常见和重要的广告形式之一。但是,要让Facebook广告真正发挥作用,需要通过数据驱…

Allegro如何自动添加测试点操作指导

Allegro如何自动添加测试点操作指导 在做PCB设计的时候,在一些应用场合下需要给PCB上的网络添加测试点,如下图 测试点除了可以手动逐个添加之外,Allegro还支持自动添加测试点,具体操作如下 点击Manufacture点击Testprep

PlantUML画出如女神漂亮的流程图

一:环境准备 1,本地安装好vscode 2,vscode安装PlantUML插件 3,本地安装java环境,我本地用的是jdk-11.0.178,配置好环境变量 4,在vscode上新建一个文件以wsd结尾,输入以下两行&#x…

Go语言容器之数组和切片

Go语言的容器分为值类型和引用数据类型 一、数组 1.数组的声明和初始化 (1) 数组声明的语法 var 数组变量名 [数组大小]数组类型 举例: package main import "fmt"func main(){//数组的声明var arr[10]int//打印数组长度fmt.Println("arr的长度为…

《Java高并发核心编程. 卷1, NIO、Netty、Redis、ZooKeeper》 读书笔记

第2章 高并发IO的底层原理 2.1 IO读写的基本原理 为了避免用户进程直接操作内核,保证内核安全,操作系统将内存(虚拟内存)划分为两部分:一部分是内核空间(Kernel-Space),另一部分…

Ubantu docker学习笔记(二)拉取构建,属于你的容器

文章目录一、拉取启动容器二、本地镜像初解三、构建镜像3.1使用docker commit构建镜像切换阿里镜像3.2使用dockerfile构建镜像四、总个结吧这里的话,就详细说说小唐对于容器的配置,对了!小唐参考的书籍是Linux容器云实战!&#xf…