零. 遗留问题
首先让我们解答上一篇中所遗留的问题吧.
上一篇所谓的"边界融合"指的是 :在我们视角空间下,相同描边材质的两个物体挨在一起的时候,两个物体中间视线遮挡处的描边边界不见了,看起来就像是给一个物体描边一样.
就如下面这张↓图
这是为什么呢?
答案其实上一篇已经解释过了噢,在上一篇文章里我们假设描边处参考值是1,物体原本模型处参考值是0.
又因为他们所用同一个材质,那么进行模板测试的时候,较前的物体边框参考值1与较后物体原本模型参考值0进行比较.
1和0显然不一样,那么就抛弃较前物体边框的颜色,保留原有较后物体原本模型(纹理)颜色.就是描边边界消融的效果啦.
一. 非欧几里得空间
什么是非欧几里得空间?
那就得先解释什么是欧几里得空间. 简单来说 : 我们现实所处在的3维立体空间就是欧几里得空间.
那非欧几里得空间, 又简单来说 : 违反现实三维空间几何规律的空间就可以认为是非欧几里得空间辣.
比如大名鼎鼎的《传送门(Portal)》,还有近期的《笼中窥梦(Moncage)》都是很典型的例子.
《笼中窥梦(Moncage)》游戏
二. 实现原理
1. 我们首先来做个拆解,将上图正方体拆成6个面世界(后面也有三个面没画出来). 一个面显示一个世界.
2. 我们再单独拿出一个面世界来说明 :
一个面世界在游戏引擎里其实由一个四边形面片(Quad)和一个(组)三维物体(GameObjects)组成. 如下图
想要达成非欧几里得的效果,只需要如下设置:
- 一个面世界中,只有通过这个四边形面片(Quad),才能看到这个里面的三维物体(GameObjects)
- 各个面世界不相互干扰,一个面只负责显示一个世界.
我们首先解决第1点问题 : 单独一个面世界的显示
我们如何达到上图的效果呢?
首先在上图中我们发现 :四边形面片(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原理的镜面反射!!!
暂无关于此日志的评论。