百人计划(图形部分)模板测试和深度测试

https://space.bilibili.com/7398208?spm_id_from=333.788.b_765f7570696e666f.1

苏格拉没有底笔记:https://www.yuque.com/sugelameiyoudi-jadcc/okgm7e/nqoaio#iedzH

古守音笔记:https://blog.csdn.net/qq_43210334/article/details/118179532

 模板测试

例子

image.png

 左图为颜色缓冲区中的一张图,在模板缓冲区中我们会给这张图的每一个片元分配一个0-255的数字(8位,默认为0)

中、右图可以看到,我们修改了一些0为1,通过自定义的一些准则,如输出模板缓冲区中1对应的片元的颜色;0的不输出,最后通过模板测试的结果就如右图所示

模板测试的应用

在这里插入图片描述

一般模板测试由3层组成,分别是两层物体/场景、和一层遮罩

什么是模板测试

从渲染管线上理解

逐片元操作是可以配置但不可编程的(对应下图中为黄色背景),也就是说是由管线/硬件自身规定好的,我们只能对里边的内容进行配置。

 Pixel Ownership Test :控制当前屏幕像素的使用权限,如Unity中的Game/Scence窗口;
Scissor Test:裁剪测试,根据自定义,在Game/Scence确定要渲染的部分,默认是都渲染;
Alpha Test:透明度测试,根据透明度阈值判断。只能实现不透明效果和全透明效果,设置透明度a为0.5,如果片元大于这个值就通过测试,如果小于0.5就剔除掉(在OpenGL ES3.0中被删除,因为可以在片段着色器中进行)
Stencil Test :模板测试,在输入片段的模板上进行,以确定片段是否该被拒绝;
Depth Tset :深度测试,在输入片段的深度值上进行,以确定片段是否该被拒绝;
Blending:透明度混合,用于实现半透明效果;
Dithering:抖动,可用于最小化,因为使用有限精度在帧缓冲区中保存颜色值而产生伪像,这里有看了下OpenGL ES3.0,暂时也可以先参考下这篇;
Logic Op;逻辑操作;(在OpenGL ES3.0中被删除,因为很少被应用程序使用)

完成接下来的其他一系列操作后,我们会讲合格的片元/像素输出到帧缓冲区(FrameBuffer

从逻辑上理解

//掩码类比于PS,AE中的蒙版遮罩,属于程序“黑话”
if(当前模板缓冲参考值&读掩码 比较 模板缓冲区中值&读掩码){
  通过像素
}else{
  舍弃像素
}

就是通过一定条件来判断这个片元/片元属性执行保留还是抛弃的操作

概念上理解

 基本语法

基本语法

Stencil{
   Ref referenceValue      //给片元设置参考值取值范围为0——255
   ReadMask readMask       //读取掩码
   WriteMask writeMask     //写入掩码
   Comp comparisonFunction //比较操作
   Pass stencilOperation   //通过模板测试
   Fail stencilOperation   //未通过模板测试
   Zfail stencilOperation  //模板测试通过,深度测试未通过
}

ComparisonFunction 比较函数

Greater相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素
GEqual相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素
Less相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素
LEqual相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素
Equal相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素
NotEqual相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素
Always不管公式两边为何值,模板测试总是通过,渲染像素
Never不敢公式两边为何值,模板测试总是失败 ,像素被抛弃

 StencilOperation 更新值

Keep保留当前缓冲中的内容,即stencilBufferValue不变。
Zero将0写入缓冲,即stencilBufferValue值变为0。
Replace将参考值写入缓冲,即将referenceValue赋值给stencilBufferValue。
IncrSatstencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255。
DecrSatstencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0。
Invert将当前模板缓冲值(stencilBufferValue)按位取反
IncrWrap当前缓冲的值加1,如果缓冲值超过255了,那么变成0,(然后继续自增)。
DecrWrap当前缓冲的值减1,如果缓冲值已经为0,那么变成255,(然后继续自减)  

 案例解析

(1)3D卡牌

利用提前摆置好的模型,进行模板测试,将多余地方剔除

在这里插入图片描述

Unity中模板缓冲区默认都是0

 蒙版shader

image.png

  1.  只有一个int类型的属性,命名了一个ID
  2. 渲染类型为不透明物体,队列为Geometry+1(默认的不透明物体后进行蒙版的渲染)
  3. ColorMask 颜色遮罩,0就是什么都不输出(是高度可配置的,可以改为RGBA(没有遮罩)、输出单通道R、G、 B)
  4. ZWrite off 关闭深度写入,防止显示的东西被深度剔除(后边深度测试细讲)
  5. Stencil { } 模板测试部分
  • Ref [ _ID ] 索引值就是前边属性声明的ID
  • Comp always //默认比较
  • Pass replace //默认是keep
  • Fail、ZFail,不写的话都是默认值,如代码所示

6.颜色给一个half4的就行,因为前边已经ColorMask0了(什么都不输出

物体的shader

image.png

 ID值用于判断遮罩关系的;

渲染序列为“Geometry+2”,意思为在渲染完模板遮罩后再渲染物体;

Stencil中相关的Ref后是定义参考值,比较方法为equal,如果模板缓冲区中值和当前的参考值一致时就算通过,渲染该片元否则舍弃;

思路梳理

前提:Unity的模板缓冲区的默认值是0

(1)默认物体(mask外边的物体)的索引值也设为0,Comp设为always,也就是比较是一直通过的,且保持保持模板缓冲区的值不变(不进行模板测试操作的物体渲染完之后,模板缓冲区的值还是0)

(2)渲染完卡面之后,然后开始渲染蒙版(mask)

mask的设置Comp也是always,但是不同的是:ID给了1(属性部分定义),并且Pass的设置为replace,也就是说mask所在的模板缓冲区的值变成了1。

到这里,总结一下,mask外的物体 值是0,mask的值是1

(3)最后是mask里边的物体。

mask的模板测试是这样的:ID是1,Comp是equal。就是:模板缓冲区的值为1,比较的条件是相等

此时,里边物体的模板缓冲区的值是1,外边物体的模板缓冲区是0,Comp的条件是相等,结果很明显不相等,这样的效果就是:除mask显示的部分,外边的场景不渲染。

回顾前边,我们mask的缓冲区的值也为1,通过了测试,所以mask部分(卡牌部分)渲染了出来。

深度测试

什么是深度测试

处理物体的遮挡关系

从渲染管线理解

深度测试同样位于逐片元操作过程中,在模板测试之后,透明度混合之前

 逻辑上理解

//深度
if(深度测试开启&&(当前深度值 比较 当前深度缓冲区)){
  写入深度;
}else{
  忽略深度;
}

//颜色
if(当前深度值 比较 当前深度缓冲区){
  写入颜色缓冲;
}else{
  不写入颜色缓冲;
}

从书面概念上理解

所谓深度测试,就是针对当前屏幕上(更准确的说是FrameBuffer)对应的像素点,将对象自身的深度值与当前深度缓冲区的深度值做比较,如果通过了,这个对象在该像素点才会将颜色写入颜色缓冲区

从发展上理解

我们要渲染一个场景的话,通常会有多个物体。

首先要控制渲染顺序

画家算法:像画画一样,先从远处开始画,然后近处的东西一点点叠加在上面

存在的问题:当出现遮挡关系时,由于先渲染后面的,再渲染前面的,会造成不必要的渲染消耗,形成很大的计算量,造成在一帧中同一个像素被重复绘制,也就是overdraw

Z-Buffer算法:通过深度缓冲区来控制渲染顺序

控制Z-Buffer对深度的存储

例如:什么时候更新深度缓冲区、什么时候使用深度缓冲区

两个典型的功能:Z Test      Z Write

透明,半透,不透明物体需在意其渲染顺序,举个例子,在做透明度混合时,如果先渲染前面的透明物体,再渲染后面的不透明物体,就会形成后面的物体颜色信息覆盖透明物体的颜色信息,视觉观感上是不透明物体在透明物体之前,这里引入渲染队列的概念,队列是先进先出的数据结构。

为了更好的减少overdraw,使用到Early-Z,Z-cull以及Z-check

深度缓冲区   Z-Buffer

 颜色缓冲区:就是最终在显示屏硬件上显示颜色的GPU显存区域了,这个缓冲区储存了每帧更新后的最终颜色值,图形流水线经过一系列测试,包括片段丢弃、颜色混合等,最终生成的像素颜色值就储存在这里,然后提交给显示硬件显示。

Z Writer(深度写入)

 Z Test比较操作

ZTest 状态描述
Greater深度大于当前缓存则通过
GEqual深度大于等于当前缓存则通过
Less深度小于当前缓存则通过
LEqual深度小于等与当前缓存则通过
Equal深度等于当前缓存则通过
NotEqual深度不等于当前缓存则通过
Always深度不论如何都通过
Never深度不论如何都不通过

深度缓冲一开始为无穷大

默认ZWrite On、ZTest LEqual

渲染队列

 简述Early-Z技术

early-Z位于光栅化之后,逐片元之前,提前测试一遍,提高性能

 深度值

模型一开始所在的模型空间:无深度。

通过M矩阵变换到世界空间,此时模型坐标已经变换到了齐次坐标(x,y,z,w):深度存在z分量。

通过V矩阵变换到视察空间(摄像机空间):深度存在z分量(线性)

通过P矩阵变换到裁剪空间:深度缓冲中此空间的z/w中(已经变成了非线性的深度)

最后通过一些投影映射变换到屏幕空间

为什么深度缓冲区中要存储一个非线性的深度?

1.给近处更多的精度

在深度缓冲区中的深度值是介于 0.0~1.0之间的,从观察者看到的内容与场景中所有对象的z值作比较。

这些z值可以投影平截头体(就是视锥)的近平面和远平面之间的任何值。

  • 平截头体:又称视景体、视锥,是三维世界中在屏幕上可见的区域,即虚拟摄像机的视野
  • 下图中红框的位置是平截头体,就是摄像机拍摄的范围。

image.png

 要转换这些视图空间的z值到[0,1]范围内,方法之一就是线性转换,具体如下图

image.png

然而实践中几乎不使用线性深度缓冲区,正确的投影特性的非线性深度方程是和1/z成正比的。这样一来会有如下效果:在Z很近的时候有高精度,Z很远的时候低精度,这符合我们生活中的情况,具体如下图

image.png

 其实可以回想前几节课中伽马校正部分对比理解一下,也是根据实际情况(人眼特性),给暗部更多的精度,这里是近处给更多精度。

2.Z-Fight-深度冲突

 当两个平面或三角形紧密贴合时,深度缓冲区不具有足够精度,无法判断谁前谁后,导致两个形状不断切换出现闪烁,这叫深度冲突(UE4里我记得很清楚,但凡重叠一点,就会频闪)

Demo效果展示和讲解

 图1详解

image.png

    梳理渲染过程:没渲染时,此时Unity的深度缓冲区默认值为无穷

渲染蓝色正方体

相对于默认深度缓冲区的无穷大,肯定是小于等于,所以测试通过

渲染绿色正方体

  • 此时蓝色物体位置的深度缓冲区的值已经不是无穷大了,其它位置还是
  • 绿色正方体进行深度测试,深度测试同样是LessEqual,并且绿色的深度值比蓝色正方体的大。
  • 结果就是:两个正方体重叠部分是大于深度缓冲区的,也就是测试不通过,所以重叠部分没有写入绿色,还是蓝色
  • 注:深度缓冲区和颜色缓冲区都是相对于片元来讲的(片元可以理解为未完成的一个像素,还处于渲染管线中的像素)
  • 没有重叠部分,深度当然比无穷大小,所以写入, 渲染出来了绿色正方体未重叠的部分。

红色同理。

图2详解:

image.png

 梳理渲染过程:

设置:将蓝色正方体的深度写入ZWrite 关掉了;

思路:第一个蓝色正方体的渲染时,测试通过,但是并没有写入深度。

也就是说,渲染完蓝色正方体时,深度缓冲区的值还是无穷大。

这就是蓝绿重叠部分,显示绿色的原因。

图3详解:

相较于图2,只是把绿色正方体的ZTest改为了always

无论是LessEqual还是always,测试都通过,所以效果和图2一样

图4详解

将红色正方体的ZTest也改为了always,这样一来红色正方体的深度测试也是一直通过,并且写入。

因为是从前往后渲染的,所有依次为蓝、绿、红,深度缓冲区中的值也是后边渲染的

可以理解为后边遮住前边的效果。

 图5详解:

image.png

 相对于图4,改变了绿色正方体的渲染队列为Geometry+1

此时的帧缓冲区面板如下

尽管绿色正方体在红色正方体前面,因为队列+1,它的渲染顺序变为了红色正方体后

也就是说,渲染队列优先级 > 透明物体的渲染顺序(从前到后)

图6详解

image.png

 相对于图1,将绿色正方体的ZTest改为了Greater,

也就是说蓝色正方体和绿色正方体重叠部分,大于模板缓冲区的部分通过测试,写入模板缓冲区

结果就是重叠部分为绿色,而未重叠部分的深度当然小于无穷大,所以没通过测试,自然也就不渲染。

红色部分正常。

shader截图

image.png

image.png

image.png

 案例二:X-Ray效果

image.png

 前面的墙没啥好说的,默认即可,后面的布偶兔用了两个Pass,一个是采用greater比较深度大小,将其颜色覆盖到墙上的外描边效果,一个是默认的布偶兔渲染效果;

实现思路

分为三部分:前边的墙、被墙挡住的X-Ray效果部分、高出墙部分的物体

回想一下前边6张图,哪张图是前边渲染完,后边渲染显示在先渲染完前边的?

也就是说,X-Ray效果部分我们使用到了ZTest :Greater,深度写入关闭

高出墙体部分是默认的渲染:LessEqual、ZWrite On

shader截图

image.png

image.png

image.png

image.png

image.png

image.png

代码理解:

写CGINCLUDE的好处:将顶点和片元着色器写在里边,在多passshade的时候,直接调用就可以了。

X-Ray绘制部分

和之前实现思路相同,ZWrite Off,ZTest Greater

Cull back 是剔除背面,为了优化

Blend     SrcAlpha One :由于有一个透明的效果,除了上边的,还需要一步Blend,来做透明度混合

渲染类型和渲染队列为Transparent

正常绘制部分略

 粒子系统中的深度测试

创建一个粒子系统ParticleSystem,可以看到默认的是透明的

 创建一个材质,给到粒子上

此时粒子系统变成了这样

image.png

 创建一个shader(Unlit),把粒子的贴图选上,附到材质上,效果如下(是不透明的),这显然不是我们要的效果

image.png

 打开shader修改代码

首先回顾前边说的:Unity中默认的ZWrite On、ZTest是LessEqual、渲染队列是Geometry

我们想要让粒子透明,就需要做如下配置

渲染队列改为透明物体的渲染队列:Transparent

ZWrite Off,对于透明物体,是有相互叠加关系的,所以关掉写入

ZTest 默认(LessEqual),对于透明物体是这样的:如果透明物体前有不透明物体,此时 透明物体看不到;如果透明物体后面有不透明物体,此时透明物体可以看到。

要渲染透明物体,还要进行Blend操作:Blend One One(加法混合,叠加效果的显示)

修改完成后效果如下:(正是我们想要的效果)

image.png

 深度测试的总结

 扩展及参考资料

 

作业

把视频里效果过了一遍

 

 (gif压缩的太狠,导致颜色有失真)

 

 太菜了,等以后学到了更多再研究水面效果吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值