This is the documentation for Enlighten.

7.4. ライト プローブの評価


概要

SH 係数からのイラディアンス ライティングのレンダリングに関する優れた参考資料として、Ramamoorthi と Hanrahan の論文『An Efficient Representation for Irradiance Environment Maps』 (http://graphics.stanford.edu/papers/envmap/envmap.pdf) があります。論文と比較すると、Enlighten では y 軸と z 軸が入れ替わっていることに注意してください。また、L2 係数に関して、ラディアンスからイラディアンスに変換するために必要な定数を入れ、シェーダー コードでの評価をできる限り簡素にしています。

このように、サンプル アプリケーション GeoRadiosityで L1 および L2 プローブがレンダリングされています。詳細については、コード例をご覧ください。

L1 球面調和関数

Enlighten L1 出力には、各カラー チャネルに対し球面基底関数に対応する 4 つの係数、1、xyz (1 つのアンビエント項と各座標軸に従う 3 つの方向項) があります。Ramamoorthi & Hanrahan の表記では、出力順は L00、L11、L10、L1-1 です。L1 について、Enlighten の出力はラディアンスであり、シェーダー コードで既定の法線方向に対するイラディアンスに変換する必要があります。標準の線形変換の結果は、ライティング環境の指向性が非常に高い場合、負の値になることがあります。この問題を避けるため、負の値を避けつつ正しいエネルギーとダイナミック レンジを保持する非線形モデルを使用してイラディアンスを計算します。これは、各カラー チャネルに対して個別に適用する必要があります。シェーダー コードは以下のとおりです。

非線形モデルの詳細と導出については、Geomerics CEDEC talk about L1 irradiance reconstruction (L1 イラディアンス再構築に関する Geomerics CEDEC 講演) をご覧ください。また、このブログ記事で Enlighten がどのように球面調和関数を使用しているかの詳細が確認できます。

float NonlinearL1ComputeChannel(float3 normal, float4 coeffs)
{
    float L0 = coeffs.x;
    float3 L1 = coeffs.yzw;
    float modL1 = length(L1);
    if (modL1 == 0.0f)
    {
        return L0;
    }
 
    float q = 0.5f + 0.5f * dot(normal, normalize(L1));
    float r = modL1 / L0;
    float p = 1.0f + 2.0f * r;
    float a = (1.0f - r) / (1.0f + r);
 
    return L0 * lerp((1.0f + p) * pow(q, p), 1.0f, a);
}
 
//-------------------------------------------------------------------------------------------------
float3 NonlinearL1(float3 normal)
{
    float3 output;
 
    output.r = NonlinearL1ComputeChannel(normal, g_L1CoeffR);
    output.g = NonlinearL1ComputeChannel(normal, g_L1CoeffG);
    output.b = NonlinearL1ComputeChannel(normal, g_L1CoeffB);
 
    return max(float3(0.f, 0.f, 0.f), output);
}

圧縮された L1 球面調和関数

Enlighten の L1 出力は、圧縮されたチャネルあたり 8 ビットのデータとして記述することもできます。キャプチャできるダイナミック レンジを改善するため、線形量子化の代わりに、範囲 [0,1]、[0...255] にマッピングされた L0 (アンビエント) 項の平方根が第 1 チャネルにエンコードされています。3 つの L1 項は線形に量子化されています。最初に L0 で除算され、次に範囲 [-1,1] が [0...254] にマッピングされます。デコードは C++ コードで以下のようになります。

float L0sqrt = (static_cast<float>(outputU8[0])) / 255.0f;         // ここでの outputU8 は、8ビット エンコードされた SH データに対するポインターです
float L0 = L0sqrt * L0sqrt;
float L1_x = ((static_cast<float>(outputU8[1]) - 127.0f) * L0 / 127.0f);
float L1_y = ((static_cast<float>(outputU8[2]) - 127.0f) * L0 / 127.0f);
float L1_z = ((static_cast<float>(outputU8[3]) - 127.0f) * L0 / 127.0f);

シェーダー コードでは、データがテクスチャから読み取られていることを前提として以下のようになります。

float3 RamaCompressedL1(float3 normal, ...)
{
    float3 output;
    float4 n = float4(1.0f, normalize(normal.xyz));   // アンビエント チャネルで法線ベクトルを 1.0 で延長します
  
    float4 RCoeff = tex2D( <read the compressed Red L1 coefficients from an 8-bit-per-channel texture> );
 
    RCoeff.x *= RCoeff.x;
    RCoeff.yzw -= 0.5f;
    RCoeff.yzw *= RCoeff.yzw * 2.0f;
 
        output.r = RamaL1ComputeChannel(normal, RCoeff); // Red 出力をデコードされた Red L1 係数から計算します 
 
    <repeat twice more, Green and Blue>               // Green と Blueの分にも同じ処理を繰り返します
 
    return max(float3(0.0f, 0.0f, 0.0f), output);     // 結果を 0.0 にクランプします
}

圧縮されたプローブ出力をエンコードする際、Enlighten プローブ ソルバーがデータを [-1,1] にクランプします。異なる範囲をキャプチャしたい場合、RadProbeTask または EntireProbeSetTaskm_U8OutputScale パラメーターを使用します。このスケールは、エンコードと 8 ビット データへの量子化の前に生出力データに適用されます。たとえば、範囲 [-4,4] にあるデータをキャプチャするには、0.25 の m_U8OutputScale を使用し、次にデコード プロセスの最中に追加のスケール ファクター 4.0 を適用します。

L2 球面調和関数

Enlighten の L2 出力には、各カラー チャネルに 9 つの係数があり、球面基底関数 1、xyz、3y2 - 1、xyyzxzx2 - z2 に対応します。Ramamoorthi & Hanrahan の表記では、出力順は L00、L11、L10、L1-1、L20、L21、L2-1、L2-2、L22 になります。Enlighten の出力をサンプリングし、特定のサーフェス法線の方向にイラディアンスを付与するには、これらの係数の 9 つのコンポーネントのドット積を法線方向 (xyz) に対して評価された基底関数で計算する必要があります。

この評価を行う方法の 1 つは、ドット積を nt M n と書き換えることです。ここで n は延長された法線ベクトル (1、xyz) であり、M は以下の 4x4 の対称行列です。

( L00 - L20, ½L11, ½L10, ½L1-1 )
( ½L11, L22, ½L21, ½L2-2 )
( ½L10, ½L21, 3L20, ½L2-1 )
( ½L1-1, ½L2-2, ½L2-1, -L22 )

この行列の重みは Ramamoorthi & Hanrahan のオリジナルの論文の重みとは異なります。Enlighten の出力は、ここに記載されているカスタムの重みに対して最適化されており、オリジナルの論文の重みを使用した場合の結果はこれより劣ります。

その後、評価シェーダー コードは次のようになります。

float3 RamaL2(float3 normal)
{
    float3 output;
    float4 n = float4(1.0f, normalize(normal.xyz));  // アンビエント チャネルで法線ベクトルを 1.0 で延長します
 
    output.r = dot(n, mul(g_L2MatrixR, n));          // Red L2 係数を使用した行列とドット積で Red 出力を計算します
    output.g = dot(n, mul(g_L2MatrixG, n));          // Green L2 係数を使用した行列とドット積で Green 出力を計算します
    output.b = dot(n, mul(g_L2MatrixB, n));          // Blue L2 係数を使用した行列とドット積で Blue 出力を計算します
 
    return max(float3(0.0f, 0.0f, 0.0f), output);    // 結果を 0.0 にクランプします
}

環境可視性 SH 評価

環境可視性 SH データが Enlighten プリコンピュートにより生成された場合、上記の「RamaL2」 SH 評価関数が想定通りに機能するよう正規化され、範囲 [0,1] の可視性の結果が生成されます。これは、L1 プローブを使用するか L2 プローブを使用するかとは関係ありません。つまり、L1 の環境可視性 SH データは、常に [1,2,2,2] でプレスケーリングされます。これは、L2 プローブを使用した場合に L2 SH データの L1 サブセットが持つスケール ファクターと同じです。