Unity標準アセットWater4Advanceの水面をCompute Shaderで揺らす


経緯

水面を走るものの後ろに波を立てたいなーと思い、Asset Storeを覗いてみました。しかし、ものによって求めている雰囲気とは若干違ったり、標準アセットの水面の方が綺麗なので見劣りしたり、高級なものはお金がかかるしいきなり使いこなせなさそうだったり…。ということで、まず自分でできる限りのことをやってみました。

アルゴリズムを調べる

シミュレーションの理論的なところを勉強しましょう。

波を再現するモデルは他にもいくつかあるようですが、おそらくこれが一番単純明快なので、この方法を採用してみます。サンプルプログラムも載ってるので、ありがたく参考にします。

しかし、それなりの解像度でシミュレーションするには万単位の点の座標を計算する必要がありそうです。そこで、そのような大量の計算をGPUに任せることのできる Compute Shader について調べてみました(注:DirectX11が必須のため、Macだと動きません)。

大量の格子点の座標を高速に計算する方法を調べる

以下のサンプルが分かりやすかったです。これにいろいろ手を加えてみて、まずCompute Shaderの使い方を覚えました。

シェーダ特有の記法などは覚える必要がありますが、基本的にはC系の言語と大差ありません。厄介なのは、C#のスクリプトとシェーダ間でデータをやり取りする部分ですね。

ざっくり以上を参考に作ったものが以下になります。他にもいろんなことを試してますが、細かいところはまた改めて解説することにして、とりあえず動くものを見てみましょう。

デモ

デモは https://github.com/but80/OceanSurfaceDemo に置いてあります。cloneするなりZIPをダウンロードして展開するなりしておいてください。そのあと、以下の通り操作すると実行できます。

  1. 新規プロジェクトを作成
  2. メニューの Assets > Import Package > Environment を選択
  3. 表示されたImportダイアログで一旦 None を押してチェックを外し、Standard Assets/Environment/Water 以下のみが含まれるようにチェックして Import を押す
  4. 先ほどcloneしておいた OceanSurface.unitypackage をインポート
  5. OceanSurface/OceanSurfaceSampleScene を開く
     
    ここでシーンを実行してみると、白い円柱をドラッグできたり、画面を右ドラッグで回転できたりするのが確認できると思いますが、まだ円柱を動かしても波は立ちません。Water4Advanceのシェーダ中で、Gerstner Displaceによる波の生成を行っている箇所を、自前のコードで置き換えてやる必要があります。
     
  6. Assets/Standard Assets/Environment/Water/Water4/Shaders/FXWater4Advanced を以下のとおり編集(114行目あたりから、コメント中の INSERTEDREMOVED を参考に)
FXWater4Advanced.shader

    // foam
    uniform float4 _Foam;

    // !!! INSERTED
    #include "../../../../../OceanSurface/OceanSurfaceInclude.cginc"
    // !!! /INSERTED

    // shortcuts
    #define PER_PIXEL_DISPLACE _DistortParams.x
    #define REALTIME_DISTORTION _DistortParams.y
    #define FRESNEL_POWER _DistortParams.z
    #define VERTEX_WORLD_NORMAL i.normalInterpolator.xyz
    #define FRESNEL_BIAS _DistortParams.w
    #define NORMAL_DISPLACEMENT_PER_VERTEX _InvFadeParemeter.z

    //
    // HQ VERSION
    //

    v2f vert(appdata_full v)
    {
        v2f o;

        half3 worldSpaceVertex = mul(_Object2World,(v.vertex)).xyz;
        half3 vtxForAni = (worldSpaceVertex).xzz;

        half3 nrml;
        half3 offsets;

        //!!! REMOVED
        //Gerstner (
        //  offsets, nrml, v.vertex.xyz, vtxForAni,                     // offsets, nrml will be written
        //  _GAmplitude,                                                // amplitude
        //  _GFrequency,                                                // frequency
        //  _GSteepness,                                                // steepness
        //  _GSpeed,                                                    // speed
        //  _GDirectionAB,                                              // direction # 1, 2
        //  _GDirectionCD                                               // direction # 3, 4
        //);
        //!!! /REMOVED

        //!!! INSERTED
        OceanParticleDisplace(offsets, nrml, v.texcoord);
        //!!! /INSERTED

        v.vertex.xyz += offsets;

注意点ですが、シェーダのコードを書き換えた後、エディタがうまく再コンパイルしてくれないときがありました。そのような時は、コードに何かしら空行でもなんでも差分を加えた状態で保存してから、もう一度エディタにフォーカスすると再コンパイルしてくれるようです。

ここまで手を加えてから、改めてシーンを実行してみてください。

実行結果


製作途中に撮った動画

という具合に、白い円柱をドラッグすると軌跡に波が立ちます。円柱のある場所は波が貫通しないようになっており、波の回折なども確認できると思います。

補足

水面下にある砂のテクスチャ画像は Creative Commons Attribution 4.0 International License にて Pixar One Twenty Eight by Pixar Animation Studios のものをお借りしております。

テクスチャ画像以外のすべてのアセット(コードやシェーダ)は MIT license で配布します。改変しないと役に立たないかと思いますが、ご自由にどうぞ。

本当はWeb Playerでデモを置いておきたかったのですが、Compute Shaderはdoes not supportedだと言われてしまいました。が、うまくやっておられる方もいるようで、どうやるのかは調べないとわからんです…。他にも見よう見まねでやっているので「ここはこうするもんなのか?」とか分からないことが多いのですが、皆様何かありましたらアドバイスくださるとうれしいです

波の計算について独自にいろいろ工夫した点があるのですが、それについてはまた今度書きます。多分!


  1. 鯖落ちしてるようなので → Googleキャッシュ