Unity URP 遮挡显示实战:从Shader到RenderFeature的完整实现

1. 为什么你的游戏角色总被墙挡住?聊聊遮挡显示

不知道你有没有遇到过这种情况:辛辛苦苦做好的游戏角色,一跑到墙后面或者大树后面,直接就“消失”了。玩家操控的角色被完全遮挡,瞬间就失去了方向感,体验大打折扣。这在第三人称游戏、MOBA游戏或者一些需要清晰视野的AR应用中,简直是个灾难。

传统的3D渲染遵循“谁离相机近谁先画”的深度测试规则,这本身没错,保证了视觉正确性。但游戏体验有时候需要一点“作弊”。我们得让玩家知道,“哦,我的角色还在那儿,只是被挡住了”。这就是“遮挡显示”(Occlusion Highlight/Outline)技术要解决的问题。它不是去改变渲染的物理正确性,而是在那个被遮挡的物体上,叠加一层特殊的、穿透遮挡物的视觉效果,比如一个发光的轮廓、一圈半透明的描边,或者像X光一样的透视效果。

在Unity的URP(通用渲染管线)里实现这个功能,听起来很高大上,涉及Shader、模板测试、RenderFeature这些词。但别怕,我干了这么多年技术美术,可以很负责任地告诉你,它的核心思想非常直白:让同一个物体画两次。第一次,让它正常渲染,同时偷偷做个“标记”;第二次,专门去渲染那些被标记了“我被挡住了”的部分,画上我们想要的特殊效果。整个流程就像给被挡住的角色贴了个“请注意”的荧光贴纸。

这篇文章,我就带你从零开始,手把手实现一遍。我会掰开揉碎了讲,从Shader里怎么写那个“标记”,到URP里怎么配置那个负责“第二次画画”的RenderFeature。保证你跟着做下来,不仅能搞定功能,还能真正明白背后的“所以然”。咱们不玩虚的,直接上干货。

2. 核心原理拆解:两次渲染与模板缓冲区

在深入代码之前,我们必须把原理吃透。很多教程只给代码,原理一笔带过,结果你换了个需求就懵了。咱们这里得搞清楚。

2.1 深度测试与模板测试:GPU的两位门卫

你可以把GPU渲染想象成一场严格的入场检查。每个像素点(屏幕上的一个点)就是一个小座位。

  • 深度测试(ZTest) 是第一位门卫。他手里有个本子,记录着当前座位上已经坐下的那位客人离摄像机的距离(深度值)。新来的客人(像素)想坐下,必须报上自己的距离。如果新客人比本子上记的距离更近(值更小),说明他应该在前面,门卫就让他进去坐下,并更新本子上的记录。如果更远,对不起,你被挡住了,不能进。这就是为什么远处的物体会被近处的挡住。
  • 模板测试(Stencil Test) 是第二位门卫。他也有个本子,但这个本子记录的不是距离,而是一个编号(模板值)。这个本子一开始全是0。门卫可以按照规则,给进场的客人盖个章(写入模板值),或者根据客人是否满足某些条件(比如深度测试过没过)来决定盖不盖章。

我们的遮挡显示,就要巧妙地利用这两位“门卫”的协作。

2.2 我们的“作弊”流程

  1. 第一次渲染(正常渲染 + 秘密盖章):我们的角色模型正常渲染。对于每个像素:

    • 深度测试门卫照常工作。如果这个像素没被挡住(深度测试通过),它就正常上色,显示在屏幕上。
    • 关键在这里:我们给模板测试门卫下了一个特殊的指令:“不管这个像素的深度测试过没过,你都给它盖一个‘2’的章(写入模板值2)。” 注意,这个“盖章”动作是独立于深度测试结果的。也就是说,即使这个像素因为被墙挡住而没有通过深度测试(最终颜色没画到屏幕上),它的模板值也被写成了2。
  2. 第二次渲染(只画被挡住的轮廓):我们创建一个新的、只画轮廓或半透效果的Pass(可以理解为一支特殊的画笔)。这支画笔的规则是:

    • 深度测试 Always:无视深度,永远通过。因为我们要画的轮廓需要穿透前面的墙显示出来。
    • 模板测试 Equal 2:模板测试门卫检查,只有座位上盖的章正好是2的像素,才允许这支笔画上去。
    • 那么,哪些像素的模板值是2呢?根据第一步的规则,就是那些被遮挡的像素(因为通过的像素正常显示了,被挡的像素没显示但被标记为2)。于是,这支特殊的画笔就精准地只在了被墙挡住的那部分角色模型上,画出了我们想要的发光轮廓。

这样一来,玩家看到的画面就是:角色正常显示,当角色走到墙后时,他原本应该被遮住的部分,会浮现出一层我们自定义的高亮效果,清晰指示位置。整个过程中,墙的渲染完全不受影响,深度关系在视觉上依然是正确的,我们只是额外叠加了一层信息。

3. 实战第一步:编写被遮挡物体的Shader

理论通了,咱们开始动手。首先处理需要被高亮显示的物体,比如你的游戏主角。我们需要修改它的Shader,加入那个关键的“盖章”逻辑。

3.1 创建与基础设置

在Unity中新建一个Unlit Shader(URP下通常选择“Universal Render Pipeline/Unlit Shader”作为模板),命名为“OccludedObjectShader”。我们先搭建一个最基础的、只显示颜色的Shader框架。

Shader "Custom/OccludedObjectShader"
{
    Properties
    {
        _BaseColor ("Base Color", Color) = (1, 1, 1, 1)
        _BaseMap ("Base Map", 2D) = "white" {}
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
            "RenderPipeline"="UniversalPipeline"
            "Queue"="Geometry"
        }

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes { ... };
            struct Varyings { ... };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);
            float4 _BaseMap_ST;
            half4 _BaseColor;

     
本数据集来源于 2024 年 7 月在江西省中东部余干县、贵溪市、金溪县丘陵林地采集的千枚岩、红砂岩、花岗岩母质发育红壤关键带剖面土壤实测数据,空间覆盖 3 个县域不同岩性风化壳林地,采样点位经纬度分别为千枚岩剖面 P10(116.8316°E,28.5269°N)、红砂岩剖面 P08(117.1048°E,28.3492°N)、花岗岩剖面 P04(116.6883°E,27.9963°N);垂直空间采样深度存在差异,千枚岩与花岗岩剖面采样深度 0~600 cm,红砂岩剖面采样深度 0~450 cm,垂直分层采样分辨率为 0~50 cm 区间分 0~20 cm、20~50 cm 两层,50 cm 以下土层以 50 cm 为固定间隔分层,整套数据集共包含 36 条土壤剖面分层记录,其中 P10 千枚岩剖面 13 条、P08 红砂岩剖面 11 条、P04 花岗岩剖面 13 条。数据采集时间为 2024 年 7 月,实验室理化指标、矿物测试、酸碱滴定及统计建模工作于 2024 年 7 月 —2026 年 5 月完成,无时间序列连续监测数据,仅为单次野外剖面采样静态数据集。 数据集包含野外剖面基础信息、土壤酸碱滴定原始数据、土壤酸度指标、交换性盐基与交换性酸、土壤机械组成、有机质、黏土与原生矿物半定量 XRD 数据、无定形 / 晶形铁铝氧化物含量。全量理化指标计量单位统一规范:酸缓冲容量 pHBC 单位为 cmol・kg⁻¹・pH⁻¹,交换性酸、交换性盐基离子单位为 cmol・kg⁻¹,矿物以质量百分比(%)表示,、黏粒 / 粉粒 / 砂粒、有机质、铁铝氧化物单位均为g/kg,pH 为无量纲数值。 覆盖范围: 中位纬度: 28.2616 中位经度: 116.89654999999999 南界纬度: 27.9963 西界经度: 116.6883 北界纬度: 28.5269 东界经
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值