"Pixar の物理ベースなライティング(SIGGRAPH 2013)" の説明

概要

  • この記事では SIGGRAPH 2013 で発表があった Pixar の物理ベースなシェーディングの講演について説明します.
  • 以下は今回 Pixar が発表した新しい物理ベースなライティングを利用して作成された映画の Monster's University の場面です.





  • また下図は Pixar の短編映画の The Blue Umbrella の画面です. (これらは実写ではなく CG です. )



参考文献

要旨

  • この講演では主に下の 4 つの要素について説明しています.
    • レンダリング方程式を効率的に解くための 複数の integrator (積分器)への分解
    • ライトや BRDF の重点サンプリングを使った直接光のライティングの計算例
    • Beckmann BRDF の利用
    • 物理ライト
  • Pixar では今回, この新しいライティングのシステムを導入した結果が以下のようになったらしいです.
    • 物理ベースなライトの導入によって設定がシンプルになりました.
    • また複数のライト条件下でも, 見た目がよりロバストになりました.
    • 今ままでの長年のやり方を再考する必要がありましたが, 結果としてスペキュラの見た目に一貫性が出るようになりました.
    • 物理ベースなライトの導入によって, 映画 Monster's University のライティングの仕上げ作業の生産性が 2 倍になり, またライティングのショットの作業効率が 55 % 上がりました.
    • またプリビィズも平均 1-2 日で済むようになりました.

感想

  • この講演内容はレンダラーの話で, 特に「ライトや BRDF の重点サンプリングを使って、以下に効率的にライティングの計算をするか?」が内容的に濃い印象を受けました.
  • ただ実際のゲーム開発ではオフラインでの事前計算を除き, ランタイム側でこういうガチの重点サンプリングはしなさそうだと思います.
  • やるとしても下のようにテクスチャのミップレベルを利用したサンプリングとかになると思います.
  • 個人的には, 今回の新しいライティングの仕組みのレンダラーで間接光のシェーディングをする仕組みも聞いてみたいと思いました.

導入

  • Pixar は Cars の映画を作っていたころには非物理ベースなライティングのシステムを使っていて, レイトレースで単純なポイントライトのシェーディングをしたり, (レイトレースシャドウではなく)シャドウマップを利用したり, 非正規化したままの BRDFを利用していました.
  • しかし, そのままだと調整するライトやシェーダの数が数千に及んで調整が大変なので,ライティングのパイプラインを書き直して物理ベースかつレイトレースを行うシステムを使うように切り替えています.
  • 実際, 映画 Monster's University と The Blue Umbrella ではこの新しいシステムが使われています.
  • また物理ベースなライティングの長所をしっかりと発揮させるにはいつもの下のようなレンダリング方程式を解く必要があります.


  • f は BRDF で, ある 1 点 x の物体表面に対して光が方向 w_i から入射したときの w_o 方向の反射特性(厳密には入射照度に対する反射の放射輝度の比)を表してます.
  • L(x, w_i)は ある 1 点 x に対して方向 w_i から入射する放射輝度です.
  • そこで, 以下の 2 つの両方がきちんと動作することが必要だと主張しています.
    • シーンで, 「物理的に正しいライト」がエネルギーを放出すること
    • シーンで, 「物理的に正しい BRDF」 がエネルギーを反射すること

ライティングの計算パスについて

  • 下の図はライトが放出した光が反射を繰り返して, 目に届くまでの経路を表したものです.


  • この光の経路の表し方は Heckber's 記法と呼ばれていて, 記号は以下のような意味合いを持っています.
    • L : ライト
    • D : ディフューズ反射
    • S : スペキュラ反射
    • E : 視点
  • つまり, 上の図の LSDDE だと, ライト -> スペキュラ反射 -> ディフューズ反射 -> ディフューズ反射 -> 視点 という光の経路を表していることになります.
  • 物理ベースなシェーダに移行するにはいつもの下図のようなレンダリング方程式を解く必要があります.


  • しかし, 式変形のないままモンテカルロ法で解くと計算が重過ぎるので, 下の数式のように 「光 = 直接光 + 間接光」 や「反射 = ディフューズ反射 + スペキュラ反射」というように展開して, 最終的に3 つの要素( L_direct, L_indirect_diffuse, L_indirect_specular) に分解して, それぞれを個別に計算します.


上のようにレンダリング方程式を分解したそれぞの項目の値を求めるためには半球状の積分(∫)の計算をする必要があり, この積分計算を解くために 下の 4 種類の積分器(以下, integrator) があります.

  • (a) "directLighting integrator" : 直接光用の積分
  • (b) "indirectDiffuse integrator" : ディフューズ間接光用の積分
  • (c) "reflection integrator" : リフレクション用の積分
  • (d) "photonCaustic Integrator" : コースティクス用の積分

(a) "directLighting integrator", L { D, S } E : 直接光用の積分

  • これはライトからの直接光である L_direct ( 1 回だけのディフューズ反射 あるいは 1 回だけのスペキュラ反射)を計算します.
  • 光の経路としては L {D, S} E になります. ( {D, S} は D か S のどちらか 1 回を表します. )
  • 下図は directLighting integrator と書いてありますが, 他の要素(間接光)も考慮した図です.

(b) "indirectDiffuse integrator", L { D }+ D E : ディフューズ間接光用の積分

  • これは 2 回以上のディフューズ反射を計算するためのものです.
  • 光の経路としては L { D }+ D E になります. ( { D }+ は D が 1 回以上であることを表します. )
  • 下図はこのディフューズ間接光だけを見えるようにした図です.


  • また下図のようにディフューズ反射によって, 左側の赤い壁の色味が周囲の白い床や壁に載るようなカラーブリーディングが発生します.


  • この indirectDiffuse integrator は内部では下の 2 種類の方法を使って計算を行います.
  • (b0) "Photon integrator"
  • (b1) "Indirect integrator"
    • 複数のディフューズ反射による間接光をレイトレースで再帰的に計算します.
    • Radiosity キャッシュや Irradiance キャッシュを利用します.

(c) "reflectionIntegrator", L { D, S }+ S E : リフレクション用の積分

  • これはスペキュラ反射によるリフレクションを計算するものです.
  • レイトレースによる再帰的な計算によって, リフレクションを計算します.

(d) "photonCaustic Integrator", L { S }+ D E : コースティクス用の積分

Beckmann BRDF の利用

  • スペキュラ反射用の BRDF には下図の Beckmann BRDF を利用していました.


  • 上の数式は入力としてラフネスα(0〜1), 法線(n), ハーフベクトル(m)を受け取る関数で, 出力はハーフベクトルの分布を表わす D_b(m) となります.
  • そして今回はBRDF を使った重点サンプリングをする必要があるので, そのときには下図の確率密度分布関数(pdf)を利用しています.

  • また異方性を持たせる場合には表面の 2 方向に対して, 2 つのラフネスの値 α1, α2 を使っています.

  • 最後に下図は ラフネスの値を 0.001-1.0 に変化させていったときのスペキュラの変化で, 左上が滑らかな状態で右下が表面が非常に粗い状態です.


物理ライトの種類

  • 物理ライトの種類は下図のように長方形のライト, 円板のライト, 球状ライト, IBL を使ったドーム状の無限遠方ライトなどです.

  • これらの全てのライトに対して, テクスチャを貼ったり, ディフューズライティングやスペキュラライティングやソフトシャドウを計算することができます.
  • また点光源が 1 つだけあり, それは太陽として使っていたと書いてありました.

物理ライト : 環境マップを使った IBL

  • 環境マップを使ったディフューズシェーディングを計算する際には, それに対してイラディアンスを計算するためのフィルタを適用して, 下図のようなイラディアンスマップを作成しているようです.


  • このイラディアンスマップは, ある点のディフューズシェーディング計算に利用できる際に利用します.
  • 例えば, ある点の可視性を計算した際にそれが全く遮蔽されていないとき(例えば, 平面上の点)には, 全方向にサンプリングするのではなく代わりにイラディアンスマップを利用することができます.
  • こうした方がノイズが載らず, また大量のサンプリングをする必要がなくなり, 処理が軽くなります.
  • 下図の左側がイラディアンスマップを使っていない場合で, 右側がイラディアンスマップを使った結果です.

  • 右側の方が影部分のノイズが少なくなっていることがわかると思います.
  • また今回, ライトに対して重点サンプリングを行なっていますが, その際には下図のように環境マップの輝度値に基づいたサンプリングを行っていました.

  • 下図は青空の屋外の環境マップで, キャラクターの IBL を行った結果となります.

  • また下図は室内の環境マップで, キャラクターの IBL を行った結果となります.


DirectLighting Integrator でのライティングのために必要な要素

  • 今回の講演資料では DirectLighting integrator でのライティングの計算だけが紹介されています.
  • この計算に必要な要素は以下の 3 種類のものになります.
    • Integrator coshader : 積分の計算をするためのコシェーダ
    • Light coshader : Light 用のコシェーダ
    • BRDF coshader : BRDF 用のコシェーダ
  • 下図が DirectLighting Integrator での Integrator, Light, BRDF の相互関係を図で示したものです.
  • 詳細は後で説明しますので, ひとまずは この 3 つの要素が関連しあっているということだけ把握しておいて下さい.


  • 以下では Light, BRDF, Integrator の順に説明しておきます.

Light coshader

  • 先ほど, Light coshader は他の要素と関連し合っていることについて書きました.
  • Light の結果を他の要素に渡すために下の 2 つの構造体が定義されています.
  • ライト用の構造体
    • [ls] LightSamplingStruct 構造体 : ライトのサンプル点
      • P : ライトのサンプルの位置座標
      • Ln : シェーディング位置座標から P までの正規化ベクトル
      • Cl : サンプルのライトのカラー
      • pdf : サンプルの pdf
    • [le] LightEmissionStruct 構造体 : BRDF のサンプルベクトルから求めたライトの値
      • P : ライトのサンプルの位置座標
      • Cl : サンプルのライトのカラー
      • pdf : サンプルの pdf
  • そして ライト用の関数としては, 以下の 3 種類のものを各ライトで実装します.
  • ライト用の関数
    • void Light::sample( out LightSamplingStruct ls );
      • ライトのサンプリングを行い, ライトのサンプル点を ls に書き込みます.

    • void Light::emissionAndPdf( in BSDFSamplingStruct bs; out LightEmissionStruct le );
      • BRDF のサンプルベクトルを元に, ライトの値を le に書き込みます.

    • boolean Light::diffConvolution( out color diffConv)
      • シェーディング点 P のイラディアンスを diffConv に書き込みます.
  • あと, 上記の関数の具体的な実装例として Sphere Area Light(球状のライト)の例がコースノートに載っています.

BRDF coshader

  • 先ほどの Light coshader と同様に, BRDF coshader にもデータを渡すための下の 2 つの構造体があります.
  • [bs] BSDFSampleStruct 構造体 : BRDF のサンプルベクトル
    • weight : 重み付きのサンプル値 ( value / pdf )
    • pdf : サンプルの pdf
    • dir : サンプルの方向ベクトル
  • [bv] BSDFValueStruct 構造体 : BRDF のサンプルの値
    • value : サンプルの値
    • pdf : サンプルの pdf
  • そして BRDF 用の関数としては, 以下の 2 種類のものを各 BRDF で実装します.
  • void BRDF::sample( in vector wi, int lobeSamples; out BSDFSamplingStruct bs );
    • ある入射ベクトル wi と サンプル数 lobeSamples から, BRDF のサンプリングを行った結果を bs に書き込みます.


  • boolean BRDF::valueAndPDF( in vector wi, in vector wos[]; out BSDFValueStruct bv );
    • 視点ベクトル wi と ライトのベクトルの配列 wos を元に BRDF をサンプリングした結果を bv に書き込みます.



  • あと, 上記の関数の例として Beckman BRDF の実装例がコースノートに載っています.

DirectLighting 用の Integrator について

  • それではようやく DirectLighting の integrator について説明します.
  • 最初にまずは BRDF のサンプルベクトルを integrateBRDF()によって先に求めておきます.
  • その後に各ライトごとにループをまわして, integrateLight()によってライトによるディフューズライティングとスペキュラライティングの結果を加算します.
  • 下図がその擬似コードになります.

BRDF積分計算 integrateBRDF()

  • ここでは BRDF積分計算を行う BRDF integrator の関数の integrateBRDF() について説明します.
  • まず(スペキュラの) BRDF のサンプルベクトルは, 下図のように視線から飛ばしたレイが平面に反射したときの反射ベクトルを中心として分布します.

  • しかし, 今回のように直接光を計算する場合だと 上図の BRDF のサンプルベクトルのうち必要なものは黄色のライトにヒットする部分だけです.
  • 従って最適化のために BRDF のサンプルベクトルのうち, 全てのエリアライトを含む階層型のバウンディングボリュームと実際にヒットするものだけを抜き出しておきます.


  • この擬似コードの内容の一連の流れは下のようになっています.
    • 1. bsdf::sample() を呼び出して, BRDF のサンプルベクトル bs を求めます.
    • 2. BVHReduce() を呼び出して, エリアライトに実際にヒットする BRDF のサンプルベクトル bsbvh も別途求めておきます.

Light の積分計算 integrateLight()

  • 次に Light の積分計算をする integrateLight() について説明します.
  • 前述したように DirectLighting の integrator では, この integrateLight() は各ライトごとに呼び出されます.
  • 下がその擬似コードです.


  • 一連の流れとしては以下のようなものになります.
    • 0. li::emissionAndPdf() で, BRDF のサンプルベクトル ( bs ) からライトの値 ( lightValues )を取得します.
    • 1. li::sample() でライトのサンプル点( ls )を求めます.
    • 2. bsdf::valueAndPdf() で, ライトのサンプル点のサンプルベクトル( w_outs )と視点方向のベクトル( w_in )から, BRDF の値 ( diffValues ) を求めます.
    • 3. computeMIS() で, ライトのサンプル点や BRDF のサンプルベクトルを利用したマルチ重点サンプリングを行います.
    • 4. computeShadows() で, ライトの可視性を計算します.
    • 5. computeBRDFShadows() で, BRDF の可視性を計算します.
  • 0,1,2 で出てくる関数については既に説明しました.
  • なので, 以下では 3,4,5 で出てくる関数について順に説明します.

マルチ重点サンプリング computeMIS()

  • 最初にマルチ重点サンプリングではなく, BRDF やライトだけの重点サンプリングについて説明します.
  • 下図は BRDF のサンプルベクトルの確率密度分布関数(pdf)に基づいた重点サンプリングの結果です.

  • この長所は全体的には良いサンプリングであることです.
  • 短所はライト部分へのサンプリングが不足するのでハイライト部分にノイズがのったり, シャドウがぼけてしまいます.
  • 同様に下図はライトのサンプル点の確率密度分布関数(pdf)に基づいた重点サンプリングの結果です.

  • この長所はハイライト部分やシャドウのサンプリング結果が良いので, そこが綺麗になることです.
  • 短所はハイライト以外の暗い箇所にノイズがのることです. 理由はライトのピークと, BRDF のピークが異なるからです.
  • 上のように BRDF やライトだけのサンプリングに基づいてレンダリングをすると, 長所だけでなく短所も出てしまいます.
  • なので, 短所を補間するためにBRDF の重点サンプリングとライトの重点サンプリングの両方を使ったマルチ重点サンプリングを行います.
  • この手法を使ってレンダリングをすると, 下図のようにノイズが少ない綺麗なレンダリング結果になります.


  • あと, コースのノートによると必要に応じて再度サンプリングをし直すことがあるようです.
  • このときには再サンプリングの R がついた integrateRIS() や integrateRMIS() が呼ばれます.
  • 再サンプリングの方法については下の文献を参考にしているそうです.

ライトの可視性の計算 computeShadows()

  • 最初にいくつか式の変形をします.
  • ある点に対して半球状に光が入射し, それを BRDF によって反射した結果, ある視点方向に跳ね返る放射輝度を I とします.
  • この I を求めるための式を書くと, 以下のようにいつものレンダリング方程式になります.

  • ここでの f は入射する放射輝度BRDF の積で, v は可視性です.
  • そして 上の数式を変形して, 後ろの部分に(∫f - ∫f)を追加すると下のようになります.


  • また ∫f をサンプリングではなく解析的に求めた結果を G とすると, 下のようになります.

  • このαの値は任意の値なのですが, αを可視性の平均の値とします.
  • その場合, α=1 で遮蔽がない場合には, I = G となり解析的に求めた G の値を使えばいいことになります.
  • α=0 のとき, I = ∫fv となり, サンプリングで計算した値を使います.
  • またα=0〜1 のときでも解析的に求めた G の値を利用することができるので, サンプルの分散によるノイズを減らすことができるらしいです.


  • 一連の流れはこんな感じです.
    • 0. まず AreaShadowRays() で平均の可視性(argVis)を計算します.
    • 1. ライトのサンプルごとにライティング計算を行います. 変数の意味は以下のようになっています.
      • diffPerLight : ∫fv, 可視性を考慮したサンプリングベースのディフューズシェーディング
      • diffPerLightNoShad : ∫f, 可視性を考慮しないサンプリングベースのディフューズシェーディング
      • specPerLight : 可視性を考慮したサンプリングベースのスペキュラシェーディング
  • 2. 解析的に求めたイラディアンスがある場合は利用します. 変数の意味は以下のようになっています.
      • diffConv : G, 事前計算したイラディアンスを利用したディフューズシェーディング
  • 下図の左側が今回のようの可視性を考慮した図で, 右側が可視性を考慮しない場合の図です.
  • 当たり前ですが, 右側の可視性を考慮しない場合の絵だとすごく不自然です.

BRDF の可視性の計算 computeBRDFShadows()について

  • この項目についてはシンプルで, BRDF のサンプルベクトルの方向の可視性を求めてその値に応じてスペキュラライティングの結果を加算しているだけです.

最後に

  • というような感じで, 今回はライトや BRDF の重点サンプリングを使った直接光のライティングの計算の話がスライドやコースノートにやや多めに載っていたので, 自分のメモ用に説明を書いてみました.