技术美术百人计划学习笔记(图形3.1 深度测试和模板测试)
Stencil Test 模板测试
模板测试是什么
【从管线上来理解】
渲染管线中的逐片元操作阶段
- Pixel Ownership Test:控制对当前屏幕像素的使用权限,比如PIE时只对Scene窗口或者Game窗口有使用权限,其他窗口没有使用权限
- Scissor Test:对可渲染内部部分内容的控制
- Alpha Test:透明度测试,只能实现不透明效果或全透明效果
- Stencil Test:模板测试
- Depth Test:深度测试
逐片元操作阶段是不可编程的,他是由渲染管线和硬件自身自我规定好的,但是我们可以对其中的内容进行配置(高度可配置)
【从逻辑上来理解】
【从概念上来理解】
说到模板测试,就要先说到模板缓冲区。模板缓冲区与颜色缓冲区和深度缓冲区类似,模板缓冲区可以为屏幕上的每个像素点保存一个无符号整数值(通常的话是个8位整数)。这个值的具体意义视程序的具体应用而定。在渲染的过程中,可以用这个值与一个预先设定的参考值相比较,根据比较的结果来决定是否更新相应的像素点的颜色值。这个比较的过程被称为模板测试。模板测试发生在透明度测试(alpha test)之后,深度测试(depth test)之前。如果模板测试通过,则相应的像素点更新,否则不更新。
语法表示
比较函数(Comparison Function)
语义 | 解释 |
---|---|
Greater | 相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素 |
GEqual | 相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素 |
Less | 相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素 |
LEqual | 相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素 |
Equal | 相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素 |
NotEqual | 相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素 |
Always | 不管公式两边为何值,模板测试总是通过,渲染像素 |
Never | 不敢公式两边为何值,模板测试总是失败 ,像素被抛弃 |
更新值
语义 | 解释 |
---|---|
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,(然后继续自减) |
模板测试总结
- 使用模板缓冲区最重要的两个值:当前模板缓冲值(stencil Buffer Value)和模板参考值(reference Value)
- 模板测试主要是对这两个值使用特定的比较操作:Never、Always、Less、LEqual、Greater、Equal
- 模板测试之后要对模板缓冲区的值(stencil Buffer Value)进行更新操作,更新操作包括:Keep、Zero、Replace、IncrSat、DecrSat、Invert等等
- 模板测试之后可以根据结果对模板缓冲做不同的更新操作,比如模板测试成功操作Pass,模板测试失败操作Fail,深度测试失败操作ZFail,还有正对正面和背面精确更新操作PassBack,PassFront,FailBack等等
模板测试扩展
掌握模板测试的核心思想,利用模板测试的特性同其他测试或图形算法结合使用
参考资料
https://blog.csdn.net/u011047171/article/details/46928463
https://blog.csdn.net/liu_if_else/article/details/86316361
https://gwb.tencent.com/community/detail/127404
https://learnopengl-cn.readthedocs.io/zh/latest/04 Advanced OpenGL/02 Stencil testing/
https://www.patreon.com/posts/14832618
https://www.udemy.com/course/unity-shaders/
Depth Test 深度测试
深度测试的一些效果:
深度测试的认识
【从渲染管线上来理解】
在逐片元操作之中,模板测试之后,透明度混合之前进行的,同样是高度可配置的
其中在片元着色器之前还有一个Early-Z技术,利用了与深度测试相同的技术
【从逻辑上理解】
if(Zwrite On && (correntDepthValue ComparisonFunction DepthBufferValue)){ |
if(correntDepthValue ComparisonFunction DepthBufferValue){ |
【从概念上理解】
所谓深度测试,就是针对当前对象在屏幕上(更准确的说是frame buffer)对应的像素点,将对象自身的深度值与当前该像素点缓存的深度值进行比较,如果通过了,本对象在该像素点才会将颜色写入颜色缓冲区,否则否则不会写入颜色缓冲
【深度测试的发展】
深度缓冲区
深度缓冲就像颜色缓冲(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的
z-buffer中存储的是当前的深度信息,对于每个像素存储一个深度值
通过Zwrite和ZTest来调用Z-Buffer,实现想要的渲染结果
ZWrite
深度写入包括两种状态:ZWrite On与ZWrite Off
当我们开启深度写入的时候,物体被渲染时针对物体在屏幕(更准确地说是frame buffer)上每个像素的深度都写入到深度缓冲区;反之,如果是ZWrite Off,那么物体的深度就不会写入深度缓冲区。
但是,物体是否会写入深度,除了ZWrite这个状态之外,更重要的是需要深度测试通过,也就是ZTest通过,如果ZTest都没通过,那么也就不会写入深度了
ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况的话,一共就是四种情况:
1.深度测试通过,深度写入开启:写入深度缓冲区,写入颜色缓冲区;
2.深度测试通过,深度写入关闭:不写深度缓冲区,写入颜色缓冲区;
3.深度测试失败,深度写入开启:不写深度缓冲区,不写颜色缓冲区;
4.深度测试失败,深度写入关闭:不写深度缓冲区,不写颜色缓冲区;
ZTest
ZTest 状态 | 描述 |
---|---|
Greater | 深度大于当前缓存则通过 |
LEqual | 深度小于等于当前缓存则通过 |
Less | 深度小于当前缓存则通过 |
GEqual | 深度大于等于当前缓存则通过 |
Equal | 深度等于当前缓存则通过 |
NotEqual | 深度不等于当前缓存则通过 |
Always(Off) | 深度不论如何都通过 |
Never | 深度不论如何都不通过 |
默认ZWrite On和ZTest Lequal,深度缓存一开始为无穷大
渲染队列
Unity中内置的渲染队列,按照渲染顺序,从先往后进行排序,队列数越小的越先渲染,队列数越大的越后渲染
在Unity中设置渲染队列:Tags { “Queue” = “Transparent”}
,默认是Geometry
- 不透明物体的渲染顺序:从前往后
- 透明物体的渲染顺序:从后往前(OverDraw)
可以在shader的Inspector面板查看相关属性
Early-Z技术
传统的渲染管线中,ZTest其实是在Blending阶段,这时候进行深度测试,所有对象的像素着色器都会计算一遍,没有什么性能提升,仅仅是为了得出正确的遮挡结果,会造成大量的无用计算,因为每个像素点上肯定重叠了很多计算。因此 现代GPU中运用了Early-Z的技术,在Vertex阶段和Fragment阶段之间(光栅化之后,fragment之前)进行一次深度测试,如果深度测试失败,就不必进行fragment阶段的计算了,因此在性能上会有很大的提升。但是最终的ZTest仍然需要进行,以保证最终的遮挡关系结果正确。
前面的一次主要是Z-Cull为了裁剪以达到优化的目的,后一次主要是Z-Check,为了检查,如下图:
深度值
- 模型空间中:无深度信息存储
- 世界空间:存储在Z分量上
- 视察空间:存储在Z分量上(线性深度)
- 裁剪空间:准备投影,深度缓冲中存储z/w(透视投影非线性)
为什么深度缓冲区存储的是非线性深度而不是线性深度呢?
在精度有限的深度缓冲区中合理分配存储到精度,较近区域的深度信息分配更高的精度,较远区域的深度信息分配更低的精度(和gamma映射异曲同工)
正确的投影特性的非线性深度方程是和1/Z成正比的,这样基本做的是在Z很近的时候高精度和Z很远的时候低精度。例如1.0和2.0之间的z值,将变为1.0到0.5之间,这样在z很小的时候给了我们很高的精度,而50.0和100.0之间的z值将只占2%的浮点数的精度,这正是我们想要的。
深度值的精度不足可能会带来深度冲突的问题
所以在深度缓冲区中使用非线性深度,在近处用更高精度的数据存储,可以有效减少近处的深度冲突;而远处的因为不容易被看见,即便发生深度冲突也不会造成太多影响
如何防止深度冲突
- 一个技巧:让物体之间不要离得太近
- 另一个技巧:尽可能把近平面设置得远一些,可以有效在椎体中提高精度
- 另一个技巧:牺牲性能,使用更高精度的深度值(24→32位)
案例
- 先对渲染队列进行排列,再在同一个渲染队列中按深度进行排序
- 对于多pass shader,unity会从所以pass的所有的queue中挑选最靠前的,然后把这个物体放到一个队列中,然后他会根据pass的顺序逐pass执行
深度测试总结
- 使用深度缓冲区最重要的两个值:当前深度缓冲值ZBufferValue和深度参考值referenceValue,并通过比较操作获取理想的渲染效果
- Unity中的渲染顺序:先渲染不透明物体,顺序是从前往后;再渲染透明物体,顺序是从后到前
- 通过ZWrite和ZTest组合使用控制半透明物体的渲染
- 引入Early-Z技术后的深度测试相关渲染流程
- 深度缓冲区中存储的深度值为0到1范围的浮点值,且为非线性
深度测试扩展
参考资料
https://blog.csdn.net/puppet_master/article/details/53900568
https://learnopengl-cn.readthedocs.io/zh/latest/04 Advanced OpenGL/01 Depth testing/
https://docs.unity3d.com/cn/2018.4/Manual/SL-CullAndDepth.html
https://blog.csdn.net/yangxuan0261/article/details/79725466
《Unity ShaderLab 开发实战详解》
《Unity Shader 入门精要》