这篇文章中效果的实现思路主要参考自这里:Geometry Shader学习笔记
游戏Rime中的物体溶解效果:
在之前的基础上增加了以下内容:
- 溶解方块拖尾长度与偏移程度的控制;
- 动画持续时间的控制;
- 按照模型的比例来计算动画,不再受模型位置与大小的影响。
最后的效果如下:
shader代码与C#代码如下,需要用脚本来传入模型的位置信息,以及启动动画播放计时器。
Shader "Unlit/Dissolve"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//模型空间的最高与最低点
_StartHeightPos("StartPos",Float) = 1.0
_EndHeightPos("EndHeightPos",Float) = 0.0
//溶解环的颜色
_DissolveColor("DissolveColor",Color) = (1.0, 1.0, 1.0, 1.0)
//溶解环的宽度
_DissolveColorThreshholdPercent("DissolveColorThreshhold",Range(0.05,1)) = 0.5
//方块颜色与大小
_BoxColor("BoxColor",Color) = (1.0, 1.0, 1.0, 1.0)
_BoxSize("BoxSize",Range(0.002,0.2)) = 0.1
//方块拖尾长度
_BoxTrailLength("BoxTrailLength",Range(0.05 ,1.0)) = 0.2
//方块拖尾摆动幅度
_BoxTrailSwing("BoxTrailSwing",Range(1,50)) = 1
// 动画播放计时器与总时长
_Timer("Timer",Float) = 0.0
_LifeTime("LifeTime",Float) = 2.0
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
LOD 100
CGINCLUDE
#include "UnityCG.cginc"
struct v2f{
float2 uv : TEXCOORD0;
float3 normalWorld : NORMAL;
float4 vertex : SV_POSITION;
float4 objPos : TEXCOORD1;
};
struct v2g
{
float4 vertex : POSITION;
float3 normalWorld : NORMAL;
};
struct g2f
{
float difference : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float _StartHeightPos;
float _EndHeightPos;
float _DissolveColorThreshholdPercent;
fixed4 _DissolveColor;
float _BoxSize;
float _BoxTrailLength;
float _BoxTrailSwing;
fixed4 _BoxColor;
float _Timer;
float _LifeTime;
//计算出当前时刻中溶解高度(取决于动画播放进度)与任意顶点位置之差,用百分比表示,溶解过的部分值小于0,未溶解的部分大于0
float calculateDifferenceY(float y){
float playProgressInv = 1.0 -_Timer / _LifeTime;
float height = _StartHeightPos - _EndHeightPos;
float vertexHeightPercent = (y - _EndHeightPos) / height;
float difference = playProgressInv - vertexHeightPercent;
return difference;
}
v2f vertVanish (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.objPos = v.vertex;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normalWorld = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 fragVanish (v2f i) : SV_Target
{
// 计算差值,溶解过的部分值小于0,未溶解的部分大于0,方便裁剪和计算溶解环
float difference = calculateDifferenceY(i.objPos.y);
//裁减掉溶解的部分
clip(difference);
// 超出当前溶解位置加上阈值部分为整个溶解环
if(difference < _DissolveColorThreshholdPercent){
return _DissolveColor;
}
float3 light = normalize(UnityWorldSpaceLightDir(mul(unity_ObjectToWorld, i.objPos)));
float diffuse = abs(dot(normalize(i.normalWorld), light));
fixed4 col = tex2D(_MainTex, i.uv);
return fixed4(col.rgb * diffuse, 1.0);
}
v2g vertParticle(appdata_base v){
v2g o;
o.vertex = v.vertex;
o.normalWorld = UnityObjectToWorldNormal(v.normal);
return o;
}
#define ADD_VERTEX(v) o.vertex = UnityObjectToClipPos(v); triStream.Append(o);
#define ADD_TRIANGLE(v0, v1, v2) ADD_VERTEX(v0) ADD_VERTEX(v1) ADD_VERTEX(v2)
#define ADD_CUBE(v0, v1, v2, v3, v4, v5, v6, v7) ADD_TRIANGLE(v0, v1, v2) ADD_TRIANGLE(v2, v3, v0)
ADD_TRIANGLE(v4, v5, v7) ADD_TRIANGLE(v7, v5, v6)
ADD_TRIANGLE(v2, v4, v3) ADD_TRIANGLE(v3, v4, v7)
ADD_TRIANGLE(v5, v3, v6) ADD_TRIANGLE(v6, v3, v0)
ADD_TRIANGLE(v5, v4, v1) ADD_TRIANGLE(v1, v4, v2)
ADD_TRIANGLE(v0, v3, v6) ADD_TRIANGLE(v6, v3, v7)
[maxvertexcount(36)]
void geomParticle(triangle v2g IN[3], inout TriangleStream triStream){
// 计算方块的中心位置与原三角形面法线朝向
g2f o;
float3 centerPos = ( IN[0].vertex + IN[1].vertex + IN[2].vertex).xyz/3.0;
float3 edge1 = (IN[1].vertex - IN[0].vertex).xyz;
float3 edge2 = (IN[2].vertex - IN[0].vertex).xyz;
float3 faceNormal = normalize(cross(edge1, edge2));
// 计算差值作为偏移因子,距离当前溶解处越远的上部分,其偏移距离越大,溶解过的部分差值为负需取反
float difference = calculateDifferenceY(centerPos.y);
o.difference = difference;
centerPos = centerPos + faceNormal * _BoxTrailSwing * saturate(-difference);
// 方块的八个顶点以及12个三角形面,顺时针为正方向
float4 v0 = float4(-1, -1, -1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v1 = float4(-1, 1, -1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v2 = float4( 1, 1, -1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v3 = float4( 1, -1, -1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v4 = float4( 1, 1, 1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v5 = float4(-1, 1, 1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v6 = float4(-1, -1, 1, 0.0) * _BoxSize + float4(centerPos,1.0);
float4 v7 = float4( 1, -1, 1, 0.0) * _BoxSize + float4(centerPos,1.0);
ADD_CUBE(v0, v1, v2, v3, v4, v5, v6, v7);
triStream.RestartStrip();
}
fixed4 fragParticle(g2f i): SV_TARGET{
float difference = i.difference;
// 裁减掉尚未溶解的部分,取反使其小于0
clip(-difference);
// 使用差值来计算透明度,离当前位置越远的溶解方块差值越小,透明度越小
return fixed4(_BoxColor.rgb, smoothstep(-_BoxTrailLength,0, difference));
}
ENDCG
Pass
{
Tags{"Queue"="Geometry" "RenderType"="Opaque"}
Cull Off
CGPROGRAM
#pragma vertex vertVanish
#pragma fragment fragVanish
ENDCG
}
pass{
Tags{"Queue"="Transparent" "RenderType"="Transparent"}
Cull Off
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vertParticle
#pragma geometry geomParticle
#pragma fragment fragParticle
ENDCG
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayAnimation : MonoBehaviour
{
[SerializeField] GameObject go;
[SerializeField] Material DissolveMat;
float t0 = 0, timer = 0;
// Start is called before the first frame update
void Start()
{
}
public void StartButtonClick()
{
t0 = Time.time;
Vector3 center = go.GetComponent().mesh.bounds.center;
Vector3 extents = go.GetComponent().mesh.bounds.extents;
float startHeightPos = center.y + extents.y * 1.2f;
float endHeightPos = center.y - extents.y;
DissolveMat.SetFloat("_Timer", 0);
DissolveMat.SetFloat("_StartHeightPos", startHeightPos);
DissolveMat.SetFloat("_EndHeightPos", endHeightPos);
}
// Update is called once per frame
void Update()
{
timer = Time.time - t0;
DissolveMat.SetFloat("_Timer", timer);
}
}
效果虽然实现了,但是与游戏中的原效果比起来有较大差异,存在许多不足之处,如下:
- 最主要的一个问题,物体溶解后无法封闭上方区域;
- 必须要获取模型的包围盒数据;
- 可以加入手动调整溶解后方块飞行的方向,同时使用噪声贴图;
- 未添加泛光效果。
最后想了想还是叫做物体外壳溶解效果吧哈哈哈。



