前言:Stencil Buffer(模板缓存)做为一个核心缓冲区类型,简单介绍一下实现和功能。然后再补充一下剔除和深度的功能。
Stencil Buffer
1.什么是stencil buffer
- 如果将屏幕上所有像素想象成一串连续的0组成的矩形,那么stencil bufer的作用就是将某些0变为1,2,3 ..255
- 在每个pass中可以决定只渲染某个特定stencil值的像素并抛弃对其他非该值像素的操作
- stencil bufer与depth buffer一样,都是缓冲区,存在于显存内的某一片区域中
- 目前的显卡架构中,stenci buffer与depth bufer是在一起的,比如在depth/stencil缓冲区某个32位的区域中,有24位记录着像素A的depth数据,紧接着8位记录着像素A的stencil数据
- stencil测试在管线中写入与读取的位置在透明测试(AlphaTest)和深度测试(DepthTest)之间
2.常用语法格式
Stencil
{
Ref reference Value // 参考值 默认値为 0
Comp comparisonFunction // 定义参考值与缓冲值比较的方法 默认值为Always
Pass stencilOperation // 定义当通过模板测试时,根据参考值对缓冲值的处
// 理方法 默认值为keep
Fail stencillperation // 定义当没有通过模板测试时,根据参考值对缓冲值
// 的处理方法 默认为keep
ZFail stencilOperation // 定义当通过模板测试却没有通过深度测试时,根据
// 参考对缓冲值的处理方法 默认为keep
}

3.代码实现
使用stencil buffer实现物体描边。
之前实现描边通过法线拓展并进行前剔除实现物体边缘膨胀描边,而通过stencil buffer实现的是通过渲染顺序,跳过法线扩展和模型的重合部分实现描边。由于描边通过Ref值Comp Equal(相等判断),多个部分重合模型的描边会丢失重合部分的描边。
cs
Shader "MyCustom/StencilOutline"
{
Properties
{
// 描边宽度
_Edge ("_Edge", Float) = 0.1
// 物体颜色
_Color ("_Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Stencil
{
Ref 0 // 0-255
Comp Equal // 已经定义的ref 和 stencil buff值做对比
Pass IncrSat // ref 值自增1
}
// 第一部分Pass渲染物体,施加lambert效果,由于第一次经过Stencil Buffer,默认值为0对比通过,执行Pass逻辑,屏幕这部分值自增一为1。
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 worldNormal = normalize(i.worldNormal);
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
float diffuse = saturate(dot(worldNormal, worldLight) * 0.5 + 0.5);
// ambient
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
return float4(diffuse + ambient, 1);
}
ENDCG
}
// 第二部分Pass法线扩展实现描边,由于第二次经过Stencil Buffer,覆盖区域默认值为0对比之前模型渲染的屏幕部分的值为1,不通过跳过渲染。只有法线扩出来的部分默认值为0,进行渲染并执行Pass逻辑自增1。
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
float _Edge;
float4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = v.vertex + normalize(float4(v.normal, 0)) * _Edge;
o.vertex = UnityObjectToClipPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
通过stencil buffer实现X射线透视效果。
渲染顺序:x射线物体 --> 遮挡物体 --> 被遮挡物体
stencil buffer值:x射线物体(99,永远通过,赋值为99),遮挡物体(0,相等通过,自增1),被遮挡物体(99,相等通过,自增1)
cs
Shader "MyCustom/StencilXrayMask"
{
Properties
{
_Color ("_Color", Color) = (1, 1, 1, 1)
}
SubShader
{
//Tags { "RenderType"="Opaque" }
ZWrite Off
ColorMask 0
Stencil
{
Ref 99 // 0-255,例子中是99
Comp always // 比较的结果是永远通过
Pass replace // pass的结果是替换,就是拿99替换 stencil buff的值,由于优先渲染,X射线遮罩部分的值为99,遮挡物值为0被跳过渲染,被遮挡物值为99进行渲染。
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
float4 _Color;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
}
}
Culling和Z(剔除和深度)
1.Cull剔除
- 剔除语法:Cull Back | Front | Off
- Back:不渲染背离观察者的多边形
- Front:不渲染面向观察者的多边形,用于从里到外翻转对象
- Off:禁用剔除-绘制所有面
- VFace:表示一个渲染表面是否面朝相机。使用这个语义需要 Shader Model 3.0,也就是#pragma target 3.0
在使用Cull Off绘制所以面时,可以在片元着色器中使用VFace参数表示正反面
fixed4 frag (v2f i, float facing : VFACE) : SV_Target
{
// facing>0为正面,<0为反面
float4 col = lerp(_Color0, _Color1, facing > 0);
float3 normal = i.normal * 0.5 + 0.5;
col.rgb = lerp(normal, float3(0, 0, 1), facing > 0);
return col;
}
2.深度
深度缓存(depth buffer)
存储着准备要绘制在屏幕上的像素点的深度值。
如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把该像素的深度值和深度缓存的深度值进行比较。
深度测试 (depth test)将要绘制的新像素的z值与深度缓冲区中对应位置的z值进行比较,如果比深度缓存中的值小,那么用新像素的颜色值更新深度缓存中对应像素的颜色值。
ZTest Less | Greater | LEqual | GEqual | Equal | NotEqual | Always应如何执行深度测试
默认值为 LEqual
ZWrite On l Off
- 控制是否将此对象的像素写入深度缓冲区
- 默认值为 On
- 如果要绘制实体对象,将其保留为 On。如果要绘制半透明效果,请切换到ZWrite Off
Offset Factor, Units
- 允许使用两个参数指定深度偏移