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テクスチャーを生成するエクスペリメント」です。明日の記事もお楽しみに!
