图形学进阶——深度与模板测试

Z Test & Stencil Test深度测试与模板测试

百人计划学习地址:图形 3.1 深度与模板测试 传送门效果示例

一、模板测试 Stencil Test

流程:
图形学进阶——深度与模板测试_第1张图片

1. 什么是模板测试

① 从渲染管线出发

图形学进阶——深度与模板测试_第2张图片
像素权限测试——》裁剪测试——》透明度测试——》模板测试——》深度测试——》混合等操作——》输出片元到帧缓冲区

② 从逻辑上理解

就是通过一定条件判断该片元或者片元属性执行抛弃操作还是保留操作
图形学进阶——深度与模板测试_第3张图片

③ 从书面概念上理解

图形学进阶——深度与模板测试_第4张图片
需要注意的是:模板测试发生在透明度测试之后,深度测试之前

2 .模板测试的语法表示

图形学进阶——深度与模板测试_第5张图片
具体的含义如下:

  • referenceValue :自己定义的模版参考值,每个片元或者说像素点就是用这个值进行比较
  • readMask::读取掩码,取值范围也是0-255的整数,默认值为255。即读取的时候不对referenceValue和stencilBufferValue产生效果,读取的还是原始值
  • writeMask:输出掩码,当写入模板缓冲时进行掩码操作(按位与&),writeMask取值范围是0-255的整数,默认值也是255,即当修改stencilBufferValue值时,写入的仍然是原始值。
  • ComparisonFunction :重点,进行模板测试时候的判断方法,具体有以下几种
    图形学进阶——深度与模板测试_第6张图片
  • Pass stencilOperation:条件满足后的更新操作
  • Fail stencilOperation:条件不满足后的更新操作
  • ZFail stencilOperation:模板测试通过,但是深度测试不通过的更新操作

上诉三种具体更新操作方法的值如下
图形学进阶——深度与模板测试_第7张图片

3. Unity3d中使用模板测试

用作遮罩效果的shader:默认ref为1,总是通过模板测试,并替换模板缓冲区的值
图形学进阶——深度与模板测试_第8张图片
用作显示在遮罩后的shader,需要满足equal的像素才会通过模板缓冲区
图形学进阶——深度与模板测试_第9张图片
最终效果:
图形学进阶——深度与模板测试_第10张图片

4 .总结

图形学进阶——深度与模板测试_第11张图片

5. 模板测试扩展

图形学进阶——深度与模板测试_第12张图片

二、深度测试 Z-Test

1.一些效果

图形学进阶——深度与模板测试_第13张图片

2. 什么是深度测试

①从渲染管线上看

图形学进阶——深度与模板测试_第14张图片
图中可以看到深度测试是发生在逐片元操作的,而且处于模板测试之后,透明度混合之前

还有一个特殊的技术叫early-Z,它会发生在片元着色器之前

② 从逻辑上理解

图形学进阶——深度与模板测试_第15张图片
深度测试判断:先判断是否开启了ZWrite(深度写入)再片元的深度值与深度缓冲区中的值进行比较,如果满足条件则,写入深度值,不满足则忽略

颜色缓冲区也会进行同样的判断处理

与模板测试有什么异同?

主要在于多了一个ZWrite的判断

③从书面概念上理解

图形学进阶——深度与模板测试_第16张图片

④从发展上理解

图形学进阶——深度与模板测试_第17张图片
关于画家算法,大概可理解成:先画远处的物体,再画近处的物体,近处就可以遮挡住远处
但是这样会导致计算量过大,所以才需要使用深度缓冲区(Z-Buffer)来减少overdraw

3. 深度缓冲区 Z-Buffer

图形学进阶——深度与模板测试_第18张图片
与模板缓冲区不同的是,模板缓冲区存储的值是8位的int值,深度缓冲区通常是24位的

通过Z Write和Z Test来调用Z-Buffer,实现想要的渲染结果

4. Z Write

图形学进阶——深度与模板测试_第19张图片
简单的说:就是首先判断是否通过深度测试(Z-Test),不通过则什么都不会发生;
如果通过了深度测试,首先会将像素值写入颜色缓冲区,再判断是否开启了深度写入(ZWrite),如果开启则写入深度缓冲区,反之不写入。

5. ZTest的比较操作

图形学进阶——深度与模板测试_第20张图片
需要注意的是,一开始缓存值是无穷大的,而默认的是小于等于缓存值则通过

6 .渲染队列

首先看一下Unity中的几种内置的渲染队列,按照渲染顺序,从先到后进行排序
队列数越小的,越先渲染,队列数越大的,越后渲染。

名称 队列数 定义
Background 1000 最早被渲染的队列,一般用于设置游戏背景
Geometry 2000 不透明的物体的渲染队列,也是Unity3d的默认队列,大多数物体都应该使用这个渲染队列
AlphaTest 2450 含有透明通道,需要进行透明度测试(Alpha Test)物体的队列,为了保证在不透明物体之后渲染使用
Transparent 3000 半透明物体的渲染队列,一般不写入深度缓冲区或者需要Alpha Blend的物体在该队列渲染
Overlay 4000 最后渲染的物体队列,一般用作后处理,比如镜头光晕,屏幕贴片之类的

Unity默认的渲染队列是Geometry

  • 不透明物体的渲染顺序是从前往后
  • 透明物体的渲染顺序是从后往前(因此会发生OverDraw

另外,在shader中还有个Tag叫Render Type,这个没有Render Queue那么常用,这里也顺便记录一下:

  • Opaque: 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)。
  • Transparent:用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)。
  • TransparentCutout: 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)。
  • Background: 天空盒着色器。
  • Overlay: GUITexture,镜头光晕,屏幕闪光等效果使用的着色器。
  • TreeOpaque: 地形引擎中的树皮。
  • TreeTransparentCutout: 地形引擎中的树叶。
  • TreeBillboard: 地形引擎中的广告牌树。
  • Grass: 地形引擎中的草。
  • GrassBillboard: 地形引擎何中的广告牌草。

7. 简述 Early-Z技术

图形学进阶——深度与模板测试_第21张图片
注:Early-Z是在片元着色器之前进行的操作,因此被抛弃的片元是无法进入片元着色器操作的;通过的像素最终还是会进行ZTest操作

8 .深度值

深度值是从哪里来的?
图形学进阶——深度与模板测试_第22张图片
为什么深度缓冲区中的值不是线性的?

我们先来讨论下关于深度值精度

在深度缓冲区中包含深度值介于0.0和1.0之间,从观察者看到其内容与场景中的所有对象的 z 值进行了比较。
我们需要转换这些视图空间 z 值到 [0,1] 的范围内,方法之一就是线性将它们转换为 [0,1] 范围内。下面的 (线性) 方程把 z 值转换为 0.0 和 1.0 之间的值 :
在这里插入图片描述
far和near分别是投影矩阵中可见视图截锥的远近值,Z值与深度值的关系如下图
图形学进阶——深度与模板测试_第23张图片
物体接近近平面的时候,方程给出的深度值接近0.0,物体接近远平面时,方程给出的深度接近1.0。

然而,在实践中是几乎从来不使用这样的线性深度缓冲区。正确的投影特性的非线性深度方程是和1/z成正比的
在这里插入图片描述
z 值和产生深度缓冲区的值在下列图中的非线性关系:
图形学进阶——深度与模板测试_第24张图片
从图中看到:物体的Z值越小(离得越近),深度值的精度越高,Z值越大(离得越远),深度值的精度越低;

现在我们再来理解为什么要时候用非线性的精度:

  • 根据人眼的特性,离得越近的物体,层次的精度需要更精确,离得越远则不需要太精确
  • 防止深度冲突 Z-Fighting

什么是深度冲突 Z-Fighting?

当两个平面或者三角形十分紧密相互平行,而深度缓冲区不具有足够的精度以至于无法得到哪个在前/在后,会导致两个形状不断切换导致怪异出问题,这种情况被称为深度冲突Z-fighting
图形学进阶——深度与模板测试_第25张图片

因此,为了防止深度冲突,我们需要近处的物体需要更高的深度值精度,解决深度冲突还有以下几种方法:

  • 物体之间不要离得太近,防止他们的三角形重叠
  • 尽可能把近平面设置得远一些。前面我们讨论过越靠近近平面的位置精度越高。所以我们移动近平面远离观察者,我们可以在椎体内很有效的提高精度。
  • 放弃一些性能来得到更高的深度值的精度。大多数的深度缓冲区都是24位。但现在显卡支持32位深度值,这让深度缓冲区的精度提高了一大节。

9. Unity中的使用

略过,视频中已经讲解的非常详细

注:一个物体有多pass渲染时,Unity会把物体放到所有pass中队列最靠前的那个队列中,并根据代码逐Pass执行

10.深度测试总结

图形学进阶——深度与模板测试_第26张图片

11.深度测试的扩展

图形学进阶——深度与模板测试_第27张图片

作业

想尝试做一下深度测试扩展中湖水的效果

首先在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)计算。根据“深度”计算水应呈现的颜色。
需要用到几个函数和变量

  • _CameraDepthTexture:Unity内置的变量。用于表示camera的深度贴图。
  • SAMPLE_DEPTH_TEXTURE_PROJ:对深度贴图进行采样。该方法接受深度纹理和纹理坐标这两个参数。纹理坐标通常是由顶点着色器输出插值而得的屏幕坐标。
  • LinearEyeDepth:通过纹理采样得到深度值后,这些深度值往往是非线性的,这种非线性来自于透视投影使用的裁剪矩阵。通过LinearEyeDepth把投影后的深度值变换到线性空间下。

首先在顶点着色器中获取到像素得的屏幕坐标

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;

就可以得水花的效果了

材质球设置如下
图形学进阶——深度与模板测试_第28张图片


笔记参考内容: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

你可能感兴趣的:(Unity3d,Shader,shader,unity)