Imaginantia

思ったことを書きます

メモ VRChatのGI

確かにいろいろごちゃごちゃしてると思ったので根本的なところからメモを書こうと思いました。

光とは?

空間中のある位置にある方向から届く電磁波ですが、とりあえず波の性質は無視します。

直接光

「光を発する物体」(emitter) から"直接"届く光。正確には 1. emitter から出た光が → 2. なんかの物体で反射して → 3. 眼に届いたもの。

emitter 自体からまっすぐ来る光を表現するのは、もう単純に Emission を炊けば良い (point light は大きさが 0 なので見えないけど) だけなので気にすることはありません。

以下、光を発する物体を紹介します。(emitterという名前は一般的な表現ではないですが、とりあえずここではそう呼ぶことにしました)

point light

はい。薄く横に広がってるのが拡散反射光 (diffuse) と呼ばれる「視線方向に依存しない成分」、手前に縦に広がってるのが鏡面反射光 (specular) と呼ばれる「視線方向に依存する成分」ですね (これはemitterの性質ではなく「床の材質」から来る性質)。

大きさが無い (現実には存在しないけど) ので、影はぱっきり出ます。本来。

「1点からの影響」を計算すればいいだけなので、リアルタイムでも簡単です。

directional light

無限に遠く無限に明るい point light です (現実の太陽はめっちゃ遠くめっちゃ明るい emitter です)。point light と同様に影はぱっきり出ます。本来は。

これも大きさはありません (なので)。

これも「1方向からの影響」を計算すればいいだけなのでリアルタイムで出せます。

skybox

そういえばこれも一種のemitterでした。無限遠からくる光です。

リアルタイムで計算する場合は、拡散反射はLight Probeに落とし込んで、鏡面反射はReflection Probeで対応します。

emission物体

emitterの大きさがあるケースです。現実の物体はまぁ全部そうです (電球や太陽もそう) が、まぁ計算が大変です。

なので Unity 自体はリアルタイムでの emission 物体の直接光をサポートしていません。(emission 物体の光が直接眼に届くのは、自明に計算できます)

だからコレをやりたい場合はまぁなんかいろんなところをいじらなければなりません。つまり、「emitterの存在をシステムに伝え」「その情報を受け取って直接光を計算して表示する」機能が必要です。

例えば前にやったこれ↑は、この4つの板の座標をシェーダに前もって渡し、その情報をもとに床が色を出力しています。

この観点はたぶん大事で、point light や directional light も、「ライト色を出力する」のではなく、「ライトの情報を受け取った物体が色を計算し出力している」のです。

さて、大きさのあるemitterはまぁ大変なんですが、リアルタイムでもなんとかなる手法が2016年に公開されました。それが LTC (Linearly transformed cosines)です (これは計算方法の名前です)。

名前や原理はさておき、これによってリアライトの直接光をリアルタイムに描画できるようになりました。嬉しい。

間接光

光というのはいっぱい反射してなんやかんやあって眼に届くものです。直接光はたった1回の反射しか考えていません。これだけではいわゆる「CGっぽい光」になってしまいます。逆に言えばCGっぽさというのはそういうところ (ものとものの関係性が不正確) にあります。

そこでちゃんと周囲の環境を加味した複数回の光の反射をしたいわけです。それが私達が時間を掛けてベイクをしている理由です。

一部の要素だけ (local) ではなく環境全体を加味する (global) 光の計算、ということで一般に大域照明 (Global Illumination) と呼ばれます。

ちゃんとした間接光をリアルタイムで計算するのはぶっちゃけ無理です。まだ無理です。少なくともポリゴンベースの描画形態だけでは無理があって、まぁレイトレとかはどうにかそれをやろうとするための手法なわけです。

 

どうにか変化するemitter (色が変わる程度) に対しても間接光を求めたい気持ちがありますが、「最も良い方法」があるわけではないので色々と、いろいろがあります。

まず、いわゆるベイクソフトを使わずに間接光を求めることはありません。正確に言うと、間接光を計算するということはベイクをするということです。現代では。

で、その結果を補助的に使うことでどうにかemitterの色の変化を計算することが多いです。

  • 通常のベイク
    • Light map を計算しておく。
  • Realtime GI
    • どうやら空間の全ライトマップから実際に「1回の」光の反射をリアルタイムで計算してるらしい (だから収束するまでちょっと遅延する)
  • GC5.0 Ghost Illumination / ALT3
    • 照明一個一個・画面区画それぞれに対して Light map を予め求めておき、リアルタイムではそれらの足し算をする。(かなりボケるけど問題ないシーン設定にしてます)
  • LTCGI
    • Light map を「遮蔽情報」として使ってるっぽい?

丁寧に書くと丁寧に書けますがまぁとりあえず…。(鏡面反射の話は面倒なのでとりあえずなしで)

遮蔽の話

えーと先程直接光は簡単と言いましたが、実は難しいところがあって、「遮蔽」です。つまり「光と物体があれば物体には必ず光が届く」のではなく、間に遮蔽物があると届かないのです。

これも広義の global illumination と言えると思いますが (local な情報では計算できないので) 、一般には普通にって言いますね。

point light / directional light の影はまだ簡単で、一点・一方向から見たときにどこまで光が届くのかを計算することで「直接光が届かない範囲」をリアルタイムで算出しています。

skybox からも本来は影は落ちますが Unity は単に無視してますね。

で、emission物体の影はもっと大変で、何故なら半影が発生するからです。物体が半分しか見えない部分には半分しか光が届きません。

なのでこれもリアルタイムでは計算できません。悲しい。

そこで LTCGI が用意しているのが「どこが影になるかを前もってベイクしておく方法」っぽいです (Shadowmap って呼んでるらしい)。まぁやり方は普通にベイクするのと同じです (その光源単体でのベイク結果を用意すれば良い)。

(GCのGIは元々 Light map ベースで動いてるので、その時点で遮蔽は考慮されています)

まとめ

  • 直接光
    • point light (かんたん)
    • directional light (かんたん)
    • skybox (なんとかなる)
    • emission物体 (LTCを頑張ればギリギリなんとかなる (平面形状のほうが計算が少ない))
  • 間接光
    • ベイクするしかない
    • 遮蔽
      • point light (shadow map)
      • directional light (shadow map)
  • VRChatの (動的) GI
    • Realtime GI (なんかすごいことしてる)
    • GCの Ghost Illumination (予め light map を複数生成して足し合わせる)
    • LTCGI (間接光というより、遮蔽の正常化をやってるっぽい)

一枚のモニタのGIの直接光がほしい、というのであれば現代はLTCGIが一番良いソリューションではないかと思います。はい。

適当にがーっと書いたので間違ってることあれば教えてください。

おわり。


 

おまけ いろいろGI

  • KanikamaGI
    • ベイクした light map を足し合わせる方式なので、間接光がちゃんと出ます
  • LTCGI
    • 紹介済み
  • AreaLit
    • LTCベースらしいです、中身はちゃんと見てない
    • 任意メッシュ使えるらしい (元々LTCはどんな形状でも対応できる手法)

ぶっちゃけ私はどれも触ったことないです

おまけ2 AO

Ambient Occlusion は GI の典型例ですが、コレ自体は「光」と関係ありません。

どんなライティング環境だとしてもシーンの形状的に暗くなるであろう場所を暗くしておくのが Ambient Occlusion (環境遮蔽) です。

だから影は出ません。モデルにそれぞれ仕込むのがまぁ正しいのだと思います。