Dissolveシェーダーとは?
Dissolveとは「分解する」「溶かす」という意味です。
有名ドコロでは、Falloutシリーズのエネルギー系の武器で敵を倒した時のエフェクトが近いかもしれません。
今回作ったシェーダーを実行するとこんな感じになります。
※Bloomイメージエフェクト+HDR
シェーダーソース
ざっくり説明すると、テクスチャーのUV座標から分解用のマスクテクスチャの色を取得し、その値がしきい値よりも下だった場合、出力のアルファ値を0にして描画しないようにします。
マスクテクスチャは、Photoshopの雲模様で作りました。
おまけで、しきい値付近を発光させて燃えているような感じにしてみます。
詳細はソース中のコメントを見てください。
Shader "Custom/Dissolve" {
Properties{
_MainColor("Main Color", Color) = (1,1,1,1) // モデルの色
_MainTex("Base (RGB)", 2D) = "white" {} // モデルのテクスチャー
_Mask("Mask To Dissolve", 2D) = "white" {} // 分解用のマスク
_CutOff("CutOff Range", Range(0,1)) = 0 // 分解のしきい値
_Width("Width", Range(0,1)) = 0.001 // しきい値の幅
_ColorIntensity("Intensity", Float) = 1 // 燃え尽きる部分の明るさの強度(Bloom+HDRを使わない場合は不要)
_Color("Line Color", Color) = (1,1,1,1) // 燃え尽きる部分の色
_BumpMap("Normalmap", 2D) = "bump" {} // モデルのバンプマッピング
}
SubShader{
Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
LOD 300
// モデルの描画順がおかしくなる問題の対処
Pass{
ZWrite On
ColorMask 0
}
CGPROGRAM
#pragma target 2.0
#include "UnityCG.cginc"
#pragma surface surf Lambert alpha
sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _Mask;
fixed4 _Color;
fixed4 _MainColor;
fixed _CutOff;
fixed _Width;
fixed _ColorIntensity;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
void surf(Input IN, inout SurfaceOutput o) {
// テクスチャの色を取得
o.Albedo = tex2D(_MainTex, IN.uv_MainTex) * _MainColor;
// バンプマッピング
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
// マスク用テクスチャから濃度を取得(モノクロなので赤チャンネルの値だけ使用する)
fixed a = tex2D(_Mask, IN.uv_MainTex).r;
// 燃える切れ端表現(aの値を、しきい値~しきい値+幅の範囲を0~1として丸める)
fixed b = smoothstep(_CutOff, _CutOff + _Width, a);
o.Emission = _Color * b * _ColorIntensity;
// 消失する範囲を求める (_CutOff + _Width * 2.0 >= a) ? 1 : 0
fixed b2 = step(a, _CutOff + _Width * 2.0);
o.Alpha = b2;
}
ENDCG
}
Fallback "Diffuse"
}
C#スクリプト
Unity側からアニメーションさせるためのスクリプトを用意しました。
テストプログラムなので汎用性はありません。
using UnityEngine;
public class DissolveTest : MonoBehaviour {
[SerializeField]
private float time = 1f; // 再生時間
[SerializeField]
private float waitTime = 1f; // 再生までの待ち時間
private Material material = null;
private int _Width = 0;
private int _Cutoff = 0;
private float duration = 0f; // 残時間
private float halfTime = 0f; // 再生時間の半分
void Start () {
material = GetComponentInChildren<Renderer>().material;
_Width = Shader.PropertyToID("_Width");
_Cutoff = Shader.PropertyToID("_CutOff");
if(material != null)
{
material.SetFloat(_Cutoff, 1f);
material.SetFloat(_Width, 1f);
}
halfTime = time / 4f * 3f; // 半分といいつつ4/3にしているのは、見た目の調整のため
duration = time;
}
void Update () {
float delta = Time.deltaTime;
// 待ち時間
waitTime -= delta;
if (waitTime > 0f)
return;
duration -= delta;
if (duration < 0f) duration = 0f;
// しきい値のアニメーション(再生時間の上半分の時間で1~0に推移)
float cutoff = (duration - halfTime) / halfTime;
if (cutoff < 0f) cutoff = 0f;
// 幅のアニメーション(再生時間の下半分の時間で1~0に推移)
float width = (halfTime - duration) / halfTime;
if (width < 0f) width = 0f;
width = 1f - width;
// シェーダーに値を渡す
if (material != null)
{
material.SetFloat(_Cutoff, cutoff);
material.SetFloat(_Width, width);
}
}
}
難しかったところ
実際、しきい値は0~1の範囲ですが、幅の分だけ上乗せされてアルファ値が0にならないです(燃えカスが残る)
本来ならしきい値を0にした時に完全に消失するようにしたかったのですが、計算式が思いつかなかったです…。
苦肉の策で、マスクテクスチャ側をレベル補正して若干明るくして、相対的にしきい値を下回るようにごまかしています。
また、モデルのUVの貼り方やマスクテクスチャの明暗の分布によっても分解されていく速度が変わります。