零、遗留问题
首先让我们解答上一篇中所遗留的问题吧。
上一篇所谓的“边界融合”指的是:在我们视角空间下,相同描边材质的两个物体挨在一起的时候,两个物体中间视线遮挡处的描边边界不见了,看起来就像是给一个物体描边一样。
就如下面这张↓图
这是为什么呢?
答案其实上一篇已经解释过了噢,在上一篇文章里我们假设描边处参考值是1
,物体原本模型处参考值是 0
。
又因为他们所用同一个材质,那么进行模板测试的时候,较前的物体边框参考值 1
与较后物体原本模型参考值 0
进行比较。
1
和 0
显然不一样,那么就抛弃较前物体边框的颜色,保留原有较后物体原本模型(纹理)颜色。就是描边边界消融的效果啦。
一、非欧几里得空间
什么是非欧几里得空间?
那就得先解释什么是欧几里得空间。简单来说:我们现实所处在的三维立体空间就是欧几里得空间。
那非欧几里得空间,又简单来说:违反现实三维空间几何规律的空间就可以认为是非欧几里得空间辣。
比如大名鼎鼎的《传送门(Portal)》,还有近期的《笼中窥梦(Moncage)》都是很典型的例子。
二、实现原理
1. 我们首先来做个拆解,将上图正方体拆成 6 个面世界(后面也有三个面没画出来)。一个面显示一个世界。
2. 我们再单独拿出一个面世界来说明:
一个面世界在游戏引擎里其实由一个四边形面片(Quad)和一个(组)三维物体(GameObjects)组成。如下图:
想要达成非欧几里得的效果,只需要如下设置:
- 一个面世界中,只有通过这个四边形面片(Quad),才能看到这个里面的三维物体(GameObjects)。
- 各个面世界不相互干扰,一个面只负责显示一个世界。
我们首先解决第一点问题:单独一个面世界的显示
我们如何达到上图的效果呢?
首先在上图中我们发现:四边形面片(Quad)是没有颜色的。像全透明的一样。
在 ShaderLab 中,我们可以使用 ColorMask
指令来控制 Pass
渲染颜色的输出
ColorMask RGBA // 默认输出 Pass 计算出的所有通道颜色 ColorMask R // 只输出 Pass 计算出的 R 通道颜色 ... ColorMask 0 // 不输出任何通道颜色
这里我们使用 ColorMask 0
指令 就不会输出任何颜色,就好像全透明一样。
其次,也是最最最重要的设置:只有通过这个四边形面片(Quad),才能看到这个里面的三维物体(GameObjects)
(不用想,这系列都是讲 Stencil Test 模板测试的,绝对和它脱离不了关系 ←_←)。
没有错,又双叒叕是 Stencil Test 模板测试哈哈哈。
其实和轮廓描边本质上原理是一样的:
- 首先渲染四边形面片(Quad),并且向 Stencil Buffer 中写入一个
Ref
参考值,假设是1
吧; - 然后为面世界中的所有物体们设置一个
Ref
参考值也是1
,并且使用Comp Equal
进行模板测试比较。又因为只有在四边形面片(Quad)渲染过的地方,Stencil Buffer 中缓冲值才会1
,使其相等,才能通过模板测试,保留模型本身渲染的颜色(即正常显示出来); - 在其余地方,因为
Ref
参考值和缓冲值不相等,物体渲染出颜色将会被抛弃(即不能显示出来)。
我们再来解决第二点问题:面世界之间互不干扰
为了更好的区分各个面世界之间的边界。我们简单地做了上面↑这样的模型。
要想让面世界之间互不干扰:你显示你的,我显示我的。就像上图所显示那样。
其实很简单,只需要为每个面世界设置不同的 Ref
参考值就好了。
比如左边显示圆球的面世界中,四边形面片(Quad)与其中的物体们(GameObjects)的参考值都设置为 1
。
右边显示圆柱的面世界中,四边形面片(Quad)与其中的物体们(GameObjects)的参考值都设置为 2
。
原理和上面是一样的,只有相同参考值参会被显示。就不再赘述了。
三、具体实现
1. 首先创建两个 Shader 文件
StencilGeometry 是给面世界中的物体们(GameObjects)的,
StencilMask 是给面世界的四边形面片(Quad)的。(PS:从名字 Mask 遮罩看出,四边形面片的作用就像遮罩一样)
2. StencilMask 的核心代码
Shader "Unlit/StencilMask" { Properties { _RefValue("Stencil RefValue",Int) = 0 } SubShader { //Queue 渲染队列设置到 Geometry-1 是因为想在被遮挡物体渲染之前就进行渲染,写入 stencil 值 Tags { "RenderType"="Opaque" "Queue"="Geometry-1"} //[_RefValue] 就是我们自己设置的参考值 //Always 表示了无论如何都通过模板测试 //Replace 表示通过模板测试后用参考值替换掉 Stencil Buffer 中此像素原有的 stencil 值(缓冲值) Stencil{ Ref [_RefValue] Comp Always Pass Replace } //关闭深度写入,因为是四边形面片 Queue 较小,较先渲染 //如果还开启深度写入,后续的面世界内的物体都不能通过深度测试(Depth Test),就都不会被显示出来了。 ZWrite Off //关闭颜色写入 ColorMask 0 Pass{ //普通正常 Unlit 的 Pass } } }
以上基本有注释的地方,都是重点。具体原理就不再赘述了。
额外需要注意的地方:
- 渲染顺序 Queue 标签。
- 关闭 Zwrite 深度写入。
3. StencilGeometry 核心代码
Shader "Unlit/StencilGeometry" { Properties { _MainTex ("Texture", 2D) = "white" {} _Diffuse("Color Tint",Color) = (1,1,1,1) _RefValue("Stencil RefValue",Int) = 0 } SubShader { //除了要注意 Queue 要比 StencilMask 的要大,正常设置就好。 Tags { "RenderType"="Opaque" "Queue"="Geometry"} //[_RefValue] 就是我们自己设置的参考值 //Equal 表示了只有和缓冲值相等才通过测试,物体才能被显示出来 //Keep 表示通过模板测试后,保留原有缓冲值。 Stencil{ Ref [_RefValue] Comp Equal Pass Keep } Pass{ //面世界内物体正常的渲染 } } }
同上 基本有注释的地方,都是重点。具体原理就不再赘述了。
四、配置与展示
1. 各面世界的材质
我们需要为每个面世界单独制作不同 Ref
材质。
就比如其中一个面世界的 Geometry 和 Mask 材质中的 Stencil RefValue
参考值参数是 2
其他各个面世界的参考值都设置为不一样的,就可以得到以下效果辣~
本章参考资料:
五、下一章预告
Stencil 原理的镜面反射!!!
不错!!