Unity Shader URP 使用模板测试 · 深度测试实现秘境空间效果

一、效果预览

传送门、X光透视窗、镜中世界、密室入口------这些"开个洞看见另一个空间"的效果,背后都是同一套技术:模板缓冲区(Stencil Buffer)。它能让GPU在像素级别精确决定"哪里能画、哪里不能画"。

最终效果:一个原本隐藏在场景中的"秘境空间",只有在特定的遮罩区域内才能被看到,离开遮罩区域则完全不可见。

二、原理简述

2.1 模板测试(Stencil Test)的本质

模板缓冲区的本质是:在颜色缓冲区和深度缓冲区之外,再给每个像素挂一个8位整数标签(取值范围0~255)。绘制时按照标签做"准入检查"。

模板测试发生在片元着色器之后、写入颜色之前。它读取该像素当前的模板值,与Shader里指定的参考值(Ref)做比较(Comp),通过则继续走深度测试和颜色写入;不通过则丢弃。

2.2 "秘境空间"的双Pass核心原理

实现秘境空间效果,需两个Shader配合完成两个Pass(或使用两个独立的Shader):

第一步:遮罩Pass(Mask) ------ 先画一个不可见的"门框"几何体,把它覆盖到的像素区域的模板值,统统写入指定数值(如1)。颜色不写入,深度可选写可选不写,但通常关闭深度写入以避免遮挡后面的物体。

第二步:内容Pass(Content) ------ 再画"秘境空间"的物体,配置成"只画模板值等于1的像素"。结果就是秘境空间的内容只出现在遮罩形状里。

伪代码示意:

cs 复制代码
// Pass 1: 遮罩------只盖戳,不画颜色
Stencil {
    Ref 1
    Comp Always
    Pass Replace
}
ColorMask 0

// Pass 2: 内容------只画带戳的像素
Stencil {
    Ref 1
    Comp Equal
    Pass Keep
}

三、完整 Shader 代码

以下提供两个Shader:Portal_Mask_URP (贴在门框/秘境入口上)和 Portal_Content_URP(贴在秘境空间内的所有物体上)。

3.1 Portal_Mask_URP ------ 模板写入器(遮罩)

cs 复制代码
Shader "Custom/Portal_Mask_URP"
{
    Properties
    {
        [IntRange] _StencilID ("Stencil ID", Range(0, 255)) = 1
        [IntRange] _StencilWriteMask ("Stencil Write Mask", Range(0, 255)) = 255
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
            "RenderPipeline" = "UniversalPipeline"
        }

        // 禁用颜色写入,确保完全透明不可见
        ColorMask 0
        // 关闭深度写入,避免影响场景原有深度判断
        ZWrite Off

        Pass
        {
            Name "StencilMask"
            
            Stencil
            {
                Ref [_StencilID]
                WriteMask [_StencilWriteMask]
                Comp Always      // 总是通过模板测试
                Pass Replace     // 通过后,用Ref值替换缓冲区原值
                Fail Keep        // 失败则保持原值
                ZFail Keep       // 深度测试失败则保持原值
            }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            struct Attributes
            {
                float4 positionOS : POSITION;
            };
            
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
            };
            
            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                return output;
            }
            
            half4 frag(Varyings input) : SV_Target
            {
                // 由于 ColorMask 0,颜色不会输出到帧缓冲区
                return half4(0, 0, 0, 0);
            }
            ENDHLSL
        }
    }
}

参数说明:

  • _StencilID:写入模板缓冲区的参考值,必须与对应的内容材质一致

  • _StencilWriteMask:写入时的位掩码,控制哪几位被写入(默认255表示全部8位)

3.2 Portal_Content_URP ------ 模板测试器(秘境内容)

cs 复制代码
Shader "Custom/Portal_Content_URP"
{
    Properties
    {
        _BaseMap ("Base Map", 2D) = "white" {}
        _BaseColor ("Base Color", Color) = (1, 1, 1, 1)
        [IntRange] _StencilID ("Stencil ID", Range(0, 255)) = 1
        [IntRange] _StencilReadMask ("Stencil Read Mask", Range(0, 255)) = 255
        [KeywordEnum(Off, On)] _AlphaClip ("Alpha Clipping", Float) = 0
        _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Name "StencilTest"
            
            Stencil
            {
                Ref [_StencilID]
                ReadMask [_StencilReadMask]
                Comp Equal       // 模板值等于Ref才通过
                Pass Keep        // 通过后保持模板缓冲区原值
                Fail Keep
                ZFail Keep
            }
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ALPHACLIP_ON
            
            #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(_BaseMap);
            SAMPLER(sampler_BaseMap);
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
                float _Cutoff;
            CBUFFER_END
            
            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                return output;
            }
            
            half4 frag(Varyings input) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
                #if _ALPHACLIP_ON
                    clip(color.a - _Cutoff);
                #endif
                return color;
            }
            ENDHLSL
        }
    }
}

参数说明:

  • _BaseMap / _BaseColor:秘境内容物体的主纹理和主颜色

  • _StencilID:模板比较的参考值,必须与对应的遮罩材质ID一致才会被绘制

  • _StencilReadMask:读取时的位掩码,控制比较时检查哪几位

  • _AlphaClip / _Cutoff:Alpha裁剪开关与阈值,用于透明纹理的边缘裁剪

3.3 带光照的秘境内容(Lit版本扩展)

如果需要秘境空间内的物体受场景灯光影响,可将上述 Portal_Content_URP 替换为内置Lit框架。核心Stencil部分保持不变,只需将HLSL代码替换为标准URP Lit Shader的结构:

cs 复制代码
// 在Pass中添加Stencil块
Stencil {
    Ref [_StencilID]
    Comp Equal
    Pass Keep
    Fail Keep
    ZFail Keep
}

其他部分保持URP Lit Shader的标准写法,包含 #include "Packages/com.unity.render-pipelines.universal/Lighting.hlsl" 即可。

3.4 反向遮罩(门外才显示)

把内容Shader中的 Comp Equal 改成 Comp NotEqual,效果就会反转------只有门框的区域才显示这个物体。常用于"被门挡住一部分"的视觉特效:

cs 复制代码
Stencil {
    Ref [_StencilID]
    Comp NotEqual   // 模板值不等于Ref才通过
    Pass Keep
    Fail Keep
    ZFail Keep
}

四、使用方法

4.1 创建Shader和材质

  1. 在Unity项目中创建两个Shader文件,分别粘贴上述 Portal_Mask_URPPortal_Content_URP 代码

  2. 创建材质 M_PortalMask,Shader选择 Custom/Portal_Mask_URP,Stencil ID 设为 1

  3. 创建材质 M_PortalContent,Shader选择 Custom/Portal_Content_URP,Stencil ID 同样设为 1,并挂上纹理和颜色

4.2 搭建场景

  1. 在场景中放置一个模型作为秘境入口(Quad、Cube或任意Mesh),挂上 M_PortalMask 材质------这个模型的形状决定了秘境的开口形状

  2. 在入口的背后/内部位置放置"秘境空间"的所有物体(地形、角色、装饰物等),全部挂上 M_PortalContent 材质

  3. 运行Unity,绕到入口前查看,能看到入口范围内显示出秘境空间;视线偏离入口时,秘境内容立即被裁掉

4.3 多门隔离(多秘境共存)

同一个场景有多个互不干扰的秘境入口,只需复制两份材质,把Stencil ID改为不同的数值(如2、3...),分别挂到对应的门框+秘境内容上。不同ID的门彼此独立,不会互相串扰。

五、关键技术要点

5.1 渲染顺序(Render Queue)

由于Mask需要先写入Stencil缓冲区,必须让它早于Content物体被渲染。建议将Mask材质的 Queue 设置为 "Geometry"(默认值),Content材质的 Queue 设置为 "Geometry+1" 或更高,确保渲染顺序正确。也可通过Unity的材质面板直接设置Render Queue值。

5.2 深度测试的配置策略

Mask材质中设置 ZWrite Off 至关关键。如果Mask写入了深度,后续渲染的秘境内容物体可能会因深度测试失败而被错误剔除。

5.3 模板缓冲区的位操作

模板缓冲区是8位整数,可使用 ReadMaskWriteMask 实现更精细的控制。例如,只有某些位参与测试,其他位保持不变------这在复杂效果(如多个门嵌套)中非常有用。

5.4 URP内置模板值占用

需要注意,URP本身会使用一些模板位用于内部功能(如UI遮盖、XR立体渲染等)。建议优先使用模板值 1~15 等较小数值,避开URP内部占用的高位,避免冲突。

5.5 渲染管线的全支持

模板命令在URP、HDRP和内置渲染管线中均可使用,只需要在SubShader或Pass的 Tags 中正确声明 "RenderPipeline" = "UniversalPipeline" 即可。

六、常见问题排查

问题现象 可能原因 解决方案
秘境范围内什么都看不见,全黑屏 渲染顺序问题,Mask还没写入Content就开始渲染了 降低Mask的RenderQueue值(设为更小的数值),确保它先于Content渲染
秘境内容出现在门框外 模板ID不匹配 检查Mask和Content材质的_StencilID是否一致
秘境内容被其他物体错误遮挡 深度测试配置不当 确保Content材质的深度写入(ZWrite)配置正确,必要时调整Content的RenderQueue
URP后处理特效失效 Stencil值被后处理覆盖 将Stencil操作放在后处理执行之前,或单独设置渲染层隔离
相关推荐
真鬼12320 小时前
【Unity 6】Unity6快捷下载,快速下载
unity·游戏引擎
会潜水的小火龙1 天前
unity打包apk报错Failure to initialize问题解决方法
unity·游戏引擎
平行云1 天前
实时云渲染平台数据通道,支持3D应用文件上传下载分享无缝交互
linux·unity·云原生·ue5·gpu算力·实时云渲染·像素流送
Sator11 天前
unity仅用粒子系统实现拖尾
unity·游戏引擎
游乐码1 天前
Unity基础(五)四元数相关
unity·游戏引擎
想做后端的前端1 天前
Unity热更新 - HybridCLR & YooAsset
unity·游戏引擎
鹿野素材屋1 天前
Unity预加载:减少游戏中首次加载资源时的卡顿
windows·游戏·unity
RPGMZ1 天前
RPGMZ游戏引擎事件技巧大全
javascript·游戏引擎·事件·rpgmz·rpgmakermz
天若有情6731 天前
Superpowers 游戏引擎核心应用场景与落地指南
游戏引擎·superpowers