1 はじめに
こんにちは、カヤックのソーシャルゲーム事業部のmadaです。この記事はカヤックUnityアドベントカレンダー2018の22日目の記事です。
「Lion Studios」が作成した「Happy Glass」というゲームを遊んでみて、液体の表現に興味を持ちました。この記事では「簡単に水に近い表現を実現したい」話をします。
Happy Glass - Lion Studios- Youtube
2 Metaball?
調べてみると水の表現に、Metaballという手法が使えそうでした。Metaballの説明をしようとすると数式が出てくるので、先に実装した結果を紹介します。2つの水滴が接近したときに水滴がひっつくような表現が、なんとなくできているのではないでしょうか。
2DのMetaballを数式で説明すると、以下の式になります。画面上の(x,y)ピクセルを塗るときに、式の条件を満たしている場合塗る、そうでない場合塗らない、と制御します。例として2つのメタボールが存在するときの2次元の等高線グラフも作成してみました。
3 Metaballの実装概要
早速Metaballを実装していきましょう。実装するのは先程紹介した数式です。数式を分解し、3ステップに分けて紹介します。
3.1 ShaderでΣ内の計算
Σ内の計算を実装します。数式は以下の通りです。Shaderで実装するので、出力を画像のRGBAにする必要があり、_Scale
, _Clip
という調整用パラメーターを用意しています。frag関数の中で、uvの中心(0.5, 0.5)からの距離を色に変換しています。今回は後から調整しやすいようにShaderで実装しましたが、調整用パラメーターが定まれば画像データにしてしまっても良いところです。
Shader "Metaball/MetaballParticle" { Properties { _Color ("Color", Color) = (1,1,1,1) _Scale ("Scale", Range(0,0.05)) = 0.01 _Cutoff ("Cutoff", Range(0,05)) = 0.01 } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; sampler2D _MainTex; fixed4 _Color; fixed _Scale; fixed _Cutoff; v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; return OUT; } fixed4 frag (v2f i) : SV_Target { fixed2 uv = i.texcoord - 0.5; fixed a = 1 / (uv.x * uv.x + uv.y * uv.y); a *= _Scale; fixed4 color = i.color * a; clip(color.a - _Cutoff); return color; } ENDCG } } }
3.2 カメラとRenderTextureでΣの実装
つぎにΣの実装を行います。3.1でΣ内の計算出力を画像にしたので、Σの実装はn個のMetaballを描写するだけです。次の処理で必要なのでCameraの出力はRenderTextureに設定しておきます。
3.3 ShaderでThresholdの実装
次にthresholdの実装を行います。実装は3.2で出力したRenderTextureを描写するShaderのfrag関数の中で、clip(color.a - _Cutoff);
を呼ぶだけです。内側と境界に異なる色を設定できるように、clip
関数の後に条件分岐を設定しています。
Shader "Metaball/MetaballRenderer" { Properties { _MainTex ("MainTex", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) _Cutoff ("Cutoff", Range(0,1)) = 0.5 _Stroke ("Storke", Range(0,1)) = 0.1 _StrokeColor ("StrokeColor", Color) = (1,1,1,1) } SubShader { Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata_t { float4 vertex : POSITION; float4 color : COLOR; float2 texcoord : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 texcoord : TEXCOORD0; }; sampler2D _MainTex; half4 _Color; fixed _Cutoff; fixed _Stroke; half4 _StrokeColor; v2f vert(appdata_t IN) { v2f OUT; OUT.vertex = UnityObjectToClipPos(IN.vertex); OUT.texcoord = IN.texcoord; OUT.color = IN.color * _Color; return OUT; } fixed4 frag (v2f i) : SV_Target { fixed4 color = tex2D(_MainTex, i.texcoord); clip(color.a - _Cutoff); color = color.a < _Stroke ? _StrokeColor : _Color; return color; } ENDCG } } }
成果物
以上で2DのMetaballのRenderingができるようになりました。ボールにColliderを付けて上から落としてみた画像を貼ります。まぁまぁ水に近い表現ができていると思います。比較的に簡単にできるのでタイトル通り、「簡単に水に近い表現が実現できた!」と言い切っておきます。
簡単に上のシーンの構成を紹介します。
1. 「MetaBallLayer」のみを描写する「MetaBallCamera」を作成します。 2. 単体Metaball(水玉)を描写するために空のPrefabを作成します。 3. Prefabに(3.1)で紹介したShaderで描写するQuadを作成します。 4. Prefabのレイヤーを「MetaBallLayer」に設定します。 5. (3.2)で紹介したように、「MetaBallCamera」のRenderTargetにRenderTextureを設定します。 6. RenderTextureを「MainCamera」に描写するQuadを作成します。 7. ステージ(黒い棒)にColliderを付け、MainCameraで描写します。 8. 単体MetaballのPrefabを大量にInstantiateし、物理挙動に任せます。 ※ Quadは UnityMenu > GameObject > 3D Object > Quad から生成できます
明日は?
明日は目からビームが出ている漫画名刺の「アファトさん」による「Compute ShaderでランタイムにSDFテクスチャーを生成するエクスペリメント」です。明日の記事もお楽しみに!