开篇

经过一段时间的持续输出,社区中越来越多的人踏上了3D图形渲染学习之旅,麒麟子非常开心,说明输出的内容对大家都产生了实际的帮助。

特别是上一篇 《用实时反射Shader增强画面颜值》 的文章发表后,有开发者竟然等不及了,找上门来索取。

这样的要求还算合理,毕竟实时3D渲染最终的目的,是创造出更高效、炫酷的画面效果。

因此,在后面的内容输出中,麒麟子的案例会尽可能兼顾这个广大人民群众都能感知到的需求。

上个星期,一个朋友问我如下所示的水面深度效果是如何实现的。

还有一个朋友问我,为什么别人游戏里的特效都非常柔和(参考本文开头的动图),而自己做的就有很明显的接缝,如下所示:

这些事,通常依靠深度图才能够解决。

如果说高级Shader效果中,什么东西最重要的话,那肯定要数深度图了。

所以今天向大家完整介绍一下深度相关知识,希望大家看完本文后,对深度图相关问题再无死角。

深度缓冲区(Depth Buffer)

深度缓冲区解决的问题

假如有两个 3D 模型 AB,且 AB 有一定的交叉。

图形管线在渲染模型的时候是以单个几何体为单位的,会先渲染完一个,再渲染另一个。

这种情况下,不管是先渲染 A 还是先渲染 B,都需要解决像素遮挡问题,如果没有深度缓冲区的协助,可能出现下图中,左边这样的情况:

什么是深度缓冲区

各类计算机图形学书籍、文章上都对深度缓冲区作出了解释,麒麟子综合各家之言,给一个通俗点的解释:

深度缓冲区为非透明物体提供了像素级的前后关系判断能力。

深度缓冲区用于记录帧缓冲区(帧缓冲区用于存放颜色信息)中每个像素的深度值。

深度缓冲区的宽高通常与帧缓冲区大小相等,每一个像素位置的值是 0.0 ~ 1.0,若输出为图片,则如下所示:

离摄像机越近的值越小,摄像机近裁面处的值为 0.0

离摄像机越远的值越大,摄像机远裁面处的值为 1.0

所以,上图中离摄像机越近的像素越黑,离摄像机越远的像素越白。

深度写入

并不是所有的像素都会写入深度的!

3D 模型在渲染时,可以通过渲染状态决定是否开启深度写入,材质面板的开关如下所示:

当一个像素被渲染时,除了将颜色信息写入帧缓冲区外,同时还会将它的深度值写入深度缓冲区。

深度测试

写入的深度拿来干什么呢?

当一个像素被渲染时,在颜色被写入帧缓冲区之前,会进行深度测试,此时就会用到深度缓冲区。

我们可以通过开启深度测试,来丢弃较后绘制但被 “遮挡” 的像素,从而保证渲染结果的正确性。

如上图所示,深度测试有两个控制因子,一个是开关,另一个是比较函数

默认的比较方式为 LESS ,表示小于则通过测试(除非有特别的需要,否则应该保持这个值)。

通过深度测试的像素才会改写深度缓冲区和帧缓冲区的值,否则会被丢弃,不再执行后面的像素处理流程。

深度测试的过程如下图所示:

有了深度缓冲区以后,绘制非透明物体时的顺序就不那么重要了,任意顺序都能获得正确的渲染效果。

麒麟小贴士:

非透明物体的深度写入开启,深度测试开启,绘制顺序可以与远近无关。

半透明物体的深度写入关闭,深度测试开启,需要按从远及近的顺序进行绘制。

深度图

什么是深度图

深度图是一种其像素内容可以反映深度缓冲区内容的纹理,通常是一张渲染纹理(Render Texture)。

对于一些特殊的场合,需要使用深度图才能实现更好的效果。比如:

  • 半透明物体与非透明物体接缝柔和处理
  • 景深等后期效果
  • 水体深浅效果
  • 动态阴影

下面是一张完整的深度图效果:

深度图的存储格式

常见的深度图存储方式有 3 种,接下来我们从兼容性、数值精度、存取效率三个维度来比较的它们的优缺点。

方式1:使用 RGBA8 格式纹理中的 R 通道

由于每个像素的深度值在 0.0~1.0 之间,我们很容易想到,直接将深度信息存储到某个颜色通道中即可。

兼容性: 优,RGBA8 可以兼容所有设备。

数值精度: 差,RGBA8 中每个通道可以表示的浮点数精度为 1.0/255.0,约等于 0.0039精度不够将会导致深度图在用于深度判定时出现较大误差。

存取效率: 优,直接存取,无额外运算。

2、R32F 浮点纹理

R32F 浮点纹理是单通道纹理,且这个通道可以存储32位浮点数。

兼容性: 一般,原生平台普及度已经非常高,但在 Web 平台,WebGL 2.0 才正式支持浮点纹理。

数值精度: 优,可以达到 32 位浮点数精度。

存取效率: 优,直接存取,无额外运算。

3、RGBA8 格式纹理编码
此方案是将深度信息编码到 RGBA8 格式纹理的四个通道中,使用时再用与编码对等的解码方式进行解码。

兼容性: 优,RGBA8 可以兼容所有设备。

数值精度: 优,可以达到 32 位浮点数精度。

存取效率: 一般,需要额外的编码、解码运算。

Cocos Creator 引擎内的深度信息编码、解码函数,如下图所示:

**麒麟小贴士:**只需要添加 #include< packing > ,就可以在自己写的 Cocos Shader 中调用。

深度图的获取

深度图的获取是非常简单的,只需要写一个简单的 Cocos Shader 就可以。

1、Vertex Shader

新建一个 Cocos Effect,写上下图这样的 unlit-vs,将投影过后的位置信息通过 v_screenPos 传给 fs

2、Fragment Shader

fs 中,执行透视除法,将 v_screenPos 转换到 NDC空间, NDC 空间中的 z 值,就是深度值。

麒麟子在这里写了两种深度显示方法,depth_8bits 用于 R 通道存储, depth_32bits 用于RGBA 四通道编码存储。

3、模型渲染

我们新建两个材质,分别使用 8bits32bits 两种方法渲染模型。

depth_8bits 的内容如下所示:

depth_32bits 的内容如下所示(不必感到诧异,它只是没有解码而已,解码之后的显示效果与上图一致):

麒麟小贴士:

负责渲染深度信息的摄像机的 clearFlags 属性需要设置为 SOLID_COLORclearColor 属性需要设置为纯白色(255,255,255,255)

4、深度图

新建一个渲染纹理(Render Texture),并将负责渲染深度材质的摄像机输出到此渲染纹理。

5、注意事项

本示例中,我们在 v_screenPos.z/v_screenPos.w 的基础上还进行了 *0.5 + 0.5 操作。

这是由于在 Cocos Creator 引擎中,v_screenPos.z/v_screenPos.w 的值域为 [-1.0,1.0],我们需要将它映射到 [0.0,1.0]。

对于这个值域的范围,在进阶阅读:线性深度和非线性深度会讲到。

我们可以通过下面的简单测试来验证其范围:

如上图所示,只需要加上,若小于零就显示为红色的判断,就能得到如下图所示的深度图。

图中有红色,表示这个值是会小于零的,说明 depth 的值域为 [-1.0,1.0],需要修正到 [0.0,1.0]。

深度图使用案例

接下来,我们以如何实现柔和特效为例,讲解深度图的使用。

第一步:

新建一个场景,拖入我们的平台模型。如下图所示:

第二步:

复制此模型作深度渲染使用,并将所有子节点的材质,改为 material-depth-32bits。如下图所示:

第三步:

新增一个 LAYER,取名为 DEPTH,并将上一步复制的模型 LAYER 切换为 DEPTH(包括其子节点),如下图所示:

第四步:

新建一个摄像机,作为 Main Camera 的子节点。

需要注意以下几点:

1、确保此摄像机的 position、rotation、scale 均为初始化状态。

2、确保此摄像机的参数,如 fov、near、far 等与 Main Camera 一致。

3、确保此摄像机的渲染层级(Visibility)仅保留 DEPTH

4、确保此摄像机的 clearFlagsSOLID_COLORclearColor纯白色(255,255,255,255)

详细情况,如下图所示:

第五步:

新建一个 Render Texture,起名为 RT_Depth,并赋值给上一步创建的摄像机。

第六步:

拖入本系列教程上一节课中使用的 “虚空幻镜入口” 特效,如下图所示:

第七步:

复制一个 builtin-unlit.effect,并加入深度判断。

核心内容为,求得当前特效像素的深度与深度图中的差值,并做一个线性过渡。如下图所示:

完整 Shader 请看 DEMO 源文件 assets/tutorial_7/effects/effect-unlit.effect

第八步:

将第六步特效使用的材质的 effect 切换为 effect-unlit.effect,并将 RT_Depth 赋值给材质的 DepthMap 参数。

最终得到的效果如下所示:

从上面的动图中,我们可以很清晰的看到,不管是近处还是远处,半透明特效与非透明物体的接缝变得柔和了。

进阶阅读:线性深度与非线性深度

如果不太想面对数学的朋友,可以直接拖到末尾点赞、评论、转发了,避免中途流失。

初中几何角度解释非线性


上图为透视投影视锥体,所有视锥体内的物体会投影到**近裁面(Front/Near Clipping Plane)**成像。

由初中学过的三角形比例定理我们可以推导出,越远的物体在近裁面成像时缩小越多,从而出现了近大远小的效果。

代数角度解释非线性深度

如上图所示,是非常经典的 OPENGL 透视投影矩阵(不同引擎由于选择的坐系不同,正方向不同,会略有差异)。

在与 Cocos Creator 引擎的 Mat4.perspective 对比后,确认 Cocos Creator 也是用的上图的透视投影矩阵。

设观察空间中有一个点 Pview(x,y,z,1.0) ,那它投影后的各值为:

  • Pproj.x = x*cot(fovy/2)/aspect
  • Pproj.y = y*cot(fovy/2)
  • Pproj.z = z*-(f+n)/(f-n)-(2*f*n/(f-n))
  • Pproj.w = -z

为方便阅读

Pview.z 简写为 z

Pproj.z 简写为 z

Pproj.z/Pproj.w 简写为 z’'

由此可得:

此时的 z’’ (Pproj.z/Pproj.w) 是一个处于[-1.01.0]区间的值,将其映射到[0.01.0]区间,就能得到我们的非线性深度。

最终公式整理后可得:

depth = z’’ * 0.5 + 0.5

为了更直观的体现这个非线性关系,麒麟子花了较长时间做了下面的图:

需要关注几个点:

1、上面的投影公式中,正方向为 -Z,所以图中 Pview.z 值为负。

2、Pview.z-10 时,depth 就已经大于 0.9 了,这就说明靠近摄像机跟前的这 10个单位,占用了 90% 的深度缓冲区精度。

3、整个表很长, -1.0~-1000.0,无法完整截图。

线性深度

线性深度一般用于需要精确表达两个物体深度差异反算像素在观察坐标系中的位置或者基于物理公式的运算等情况。

线性深度有两种表示方法:

  • LinearEyeDepth = -Pview.z
  • Linear01Depth = (-Pview.z-n)/(f-n) 或者 -Pview.z/f

麒麟小贴士: 再次提醒,由于系统中是以 -Z(0.0,0.0,-1.0) 为正方向,所以求深度的时候,z 值需要取反。

线性深度的使用场合,一般是指当我们拥有深度图时,此时并没有 Pview.z 这个数据。

所以上面的公式只能作为理解什么是线性深度用,在实际生产环境中派不上用场。

下面我们来研究,如何在拥有深度图的情况下,反算出线性深度。

LinearEyeDepth 推导过程

由我们刚刚讲到的非线性深度公式,可以反推出以下结论:

depthz’’'

则可以得到 z’’ = z’’’ * 2.0 - 1.0

代入上面的投影公式可以反向求解出观察坐标系下的 Pview.z

由于坐标系以 -Z 为正方向,这样求出来的 Pview.z 是负数,取反即可得到我们视野空间的线性深度。

最后我们得到的 LinearEyeDepth 计算公式如下:

很容易做一个验算:

  • 当 z’’’ 为 0 时,LinearEyeDepth 为 n。
  • 当 z’’’ 为 1 时,LinearEyeDepth 为 f。

如果担心端点是特例难以信服,可以在 Excel 中,双向验证公式,如下图所示:

Linear01Depth 推导过程

线性深度除了用 -Pview.z 表示外,还可以将其归一化到 [0,1] 区间。

实际上,[0,1]的线性深度使用率远高于 LinearEyeDepth,因为它可以不用关注 nearfar 的数值变化。

Linear01Depth 有两个公式可以使用:

  • 公式 A: (-Pview.z-n)/(f-n)
  • 公式 B: -Pview.z/f

两个公式的区别在于区间的选择。

公式 A 仅考虑 nf 之间的值,当 Pview.z 值为 -n 时,Linear01Depth0

公式 B 则将观察点也纳入了考虑,当 Linear01Depth0 时,Pview.z 与观察点重合。

两个公式都是可以的,但由于 公式 B 的运算更为简单,且能够处理 Pview.zn 离摄像机更近的情况,所以大多数系统中采用了公式 B

采用 公式 B,只需将 LinearEyeDepth 除以 f,即可得到 Linear01Depth 最终计算公式,如下图所示:

核心代码

代码反而是最简单的了,如下图所示:

敲黑板!

学习过程中可能遇上的疑惑!!!

如果一切都像上面这样的话,本文就可以开心的结束。

但麒麟子在核实深度图相关内容的过程中,在网上找到了一个出场率非常高的内容,如下所示:

如果盲目照着这段代码来的处理的话,只会掉入一个深坑。这段代码有一个大前提是:

该引擎的运行时中,OPENGL 环境下拿到的深度信息处于 [-1.0,1.0] 的非线性区间。D3D 环境下拿到的深度信息处于 [0.0,1.0]区间。

因此,采用下面哪个组合,是要根据深度信息的范围而定,而不是平台和图形 API。

  • zc0 = 1.0 - f/n , zc1 = f/n

  • zc0 = (1.0 - f/n)*2.0 , zc1 = (1.0 + f/n) * 2.0

线性深度与非线性深度曲线

由于 LinearEyeDepth 即为 -Pview.z,不方便展示,所以下图仅展示了 Linear01Depth(红线):

值得注意的是,Linear01Depth 的精度是受 f 决定的。f 的值会影响同一视角下同一像素的深度值,如下图所示:

关于写作

有人问过麒麟子,说市面上已经有非常多的计算机图形学、3D渲染相关教程了,为什么我还要重写。

我统一答复一下吧:

市面上的文章、教程,不管是讲某引擎的,还是讲某API的。

要么是贴代码,要么是抄公式,去吻合计算机图形学理论,生怕与理论相悖。

最终导致了国内3D图形相关人才与项目需求的断层。

而麒麟子输出的内容,是结合实际项目需求中广泛采用的方案来讲解,从原理、实现、到应用。

麒麟子提供的不是理论,是理论知识在行业中的实际应用。

相信这样的方式更能提升大家的学习效率,做到学以致用。

DEMO免费获取

方式一:

https://store.cocos.com/app/detail/3521

**方式二:**Cocos Dashboard -> 商城 搜索 kylins

Cocos Shader入门基础七:一文彻底读懂深度图。相关推荐

  1. Cocos Shader入门基础四:Uniform与材质参数控制

    零.这个时代,太快 如果有朋友年龄和麒麟子相仿的话,小时候应该玩过DVD播放机,就下面图里这东西. 那么问题来了,你还记得,如果想要播放自己想看的内容,一共分几步吗? 和把大象装进冰箱一样简单,只需要 ...

  2. 【Zigbee技术入门教程-02】一图读懂ZStack协议栈的核心思想与工作机理

    [Zigbee技术入门教程-02]一图读懂ZStack协议栈的核心思想与工作机理 广东职业技术学院  欧浩源   Z-Stack协议栈是一个基于任务轮询方式的操作系统,其任务调度和资源分配由操作系统抽 ...

  3. 一文深入浅出读懂NoSQL

    一文深入浅出读懂NoSQL 2016-11-25 Runoot.com ICT架构师技术交流 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL".在现 ...

  4. 一文彻底读懂物联网关键技术之——ZigBee!

    一文彻底读懂物联网关键技术之--ZigBee! 本文采用问答形式向你详细地介绍了方方面面,不夸口的说,你所需要知道的关于 ZigBee的一切,在这里基本可以了解到! 在智能硬件和物联网领域,时下大名鼎 ...

  5. 一文极速读懂 Gene Ontology (GO)数据库

    一.介绍 官方:基因本体(GO)知识库是有关基因功能的全球最大信息来源. 这些知识既是人类可读的,也是机器可读的,并且是生物医学研究中大规模分子生物学和遗传学实验的计算分析的基础. 在读懂基因本体论( ...

  6. Unity 3D开发--Shader入门基础

    Shader "Unlit/xxShader" {Properties{//基础属性 并可以显示在属性板上_MainTex ("Texture", 2D) = ...

  7. unity shader 之基础 七八 纹理采样、透明度渲染

    7.1.1 逐纹素:对纹理贴图进行采样,采样后的结果就叫纹素 7.1.2 unity使用的是OpenGL的标准,即:左下角是坐标原点 7.1.3 _MainTex_ST:表示该纹理的偏移缩放属性,在属 ...

  8. 会计入门基础(一句口诀搞懂会计里的借贷关系)

    目录 一.关于借贷的记忆技巧 二.关于科目有无余额的口诀: 本文是本人学习会计中级时的笔记,贴出来给想转行作财务顾问同学参考一下,非常基础,有会计基础的同学请绕道. 一.关于借贷的记忆技巧 借贷记账法 ...

  9. 一个文档读懂计算机网络

    计算机网络 1.互联网协议入门 我们每天使用互联网,你是否想过,它是如何实现的? 全世界几十亿台电脑,连接在一起,两两通信.上海的某一块网卡送出信号,洛杉矶的另一块网卡居然就收到了,两者实际上根本不知 ...

最新文章

  1. nagios 3.2安装详解(一)
  2. MAC安装MySQL
  3. percentiles of live data capture
  4. 指针数组,数组指针,指针函数,函数指针,二级指针详解
  5. 面试被问 | 防止 Java 代码被反编译的方法有几种?
  6. 计算机辅助翻译的启示,翻译单位研究对计算机辅助翻译启示.PDF
  7. 解决 ubuntu 无法关机 Dell Studio 1569 Cannot Shutdown in Ubuntu 11.10 or 12.04
  8. Sql Server 2005跨数据查询
  9. 【Zookeeper学习】Apache Zookeeper项目简介
  10. mysqli 操作数据库(转)
  11. 极限学习机和支持向量机_极限学习机的发展
  12. 简明python教程电子书下载_简明Python教程PDF
  13. Android Studio完成音乐盒demo
  14. SSD目标检测算法——通俗易懂解析
  15. seaweedfs java_seaweedfs-java-client
  16. 依赖搞定 Spring Boot 接口防盗刷
  17. LSV(Loca Space Viewer)学习记录
  18. 实时控制软件第一次作业总结
  19. 爱上Axure之软件基础视频教程-昝磊-专题视频课程
  20. Unity3D 游戏开发之内存优化

热门文章

  1. Linux使用445端口,利用enum4linux 445端口+wordpress插件任意文件上传的一次渗透
  2. 惠普打印机卡纸问题,解决.
  3. 02-Vue基础之条件渲染和列表渲染
  4. 【Re-ID】现有方法调研 - 无监督/半监督方法 - 其他方法
  5. PerformanceManagementSystem
  6. 中兴CS大赛推3G营销
  7. 【错误解决】Ubuntu 配置ibus中文输入法后却不能添加
  8. 微信跳一跳辅助之JAVA版(最容易理解的算法)实现原理分析
  9. 对于青少年编程等级考试的认识
  10. PointNet++等3D点云中用到的.cu、.cpp文件的编译的简单理解