【Unity】Shader spine动画冻结效果

效果

Shader 源码

bash 复制代码
Shader "Custom/Freeze"
{
    Properties
    {
        [Header(_General Properties_)]
        _MainTex ("Main Texture", 2D) = "white" {}
        _Color ("Main Color", Color) = (1,1,1,1)
        _Alpha ("General Alpha", Range(0,1)) = 1

        [Header(_Render State_)]
        _ZWrite ("Depth Write", Float) = 0.0
        _MySrcMode ("SrcMode", Float) = 5
        _MyDstMode ("DstMode", Float) = 10
        _ZTestMode ("Z Test Mode", Float) = 4

        [Header(_Freeze_)]
        [Toggle(FREEZE_ON)] _FreezeToggle("Freeze effect?", float) = 0
        _FreezeColor("Freeze Color", Color) = (0.45,0.75,1,1)
        _FreezeAmount("Freeze Amount", Range(0,1)) = 1
        _FreezeShadow("Freeze Shadow Scale", Range(0,2)) = 0.25
        _FreezeHighlight("Freeze Highlight Scale", Range(0,3)) = 1.6
        _FreezeRimColor("Freeze Rim Color", Color) = (0.8,0.95,1,1)
        _FreezeRimWidth("Freeze Rim Width", Range(0,10)) = 2
        _FreezeRimStrength("Freeze Rim Strength", Range(0,3)) = 0.8
    }

    SubShader
    {
        Tags { "Queue" = "Transparent" "CanUseSpriteAtlas" = "True" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" }
        Blend [_MySrcMode] [_MyDstMode]
        Cull Off
        ZWrite [_ZWrite]
        ZTest [_ZTestMode]

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile FREEZE_ON

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                half4 color : COLOR;
            };

            struct v2f
            {
                // UV 与顶点位置用 float,避免 WebGL/mobile 上 half(mediump) 精度不足导致采样偏移丢失
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                half4 color : COLOR;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST, _MainTex_TexelSize, _Color;
            half _Alpha;

            #if FREEZE_ON
            half4 _FreezeColor, _FreezeRimColor;
            half _FreezeAmount, _FreezeShadow, _FreezeHighlight, _FreezeRimWidth, _FreezeRimStrength;
            #endif

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.color = v.color;
                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv) * i.color;
                col.rgb *= col.a;

                col.a *= _Alpha;

                #if FREEZE_ON
                // 冰冻效果:去色后按亮度映射为冰蓝色(暗部偏深蓝、亮部偏浅青白)
                // alpha 过小时除法不稳定,WebGL(mediump) 上会有色块,这里直接跳过
                half3 freezeSrc = col.a > 0.001 ? col.rgb / col.a : half3(0, 0, 0);
                half freezeLum = dot(freezeSrc, half3(0.299, 0.587, 0.114));
                half3 freezeCol = _FreezeColor.rgb * lerp(_FreezeShadow, _FreezeHighlight, freezeLum);
                col.rgb = lerp(col.rgb, freezeCol * col.a, _FreezeAmount);

                // 轮廓内侧叠加冰晶高光,模拟表面结霜
                // 偏移量为纹素级(约 1/图集宽度),必须用 float 计算,否则 WebGL 上会被精度舍掉
                float2 freezeOffset = _MainTex_TexelSize.xy * _FreezeRimWidth;
                float freezeL = tex2D(_MainTex, i.uv - float2(freezeOffset.x, 0)).a;
                float freezeR = tex2D(_MainTex, i.uv + float2(freezeOffset.x, 0)).a;
                float freezeD = tex2D(_MainTex, i.uv - float2(0, freezeOffset.y)).a;
                float freezeU = tex2D(_MainTex, i.uv + float2(0, freezeOffset.y)).a;
                float freezeEdge = saturate(1.0 - min(min(freezeL, freezeR), min(freezeD, freezeU)));
                col.rgb += _FreezeRimColor.rgb * freezeEdge * col.a * _FreezeRimStrength;
                #endif

                col *= _Color;
                return col;
            }
            ENDCG
        }
    }
    Fallback "Sprites/Default"
}

使用实例

csharp 复制代码
using GameLogic;
using Spine.Unity;
using System.Collections.Generic;
using UnityEngine;

public class RoleBody : MonoBehaviour
{
    string curName = "";
    float jiasuSacle = 1.0f;
    float curAnimaTimeScale = 1.0f;

    float _factor = 1.0f;//其它因素影响

    [SerializeField]
    Material _freezeMaterialTemplate;

    // 原材质 -> 冰冻材质,按角色实际使用的图集材质分别缓存
    readonly Dictionary<Material, Material> _freezeMaterials = new Dictionary<Material, Material>();

    void Start()
    {
        var db = GetComponent<SkeletonAnimation>();
        var stateData = db.SkeletonDataAsset.GetAnimationStateData();

        ResetJiasuState();

        EventManager.Instance.AddEventListener(Game_EventType.FlushJiasuState, ResetJiasuState);
    }

    void OnDestroy()
    {
        EventManager.Instance.RemoveEventListener(Game_EventType.FlushJiasuState, ResetJiasuState);
    }

    public void IdleAnima()
    {
        if (curName != "daiji")
        {
            curAnimaTimeScale = 1.0f;

            curName = "daiji";
            var db = GetComponent<SkeletonAnimation>();
            db.AnimationState.TimeScale = curAnimaTimeScale * jiasuSacle * _factor;
            db.AnimationState.SetAnimation(0, "daiji", true);
        }
    }

    public void MoveAnima()
    {
        if (curName != "zoulu")
        {
            curAnimaTimeScale = 1.0f;

            var db = GetComponent<SkeletonAnimation>();
            db.AnimationState.TimeScale = curAnimaTimeScale * jiasuSacle * _factor;
            db.AnimationState.SetAnimation(0, "zoulu", true);
            curName = "zoulu";
            
        }
    }

    public void FightAnima(float timeScale)
    {
        if(curName != "gongji")
        {
            curAnimaTimeScale = timeScale;

            var db = GetComponent<SkeletonAnimation>();
            db.AnimationState.TimeScale= curAnimaTimeScale * jiasuSacle * _factor;
            db.AnimationState.SetAnimation(0, "gongji", false);
            curName = "gongji";
        }
    }

    public void Injured()
    {
        var shanbai = GetComponent<ShanBaiSpine>();
        shanbai.Begin();
    }

    /// <summary>
    /// 开关冰冻材质:冰冻时用 Freeze 材质替换 Spine 渲染材质,取消时还原
    /// </summary>
    public void SetFreeze(bool isFreeze)
    {
        var db = GetComponent<SkeletonAnimation>();
        if (db == null)
        {
            return;
        }

        if (!isFreeze)
        {
            // 只移除自己添加的覆盖,避免影响其它系统的材质覆盖
            foreach (var pair in _freezeMaterials)
            {
                if (pair.Key != null)
                {
                    // 直接移除,无需赋值
                    // 移除字典项后, 下一次 LateUpdate 重建网格时,sharedMaterials 会自动被设回原图集材质,画面上一帧内就恢复正常
                    db.CustomMaterialOverride.Remove(pair.Key);
                }
            }

            return;
        }

        var meshRenderer = GetComponent<MeshRenderer>();
        if (meshRenderer == null)
        {
            return;
        }

        var originMaterials = meshRenderer.sharedMaterials;

        for (int i = 0; i < originMaterials.Length; i++)
        {
            var origin = originMaterials[i];
            if (origin == null)
            {
                continue;
            }

            Material freezeMat;
            if (!_freezeMaterials.TryGetValue(origin, out freezeMat))
            {
                var template = GetFreezeMaterialTemplate();
                if (template == null)
                {
                    return;
                }

                freezeMat = new Material(template);
                freezeMat.EnableKeyword("FREEZE_ON");
                freezeMat.SetFloat("_FreezeToggle", 1f);

                _freezeMaterials.Add(origin, freezeMat);
            }

            // 沿用角色自身图集,保证冰冻后贴图正确
            freezeMat.mainTexture = origin.mainTexture;
            freezeMat.mainTextureScale = origin.mainTextureScale;
            freezeMat.mainTextureOffset = origin.mainTextureOffset;

            db.CustomMaterialOverride[origin] = freezeMat;
        }
    }

    Material GetFreezeMaterialTemplate()
    {
        if (_freezeMaterialTemplate != null)
        {
            return _freezeMaterialTemplate;
        }

        var shader = Resources.Load<Shader>("Shader/Freeze");

        if (shader == null)
        {
            return null;
        }

        _freezeMaterialTemplate = new Material(shader);

        return _freezeMaterialTemplate;
    }

    public void Clear()
    {
        var db = GetComponent<SkeletonAnimation>();
        db.ClearState();

        foreach (var pair in _freezeMaterials)
        {
            if (pair.Key != null)
            {
                db.CustomMaterialOverride.Remove(pair.Key);
            }

            Destroy(pair.Value);
        }

        _freezeMaterials.Clear();

        if (_freezeMaterialTemplate != null)
        {
            Destroy(_freezeMaterialTemplate);
            _freezeMaterialTemplate = null;
        }
    }

    public void SetCamp(Camp camp)
    {
        var db = GetComponent<SkeletonAnimation>();

        switch (camp)
        {
            case Camp.My:
                {
                    db.initialSkinName = "lan";
                    db.Initialize(true);
                    // db.skeleton.SetSkin(db.initialSkinName);
                }
                break;
            case Camp.Target:
                {
                    db.initialSkinName = "hong";
                    db.Initialize(true);
                }
                break;
            default:
                break;
        }
    }

    public void SetFactor(float factor)
    {
        _factor = factor;

        var db = GetComponent<SkeletonAnimation>();
        db.AnimationState.TimeScale = curAnimaTimeScale * jiasuSacle * _factor;
    }

    public void ResetJiasuState(string info = "", string info2 = "", string info3 = "")
    {
        var AdsJiasu = PlayerData.GetDataForId<AdsJiasu>(DataEnum.AdsJiasu);
        var isJiasu = AdsJiasu.IsJiasuState();

        if (isJiasu)
        {
            jiasuSacle = 2.0f;
        }
        else
        {
            
            jiasuSacle = 1.0f;
        }

        var db = GetComponent<SkeletonAnimation>();
        db.AnimationState.TimeScale = curAnimaTimeScale * jiasuSacle;
    }

}
相关推荐
淡海水2 小时前
05-02-源码-Boehm GC标记阶段
算法·unity·c#·游戏引擎·gc
淡海水6 小时前
05-03-源码-Boehm GC分配与碎片管理
开发语言·算法·unity·c#·游戏引擎·gc
WarPigs2 天前
Unity无法修改Assembly-CSharp.csproj错误
unity
FairGuard手游加固3 天前
手游加固怎么做?SO、IL2CPP、AssetBundle 与运行时保护详解
安全·游戏·unity
阿松爱学习4 天前
【Unity开发】监听Scene场景事件
unity·c#
xiaogai_gai4 天前
BOM 物料清单同步实战:金蝶云星空 → 四化智造 MES 修改场景深度拆解
unity·游戏引擎
淡海水4 天前
03-03-架构篇-SGen GC架构解析
unity·架构·c#·gc·sgen
萌萌的提莫队长5 天前
Unity相机捕捉,图像识别,抠像+合成
opencv·unity
mxwin6 天前
Unity Shader 光线追踪原理与实战案例
unity·游戏引擎