ひとことで
プールの底や浅瀬の海底でゆらゆら揺れる光の網目模様のことです。波打つ水面がたくさんの小さなレンズのように働き、太陽の光を屈折させて、ある所には光を集め、ある所からは光を奪います。光が集まった線が明るく光り、網目の模様になります。
ゲームでの使いどころ
- 水の存在感:浅瀬、プール、川底にコースティクスがあるだけで、「澄んだ水がある」と一目で分かります。水そのものは透明で見えにくいので、底の光の揺れが水を表現してくれます。
- 水中シーン:水中に入ったとき、地形やキャラクターの上に揺れる光を落とすと、海の中らしさが大きく増します。
- ガラスや宝石:コップの水やガラス玉が机に落とす明るい模様も同じ現象です。
- 安価な方法と正確な方法の使い分け:ほとんどのゲームは、模様の画像をUV スクロールさせて底に投影するだけの安価な方法を使います。水面の波と連動させたいときだけ、屈折を計算する方法を使います。
しくみ
屈折を計算する方法
- 水面を細かい格子に分ける:格子の 1 マスごとに、太陽から届く光の束が 1 本あると考えます。
- 屈折させて底まで進める:各頂点で水面の法線を求め、スネルの法則(空気 → 水は屈折率 1.33)で光の向きを曲げ、底の平面とぶつかる位置を求めます。
- 面積の比が明るさ:格子の 1 マスは、波がなければ底でも同じ面積に届くはずです。屈折で面積が縮めば光が集まって明るく、広がれば暗くなります。
- テクスチャに描き込む:底を真上から見た画像(レンダーターゲット)に、屈折後の位置へ移した格子を加算合成で描きます。フラグメントシェーダーで
dFdx/dFdyを使うと、1 画素あたりの面積の比が簡単に求まります。最後に、底のマテリアルがこの画像を読み、太陽の光の代わりに使います。
光の束同士が交差する所(焦線)が明るい線になります。水深によって模様が変わるのもこのためで、浅いと光はまだ集まりきらずぼんやりし、ある深さで最も鋭くなり、さらに深いと束が交差したあと広がって細かく複雑な模様になります。
模様を流す安価な方法
あらかじめ作ったコースティクス模様(またはシェーダーで作った網目)を、2 枚ずらしてスクロールさせ、明るい方を取る(min や積で重ねる)だけです。水面の波とは関係なく動きますが、見た目の効果は十分で、ほとんど負荷がかかりません。水中の物体には、ワールド座標を太陽の向きに投影した座標で模様を読むと、どんな形の物にも貼れます。
実装のポイント
- 格子は画像より細かく:水面の格子が粗いと、光の束が大きな三角形になって模様がぼやけます。画像の解像度と同程度か、それより細かい格子にします。
- 明るさの上限:面積がほぼ 0 になる所では明るさが無限大になります。上限を設けてから加算します。
- 影と組み合わせる:コースティクスは太陽の光そのものなので、影になる所には出ないようにシャドウマップを掛け合わせます。
つまずきやすい点
- 波が大きく長すぎると模様が出ない:水面の曲がり方がゆるいと、光が集まる焦点が底よりずっと深くなり、ぼんやりした明暗しか出ません。短い波長の波を含めると、浅いプールでもくっきりした網目になります。
- 底全体が明るくなりすぎる:直射光にコースティクスを「足す」と、底が白飛びしてコントラストが消えます。直射光をコースティクスで「置き換える」(平均が 1 になるように)のが自然です。
- 模様を流す方法で動きが速すぎる:スクロールが速いと落ち着きのない水に見えます。2 枚の速度と向きを少し変え、ゆっくり動かします。
デモで試すこと
- 「屈折を計算する」では、黄色い線が太陽の光の経路です。水面で曲がった光が底で寄り集まっている所に、明るい線ができているのを確かめてください。
- 「水深」を変えると模様が変わります。浅いとぼんやり、深くすると光の束が交差して細かく鋭い網目になります。
- 「波の高さ」を 0 にすると、水面が平らになり、模様が消えて底が一様に照らされます。
- 「模様を流すだけ(安価)」に切り替えると、見た目は似ていても水面の波とは連動していないことが分かります。「なし」と比べると、コースティクスが水らしさにどれだけ効いているかが分かります。
- 「太陽の高さ」を下げると、光が斜めに入り、模様が一方向に引き伸ばされます。