Heliodor 2019/12/09 17:12

水の表現について

(おかしいな、エロゲーを作っているハズなのに……?)


先日、プール用のテスト動画を twitter で流したところ結構な数のRTいいねを頂いて、注目されすぎたらビビるディスコミュ系……じゃなくて、どんな風に実現しているのか少し解説します。

https://twitter.com/helio_dor/status/1198521698493952000
初期段階。
結構プールっぽく出来てると思ったんですが、今見るとちょっと微妙な感じ。プルプル。



https://twitter.com/helio_dor/status/1198923732149915648
後日、色々足したり調整したりしていかにもな感じに。
やはり水しぶきが付くと一気にそれらしくなりますね。



https://twitter.com/helio_dor/status/1199658082592124929
更に調整、水の色と水位変更機能搭載。
血の海やらLCLやらバスクリンも可能に(やりませんが)。


以前、3Dぽいものを2Dで表現するとかんなんとかって話をしたのですが、その後いろいろありまして、結局Zバッファやガチ3Dモデルは使わないでいます。

というわけで、今回のプールもZバッファとか使ってません(!)。
あと、水面の波ですが、波動方程式で解くとか難しい事はやらずに、加減乗除だけで表現しています。
プレイヤーが入るとそこを中心に円状に波が広がったりしますが、あれって三角関数すら使ってないんですよ。

でも結構それっぽく見えますよね?

プールにキャラクターが入り込む表現

プールにキャラクターが入るということは、水で満たされた箱にキャラクターが「めりこんで」いるという事です。
水位がプレイヤーの腰ぐらいだとしたら、プレイヤー下半身は水の中で、上半身は水の外です。
Zバッファも使わずにキャラクターよりも奥・手前の水をどうやって分けているのかというと、単にプールを薄くスライスして奥行き方向に重ねているだけです。
(テストで置いてあるハードルがおかしなことになっているのが分かりやすいかも?)

ちなみに、ひとつのスライスの奥行きが画面上で10ドットぐらいの幅があるので、キャラクターが10ドット以上奥行き方向に移動しないと、各スライスとキャラクターの前後関係が更新されません。

でも水面が揺れるので意外とバレないです。


水面の模様

水面に現れている独特の模様は「コースティクス」というもので、そのものズバリなテクスチャ画像を探してきて、水面に貼りつけているだけです。

1枚のコースティクスを張るだけだと模様が全く変化しないので、16枚のコースティクス画像をクロスフェードで切り替えながらアニメさせています。

このあたりは caustic, texture, animation, water, surface, public-domain などのキーワードを組み合わせて検索するといい感じでフリーなものが見つけられると思います。

この画像を自力で作る場合、元ネタとして使えそうなものに亀甲模様とボロノイ図があります。
どちらも様々なサイトで描き方が紹介されていますし、絵を描くツールを使ってる人なら、なんとなくこれを加工して作れるのではないでしょうか(というかクリスタならそういうプラグインあるかも?)。


水の向こう側の背景の歪み

これは適当な法線マップを使って背景をゆがませて描画しているだけです。
まず大前提として、プールの各スライスは奥から順番に描画しています(Zバッファつかってないし、半透明なので)。

この法線マップを自力で真面目に作ろうとしすると結構難しいと思うのですが、物理的に正確である必要は全くないので、単純にパーリンノイズ画像を用意するだけで充分それっぽいものが作れると思います。
(パーリンノイズぽい画像は Photoshop なら「雲模様」で作成できます)

各スライスを描画する直前に、その時点でバックバッファに書き込んである画像の内容を取得しておきます(これは各スライスを描画する直前に毎回行います)。
これは Unity でいえば GrabPass ですが、そんなモダンなものは使っていないので頑張って IDirect3DDevice9::GetRenderTargetData を使って取得します。

この時取得したバックバッファ画像は「そのスライスの向こう側に存在する景色」を表しているので、これをスライスのテクスチャとして使い、適当に歪ませて水色に染めてやります。

肝心の歪ませる方法ですが、法線マップを使い、そのRGB値に従って背景テクスチャを適当に歪ませます。
これはもちろんシェーダー内で行います。この時使う法線マップですが、これもコースティクス画像と同様にwater, normal-map, texture, public-domain などのキーワードを組み合わせて検索すると見つけられます(便利ですねぇ……)。


波の表現

水面部分の上下運動は、格子状に並んだ点を用意してそれぞれの点の上下運動をシミュレートした結果を用います。
とはいっても理屈は簡単で、点として用意するのは


struct POINT {
 float y; // 点の高さ(水深ではなく、平均水面からの高さを表す)
 float speed; // 点の移動速度(上下方向のみ)
};


これだけです。これを 10x10 とか、20x20 とか、必要なだけ格子状に並べることを考えます。各点は、その前後左右の点からの影響だけを受けます。

「影響」とはどんな影響かと言えば、「隣接する点と自分の点の高低差」です。
高低差が小さければ点は遅く動き、大きければ早く動きます。要するに高低差に比例した加速度を得るという事です。

具体的にはこんな感じ。


float K = 適当な比例定数 (0 以上 1 未満)
for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT p = GetPoint(x, z); // 自分
 POINT
L = GetPoint(x-1, z); // 左の点
 POINT R = GetPoint(x+1, z); // 右の点
 POINT
F = GetPoint(x, z-1); // 前の点
 POINT *B = GetPoint(x, z+1); // 奥の点

 p->speed += (L->y - p->y) K; // 左の点との高低差に比例して加速する
 p->speed += (R->y - p->y)
K; // 右の点との高低差に比例して加速する
 p->speed += (F->y - p->y) K; // 前の点との高低差に比例して加速する
 p->speed += (B->y - p->y)
K; // 奥の点との高低差に比例して加速する
}


こうすると各点の速度が求まるので、それに従って各点の y 座標を更新します。

for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT *p = GetPoint(x, z);
 p->y += p->speed; // y 座標を更新
}


で、このままだと発散してしまったり、いつまでたっても水面が落ち着かない事があるので、各点の y 座標が平均水面高さに戻るように小細工します


float WATER_DEPTH = 100.0f; // 水面高さ
float R = 0.01f; // 適当な定数 (0 以上 1 未満。小さい数にする)
for (int z=0; z<ZCount; z++) for (int x=0; x<XCount; x++) {
 POINT p = GetPoint(x, z);
 p.y += (WATER_DEPTH - p.y)
R; // 水面高さに戻るように
}


これであとは放っておけば、ワリとそれっぽい水面運動ができます。
キャラクターが水の中に入った時は、そのキャラクターの位置に対応する点の y 座標をぐいっと持ち上げたり、押し下げたりしてやるだけです。あとは勝手に波紋が広がっていきます。


float WAVE_HEIGHT = 20.0f; // 波の高さ
POINT *p = GetPointAtCharacter(); // 波紋の生成位置にある点を得る
p->y = WATER_DEPTH + WAVE_HEIGHT; // 適当な高さまで持ち上げる


端の点はどうするんだとか、そういう説明は省きましたが 水面、波、格子法、シミュレーション、アルゴリズムなどの単語で検索するとそれっぽいサイトが見つかると思いますので、参考にしてみてください。


その他

細かい所では、水面の傾きに応じて水面メッシュの頂点色を調整して陰影をつけてみたり、キャラクターが水の中を一定以上の速度で移動しているときはドット絵の水しぶきや泡を出してみたり、水底部分にコースティック模様を黒色で重ねて影っぽくしてみたりと、いろいろやっています。


https://twitter.com/helio_dor/status/1201834340071526402


……で、結局なんでプール作ってるのかって?
いやーそれはまだ秘密デスヨー(棒)

この記事が良かったらチップを贈って支援しましょう!

チップを贈るにはユーザー登録が必要です。チップについてはこちら

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索