效果


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;
}
}