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
模板测试
例子
左图为颜色缓冲区中的一张图,在模板缓冲区中我们会给这张图的每一个片元分配一个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。 |
IncrSat | stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255。 |
DecrSat | stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0。 |
Invert | 将当前模板缓冲值(stencilBufferValue)按位取反 |
IncrWrap | 当前缓冲的值加1,如果缓冲值超过255了,那么变成0,(然后继续自增)。 |
DecrWrap | 当前缓冲的值减1,如果缓冲值已经为0,那么变成255,(然后继续自减) 。 |
案例解析
(1)3D卡牌
利用提前摆置好的模型,进行模板测试,将多余地方剔除
Unity中模板缓冲区默认都是0
蒙版shader
- 只有一个int类型的属性,命名了一个ID
- 渲染类型为不透明物体,队列为Geometry+1(默认的不透明物体后进行蒙版的渲染)
- ColorMask 颜色遮罩,0就是什么都不输出(是高度可配置的,可以改为RGBA(没有遮罩)、输出单通道R、G、 B)
- ZWrite off 关闭深度写入,防止显示的东西被深度剔除(后边深度测试细讲)
- Stencil { } 模板测试部分
- Ref [ _ID ] 索引值就是前边属性声明的ID
- Comp always //默认比较
- Pass replace //默认是keep
- Fail、ZFail,不写的话都是默认值,如代码所示
6.颜色给一个half4的就行,因为前边已经ColorMask0了(什么都不输出
物体的shader
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值可以投影平截头体(就是视锥)的近平面和远平面之间的任何值。
- 平截头体:又称视景体、视锥,是三维世界中在屏幕上可见的区域,即虚拟摄像机的视野
- 下图中红框的位置是平截头体,就是摄像机拍摄的范围。
要转换这些视图空间的z值到[0,1]范围内,方法之一就是线性转换,具体如下图
然而实践中几乎不使用线性深度缓冲区,正确的投影特性的非线性深度方程是和1/z成正比的。这样一来会有如下效果:在Z很近的时候有高精度,Z很远的时候低精度,这符合我们生活中的情况,具体如下图
其实可以回想前几节课中伽马校正部分对比理解一下,也是根据实际情况(人眼特性),给暗部更多的精度,这里是近处给更多精度。
2.Z-Fight-深度冲突
当两个平面或三角形紧密贴合时,深度缓冲区不具有足够精度,无法判断谁前谁后,导致两个形状不断切换出现闪烁,这叫深度冲突(UE4里我记得很清楚,但凡重叠一点,就会频闪)
Demo效果展示和讲解
图1详解
梳理渲染过程:没渲染时,此时Unity的深度缓冲区默认值为无穷
渲染蓝色正方体
相对于默认深度缓冲区的无穷大,肯定是小于等于,所以测试通过
渲染绿色正方体
- 此时蓝色物体位置的深度缓冲区的值已经不是无穷大了,其它位置还是
- 绿色正方体进行深度测试,深度测试同样是LessEqual,并且绿色的深度值比蓝色正方体的大。
- 结果就是:两个正方体重叠部分是大于深度缓冲区的,也就是测试不通过,所以重叠部分没有写入绿色,还是蓝色
- 注:深度缓冲区和颜色缓冲区都是相对于片元来讲的(片元可以理解为未完成的一个像素,还处于渲染管线中的像素)
- 没有重叠部分,深度当然比无穷大小,所以写入, 渲染出来了绿色正方体未重叠的部分。
红色同理。
图2详解:
梳理渲染过程:
设置:将蓝色正方体的深度写入ZWrite 关掉了;
思路:第一个蓝色正方体的渲染时,测试通过,但是并没有写入深度。
也就是说,渲染完蓝色正方体时,深度缓冲区的值还是无穷大。
这就是蓝绿重叠部分,显示绿色的原因。
图3详解:
相较于图2,只是把绿色正方体的ZTest改为了always
无论是LessEqual还是always,测试都通过,所以效果和图2一样
图4详解
将红色正方体的ZTest也改为了always,这样一来红色正方体的深度测试也是一直通过,并且写入。
因为是从前往后渲染的,所有依次为蓝、绿、红,深度缓冲区中的值也是后边渲染的
可以理解为后边遮住前边的效果。
图5详解:
相对于图4,改变了绿色正方体的渲染队列为Geometry+1
此时的帧缓冲区面板如下
尽管绿色正方体在红色正方体前面,因为队列+1,它的渲染顺序变为了红色正方体后
也就是说,渲染队列优先级 > 透明物体的渲染顺序(从前到后)
图6详解
相对于图1,将绿色正方体的ZTest改为了Greater,
也就是说蓝色正方体和绿色正方体重叠部分,大于模板缓冲区的部分通过测试,写入模板缓冲区
结果就是重叠部分为绿色,而未重叠部分的深度当然小于无穷大,所以没通过测试,自然也就不渲染。
红色部分正常。
shader截图
案例二:X-Ray效果
前面的墙没啥好说的,默认即可,后面的布偶兔用了两个Pass,一个是采用greater比较深度大小,将其颜色覆盖到墙上的外描边效果,一个是默认的布偶兔渲染效果;
实现思路
分为三部分:前边的墙、被墙挡住的X-Ray效果部分、高出墙部分的物体
回想一下前边6张图,哪张图是前边渲染完,后边渲染显示在先渲染完前边的?
也就是说,X-Ray效果部分我们使用到了ZTest :Greater,深度写入关闭
高出墙体部分是默认的渲染:LessEqual、ZWrite On
shader截图
代码理解:
写CGINCLUDE的好处:将顶点和片元着色器写在里边,在多passshade的时候,直接调用就可以了。
X-Ray绘制部分
和之前实现思路相同,ZWrite Off,ZTest Greater
Cull back 是剔除背面,为了优化
Blend SrcAlpha One :由于有一个透明的效果,除了上边的,还需要一步Blend,来做透明度混合
渲染类型和渲染队列为Transparent
正常绘制部分略
粒子系统中的深度测试
创建一个粒子系统ParticleSystem,可以看到默认的是透明的
创建一个材质,给到粒子上
此时粒子系统变成了这样
创建一个shader(Unlit),把粒子的贴图选上,附到材质上,效果如下(是不透明的),这显然不是我们要的效果
打开shader修改代码
首先回顾前边说的:Unity中默认的ZWrite On、ZTest是LessEqual、渲染队列是Geometry
我们想要让粒子透明,就需要做如下配置
渲染队列改为透明物体的渲染队列:Transparent
ZWrite Off,对于透明物体,是有相互叠加关系的,所以关掉写入
ZTest 默认(LessEqual),对于透明物体是这样的:如果透明物体前有不透明物体,此时 透明物体看不到;如果透明物体后面有不透明物体,此时透明物体可以看到。
要渲染透明物体,还要进行Blend操作:Blend One One(加法混合,叠加效果的显示)
修改完成后效果如下:(正是我们想要的效果)
深度测试的总结
扩展及参考资料
作业
把视频里效果过了一遍
(gif压缩的太狠,导致颜色有失真)
太菜了,等以后学到了更多再研究水面效果吧