Clearwater VRC 1.0.0

Clearwater VRC の仕組み

VRChat ワールドの透明な浅瀬を、エディターで焼き込んだ少しのデータと、毎フレームの GPU の計算で描いています。水面の揺れ、水越しの水底、光の模様、打ち寄せる波、水中からの眺め、空の色、最後の色の整え方を、動く図で順に見ていきます。

屈折率、水の吸収、臨界角、トーンカーブなどの数値は、パッケージの実装と同じ値です。寸法と一部の色は、見やすさのために誇張したり簡略化したりしています。詳しくは docs/technical.md にあります。

technical.md 1 章

毎フレーム、材料を作ってから 5 つのシェーダーで描く

左の Udon と真ん中の下ごしらえは、画面には出ない材料作りです。Udon は、見ている人がどの水にいるか、どこに波紋を落とすか、波音がどこまで進んだかを決めます。下ごしらえでは、Custom Render Texture(CRT)の連鎖で波の高さと波紋を計算し、専用のカメラで光の模様を描きます。右の 5 つのシェーダーが、その材料を読んで画面を描きます。流れる点は、材料の受け渡しを表しています。

毎フレーム作る材料は 3 つです。波の高さ(CRT_FFT_Y1_Surface、256²、半精度)、波紋(CRT_RippleNormals、見ている人の前の 14 m 四方を 512²)、光の模様(RT_Caustics、1024²)。Seabed は深度テクスチャと影の印を通して、Water に水底の影を渡します。
technical.md 4 章

波は 4.6 m 四方の模様を敷き詰め、60 秒で元に戻る

水面の揺れは、4.6 m 四方を 256 × 256 に分けた波の模様です。エディターで一度だけ波のスペクトルを作り、毎フレーム、時刻の分だけ位相を進めて、逆 FFT で高さと傾きを求めます。周波数を 2π/60 の倍数に丸めてあるので、波は 60 秒でちょうど元に戻ります。

模様は場所によらないので、海の広さやプールの数が増えても、この計算の負荷は変わりません。ただ、同じ模様を並べると繰り返しが見えてしまいます。そこで水面では、0.41 倍に縮めて回した 2 枚目を小さく重ねています。

模様の大きさ
4.6 m 四方、256²
波の山
繰り返し
60 s
計算
点線が 4.6 m の模様の区切りです。チェックを外したままだと、どの区画にも同じ模様が並んでいるのが分かります。チェックを入れると、実際の水面と同じく 2 枚目が重なり、繰り返しが目立たなくなります。図の模様は、FFT の代わりに、同じ条件(4.6 m と 60 秒で繰り返す)を満たす 12 本の波を足して描いています。
technical.md 5・7 章

水越しの水底は、画面から拾わずに計算で求める

水面から見える水底は、画面に描かれた水底のメッシュではありません。水面のシェーダーが、屈折した視線を、焼き込んだ水底の深さの式に当てて求めています。まず水面の真下の深さで交点を仮に置き(1)、その位置の深さで置き直し(2)、もう一度置き直します(3)。3 回で、起伏のある水底にもほぼ正確に当たります。

交わった所の砂の色に、水の中を進んだ距離だけ吸収を掛けます。赤が一番早く消えるので、深い所ほど青緑になります。この方式なら、画面の外や手前の物に隠れた水底でも絵が崩れません。水の中にあるアバターや杭だけは、深度テクスチャで見つけて画面から拾います。

水面の屈折率
1.3335
吸収(1 m あたり)
R 0.40 G 0.074 B 0.088
深さの式
cwFloorDepth
断面図です。右が浜、左が沖で、高さは 2 倍ほどに誇張しています。点線の縦と横の線が、「その位置の深さを読んで、視線がその深さに達する所へ移る」1 回分の計算です。下の色は、水の中を進んだ距離で砂の色がどう変わるかを示しています。
technical.md 6 章

光の模様は、曲げた格子の縮み具合で明るさを決める

水底に揺れる光の網目は、Evan Wallace の方法で作っています。波 1 枚分を覆う格子の各頂点を、水面で屈折した太陽光に沿って深さ 1.6 m まで曲げます。曲げた後に網目が縮んだ所は光が集まった所なので、明るく塗ります。

赤・緑・青で屈折率を少しずつ変えて 3 回描くので、模様の縁にうっすら虹色がにじみます。描く先は 1024² のテクスチャで、専用の正射影カメラが、y = −20000 の地下で Caustics レイヤー(23)だけを描きます。水底・水面・アバターの 3 つが、同じ模様を読みます。

格子
283 × 283 頂点
深さ
1.6 m
屈折率(R・G・B)
1.3315 / 1.3335 / 1.3365
負荷
4.6 m 幅の断面です。上から来る平行な光が、揺れる水面で曲がります。いちばん下の帯が、水底に届いた光の明るさです。平らな水面のときを 1 として、上のグラフに描いています。
technical.md 8 章

打ち寄せる波は、波音の再生位置を時計にして動く

浜に打ち上げる波は、聞こえる波音と合うように動かしています。90 秒の波音のループから、波が崩れる瞬間を 27 か所、あらかじめ検出してあります。実行中は、コントローラーが波音の AudioSource.time を、シェーダーの時計 _SwashClock に渡します。

波打ち際は 4 つの段階で計算します。沖から来るうねりは、浅くなるほど遅く高くなります。高さが水深の 0.8 倍を超えると崩れて泡立ちます。崩れた水は浜を放物線を描いて駆け上がり、上がるときの 0.6 倍の加速で引いていきます。水が引いた後の砂は暗く光り、数秒かけて乾きます。

波音のループ
90 s、崩れる瞬間 27 か所
崩れる高さ
0.14 m
打ち上げ
場所ごとの遅れ
√(g·h)
上の帯は、波音 90 秒の音量(模式)です。三角の印が波の崩れる瞬間で、縦線が今の再生位置です。下の断面では、印の時刻に合わせて波が崩れ、浜を駆け上がります。高さは誇張しています。
technical.md 7 章

水中から見上げると、空は 97° の円の中に集まる

水から空気へ抜ける光は、水面で大きく曲がります。真上から 48.6°(臨界角)より内側の視線だけが空気へ抜け、空の半球全体がその円の中に縮んで見えます。これがスネルの窓です。窓の外では水面が鏡になり(全反射)、水底や水中の物が映ります。

水面のシェーダーは、上から見るパスと下から見るパスを持ち、カメラの側に合わないほうを潰します。カメラが水面から 60 cm 以内にあるときは、レンズのある所で波が実際にどの高さにあるかを見て、画素ごとに上か下かを決めます。窓の外に映る物は画面からたどり、見つからなければ計算した水底を映します。

臨界角
asin(1 / 1.3335) = 48.6°
窓の広さ
97.2°
水中の霧
左が断面、右が見上げたときの画面です。明るい線が今の視線で、水中での角度と、空気中へ抜けた後の角度、抜ける光の割合(フレネル)を示しています。
technical.md 13 章

空の色は、太陽の高さごとに一度だけ計算した表から引く

時刻の空は、エディターで大気の散乱を一度だけ計算し(5 秒ほど)、表にしてあります。空気の分子(Rayleigh 散乱)、細かい粒子(Mie 散乱)、オゾン、2 回以上の散乱まで入れた地球の大気です。実行中は、Udon の ClearwaterSky が時刻から太陽と月の位置を求め、グローバル変数でシェーダーに渡します。シェーダーは見る方向ごとに表を 1 回引くだけです。

太陽が低いほど、光が空気の中を進む道のりは長くなります。そのあいだに青い光が散らされ、届く光は赤く、暗くなります。図の色は、空気の分子による減り方だけで計算した太陽光の色です。

空の表
3D 64 × 64 × 48
Udon の表
空気の厚さ
時刻を進めるとき
大気の厚さは大きく誇張しています。背景の空の色は模式です。スライダーを動かすと、その位置で止まります。3 秒たつと、また動き出します。
technical.md 12 章

明るい所は 0.8 から長い肩を通り、0.9 の白で止まる

描いた明るさは、最後に 1 本の曲線(トーンカーブ)で画面の明るさに直します。露出 0.63 を掛けて ACES の近似曲線を通し、0.8 を超えた所だけは別の肩に乗せて、ゆっくり 0.9 に近づけます。画面に 100% の白が出ないので、太陽の周りや雲の縁の明るさにも階調が残ります。

既定では、同じ曲線を 33³ の LUT にして、Post Processing Stack v2 で画面全体に掛けます。シェーダーの中で掛ける方式も選べますが、水越しに拾った Standard の明るい物が白く飛ぶので、既定はポストプロセスです。

露出
0.63
肩の始まり
白
0.9
既定
破線が ACES の近似曲線だけの場合、実線が Clearwater の曲線です。下の 2 本の帯は、同じ明るさの並びをそれぞれの曲線に通したものです。ACES だけでは明るい側がほぼ一様な白になり、Clearwater では 0.9 の手前まで階調が残ります。図は露出と曲線だけで、わずかな彩度の調整は省いています。