ひとことで
水面を格子状に並んだ点の高さだけで表し、各点が周りの点との高さの差に引っ張られて動くようにすると、1 か所を押し下げただけで波紋が輪になって広がり、壁で跳ね返り、重なり合うようになります。2 次元の波動方程式を差分で解く、とても軽くて効果の大きい水面の作り方です。
ゲームでの使いどころ
- 物が触れた所の波紋:キャラクターが池を歩く、雨粒が落ちる、石を投げ込む、ボートが進む。インタラクティブな水面の定番です。
- 狭い水面:池、噴水、水たまり、プール、バスタブ。範囲が限られるので、格子で全体を計算できます。
- 大きな海と組み合わせる:海の大きなうねりはゲルストナー波や FFT で作り、船の周りだけ高さ場の波紋を重ねる、という使い方もします。
- 水以外にも:トランポリンの膜、ゼリー、力場のバリアの揺れなど、「押すと周りに伝わる」表面に広く使えます。
しくみ
2 次元の波動方程式は、次のように書けます。
(ラプラシアン)は「周りの平均より、自分がどれだけ低いか」を表します。周りより低い点は上へ、高い点は下へ加速するので、へこみは周りを引き下げながら外へ広がっていきます。
これを格子で近似すると、前のフレームと今のフレームの高さだけで次のフレームが求まります(ベルレ積分と同じ形)。
- 速度を持たなくてよい:「今の高さ − 前の高さ」が速さの代わりになるので、高さの配列を 2 枚持って交互に使うだけで済みます。
- 減衰:結果に を掛けて、波が少しずつ消えるようにします。
- 障害物:岩や壁のマスの高さを常に 0 にしておくと、そこで波が跳ね返ります。池の縁も同じです。
- 安定の条件:(1 ステップで波が進む距離の 2 乗)が 0.5 を超えると計算が発散します。速い波が欲しいときは、1 フレームを複数ステップに分けます。
実装のポイント
- GPU なら[ピンポンバッファ](/keywords/implementation/performance/ping-pong-buffers/):読むテクスチャと書くテクスチャを毎フレーム入れ替えます。 でもほとんど負荷がかかりません。
- しずくの落とし方:1 マスだけ下げるとぎざぎざの波になります。半径数マスに、中心ほど深い形でへこみを与えます。
- 描画:高さを頂点の上下に使うか、法線マップだけに使います(頂点を動かさず、法線の傾きだけで波紋を見せる方法は非常に軽量です)。法線は周りの高さの差から求めます。
- 平均の高さのずれ:押し下げるだけの操作をくり返すと、水面全体が少しずつ下がっていきます。毎フレーム平均を 0 に戻すか、押し下げたのと同じ量を周りに足します。
つまずきやすい点
- 格子の向きがにじみ出る:上下左右の 4 近傍だけを使うと、斜め方向の波が少し遅くなり、波紋が四角っぽくなることがあります。斜めの 4 点も重みを付けて加えると丸くなります。
- 境界での跳ね返りが強すぎる:開けた水面の端では、波が消えてほしいのに跳ね返ってしまいます。端の近くだけ減衰を強くする(吸収層)と自然に消えます。
- 水の流れは表せない:高さ場の波は「その場で上下する」だけで、水そのものは移動しません。流れや水かさの変化が必要なら浅水波モデルを使います。
デモで試すこと
- 水面をクリックすると、しずくが落ちて輪の波紋が広がります。岩に当たると跳ね返り、池の縁でも反射して、波同士が重なり合います。
- 「雨を降らせる」をオフにしてから 1 か所をクリックすると、1 つの波紋がきれいに広がる様子を観察できます。
- 「波の速さ」を上げると、波紋が速く広がり、波長も長くなります。
- 「減衰」を 0 に近づけると波がいつまでも残り、上げるとすぐに静まります。
- 「岩で波をさえぎる」をオフにすると、岩の後ろにも波が回り込みます。オンのときは岩の裏に波の届かない影ができます。