レンダリングアルゴリズムの実装には、基礎的なベクトルや行列の演算を始めとして、3Dデータや画像データの取り扱い、種々の交叉判定アルゴリズムや光輸送アルゴリズム、色変換など様々な要素が関連します。レンダリングアルゴリズムのデバッグにおいて、一般的なデバッグ手法はもちろん有効ですが、往々にして扱うデータが膨大になります。また、三次元空間中のベクトルなどは数値だけを見たり脳内で想像するだけではいまいちピンとこないことも多いでしょう。レンダラーのデバッグ手法として、入力データや計算途中のデータを可視化するデバッグレンダリングを実装しておくと非常に役立ちます(思った以上に)。
図1. 本ページで題材とするシーン モデルデータ:San Miguel 2.0, Morgan McGuire's Computer Graphics Archive |
本ページではいくつかのデバッグレンダリング手法を紹介する上で上のようなシーンを題材として使います。このシーンは頂点・三角形がそれぞれ600万頂点、1000万三角形、オブジェクトが428個、テクスチャーは323枚で構成されており、それなりに規模があります。このようなシーンにおけるレンダリングアルゴリズムのデバッグを数値だけを見て行うのはなかなかに辛いことは想像に難くないと思います。そもそも正しくレンダリングアルゴリズムを実装できていても、入力データがおかしなものだと結果も当然予想できないものになります。"Garbage In, Garbage Out"です。デバッグレンダリングはそのようなおかしな入力データを検出するのにも役立ちます。
図2. ベースカラー |
ライティングを行わない物体の素の色をそのまま出力します。単純な材質はともかく、複合的な材質や透明物体などはどんな色とみなせば良いかわからないかもしれませんが特に正解はありません。各々にとって妥当だと思われる色を出しておくと良いでしょう。物体の素の色、つまり反射率は[0, 1]の範囲に収まるのが普通ですが、負の値や1を超える異常値を検出した場合には、例えば純色の紫に変えて出力などしておけば(シーンにもよりますが)異常なデータを一目で見つけられるでしょう。
図3. テクスチャー座標 |
テクスチャー座標をRGBのRG成分として出力します。テクスチャー座標は必ずしも[0, 1]の範囲に収まらないので、はみ出した値は循環させています。レンダラーの実装によっては、そういった値が少し異常に感じられる場合もあるかもしれません。その場合にはB成分が余っているので、異常値の場合にはB成分に値を持たせることで見分けやすくなります。
図4. テクスチャー座標の方向 |
バンプマップなどのテクスチャー効果の基準となるテクスチャー座標の方向ベクトルを出力します。ワールド空間中のベクトルを可視化するのか、ローカル空間中なのかといった選択肢があります。このベクトルは任意の方向を向きうる、つまりベクトルの要素が負の値も取りうるので適切にスケールとオフセットを加えて可視化の色とする必要があります。
(a) ジオメトリック法線 | (b) シェーディング法線 |
(c) シェーディング接線 | (d) シェーディング従接線 |
図5. ジオメトリック法線とシェーディングフレーム |
物体表面の(ジオメトリック)法線とシェーディングフレームのベクトルを出力します。ここで言うシェーディングフレームとは、BRDFが定義されるローカル空間の基底ベクトル3つ、つまり接線(Tangent)、従接線(Bitangent)、(シェーディング)法線(Shading Normal)のことを指します。接線ベクトルはレンダラーの実装によっては前述のテクスチャー座標の方向ベクトルと同じとなるかもしれませんが、異方性BRDFなどを考える場合には異なるベクトルになることもあるでしょう。これらのベクトルもテクスチャー座標の方向と同じく、可視化にあたってはスケールとオフセットを加えた方がよいでしょう。
前述の可視化は衝突点で得られる情報の直接的な可視化でしたが、ここでは少し発展して衝突点情報間の関係の正常性確認に使える可視化手法を紹介します。
図6. ジオメトリック法線とシェーディング法線の類似度 この画像では敢えてデータを壊している(ジオメトリック法線を一部裏返している)。 |
float similarity = dot(geometricNormal, shadingNormal); bool opposite = sim < 0.0; similarity = abs(similarity); float coeff = 5; // 適当な差の強調係数 float value = 0.5 + coeff * (similarity - 1); value = clamp(value, 0, 1); if (opposite) return RGB(value, 0, 0); else return RGB(value, value, value);
ジオメトリック法線とシェーディング法線の内積を可視化します。両者が異なるほど暗い色で出力されます。バンプマップを適用した場合、これらの法線は異なる向きとなるのが自然ですが、あまりにも異なる場合にはバグの原因となるかもしれません。内積の値が負になった場合は絶対値を赤色で出力する工夫を入れることで、三角形のワインディングが想定と異なっている(つまりジオメトリック法線が反対向き)ケースを検出できます。ここではさらなる工夫として、内積が1、つまり両者が一致した場合に50%グレーとなるようにすることで、いずれかの法線の長さが1を超えている場合も検出できるようにしています。
図7. シェーディングフレームの各ベクトルの長さ |
float R = shadingTangent.length() - 1; float G = shadingBitangent.length() - 1; float B = shadingNormal.length() - 1; float coeff = 30; // 適当な強調係数 return RGB(clamp(0.5 + coeff * R, 0, 1), clamp(0.5 + coeff * G, 0, 1), clamp(0.5 + coeff * B, 0, 1));
レンダラーの実装にもよりますが、シェーディングフレームを構成するベクトルは単位ベクトルとすることも多いでしょう。ここでは長さが1から外れたフレームを可視化します。完全に正常ならば一面グレーの画像になります。上の画像を例にして言えば、黄色っぽく(かつ暗く)なっているので法線が1より短いとわかります。どの程度1からずれると問題になるかは状況によりますが、周辺と比べて明らかにおかしい場所を見つけられます。
図8. シェーディングフレームの各ベクトルの直交度合い |
float R = dot(shadingTangent, shadingBitangent); float G = dot(shadingBitangent, shadingNormal); float B = dot(shadingNormal, shadingTangent); float coeff = 100; // 適当な強調係数 return RGB(clamp(0.5 + coeff * R, 0, 1), clamp(0.5 + coeff * G, 0, 1), clamp(0.5 + coeff * B, 0, 1));
シェーディングフレームは基本的には「直交」基底となるでしょうから、それぞれの基底ベクトルは互いに直交するはずです。ここでは各ベクトル間の内積を可視化します。シェーディングフレームの直交度合いが悪い部分が一目瞭然となります。
BRDFのエネルギー保存に関する性質のテストとしてWhite Furnace Testというものがよく用いられます。そこでは、全方位一様な輝度を持つ環境光源の中に、テストしたいBRDFを適用したオブジェクトを配置します。ライティングの計算にはパストレーシングなどといった一般的な光輸送アルゴリズムを用います。受けたエネルギーを常に100%反射するBRDFの場合、オブジェクトが完全に背景に同化して見えなくなります。
(a) roughness: 0.01 | (b) roughness: 0.5 | (c) roughness: 1.0 |
図9. (単散乱)マイクロファセットBRDFのエネルギー損失 |
例として単純なマイクロファセットBRDFのラフネスを変えてテストを行った結果を上図に示します。単純なマイクロファセットBRDFは物体表面上の微小面における一回の反射イベントしか考えないために、ラフネスをあげるほど物体が暗くなってしまっていることがわかります。逆に低いラフネスの状態では背景に同化します。(この例では色がついたメタルとしているため完全には同化しない。)
(a) エネルギー保存則を破る場合 | (b) エネルギー保存則に従う場合 |
図10. エネルギー保存則を破ってしまうBRDFの例 |
White Furnace Testはエネルギー保存則を破る場合の検出にも使えます。上図におけるBRDFはスペキュラー層でコーティングされたディフューズBRDFとなっています。左の例では、フレネル反射によりグレージング角でスペキュラーが強くなってディフューズ面に光が届きにくくなることを考慮していません。そのためグレージング角のライティングが背景に比べて明るくなっている、エネルギー保存則が破られていることを確認できます。また、ここでは多重反射をサポートするレンダラーを使っているため、反射の度にエネルギーが増幅してしまうことで、入り組んだ場所では非常に明るくなっていることがわかります。一方、右のBRDFではディフューズ面にフレネル反射を考慮した修正を加えているため、エネルギー保存則に従っており、ほぼ背景に同化しています。微妙に暗くなっている箇所があるのは、レンダラーが最大反射回数を制限しているためです。最大反射回数に制限がなく(打ち切りはロシアンルーレットのみ)エネルギーを100%反射する材質の場合は理論上は完全に背景に同化します。
デバッグレンダリングを行う場合には、得られた画像から値を読み取りやすくするためにトーンマッピングやガンマ補正を切ることをお勧めします。