- 用于精确控制像素丢弃的逐片元操作,通过模板缓冲区(8位整数/像素)实现复杂遮罩效果。
- 支持8种比较函数和6种缓冲操作
- 当前模版操作,只能在Shader文件中书写操作,ShaderGraph中无法直接使用模版指令。
【从UnityURP开始探索游戏渲染】专栏-直达
核心配置预览
- 核心配置所有配置列出(非全必要):
c
Stencil {
Ref 2 // 必要模版数值
ReadMask 255
WriteMask 255
Comp Greater // 必要比较操作符
Pass Replace // 必要通过后的操作
Fail Keep
ZFail DecrSat
}
1. 核心配置命令
Ref
:设置参考值(0-255整数),用于与模板缓冲区比较ReadMask
:读取掩码(0-255),按位与操作后比较(默认255)WriteMask
:写入掩码(0-255),控制可修改的缓冲区位(默认255)
2. 比较函数命令
Comp
支持以下枚举值:
Always
/Never
:始终通过/拒绝Less
/Greater
:小于/大于时通过Equal
/NotEqual
:等于/不等于时通过LessEqual
/GreaterEqual
:小于等于/大于等于时通过
3. 操作命令
Pass
/Fail
/ZFail
支持的操作:
Keep
:保持原值Zero
:置零Replace
:用Ref值替换IncrSat
/DecrSat
:饱和增减(0/255边界)IncrWrap
/DecrWrap
:循环增减Invert
:按位取反
模板测试具体过程
-
缓冲区初始化
- 清除模板缓冲:
GL.Clear(ClearBufferMask.StencilBufferBit)
- 设置初始值:
GL.StencilMask(0xFF)
(默认全255)
- 清除模板缓冲:
-
测试阶段-逐片元
cplaintext if (片元模板值 [比较函数] 参考值) { 执行通过操作(如保留像素) } else { 执行失败操作(如丢弃像素) }
-
缓冲更新
- 根据测试结果修改模板缓冲值(可选)
比较函数(StencilFunc)
函数 | 含义 | OpenGL常量 |
---|---|---|
Never |
永远不通过 | GL_NEVER |
Always |
永远通过 | GL_ALWAYS |
Less |
模板值 < 参考值 | GL_LESS |
LEqual |
模板值 ≤ 参考值 | GL_LEQUAL |
Greater |
模板值 > 参考值 | GL_GREATER |
GEqual |
模板值 ≥ 参考值 | GL_GEQUAL |
Equal |
模板值 == 参考值 | GL_EQUAL |
NotEqual |
模板值 != 参考值 | GL_NOTEQUAL |
缓冲操作(StencilOp)
操作组合 | 含义 |
---|---|
Keep |
保持当前模板值不变(默认) |
Zero |
将模板值设为0 |
Replace |
用参考值替换模板值 |
Incr /IncrWrap |
模板值+1(超过255时,前者截断后者循环) |
Decr /DecrWrap |
模板值-1(低于0时,前者截断后者循环) |
Invert |
按位取反(~操作) |
示例1
- 遮罩区域shader
c
Shader "Custom/StencilMask" {
SubShader {
Tags { "Queue"="Geometry-1" } // 优先渲染
ColorMask 0 // 不写入颜色
ZWrite Off
Stencil {
Ref 1
Comp Always
Pass Replace // 将模板值设为1
}
Pass {} // 空Pass仅用于写入模板
}
}
- 物体模版测试shader,该Shader仅在模板值为1的区域内渲染物体。
c
Shader "Custom/StencilObject" {
SubShader {
Stencil {
Ref 1
Comp Equal // 仅渲染模板值=1的区域
Pass Keep
}
Pass {
// 正常着色代码...
}
}
}
示例2 外轮廓描边
c
Shader "Custom/StencilOutline" {
Properties {
_MainTex ("Base Texture", 2D) = "white" {}
_OutlineColor ("Outline Color", Color) = (1,0,0,1)
_OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.05
}
SubShader {
Tags {
"RenderPipeline"="UniversalRenderPipeline"
"RenderType"="Opaque"
}
// Pass 1: 正常渲染角色并写入模板
Pass {
Name "Character"
Tags
{
"LightMode" = "UniversalForward"
}
Stencil {
Ref 1
Comp Always
Pass Replace
ZFail Keep
}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes {
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings {
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
Varyings vert(Attributes IN) {
Varyings OUT;
OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
OUT.uv = IN.uv;
return OUT;
}
half4 frag(Varyings IN) : SV_Target {
return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
}
ENDHLSL
}
// Pass 2: 渲染轮廓
Pass {
Name "Outline"
Cull Front
Stencil {
Ref 1
Comp NotEqual
Pass Keep
}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes {
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings {
float4 positionCS : SV_POSITION;
};
CBUFFER_START(UnityPerMaterial)
float4 _OutlineColor;
float _OutlineWidth;
CBUFFER_END
Varyings vert(Attributes IN) {
Varyings OUT;
float3 posWS = TransformObjectToWorld(IN.positionOS.xyz * (1 + _OutlineWidth));
float3 normalWS = TransformObjectToWorldNormal(IN.normalOS);
// posWS += normalWS * _OutlineWidth; // 沿法线方向扩展
OUT.positionCS = TransformWorldToHClip(posWS);
return OUT;
}
half4 frag(Varyings IN) : SV_Target {
return _OutlineColor;
}
ENDHLSL
}
}
}
【从UnityURP开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)