百人计划学习地址:图形 3.1 深度与模板测试 传送门效果示例
像素权限测试——》裁剪测试——》透明度测试——》模板测试——》深度测试——》混合等操作——》输出片元到帧缓冲区
就是通过一定条件判断该片元或者片元属性执行抛弃操作还是保留操作
用作遮罩效果的shader:默认ref为1,总是通过模板测试,并替换模板缓冲区的值
用作显示在遮罩后的shader,需要满足equal的像素才会通过模板缓冲区
最终效果:
图中可以看到深度测试是发生在逐片元操作的,而且处于模板测试之后,透明度混合之前
还有一个特殊的技术叫early-Z,它会发生在片元着色器之前
深度测试判断:先判断是否开启了ZWrite(深度写入)再片元的深度值与深度缓冲区中的值进行比较,如果满足条件则,写入深度值,不满足则忽略
颜色缓冲区也会进行同样的判断处理
与模板测试有什么异同?
主要在于多了一个ZWrite的判断
关于画家算法,大概可理解成:先画远处的物体,再画近处的物体,近处就可以遮挡住远处
但是这样会导致计算量过大,所以才需要使用深度缓冲区(Z-Buffer)来减少overdraw
与模板缓冲区不同的是,模板缓冲区存储的值是8位的int值,深度缓冲区通常是24位的
通过Z Write和Z Test来调用Z-Buffer,实现想要的渲染结果
简单的说:就是首先判断是否通过深度测试(Z-Test),不通过则什么都不会发生;
如果通过了深度测试,首先会将像素值写入颜色缓冲区,再判断是否开启了深度写入(ZWrite),如果开启则写入深度缓冲区,反之不写入。
需要注意的是,一开始缓存值是无穷大的,而默认的是小于等于缓存值则通过
首先看一下Unity中的几种内置的渲染队列,按照渲染顺序,从先到后进行排序
队列数越小的,越先渲染,队列数越大的,越后渲染。
名称 | 队列数 | 定义 |
---|---|---|
Background | 1000 | 最早被渲染的队列,一般用于设置游戏背景 |
Geometry | 2000 | 不透明的物体的渲染队列,也是Unity3d的默认队列,大多数物体都应该使用这个渲染队列 |
AlphaTest | 2450 | 含有透明通道,需要进行透明度测试(Alpha Test)物体的队列,为了保证在不透明物体之后渲染使用 |
Transparent | 3000 | 半透明物体的渲染队列,一般不写入深度缓冲区或者需要Alpha Blend的物体在该队列渲染 |
Overlay | 4000 | 最后渲染的物体队列,一般用作后处理,比如镜头光晕,屏幕贴片之类的 |
Unity默认的渲染队列是Geometry
另外,在shader中还有个Tag叫Render Type,这个没有Render Queue那么常用,这里也顺便记录一下:
注:Early-Z是在片元着色器之前进行的操作,因此被抛弃的片元是无法进入片元着色器操作的;通过的像素最终还是会进行ZTest操作
我们先来讨论下关于深度值精度:
在深度缓冲区中包含深度值介于0.0和1.0之间,从观察者看到其内容与场景中的所有对象的 z 值进行了比较。
我们需要转换这些视图空间 z 值到 [0,1] 的范围内,方法之一就是线性将它们转换为 [0,1] 范围内。下面的 (线性) 方程把 z 值转换为 0.0 和 1.0 之间的值 :
far和near分别是投影矩阵中可见视图截锥的远近值,Z值与深度值的关系如下图
物体接近近平面的时候,方程给出的深度值接近0.0,物体接近远平面时,方程给出的深度接近1.0。
然而,在实践中是几乎从来不使用这样的线性深度缓冲区。正确的投影特性的非线性深度方程是和1/z成正比的
z 值和产生深度缓冲区的值在下列图中的非线性关系:
从图中看到:物体的Z值越小(离得越近),深度值的精度越高,Z值越大(离得越远),深度值的精度越低;
现在我们再来理解为什么要时候用非线性的精度:
什么是深度冲突 Z-Fighting?
当两个平面或者三角形十分紧密相互平行,而深度缓冲区不具有足够的精度以至于无法得到哪个在前/在后,会导致两个形状不断切换导致怪异出问题,这种情况被称为深度冲突Z-fighting
因此,为了防止深度冲突,我们需要近处的物体需要更高的深度值精度,解决深度冲突还有以下几种方法:
略过,视频中已经讲解的非常详细
注:一个物体有多pass渲染时,Unity会把物体放到所有pass中队列最靠前的那个队列中,并根据代码逐Pass执行
想尝试做一下深度测试扩展中湖水的效果
首先在Unity3d中创建一个Panel作为水的物体,并加一个简单的顶点动画效果表现水的波动
顶点着色器部分
offset.y = sin(_Time.y * _Speed + (v.vertex.z * v.vertex.x * _WaveFrequency)) * _WaveHeight;
offset.xzw = 0;
o.vertex = UnityObjectToClipPos(v.vertex + offset);
并取消深度写入和开启混合
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
可以得到一个简单的湖水晃动的效果
然后开始实现湖水边缘的颜色渐变
这里需要用到深度贴图(depth texture)计算。根据“深度”计算水应呈现的颜色。
需要用到几个函数和变量
首先在顶点着色器中获取到像素得的屏幕坐标
o.screenPos = ComputeScreenPos(o.vertex);
然后在片元着色器中进行深度的计算
需要定义两种颜色,来对湖水中心和边缘进行一个lerp
float depthTextureValue = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, i.screenPos);
float depth = LinearEyeDepth(depthTextureValue);
float depthGap = depth - i.screenPos.w;
float depthLerp = saturate(depthGap) * _FadeStrengh;;
float4 waterColor = lerp(_Color, _DeepColor, depthLerp);
得到效果如下
然后开始实现边缘水花的效果
首先使用一张noise图进行采样,并让给他一个uv动画
float speed = _Time.x * _Speed * 0.5;
float4 var_WaterNoise = tex2D(_WaterNoiseTex, uv_WaterNoise);
再次用深度值计算边缘的位置,加入2个参数控制边缘的范围,再进行step运算并取反
最后与之前的水面效果相加
float foamStep = saturate((depthGap / _FoamWidth)) * _FoamStep;
float surfaceNoise = 1 - step(var_WaterNoise, foamStep);
return surfaceNoise + waterColor;
就可以得水花的效果了
笔记参考内容:https://gameinstitute.qq.com/community/detail/125962
https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/01%20Depth%20testing/
https://blog.csdn.net/puppet_master/article/details/53900568?spm=1001.2014.3001.5501
作业参考内容:https://zhuanlan.zhihu.com/p/144818695
https://zhuanlan.zhihu.com/p/350305151