- 用于精确控制像素丢弃的逐片元操作,通过模板缓冲区(8位整数/像素)实现复杂遮罩效果。
- 支持8种比较函数和6种缓冲操作
- 当前模版操作,只能在Shader文件中书写操作,ShaderGraph中无法直接使用模版指令。
【从UnityURP开始探索游戏渲染】专栏-直达
核心配置预览
- 核心配置所有配置列出(非全必要):
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)
- 清除模板缓冲:
-
测试阶段-逐片元
plaintext 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
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的区域内渲染物体。
Shader "Custom/StencilObject" { SubShader { Stencil { Ref 1 Comp Equal // 仅渲染模板值=1的区域 Pass Keep } Pass { // 正常着色代码... } } }
示例2 外轮廓描边
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开始探索游戏渲染】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)