超雑訳 Optimized pixel-projected reflections for planar reflectors

こんあくあ。Pocolです。
今日は,
[Cichocki 2017] Adam Cichocki, “Optimized pixel-projected reflections for planar reflectors”, SIGGRAPH 2017: Advances in Real-Time Rendering Course.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける際は正しい翻訳例と共に指摘して頂けると有難いです。
特に断りが無い限り,図は[Cichocki 2017]からの引用となります。




– まず、スクリーン空間リフレクションの概要から説明しましょう。
– これは非常に広く採用されているテクニックです。
– アルゴリズムはレイ・マーチングに基づいており、反射自体はすでにレンダリングされたカラーと深度バッファから取得されます。これは重要な特性で、異なる視点からシーンジオメトリをレンダリングするために余分なコストを支払う必要がないからです。
– この方法の欠点は、結果として得られるリフレクションには、すでにレンダリングされたバッファで利用可能な情報しか含まれないことです。そのため、通常、利用可能な反射データのない領域が画面上に複数存在します。これは通常、何らかのフォールバック・システム(多くの場合リフレクション・プローブ)を使用することで対処されます。[Valient13]。
– その上、この技術は特にメモリ帯域幅の点で複雑なので、フレームバジェットを満たすために、半分の解像度で適用されることがよくあります。



– では、提示された手法と比較するとどうでしょうか。
– ピクセル投影反射は、レイマーチされたSSRの制約された形です。適用範囲がより限定され、追加コンテンツのオーサリングが必要です、
– しかし、その代わりに高いパフォーマンスと品質を提供します。
– 重要なのは、このテクニックの制限を満たした場合、レイマッチSSRに匹敵する結果が得られるということです。そのため、ピクセル投影反射は、コンピュートシェーダのインダイレクトディスパッチによって、レイマーチ反射の代わりに選択的に使用することができます。
– このアルゴリズムはデータギャザリングの代わりにデータスキャッタリングに基づいており、実際の反射は(SSRのように)すでにレンダリングされた色と深度バッファに基づいて生成されますが、さらにシーンの反射領域に関する分析情報を必要とします。



– すでにお気づきかもしれませんが、このテクニックの主なコンセプトは、反射トレースを逆にすることです。
– 反射したレイが深度バッファのどこに当たっているかをピクセルごとに見つけるのではなく、すべてのピクセルが画面のどこで反射しているかを正確に計算するだけです。これは、深度バッファを何度も読み込む必要がないため、強力な単純化です。
– そこで、計算を実行できるようにするために、シーンの反射領域を平坦な解析形状で近似し、シェーダ定数を通してシェーダに提供します。
– 最も実用的な形状は矩形なので、それを使いますが、必要であれば他の形状も使えます。



– すでに述べたように、ピクセル投影反射には、SSRと比較していくつかの制限があります。
– この方法で生成できるのは非グロッシーな反射のみで、反射は平面サーフェスにのみ適用できます、
– サーフェス法線マップはネイティブでサポートされていませんが、さまざまな方法で近似することができます。この問題の解決策の1つは、”ボーナススライド “で提案されています。
– このテクニックはまた、追加コンテンツのオーサリングを必要とします。具体的には、シーンの反射領域を近似するリフレクターシェイプです。これらは、反射ジオメトリと整列する必要がある単純なシェイプで、ピクセル投影反射を使用するジオメトリのすべての部分をカバーする必要があります。これらのリフレクタのオーサリングはプレハブレベルで手動で行う必要がありますが、このプロセスは自動化できそうです。



– 次の数枚のスライドでは、2次元のアルゴリズムの簡略版を取り上げます。
– これは、ピクセル投影のコンセプトをよく理解するためのものです。
– この後、3次元に切り替えます。



– ここにあるのはシンプルな2次元のシーンです。
– ここには、1つのオブジェクトと、床の上の水たまりがあります。
– 水たまりは線分で近似されています。



– アルゴリズムの最初のパスは「投影パス」です。
– 画面上のすべてのピクセルについて、それらが画面上のどこで反射されているかを計算します。
– この単純な例では、1つのピクセルに注目します。



– まず、ピクセルの位置を水たまりの平面に対してミラーリングしなければなりません。
– これは、水たまりの平面によって反射された場合、カメラの視点からピクセルがどのように見えるかを示しています。



– 次に、そのピクセルが水たまりの中に見えるかどうかをテストします。これを行うには、カメラからミラーリングされたピクセルに向かってレイキャストを行い、水たまりの平面との交点を取得し、この点がリフレクター形状の内側にあるかどうかをチェックするだけです。
– 境界テストが失敗した場合、元のピクセルは水たまりによって反射されません。
– しかし、この例のように正であれば、反射点を見つけたことになります。



– あとは、スクリーン上でミラーリングされたピクセルの位置を計算するだけですが、これは単純に透視投影を適用することで行います。
– そして、反射された色情報をその場所に書き込むだけです。



– しかし、今ピクセルを投影した水たまりが、他の反射する形状で覆われていたらどうなるでしょうか?
– この場合、反射色情報を書き込むことは冗長であるだけでなく、アーティファクトにつながります。
– これを避けるために、レイキャスティング中に他の反射形状の境界を追加でテストします。
– 私たちの水たまりがレイによってヒットする最も近い反射形状でない場合、反射色情報の出力をスキップするだけです。
– レイキャスティングは、CPU側の形状ごとのオクルードする可能性のある形状リストを構築し、これらのリストを「投影パス」シェーダに渡すことで、高速化できます。



– ですが、単純なケースに戻りましょう…。
– 「投影パス」が終わり、反射に関する情報が画面全体に散らばりました。
– 次は反射パスです。
– この例では、水たまりの上にある緑でマークされたピクセルの反射を取得したいです。
– 必要なのは、「投影パス」によってこのピクセルに対して保存されたデータを読み取ることだけです。



– これで、検索をすることなく反射色を得ることができ、反射パスは終了です。



– それでは「中間バッファ」を紹介します。
– 「中間バッファ」は、カラーバッファやデプスバッファと同じような、通常のピクセルデータグリッドです。
– これは、画面上のすべてのピクセルに対して単一の値を格納します。
– 「投影パス」ではデータスキャッタリングによって埋められ、「反射パス」では最終的な反射を得るために読み込まれます。



– 完全なアルゴリズムには3つのパスがあります。

– 最初のパスは中間バッファをクリアするだけです。

– 2番目のパスは「投影パス」である。

** 3Dでは線分の代わりに矩形を使います。
** すべてのピクセルについて、それを反映する矩形を見つけ、それらの矩形にピクセルを投影し、現在のピクセルが反映されている場所で、対応するピクセルデータを「中間バッファ」に書き込みます。

– 第3のパスは “反射パス “です。

** ここでは単純にvposを使って “中間バッファ “からピクセルデータを読み取ります。
** そしてこのピクセルデータをデコードし、反射色を得ます。
** そして、この色を最終的な反射バッファに書き込みます。



– このアルゴリズムを使ってレンダリングされたシーンです。
– 画面右側の虫眼鏡を見てください。
– お気づきのように、植木鉢の反射が崩れています。
– いくつかのピクセルは植木鉢を映す代わりに、その後ろにある柱の反射を含んでいます。
– 中間バッファに保存されているデータに何か問題があるに違いないです…。



– では、ここにどのようなデータを保存すればいいのでしょうか。
– 理想的なのは反射色だけですが、反射トレースは逆方向なので、結果的に中間バッファの同じ場所に複数のピクセルを書き込むことができます。



– ここで、どのようなことが起こるかを図解してみましょう。
– 緑でマークされた2つのピクセルがあり、同じ場所に投影されています。
– 左端のピクセルはもう1つのピクセルによってオクルードされるはずですが、ピクセルを個別に処理しているため、「投影パス」シェーダではこれを解決できません。
– つまり、「中間バッファ」に書き込まれた最後のピクセルは、後で「反射パス」によって読み取られ、反射として解釈されます。データの書き込み順序は不確定で、GPUスケジューリングに依存します。これはまさに「植木鉢」のスクリーンショットで起こったことです。
– 代わりに私たちが望むのは、常に最も近いピクセルが中間バッファに入ることです。



– 順序の問題を解決するには、色の代わりにスクリーン空間オフセットをエンコードすればいいです。
– 3Dシーンの場合、オフセットは2Dスクリーン空間ベクトルです。
– オフセットの取得は簡単で、「投影パス」でピクセルデータを書き込むときに、すでに反射ピクセル座標(これが現在のピクセル位置)と反射ピクセル座標(これからデータを書き込む場所)を知っているからです。
– つまり、私たちがすべきことは、オフセットが最小のピクセルデータが中間バッファに入るようにすることだけです。
– そのために、InterlockedMinオペレーションを使います。



– 非常に重要なのは、すべての可能なオフセット方向を処理する必要があるということです。
– 同時に「反射パス」では、シンプルにするために「中間バッファ」だけに頼りたい。
– しかしオフセットには2つの次元があるので、精度を落とさずにエンコードし、なおかつInterlockedMinを使えるようにするにはどうしたらいいでしょうか?



– InterlockedMinが機能するために本当に必要なものを見てみましょう。
– この図では、緑のピクセルが黄色のピクセルに投影され、オフセットを形成しているのがわかります。
– オフセットの方向は座標系を定義し、オフセットの長さは「Y」座標と同じで、「X」座標は常にゼロです。



– しかし、このオフセットを別の座標系で表現することもできるので、今回のユースケースでもInterlockedMin操作は機能します。
– 図を見てください。赤と緑のピクセルが黄色のピクセルに投影されています。
– オフセットは2つの異なる座標系で表現されています。
– 緑ピクセルは赤ピクセルよりも投影点に近いですが、緑ピクセルのY座標は赤ピクセルよりも小さくなっています。
– ここで重要なのは、この性質がどちらの座標系にも当てはまるということです。
– つまり、Y’座標が正のままであれば、どの座標系を選んでもいいということです。ただし、変換されたオフセットを十分な精度でエンコードできる場合に限ります。



– 結局のところ、我々のニーズには、4つの座標系のうちの1つを使うだけで十分です。
– 画面の右側に、オフセットの向きによってどの座標系が選ばれるかが表示されています。
– オフセットの向きに最も近い座標系を選び、この座標系でオフセットを変換し、「Y」座標が最上位ビットを占めるように中間バッファ値をエンコードします。X’座標と座標系インデックスは、それより下位のビットにエンコードされます。InterlockedMinが機能するにはこれで十分です。
– 後の「反射パス」で、このデータをデコードし、オフセットをスクリーン空間に変換して戻し、カラーバッファから色をフェッチするのに使います。
– こうすることで、任意のオフセットの向きと長さを中間バッファにエンコードし、最も近いピクセルを確実に反映させることができます。
– さらに、”反射パス “は “中間バッファ “だけに基づくようになりました。



– これが「中間バッファ」の最終的なレイアウトで、すべてのオフセットを4K解像度でエンコードできます。
– すでに述べたように、「Y」座標はオフセット長をわずかにスケーリングしたものに過ぎないので、InterlockedMin演算に最も大きな影響を与える最上位ビットに格納されます。
– それ以下のビットには、「X」座標と座標系そのものをエンコードするだけです。
(ポーズ)
– オフセット分数もエンコードしていることにお気づきでしょうか。これらは後で反射フィルタリングに使われます。



– ご覧のように、適切な “中間バッファ “エンコーディングとInterlockedMinの使用により、壊れた植木鉢の問題は解決しました。



– そしてこちらは、向きの異なる3つのリフレクターを同時に扱ったものです。



– 「投影パス」を説明したので、「反射パス」に移りましょう。
– 「反射パス」は、最終的な反射色を生成する役割を担っています。
– そのためには、「中間バッファ」データを読み込み、オフセットをデコードし、反射色を合成する必要があります。
– 実際には、これは思ったより複雑で、「反射パス」には4つの論理ステップがあり、すべて1回のシェーダ実行で実行されます。



– 「中間バッファ」を何も処理せずにデコードすると、穴のあいた反射が生じます。
– これは、反射の際に元の画像が引き伸ばされ、その結果「中間バッファ」のデータが欠落するために起こる。
– 幸いなことに、穴は大きなグループにはならないので、簡単に修正できます。



– 穴には2つのタイプがあり、問題を少し難しくしていることを留意しておく意味があります。
– 最も単純なケースは、「中間バッファ」にデータがない場合です。
– もう1つのケースは、データは「中間バッファ」に書き込まれたが、実際に近いジオメトリが引き伸ばされ、いくつかのピクセルが保存されなかった場合です。



– 穴を塞ぐために、オフセットが最も小さい隣接ピクセルデータを見つけ、選択された隣接ピクセルと同じように反射を計算します。
– ただし、このデータを使用するのは、現在のピクセルの「中間バッファ」データがないか、選択された近傍ピクセルのオフセットが現在のオフセットよりかなり小さい場合のみです。
– 他のピクセルにはすでに最適なデータが含まれているので、パッチを当てるのは穴だけに限定したいからです。



– 穴は修正されましたが、反射がわずかに歪んでいることが確認できます。
– カメラアングルによって、レンダリングされたシーンバッファが反射されると、引き伸ばされたり圧縮されたりします。
– 絞り込まれると、ピクセルの一部が「中間バッファ」に入らなくなり、色の不連続が形成されます。
– 一方、引き伸ばされると穴が開き、隣接するピクセルの反射で補うことになります。
– つまり、リフェクションが絞られた場合は、元の画像からいくつかのピクセルが欠落し、引き伸ばされた場合は、ピクセルの重複が発生します。



– 歪みを修正するために、中間バッファでエンコードしたオフセット分数のおかげで可能な、フィルタリングされたカラーサンプリングを実行します。
– 画像を見てわかるように、フィルタリングされたカラーサンプリングでは、歪みのアーチファクトはありません。



– 残念なことに、フィルター付きサンプリングは色のにじみという新たな問題を引き起こします。
– これは、反射の視差効果によって発生します。
– そこで、ここでは2つの非常に基本的な性質を利用することにします。
– 1つ目の特性は、色のにじみはコントラストが高い部分でのみ見えるというもので、にじみが少なければ特に問題にはなりません。
– 2つ目の特性は、ポイント・サンプリングを使用する場合、にじみはまったく発生しないということです。



– この観察結果を利用して、色のにじみを修正します。
– フィルタリングされていないカラーサンプルをフィルタリングされたサンプルと一緒に取り、それらを組み合わせることで、フィルタリングされたサンプルにできるだけ近い結果が得られるようにします。ただし、1つの制限があります。合成された色は、知覚的にポイントサンプリングされた色から離れすぎてはいけません。
– これを行うには、両方の色を色相と輝度に分解し、それらの値の違いに基づいて、それらの成分を別々に組み合わせます。
– これは、テンポラル・アンチエイリアシング・ソリューションにおける近傍クランピングに似ていますが、ここでは「反射パス」シェーダ内にとどまるために、フィルタリングされたサンプルカラーは、近傍全体ではなく、フィルタリングされていない1つのサンプルに基づいてトーンダウンされます。



– この時点で最終的なリフレクションの結果が出たので、さっそくリフレクション・パスをステップごとに振り返ってみましょう。



– これは、何の処理もせずに「中間バッファ」を単純に使用した場合の結果です。
– 規則的な線が見えるのは、”中間バッファ “のデータが反射伸張のために欠落しているためです。



– 次に、穴のパッチングを行いますが、その結果、画像にわずかな歪みが生じます。



– 次に、歪みを滑らかにするために、コントラストの高い部分に色にじみが生じる可能性のあるフィルタリングを適用します。



– 最後のステップとして、ブリーディング防止ソリューションを適用し、知覚的に許容できるレベルまでカラーブリーディングをトーンダウンします。
– アンチ・ブリーディングを適用すると、歪みアーチファクトがある程度目立つようになることもここでわかります。
– アンチブリーディングには、さらなる研究が必要かもしれません。より良い結果を得るには、追加パスが必要かもしれません。



– 同じ画像を拡大したのがこちらです。



– この時点で、反射がどのように生成されるかはすでに分かっているので、パフォーマンスと品質を比較してみましょう。
– 4つのリアルタイム反射アルゴリズムを比較します。
– 測定はすべてnVidia GTX 1070で、4K解像度で、反射はフル解像度で生成しました。



– 第一の技法は、反射をピクセル単位でレイ・マーチングした、ブルートフォースSSRです。

– 2番目の手法は、低品質SSRです。ここでは、レイマーチ中に64ピクセルごとにスキップし、トレースの最後に、交点のバイナリ精密化を実行します。

– 3つ目のテクニックは階層深度バッファSSRです。残念ながら、実測された実装では、ジオメトリの背後をトレースすることはサポートされていませんが、その反面、このテクニックはより効率的になります。

– 4つ目のテクニックは、提示されたテクニックであるピクセル投影反射です。



– ブルートフォースSSRは、あくまで実用的なソリューションの紹介としてここにあります。
– 高速性は期待できませんが、トレース結果は参考程度に考えてください。
– ブルートフォースSSRのコストは20.6msです。



– 低品質のSSRは素晴らしい性能を発揮しますが、反射の奥行きの不連続面では結果が悪いです。
– スライドの右側の拡大鏡でアーティファクトを見ることができます。
– このアルゴリズムを適用するコストは0.95 msです。

——- [スキップ] ——-

– ディザリングやテンポラルフィルターによって品質を向上させることは可能です。しかし、それは問題を少し目立たなくするだけです。なぜなら、この方法では、反射の深さの不連続性において、反射のシャープさが保たれないからです。
– その上、ディザリングはコヒーレンシーを低下させるため、メモリ読み込みの効率が悪くなります。
– ディザートレーシングの効率を向上させるオプションとして、アルゴリズムをデインターリーブすることが考えられます。



– HiZトレーシングは非常に優れた品質を提供し、”ブルートフォースSSR “よりも合理的なパフォーマンスを持っています。このシーンの反射のコストは3.2 msです。
– HiZ深度バッファの生成には、さらに0.35msのGPU時間がかかります。この種のデータは、グラフィックスパイプラインの他のアルゴリズムでも使用できるので、このコストはそれらの間で共有できます。
– ドラゴンの後ろに「影」があるのは、計測されたアルゴリズム実装がジオメトリの後ろでトレースを実行しないためで、より効率的になります。



– 最後に測定されたアルゴリズムは “Pixel-projected reflections “です。
– この技術は、”ブルートフォースSSR “に匹敵する高品質の画像を生成します。
– このシーンでPPRを適用するコストは0.95 msです。



– 今回のお話はここまでとします。
– 画像では、ガラスパネルに適用されたリアルタイムの反射を見ることができます。提示されたテクニックの高性能を考えると、この種の効果を達成することは、通常のレイマーチよりもお手頃です。
– 「ピクセル投影反射」についての追加情報を得るために、「ボーナス・サイド」をチェックすることをお勧めします。



– まとめとして…
– ダイナミックなリアルタイム反射を生成するための、かなりシンプルなテクニックを紹介しました。
– これは簡単に組み込みでき、高い品質対コスト比を提供します。
– レイマーチされたSSRよりも適用範囲が限定されますが、適用可能な場合に選択的に使用することができます。



– Natalya Tatarchuk、Michal Iwanicki、Tomasz Jonarski、そしてHavokドイツチーム全員に感謝したい。スライドに対する素晴らしいフィードバックとサポートに感謝します。



ありがとうございました!












実装の詳細はコード・スニペットを参照。



「複数の平行シェイプ」のセクションでは、平行シェイプの素晴らしい特性のひとつを紹介しています。