KanikamaにLTCを入れた

前々から🦀のユーザーからモニターのspecularが欲しいという要望をもらっていたけど、色々面倒でやってなかった。 というのも、🦀はPRTの実装であり光源のTRSはstaticであることが前提にあるが、LTCはdynamicなものに対しても使えるわけで、その辺の組み合わせには自由度があるし、立て付けをどうするかが悩ましかった。

v3で欲しい機能があったら自分で作ってね、というスタンスにしたので、じゃあ自分の中での「まあこんくらいの機能があればいいでしょ」を実装に入れてもいいな、ということで以下の仕様で入れた。

  • Texture LightのTRSはstaticしてTextureだけが動的に変わるとする。
  • diffuseはこれまで通りPRTでやる。
  • specularだけLTCでやる。
  • specularの影をdiffuseの影で代用する。
  • Texture Lightはシーンに3つまでで全て同じTextureを使う。

LTC自体は https://github.com/selfshadow/ltc_code/tree/master をVRC向けに移植したやつで特に珍しいことはしてない。 テクスチャのフィルタリングだけlox先生に教えてもらったサーフェスのシェーダーでmipmap使ってガウス関数を階段関数で近似するやつになってる。

LTCのちゃんとした影は https://eheitzresearch.wordpress.com/705-2/ があるけど、🦀に組み込むのは難しいのでdiffuseのvisivilityを使うことにした。

スペキュラのシャドウで欲しい値は、fをスペキュラのBRD(e.g. GGX)、Vをvisibilityとしたときに  \int f(v,l) L_i(l) V(l) n \cdot l d l が影付きのライティングで、これが計算できないので、

  \displaystyle \left( \int f(v,l) L_i(l)  n \cdot l d l \right) * \frac{ \int f(v,l) L_i(l) V(l) n \cdot l d l}{\int f(v,l) L_i(l)  n \cdot l d l}

を計算しましょうという話で、第1項はLTCで計算できて第2項を求めましょうという話でした。 それで、色々あきらめてdiffuseの影を使うというのはfをdiffuseのBRDF(e.g. 定数)にするということであり、

  \displaystyle  \frac{ \int L_i(l) V(l) n \cdot l d l}{\int L_i(l)  n \cdot l d l}

を使っているということになる。なのでもちろん全然正しくなくて、具体的には影の面積がせまくなってると思う。けど、ないよりはある方がそれっぽいのでヨシ!

それで \int L_i(l) V(l) n \cdot l d l  \int L_i(l)  n \cdot l d lを計算しましょうということになるけど、 これはUnityでライトマップをベイクするときに直接光だけ(bounce=0)にして、影ありと影なしでベイクすれば作れる。 あとは焼いたライトマップ2枚を使って、(影ありの明るさ)÷(影無しの明るさ)を計算すればよくて、16bit floatで十分っぽい雰囲気だったので、 BC6H圧縮したHDRテクスチャ1枚にTexture Light3枚までのvisibilityを格納できるということになる(3枚までにしたのはこれが理由)。

実際に動いているのはこんな感じ。わかりにくいけどDJブースの手前にはスペキュラが映り込んでいないのがわかる。


細かい話をすると、Unityのライトマッパーを使うとき、発光マテリアルのついたRendererを光源に使うと、影なしLightmapは焼けないです。 多分内部的には発光マテリアル付きRendererは光源としては扱われてないっぽくて、Bounce=0でベイクするとこれらのRendererから出てる光はライトマップに焼かれないっぽい。 MeshRendererのCastShadowフラグは光源向けの値っぽいので、なるほど、という感じがする。仕方ないので、🦀ではArea Lightを使ってる。