案例1:描边
Shader "Unlit/StentilOutline"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Outline("OutLine",range(0,1))=0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Stencil {
Ref 0 //0-255
Comp Equal //default:always
Pass IncrSat //default:keep
Fail keep //default:keep
ZFail keep //default:keep
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
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);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
// return fixed4(1,1,0,1);
return col;
}
ENDCG
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 normal: NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Outline;
v2f vert (appdata v)
{
v2f o;
o.vertex=v.vertex+normalize(v.normal)*_Outline;
o.vertex = UnityObjectToClipPos(o.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return fixed4(1,1,1,1);
}
ENDCG
}
}
}
这个案例是拷贝过来的,用来做描边功能
stencil的默认值是0,而buffer的值在当前帧结束前是不清除的,所以它可以跨越不同的shader与pass。Stencil结构写在Subshader中,那么下面的所有pass中的stencil test都按此运行。理想环境下,第一个pass渲染前屏幕上所有像素的stencil值都是0,在该pass fragment shader片段作色器结束后,所有进行了渲染的像素stencil值都变为了1。
要点:stencil写在pass外面,每个pass都会执行stencil一遍
案例2:
效果:
这是用两个shader来实现,球的shader跟两个胶囊体
代码如下:
胶囊体(先渲染Geometry+1):
Shader "SoulCoder/Mask" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
ColorMask 0
ZWrite off
Stencil {
Ref 1
Comp always
Pass replace
}
CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,1,0,1);
}
ENDCG
Pass {
Cull Front
ZTest Less
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
Pass {
Cull Back
ZTest Greater
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
球体(后渲染Geometry+2):
Shader "SoulCoder/Model" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0)
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry+2"}
ColorMask RGB
Cull Front
ZTest Always
Stencil {
Ref 0
Comp Equal
}
CGPROGRAM
#pragma surface surf Lambert
float4 _Color;
struct Input {
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = _Color.rgb;
o.Normal = half3(0,0,-1);
o.Alpha = 1;
}
ENDCG
}
}
分析:第一个shader因为ColorMask 0,所以不输出颜色,然后把其渲染的范围模板值写入1
第二个shader的ref值为0,因为上一个shader pass完后,胶囊体的范围缓冲区的ref是1,所以球体跟胶囊体公共的区域,ref1不等于ref0,公共区域不满足Comp Equal ,所以丢弃片元,但是背景是ref0,故显示透明。球体除胶囊体范围的区域,ref0,跟背景的ref0相等,故全部通过测试,球体渲染。
案例3:
原文地址
效果:
这是用一个蒙板来控制角色的显示,目的是让在想显示的区域显示角色,在不想让他显示的区域不显示,有点像裁剪。代码如下:
蒙版代码:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/UnlitStencilMaskVF" {
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry-1"}
CGINCLUDE
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(1,1,0,1);
}
ENDCG
Pass {
ColorMask 0
ZWrite Off
Stencil
{
Ref 1
Comp Always
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
角色的代码:
Shader "Custom/UnlitStencilVF" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "Queue" = "Geometry""RenderType"="Opaque" }
LOD 100
Pass {
Stencil {
Ref 1
Comp Equal
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
UNITY_FOG_COORDS(1)
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
}
分析:1.首先看遮罩的queue,先渲染遮罩蒙版shader,(蒙版的queue=Geometry,角色的queue=Geometry+1),
ColorMask 0意味着不输出颜色,然后关闭zwrite,防止后面的角色因为深度原因被剔除掉,然后ref 1 Comp Always是把模板的引用值设置为1,为了之后在渲染角色的时候,让ref=1的部分通过模版测试。Pass Replace此时模板缓冲区ref全部替换为1
2.然后渲染角色shader,在检测渲染状态的时候,模板测试过程中 Ref 1 Comp Equal,意思是只有当引用值跟模板缓冲区的值都为1,测试通过,此时因为在遮罩区域的ref为1,遮罩外面全部是0,所以,只有遮罩内的角色像素才能显示出来