【Unity】「HIT」のログイン時のようなエフェクトをつくる
HITとは?
NEXONが出してるスマホゲームです。UE4製で綺麗なグラフィックのアクションゲームです。
mobile.nexon.co.jp
ログイン時のエフェクトってどんなの?
実装方針
まずこんな画像を用意します。
こういうのはパーリンノイズが向いてる気がするので、パーリンノイズで生成しました。
色を高さと見なして、こんな風にしたらそれっぽいのができそうですね!
(図は Processing で生成しました)
EffectVisualize.pde · GitHub
実装
シェーダの全体像です。
Shader "Custom/LoginEffectShader" { Properties { [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {} _MaskTex("Mask Texture", 2D) = "white" {} _Height("Height", Float) = 0 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #pragma multi_compile _ ETC1_EXTERNAL_ALPHA #include "UnityCG.cginc" 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; UNITY_VERTEX_OUTPUT_STEREO }; v2f vert(appdata_t IN) { v2f OUT; UNITY_SETUP_INSTANCE_ID(IN); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = IN.texcoord; return OUT; } sampler2D _MainTex; sampler2D _MaskTex; float _Height; fixed4 frag(v2f IN) : SV_Target { float maskHeight= tex2D(_MaskTex, IN.texcoord).r; if (maskHeight> _Height) { discard; } return tex2D(_MainTex, IN.texcoord); } ENDCG } } }
Properties ブロック内で、ノイズ画像の _MaskTex と現在の高さを表す _Height を定義します。
_MaskTex("Mask Texture", 2D) = "white" {} _Height("Height", Float) = 0
フラグメントシェーダ内で、今見ている位置より高い位置のピクセルを破棄しています。
fixed4 frag(v2f IN) : SV_Target { float maskHeight = tex2D(_MaskTex, IN.texcoord).r; if (maskHeight > _Height) { discard; } return tex2D(_MainTex, IN.texcoord); }
あとは、C# スクリプトから適当に _Height を操作します。
using DG.Tweening; using UnityEngine; public class EffectTweener : MonoBehaviour { [SerializeField] SpriteRenderer _spriteRenderer; Material _material; int _heightPropertyID; bool _isHidden = false; void Start() { _heightPropertyID = Shader.PropertyToID("_Height"); _material = _spriteRenderer.material; } void Update() { if (Input.GetMouseButtonDown(0)) { if (_isHidden) { Play(from: 0f, to: 1f); } else { Play(from: 1f, to: 0f); } _isHidden = !_isHidden; } } void Play(float from, float to) { DOTween.Kill(this); DOTween.To( () => from, a => _material.SetFloat(_heightPropertyID, a), to, 1.5f) .SetId(this); } }
ここまでの結果です。
仕上げ
フチに色を付けたり、Bloomで光らせたりして盛ります。
fixed4 frag(v2f IN) : SV_Target { float maskHeight = tex2D(_MaskTex, IN.texcoord).r; if (maskHeight > _Height) { discard; } fixed4 color = tex2D(_MainTex, IN.texcoord); float edgeHeight = 0.015; return lerp( color, fixed4(0, 4, 2, 0), step(_Height - edgeHeight, maskHeight) ); }
まとめ
マスク画像を工夫すればもっと面白い表現ができそうです。
できればフラグメントシェーダ内の条件分岐をなくしたいです。
2017/01/26追記:シェーダーの改善
今回の記事のような処理は、αカットアウトシェーダを使うとよりシンプルに実装できると指摘をいただきました。
docs.unity3d.com
Unityのビルトインシェーダにあるαカットアウトシェーダ(Unlit/Transparent Cutout)を使って実装してみます。
仕組み
αカットアウトシェーダは名前の通り、画像のα値と閾値によってピクセルを破棄するか決定します。
元画像のα成分にマスク情報を埋め込んだ画像を用意します。
この画像を、Unlit/Transparent Cutout シェーダをセットしたマテリアルで描画して、_Cutoff プロパティを操作するとこのように動きます。
(前回と背景が変わっていますが、分かりやすさのためです。結果は前回とほぼ同じです)
フチに色を付ける
Unityのビルトインシェーダはここからダウンロードできます。
https://unity3d.com/jp/get-unity/download/archive
フチに色が付くように Unlit/Transparent Cutout の frag 関数内を改造したシェーダをつくりました。
fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.texcoord); clip(col.a - _Cutoff); UNITY_APPLY_FOG(i.fogCoord, col); float edgeHeight = 0.015; return lerp( col, fixed4(0, 4, 2, 1), step(col.a, _Cutoff + edgeHeight) ); }
改造したシェーダの全体像はこちらです。
https://gist.github.com/setchi/b5c9fd72c3cb5317dae44cb6f3eb7fef
実行結果です。
最初の実装と比較して
最初の実装では画像を二枚使用していたのに対し、一枚の画像で実装できました。
また、不要なピクセルをより手前(Alpha Test)の段階で破棄できて効率的になりました。