最近因为家庭原因回老家了,在家里办了两三周杂七杂八的事情后,考虑是否继续去北上广深漂着(不得不说码农就是这个命啊)。家人和朋友让我在老家先呆着再说,于是准备了尝试找下工作。
前两天碰到面试官问了一个问题,就是关于模板缓冲区的原理,我只说了个大概而在细节上就卡壳了,因为我自己项目中实在很少用到这个,所以基本都搞忘了,今天晚上有点时间就回顾一下。
首先贴上这个以前写的三大缓冲区概念介绍:https://blog.csdn.net/yinhun2012/article/details/86702380,然后针对模板缓冲区来详细拓展一下。
首先官方解释就是,模板缓冲区可以为屏幕上每个像素保存一个无符号整数(一般为8bit整数,这和alpha通道的8bit整数相同),在渲染流程中,使用每个像素的模板数值与一个预先设定好的模板参考值相比较(一般是按位与操作),根据这个比较的结果来决定是否更新相应像素的颜色值,这个比较的过程叫做模板测试。模板测试发生在透明度测试之后,深度测试之前。
看完上面的解释,我们大致就能猜到模板缓冲区就是用来进行region mask(区域遮罩)渲染用的,因为我们完全可以通过控制模板参考值和预设值进行片段渲染/丢弃的操作。聊完上面的原理讲解后,那么接下来就是具体的实现细节和code了。
unity中定义模板缓冲数据的结构如下:
stencil
{
Ref referenceValue
ReadMask readMask
WriteMask writeMask
Comp comparisonFunction
Pass stencilOperation
Fail stencilOperation
ZFail stencilOperation
}
依次来解释一下
①.Ref referenceValue 设定一个预设模板参考值,用来和已存在的模版缓冲值进行比较。
②.ReadMask readMask 读掩码,具体操作就是按位&模版缓冲值,readMask默认值为二进制的11111111,所以&模版缓冲值就是原始值。
③.WriteMask writeMask 写掩码,和上面的一样,写入的值也是原始值。
④.Comp comparisonFunction 对比函数,也就是预设模板参考值和模版缓冲值进行比较的函数,默认值always(包含大量预设对比函数,后面有贴出来)。
⑤.Pass stencilOperation 当模板测试(含深度测试)通过,则stencilOperation对预设模版值和模版缓冲值进行函数操作(包含大量预设操作函数,后面有贴出来)。
⑥.Fail stencilOperation 相反就是失败了,也进行stencilOperation操作。
⑦.ZFail stencilOperation 当模板测试通过,而深度Ztest失败,也进行stencilOperation操作。
这上面的结构怎么使用和实现效果呢?首先我要来说明一下,默认情况下,unity渲染初始的默认模板缓冲值为0,这点就跟我在透明原理章节表示的图形一层一层混合中背景底板是默认黑色一样,总是存在一个默认值的,那么我们构建如下shader代码用来渲染一个sphere,可以看出一些stencil的端倪,如下:
Shader "Unlit/StencilNormalUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_StencilVal("Ref Val", int) = 1 //给这个shader的定义一个预设模板参考值
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
//这里只需要定义stencil结构体,unity cg runtime就可以根据我们开发人员设定的数据进行解析渲染
Stencil
{
Ref [_StencilVal] //绑定预设模版参考值
Comp Greater //设定一个比较函数类型,Greater表示大于
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
这段shader代码主要要理解的就是stencil结构体内部:
①.Ref [_StencilVal] 这个就是Properties外包传入预设模版值,没什么好说的。
②.Comp Greater 这个则是定义了Comp函数预设类型,Greater表示如果Ref预设模版值比模版缓冲值大,那么就渲染,否则丢弃。
效果图如下:
这个很容易理解吧,因为unity默认的模版缓冲值为0,所以我给shader预设模板值为0/1/2之后,就会表现出Greater比较函数的渲染效果。
这里顺便贴一下UnityAPI上找个Comp Function的一些预设类型(),如下:
那么接下来我们继续编写一个stencil shader,如下:
Shader "Unlit/StencilMaskUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_StencilVal("Ref Val", int) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
//将渲染通道关闭,则不渲染此shader载体
ColorMask 0
//关闭深度测试/写入,避免其他的影响
ZWrite off
ZTest off
Stencil
{
Ref [_StencilVal]
Comp Greater
//如果通过模板测试后,则更新模版缓冲值,也就是将_StencilVal写入到模版缓冲区
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
这个shader的使用方法,首先通过文字解释一下,首先第一个sphere载体的stencil预设模版值为1,那么正常渲染sphere(unity默认模版缓冲值为0),同时我们将这个shader赋予一个cube,同时将cube的预设模版值设置为2,并且进行Pass Replace,那么我们就可以将cube覆盖的区域的模版缓冲值更新为2,那么在sphere的模版缓冲值为1的前提下,cube区域就会覆盖遮罩和sphere的相交区域,如下:
这个比较容易理解吧,cube因为ColorMask 0关闭了渲染,那么这个cube只用于更新覆盖区域的模版缓冲值,所以就能遮罩sphere。
这里贴一下Pass function的预设类型,如下:
当然我们还可以反过来这样做,首先将sphere的ref val = 1,comp方法设置为equal相等才渲染,那么sphere则不会渲染。同时我们将cube的ref val设置为1,同时将comp方法设置为greater,那么cube就会更新区域的模版缓冲值为1,则cube与sphere相交的区域因为预设模版值和模版缓冲值相同则渲染,如下:
则渲染效果和上面的效果刚好相反。
so,如果我有时间就继续写博客。