見出し画像

Unityでの波紋シェーダーの作り方

諸注意

◎免責事項

 本記事に書かれている内容や、配布されているシェーダーアセットは自己責任で御利用下さい。使用に際して発生したトラブル・不利益に関して記事の執筆者兼アセット制作者である射当ユウキは一切の責任を負わないものします。

◎制作物の配布先

 今回制作した波紋シェーダーアセットはBOOTHにて配布しています。
https://tar.booth.pm/items/3820560

◎著作の引用に関して

 本解説記事は著作権法が認める範囲での引用が可能です。
 また本記事の内容を元に、新たな解説記事・動画・あるいはアセットを制作しても問題ありません。

◎どのレベルのUnityユーザーに向けた記事か

 本記事で紹介している波紋シェーダーアセットの制作は、主にUnity(バージョン2019.4.29f1)で行っています。必要に応じて使用する機能についての説明を行いますが、あくまでUnityでワールドを制作するための基礎知識は読者にあるものという前提で話を進めます。そのため、「そもそもシェーダーとマテリアルって何だ?」や「各種オブジェクトの配置方法からして分からん」といったレベルの初学者にはチンプンカンプンな内容かもしれません。分からない単語が出てきた場合は、インターネット上の情報などを参照して、その都度お調べください。

【0】はじめに

◎どんな作品が制作できるのか

 本解説記事では、主にVRChatでの使用を想定した波紋シェーダーのアセットの作り方を解説します。
 今回制作したアセットを使用すると、水面上を動いている人物(アバター)やその他オブジェクトの位置にリアルタイムで波紋を発生させられます。

【gif動画】波紋シェーダーの実例

◎制作プロセスのアウトライン

 本記事で紹介している波紋シェーダーアセットを制作するための、全体的な制作プロセスは下記の通りになります。

【1】レンダーテクスチャを用意する
【2】レンダーテクスチャの画像を白黒マスク化
【3】波紋シェーダーを作る
【4】波紋シェーダーの各種設定を済ませる
【5】波紋シェーダーを元に最終マテリアル制作

 今の段階では分かったような分からんような解説ですが、各工程についての詳細は順次後述していきます。

【1】レンダーテクスチャを用意する

◎レンダーテクスチャとは何か?

 レンダーテクスチャとは、連動させてあるカメラオブジェクトで撮った映像をそのままリアルタイムで画像が更新されるテクスチャ機能のことをいいます。
 レンダーテクスチャを使うと、例えばライブステージ上のスクリーンや、映像を左右反転させて鏡をつくるなどができます。
 本記事で解説する波紋シェーダーは水面上のエリアに入ったオブジェクトの色情報(RGB)を取得し、それに対して波紋を作る仕様になっています。そのため、波紋シェーダーを機能させるためにレンダーテクスチャの用意が必須となります。

◎水面オブジェクの設置

 レンダーテクスチャを設定する前に、まずそもそも水面となるオブジェクトが必要なのでUnityのシーン上に平面(Plane)オブジェクトを追加します。

【図】平面追加

 平面オブジェクトの位置・回転・スケール設定は下図の通りです。ポイントとなるのはスケールのx軸の値が-1になっている点です。

【図】平面の設定

 なぜわざわざ値を変えているのかというと、サブカメラからの映像を元にしたレンダーテクスチャを平面に割り当てるに際してちょうど鏡面反射のように映像を左右反転させたいからです。
 一応、後々に制作するシェーダー側にUVマップを左右反転させる設定を記載する、あるいはBlender等で平面オブジェクトを用意する場合は事前にUVマップ左右反転させておく、などの処置も可能です。しかし、本記事でそれらの方法まで解説していると話が不必要に長くなるので今回は一番シンプルな平面のx軸方向のスケールを-1にするという方法でいきます。

◎レンダーテクスチャの設定

 続いてレンダーテクスチャの設定です。
 Assetエリア内でCreateを選択し『Render Texture』を追加します。

【図】レンダーテクスチャ追加

 レンダーテクスチャの設定は下図の通りです。

【図】レンダーテクスチャの設定

 今回、デフォルト設定から変更したのは以下の項目です。
【Size】
 画像解像度の設定です。値を大きくするとキメ細やかな画像になりますが処理が重くなります。ここでは512×512としましたが、水面オブジェクトの大きさ次第ではサイズを調整する必要があります。
【Depth Buffer】
 深度(奥行)に関する値を取得できます。今回はレンダーテクスチャに映っている画像の色(RGB)の情報だけあれば機能するため深度情報は不要となります。処理を軽くするため『No depth buffer』にしておきます。

◎サブカメラの配置と設定


 続いて、レンダーテクスチャ用に使うカメラの設定です。
 HierarchyからCameraを追加して、水面用の平面オブジェクトの下部分にカメラが上を向くように配置します。
 具体的な設定は下図の通りです。

【図】カメラ設定

 カメラ設定で変更した主な項目は下記の通りです。

【Clear Flags】【Background】
 オブジェクトが映っていない部分をどのように表示するかの設定です。デフォルトではスカイボックスになっていますが、空の色がカメラに映ってしまうと水面上のオブジェクトをマスクすることができなくなります。そのため、単色の黒(RGBの値がゼロ)にしておきます。

【Projection】【Size】
 カメラを透視投影にするか並行投影にするかの設定です。
 レンダーテクスチャ用のサブカメラは水面に対して極めて近い距離を撮影する上、透視投影だとカメラ中心に対して視野が広がる形状となりカメラ位置によっては視野が水面上からはみだしかねません。したがって、平行投影(Orthographic)に切り替えておきます。
『Size』は平行投影にした場合のカメラのサイズです。ここでは平面の大きさに合うように値を『5』にしています。もし平面のサイズを変更する場合、適宜調整する必要があります。

【Clipping Planes】
 カメラでどこまでの距離を撮影するかの設定です。
『Near』が手前側の端、『Far』が奥行側の端の値です。ここでは、水面の少し下あたりから水面上すれすれを通過するオブジェクトまでが映るような数値に設定しました。こちらも場合によっては調整する必要がある項目となります。

【Target Texture】
 このカメラをどのオブジェクトと連動させるかの設定です。
 ここで先ほど作成したレンダーテクスチャと連動させたいので、該当するレンダーテクスチャを選択します。

【2】レンダーテクスチャの画像を白黒マスク化

◎白黒マスク化するシェーダー作成

 続いて、今しがた作ったレンダーテクスチャの画像を白黒マスク化する作業を行います。
 詳しくは後述しますが、波紋シェーダーを作るにあたってはRGB中のRチャンネルとGチャンネルを使って波の高さ情報を格納します。
 となると、何も対策を施さないとB(青)はレンダーテクスチャに映っていても波が立つか否かに反映されないことになってしまいます。
 それだと都合が悪いです。なので、レンダーテクスチャに映っているオブジェクトの映像(RGBの色情報)が真っ黒でない(値がゼロでない)ならば、問答無用で真っ白(RGBの全値が1)になるように設定したいです。
 上記の処理は白黒マスク化用のシェーダーを作ることで解決します。
 まず、新規にUnlitシェーダーを作ります。Unlitシェーダーとはライトの光の影響を受けないシェーダーのことを言います。Unlitシェーダーを使う理由は、水面上のオブジェクトのRGB情報を取得する際に水面に反射や光沢が出ていると具合が悪いからです。

【図】Unlitシェーダーの追加

 デフォルトのUnlitシェーダーのままだとテクスチャ画像を何の処理もないまま張り付けるだけなので、『return col;』の部分を『return sign(col);』に書き換えます。

【図】Unlitシェーダー書き換え

 ここで追加した『sign』と言うのは、もし負の値なら強制的に値を-1に、値が0ならば0のまま、正の値ならば値を1にできる関数です。つまり、レンダーテクスチャ上のRGBの値が0ならば0のままにして、0でないなら強制的に1(真っ白)にできます。

◎マテリアル追加

 上記で作ったシェーダーを、新規マテリアルに設定します。そしてこれを新規のカスタムレンダーテクスチャに設定します。

◎カスタムレンダーテクスチャって何だ?

 続いて、先ほど作成した白黒マスク化用のマテリアルをカスタムレンダーテクスチャに割り当てます。
 レンダーテクスチャとカスタムレンダーテクスチャは名前が似ているので混乱しやすいですが、似て非なるものとなります。
 カスタムレンダーテクスチャは、テクスチャに対してマテリアルや別のテクスチャを任意に設定できる機能です。
 と言っても分かりにくいので実際にカスタムレンダーテクスチャを追加してみましょう。

【図】カスタムレンダーテクスチャの追加

 AssetのCreateから『CustamRenderTexture』を追加します。
 カスタムレンダーテクスチャの設定項目を見ると、マテリアルやテクスチャを設定可能なことが分かります。今しがた追加したカスタムレンダーテクスチャの項目を以下のように設定します。

【図】カスタムレンダーテクスチャの項目

 ここでデフォルトから設定を変えた項目に関して解説します。
【Size】
 画像解像度です。ここでは512×512としました。
【Color Format】
 RGBAチャンネルの内でどのチャンネルを使うかを設定できます。
 後述する波動シミュレーション用のシェーダーではRチャンネルとGチャンネルのみを使うの『R32G32_SFLOAT』を選択します。
【Depth Buffer】
 深度情報は使わないので『No depth buffer』にしておきます。
【Material】
 カスタムレンダーテクスチャで使うマテリアルを設定します。
 ここには先ほどレンダーテクスチャを白黒マスク化するシェーダーを割り当てたマテリアルを設定します。
【Initialization Mode】
 カスタムレンダーテクスチャを初期化するタイミングを設定できます。レンダーテクスチャから得られる画像情報をリアルタイムで使用したいので『Realtime』を選択します。
【Texture】
 カスタムレンダーテクスチャに割り当てたマテリアルに対して、どんなテクスチャを設定するか選べます。ここではサブカメラからの画像を投影させているレンダーテクスチャの画像をテクスチャとして使用します。
【Update Mode】
 どのタイミングでカスタムレンダーテクスチャの画像を更新するかを設定できます。こちらもリアルタイムで画像更新をしたいので『Realtime』を選択します。

【3】波紋シェーダーを作る

◎波紋シェーダーのプロトタイプ

 波動シミュレーション用のシェーダーを制作するにあたって、まず下記のURLのコードを参考にします。
https://tips.hecomi.com/entry/2017/05/17/020037

 上記の記事を参考に波紋シェーダーを作ってみると、下記の画像のようになります。

【gif動画】波紋シェーダーのプロトタイプ

◎プロトタイプの変更

 この状態ではあくまで、最初に入力した位置情報(赤丸の画像)に対して波を作れますが、リアルタイムでの更新ができません。それだと「VRChatワールドで水面上のオブジェクトに対して波紋を作る」という要件を満たせません。
 なので、何か所かコードを変更します。

 まず、上記の記事は2017年に書かれたものなので内容をUnity2019版に対応させる必要があります。
 Unity2019でカスタムレンダーテクスチャを使用する際は、下記のような宣言が必要になります。
 参照したUnityマニュアルのURLは以下のものになります。
https://docs.unity3d.com/ja/2019.4/Manual/class-CustomRenderTexture.html

 また、大元のコードでは初期状態に対して波紋を作れますが、以後の更新ができません。なので、画像の更新や波紋の減衰の影響を加味できるように書き換えます。
 書き換えに際しては、下記のURLのUnityカンファレンスの動画を参考にします(14分30秒あたりのフラグメントシェーダーのコードを参照)
https://www.youtube.com/watch?v=6EtTI5xC524&ab_channel=UnityJapan

 またインプット用のテクスチャをマテリアルに設定できるようにしたいので、プロパティとしてテクスチャ画像(先ほど作ったカスタムレンダーテクスチャの画像)をアタッチできるようにする必要もあります。

◎最終的なコード

 上記の条件を満たすようにコードを編集すると以下のようになります。

Shader "005_Simulation_Shader"
{

	Properties
	{
		_InputTexture("InputTexture", 2D) = "" {}
		_Attenuation("Attenuation", Range(0, 0.2)) = 0.005
		_PhaseVelocity("PhaseVelocity", Range(0.0, 0.5)) = 0.02
	}

	SubShader{

		Lighting Off
		Blend One Zero

		Pass {
			CGPROGRAM

			#include "UnityCustomRenderTexture.cginc"
			#pragma vertex CustomRenderTextureVertexShader
			#pragma fragment frag
			#pragma target 3.0

			sampler2D _InputTexture;
			half _PhaseVelocity;
			float _Attenuation;

			float4 frag(v2f_customrendertexture i) : SV_Target
			{
				float2 uv = i.globalTexcoord;

				float du = 1.0 / _CustomRenderTextureWidth;
				float dv = 1.0 / _CustomRenderTextureHeight;
				float3 duv = float3(du, dv, 0) * 3;

				float2 c = tex2D(_SelfTexture2D, uv);
				float p = (2 * c.r - c.g + _PhaseVelocity * (
					tex2D(_SelfTexture2D, uv - duv.zy).r +
					tex2D(_SelfTexture2D, uv + duv.zy).r +
					tex2D(_SelfTexture2D, uv - duv.xz).r +
					tex2D(_SelfTexture2D, uv + duv.xz).r - 4 * c.r)) * 0.999;

				p = p + tex2D(_InputTexture, uv).r;
				p = p * (1 - _Attenuation);

				return float4(p, c.r, 0, 0);
			}
		ENDCG
		}
	}

}

【4】波紋シェーダーの各種設定を済ませる

◎シェーダーをマテリアルに割り当てる

 前述のコードのシェーダーを、新規マテリアルに設定します。
 そして、新規マテリアルに対して白黒マスク化したカスタムレンダーテクスチャをアタッチします。

◎新規カスタムレンダーテクスチャの追加

 2枚目のカスタムレンダーテクスを新規作成して波動シミュレーション用のシェーダーを割り当てたマテリアルを設定します。
 具体的には下図のようになります。

【図】波動シミュレーション用のカスタムレンダーテクスチャ

◎水面用の平面オブジェクトへの割り当て

 上記の波動シミュレーション用のカスタムレンダーテクスチャを水面となる平面オブジェクトに割り当てると、以下の用に水面上にリアルタイムで波紋を作れるようになります。

【gif動画】波紋シミュレーションの原形

 ただし、上記の画像を見て頂けると分かるようにあくまで波紋用のマスク(黄色い波)が出来ただけです。これだと実際に波としては完成しているとは言い難いので、最後にこのマスクをベースに最終出力用のマテリアルを作ります。

◎波の減衰や伝搬速度のパラメータ

 なお、話が前後しますが波紋の減衰と位相速度は『006_Simulation_Material』からパラメータとして数値調整できるようになっています。

【図】04-04_パラメータ調整

【5】波紋シェーダーを元に最終マテリアル制作

◎シンプル版の波紋マテリアル

 前述の波動シミュレーション画像をマスクにしたマテリアルで、シンプルなものはUnityに備え付けのStandardマテリアルを使う方法があります。
 Assetエリア内でCreateから『Material』→『Standard』を選択します。
 マテリアルの名前は『008_Water_Material』とします。
 その状態で追加したマテリアルの『Occlusion』に、波動シミュレーション画像のカスタムレンダーテクスチャをテクスチャとして割り当てます。

【図】波紋マテリアルの設定

 水面(平面オブジェクト)を水のような見た目にするためにAlbedoの色を青にしたり、水面っぽいテクスチャを割り当てても良いかもしれません。今回は下記のようなテクスチャを使用しました(GIMPに備え付けのパターン模様を使用)。

【図】水の色のテクスチャ

 実際にGameタブ内で動かしてみた例が以下の画像になります。

【gif動画】波紋マテリアルの例

◎改良版の波紋マテリアル

 上記のマテリアルはシンプルな形ですが、例えば専用のシェーダー別途用意し波動シミュレーションをノーマルマップとして使ったり、水面用のテクスチャををUVスクロールさせるなどして以下の動画のようなものも作れます。なお以下のシェーダーを割り当てたマテリアルはBOOTHでの配布作品に入っています(宣伝)

【gif動画】波紋シェーダー改良版

【EX】VRChatでの使用例など

◎実際の使用例

 実際にVRChatのワールドとして使ってみた様子は以下の通りです。

【gif動画】VRChatでの実装

◎最後に

 波紋シェーダーおよびそれを使用したアセット群の作り方の解説は以上です。
 本記事が読んで下さった方々の創作活動の助けになることを祈りつつ、本日は筆を置くとします。ここまで読んで下さってありがとうございました!



この記事が気に入ったらサポートをしてみませんか?