使用stencil test实现UI上的“迷雾”效果

53 篇文章 1 订阅
16 篇文章 1 订阅

0x00 需求

在UI上实现一种类似战争迷雾的效果。

0x01分析需求

战争迷雾是来自于RTS游戏,[此处可以插入链接]并经由MOBA游戏发扬广大的一种在地图上增加一种不透明的迷雾,造成信息的不对称从而增加游戏的趣味性。那么第一个想法是寻找战争迷雾插件,导入,使用之。

0x02实现方案对比

从实现原理上分类,战争迷雾有两种实现方法:

  1. 屏幕后处理,
  2. 遮罩擦除法

屏幕后处理中,最成熟的当属Fog Of War,在Unity Asset Store中搜索,第一个跳出的就是这个插件,作者:AsehesL,blog地址在此

这个插件确实功能很丰富,也下载下来把玩了一番。

功能都可以满足,甚至还根据点亮范围实时生成了一个小地图。

太强了,然鹅我只需要一个UI上的迷雾,并不需要depth texture,计算视野,生成mesh,生成mask贴图等等功能。虽然也有2D模式,看了代码感觉还是有很多不必要的功能引入。

遮罩擦除法,这篇文章介绍了两种实现方法,对于2D游戏来说,可能确实是一种方案,对于我目前仅仅用在UI上的需求来说,还是觉得有些冗余。

本着节约性能偷懒的原则,对比了几个插件(实现方案)后,发现战争迷雾插件主要还是面向3D场景中的需求,对于UI上这种伪需求都有一种杀鹌鹑用牛刀之感。

0x03自己实现

先看效果:

首先考虑的就是使用屏幕后处理的做法来实现一个简易版本。做完demo后,移植到项目后,发现没效果。wtf!!!查了半天,发现URP管线已然不支持OnRenderImage接口了。需要实现自己的ScriptableRendererFeature类和ScriptableRenderPass类。由于还在爬SRP Batcher的坑,没有料到URP直接釜底抽薪,把OnRenderImage给抽了,是我大意了。

在实现自己的一套后处理机制时,跟虹老板讨论了一下方案,虹老板第二天灵机一动:为什么不用stencil test?我一想,也对。其实UI上的迷雾原理上与mask差不多。于是重新开始实现基于stencil test的demo。

实现原理只要明白stencil test的原理,自己实现并有什么难点。

简述一下步骤:主要是根据这个shader创建的两个材质,UIFog.mat用于迷雾,UIFogMask.mat用于遮罩(点亮范围)。

  1. 先用默认的image(UI-Default)画一个背景

  2. 使用自定义的shader,将需要标记的区域(透过迷雾的部分)设置stencil buffer。

    Stencil Comparion 对应Stencil 中的 Comp(即stencil test中的比较运算)
    Stencil ID 对应 Ref(即我们俗称的stencil buffer)
    Stencil Operation 对应 Pass(即通过stencil test时执行的操作)
    Stencil Comparion = 5(Greater)表示如果stencil buffer(1)大于当前的stencil buffer(UI上的默认值为0)时,执行Stencil Operation = 2(Replace),将Texture范围内的Stencil Buffer设置为1。

  3. 使用自定义的shader,判断渲染的区域的stencil buffer 是否等于0,等于0即通过stencil test.反之,如果区域内的stencil buffer不等于0就没有通过stencil test,默认情况下没有通过stencil test的区域将被放弃。

0x04源代码

shader代码直接拿了UI-Default.shader的代码,把_MainTex开放出来。

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "coffeecat/UIFog"
{
    Properties
    {
        // [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                #ifdef UNITY_UI_CLIP_RECT
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                return color;
            }
        ENDCG
        }
    }
}

参考资料:

一口气解决RenderQueue、Ztest、Zwrite、AlphaTest、AlphaBlend和Stencil - 知乎

【Unity Shader】 模板测试_赞美月亮的专栏-CSDN博客_unity模板测试

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值