1-Phase Occlusion Culling

完全に私的な実装メモです。
通常,オクルージョンカリングを実装する際は,GPU Zenやもんしょさんのサイトに載っているように2-Phaseでジオメトリ描画して実装するのが普通かと思います。
少し前ですが,GDC 2021の“Samurai Landscapes: Building and Rendering Tsushima Island on PS4”というセッションの,43:45あたりから,Occlusion-Cullingについての説明があり,Ghost of Tsushimaの実装では,前フレームの深度バッファと,それらから保守的(conservative)に作成したミップレベル,エピポラーサーチを用いて,現在フレームの深度バッファを復元し,1回のジオメトリ描画でオクルージョンカリングを実装する方法が紹介されています。
説明が分かりやすいので,アルゴリズムについては元動画を参照してください。
馬鹿まじめに線形探索をせずに,ミップマップを使って検索するのがアルゴリズムのキモみたいです。

一応実装コードが紹介されていますが,動画の品質が低すぎて全然良く見えませんw。
そこで,それっぽい感じに見えるコードを自分で推測しながら書いてみました。
推測で書いているのと,きちんと動作検証もしていないので,バグっている可能性があるので,まんまコピペで使用して不都合・不利益が発生しても何ら責任は負いませんのでご注意ください。
もし、「ここはこうじゃね?」とかアドバイスあればコメントください。

Depth Reprojection

// Forward-project last frame's depth to this frame's space
{
    float zPrev = pSrt->m_texIn[dispatchThreadId.xy];
    float zDepthPrev = DepthFromWorld(zPrev, pSrt->m_vecRecoryHypebolicDepth);

    float4 posClipPrev = float4(VecClipFromUv(uvThread), zDepthPrev, 1.0f);
    posClipPrev *= zPrev; // This ensure that zCur will end up in posClip.w;

    float4 posClip = mul(posClipPrev, pSrt->m_matClipPrevToClipCur);
    posClip.xyz /= posClip.w;

    float2 uv = UvFromVecClip(posClip.xy);

    if (all(uv >= pSrc->m_rectScissor.xy) && all(uv >= pSrc->m_rectScissor.zw))
    {
        // NOTE : z ends up in posClip.w so we can use that directly.
        float z = posClip.w;
        
        if (z < pSrt->m_zNear)
            z = pSrt->m_zFar;

        // NOTE : Add a small bias to resolve self-occlusion artifacts
        z += pSrt->m_dsBias;

        if (z > pSrt->m_zFar || zPrev >= pSrt->m_zFar)
            z = pSrt->m_zFar;

        float2 xy = floor(uv * pSrt->m_texture.m_dXy); // 多分、テクスチャサイズを乗算してウィンドウサイズに戻している。
  
        AtomicMax(pSrt->m_texOut[xy], z);
    }
}

{
     float4 posClip = float4(VecClipFromUv(uvThread), 0.0f, 1.0f);

     float4 posClipPrev = mul(posClip, pSrt->matClipCurToClipPrev);

     if (any(abs(posClipPrev.xy) >= abs(posClipPrev.w))
     {
          float2 uv = UvFromVecClip(posClipPrev.xy / posClipPrev.w);

          float z = pSrt->m_texIn->SampleLOD(pSrt->m_sampler, uv, 0) * pSrt->m_dsBias;

          AtomicMax(pSrt->m_texOut[dispatchThreadId.xy], z);
     }
}

Hole-Filling

float2 uv = (dispatchThreadId.xy + 0.5f.xx) * pSrt->m_texture.m_div;
float zMax = pSrt->m_texIn.SampleLOD(pSrt->m_sampler, uv, 0);

if (zMax == -FLT_MAX)
{
    float2 normalEpipole = normalize(pSrt->m_uvEpipole * uv);
    float2 dUvStep = pSrt->m_texture.m_div * normalEpipole;

    for(uint iMip = 1; iMip <= pSrt->m_iMipLast; ++iMip)
    {
        float2 uvSearch = uv - dUvStep;
        dUvStep *= 2.0;

        float z = pSrt->m_texIn.SampleLOD(pSrt->m_sampler, uvSearch, iMip);

        if (z != -FLT_MAX)
        {
            z = max(z, pSrt->m_texOut[dispatchThreadId.xy]);
            z *= pSrt->m_dsBias;
            z = min(z, pSrt->m_zFar);
            pSrt->m_texOut[dispatchThreadId.xy] = z;

            return;
        }
    }

    if (zMax < pSrt->m_zNear)
        zMax = pSrt->m_zFar;

    zMax = min(zMax, pSrt->m_zFar);

    pSrt->m_texOut[dispatchThreadId.xy] = zMax;
}

超雑訳 Supplementary Document for Moment Transparency

ちわっす。Pocolです。
前回読んだ,Moment Transparencyの補足資料をみていくことにします。
[Sharpe 2018] Brian Sharpe, “Supplementary Document for Moment Transparency”, HPG 18, https://dl.acm.org/doi/10.1145/3231578.3231585, 2018.

いつもながら、誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

超雑訳 Moment Transparency

こんぺこー。Pocolです。
引き続き,OIT関連の資料を見てみます。
今日は…
[Sharpe 2018] Brian Sharpe, “Moment Transparency”, HPG 2018, No.8, pp.1-4, 2018
を読んでみようと思います。
いつもながら、誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。
(さらに…)

超雑訳 Supplementary Document on Moment-Based Order-Independent Transparency

こんこよー。Pocolです。
今日は,前回読んだ Moment-Based OITの補足資料を読むことにします。
[Münstermann 2018], Cedrick Münstermann, Stefan Krumpen, Reinhard Klein, “Supplementary Document on Moment-Based Order-Independent Transparency”. https://momentsingraphics.de/I3D2018.html, 2018.


モーメントと量子化スキームのすべての選択を考慮に入れると,本文は透過率のための合計12の異なるモーメントベースの再構成を議論します。本稿では,バイアスと量子化変換のための最適戦略の簡潔な概要を提供します。さらに,本論文では議論されていない技術の変形に対する結果を提供します。すべてのテクニックのHLSLコードとビデオは個別に入手できます。

1 OPTIMAL BIASING

モーメントを使用する場合、丸めエラーの発生は避けられません。ベクトルモーメント\(b \in {\mathbb C}^{m+1}\)がノイズによって破壊された場合、それは最終的に無効になる可能性があり、すなわち、\(b = {\mathcal E}_Z({\mathbf b})\)のような深度分布\(Z\)が存在しない可能性があります。アルゴリズムでは,これはコレスキー分解が失敗するような負の固有値を持つ行列として現れます。その結果、不完全なピクセルを持つ引き延ばした領域ができてしまいます。
 モーメントシャドウマッピング [Peters and Klein 2015] で提案された解決法は,固定ベクトルバイアスモーメント\(b^{\star} \in {\mathbb R}^{m+1}\)に向かう線形補間を介してベクトルを許容領域に引き戻すことです。再構成への入力として使用されるバイアスおよび正規化されたベクトルは、次のようになります。

\begin{eqnarray}
(1 – \alpha) \cdot \frac{b}{b_0} + \alpha \cdot b^{\star}
\end{eqnarray}

三角モーメントの場合、\(b^{\star}\)の自然な選択は、\({b_0}^{\star}=1\)と他のすべてのエントリを0に設定することです [Peters and Klein 2015] 。これは深度領域の周期的シフトの下で不変な唯一の選択です。パワーモーメントに関して,最悪の場合に最も効果的なバイアスしたモーメントのベクトルを見つけるための洗練された最適化手順を提案しました[Peters et al. 2017]。結果は丸め誤差の正確な性質に依存し,それは量子化スキームに依存します。
 モーメントバイアス\(0 \lt \alpha \ll 1\)の強さは、結果にアーティファクトがなくなるまで、経験的に増やしていくのが最も良いです。これは、導入される丸め誤差の量に依存します。モーメントベースの再構成の他のアプリケーションでは、これはシーンではなく、選択された量子化スキームのみに依存します。論文で議論されているように、丸め誤差は多くのパスにわたって蓄積される可能性があるため、ここで採用されている加算レンダリングでは、これは当てはまりません。オーバードローが大きいほど、丸め誤差も大きくなります。実際には、これは単精度の浮動小数点数では問題ありませんが、ピクセルあたり16ビットでモーメントを保存する場合は,追加の処理が必要になります。
 表1は、上述したすべての技術におけるバイアスするモーメントの最適なベクトルです。モーメントバイアス\(\alpha\)に与えられた値は、オーバードローが少ない場合にうまく機能する値となります。モーメントあたり16ビットを使用する場合、オーバードローが大きいシーンでは値を大きくする必要がある可能性があります。

2 QUANTIZATION AT 16 BITS PER MOMENT

丸め誤差の発生を最小限に抑えるため、正規化されたモーメント・ベクトルを1モーメントあたり16ビットで保存する前に、アフィン変換\({\Theta_m}^{\star}\)を適用することが望ましいです。この変換は、出力値の有効範囲 [0, 1] [Peters and Klein 2015] に違反することなく、\(|{\rm det} {\Theta_m}^{\star}|\)を最大化するように選択されます。効率上の理由から、さらに奇数モーメントと偶数モーメントを別々に変換することが選択されます [Peters et al. 2017]。
 表2は、8つのパワーモーメントに対する我々の新しい量子化変換を含む、使用されたすべての量子化変換の概要を示しています。三角モーメントについては、このような量子化変換は適用しません。フーリエ基底は利用可能なメモリをそのまま有効に利用でき、実モーメントあたり16ビットの三角モーメントを用いた我々の手法の変形は、いずれにせよ特に興味深いトレードオフを提供するものではありません。

 これらの量子化変換のいずれかを使用する場合、、既存の正規化されモーメント\({\Theta_m}^{\star}( \frac{b}{b_0})\)の量子化されたベクトルに不透明度\(\alpha_l\)を持つ深度\(z_l\)の新しいフラグメントを取り入れる正しい方法は、次のようになります。

\begin{eqnarray}
\frac{b_0 \cdot {\Theta_m}^{\star} \left( \frac{b}{b_0} \right) – {\rm ln}(1 – \alpha_l) \cdot {\Theta_m}^{\star} ({\mathbf b}(z_l)) }{ b_0 – {\rm ln}(1 – \alpha_l) }
\end{eqnarray}

3 ADDITIONAL RESULT

以下では、議論したテクニックの他のバリエーションについて、いくつかの追加結果とタイミングを示します。

3.1 Results With Eight Power Moments

本稿では、8つのべき乗モーメントを持つモーメントベースOITの結果は示しません。なぜかというと、6つのべき乗モーメントまたは3つの三角モーメントを持つ変形がより有用であると考えるからです。図2に示すように、8つのパワーモーメントは6つのパワーモーメントよりも若干の品質向上が見られますが、3つの三角モーメントよりも明らかに悪いです。表3から、8つのパワーモーメントの実行時間コストは3つの三角モーメントに近いことがわかります。したがって、コストと品質という点ではこれらの手法の間に入りますが、品質の向上はコストの増加に比べて相対的に小さいです。もう一つの欠点は、交差点を扱えないことです(図1参照)。

3.2 Efficiency of Rasterizer Ordered Views

表3はまた、予想外の興味深い実装の詳細にも光を当てています。モーメントベースのOITにラスタライザーの順序付きビューを使用するオーバーヘッドを決定するために、単精度浮動小数点数を使用するバリアントをこのように実装しました。驚くべきことに、我々のテストハードウェアで得られたフレーム時間は、ハードウェアアクセラレーションによる加算ブレンディングで得られたフレーム時間よりも実際に短くなっています。この現象について決定的な説明はありませんが、シェーダープログラムが非常に短いため、Rasteizer Ordered Viewを使用することによるオーバーヘッドが少ないと予想されることに注意してください。

3.3 Multi-Layer Alpha Blending with Low Dynamic Range

ほとんどの最新のアセットと同様に、評価に使用するアセットでは、外観を適切に再現するには、カラーチャネルあたり8ビット以上のレンダリングが必要です。そのため、すべての手法で一貫してカラーチャネルあたり16ビットのレンダーターゲットを使用しています。しかし,多層アルファブレンディングは低ダイナミックレンジを用いた技法として最初に提案されたものであり,従って筆者らの実装は元の技法より遅いです。表3に、カラーチャネルあたり8ビットのみを使用するマルチレイヤーアルファブレンディングのバリエーションと透過率のタイミングを示します。このバリエーションでは、シーンに対して正しいイメージが作成されないことに注意してください。
この方法で帯域幅要件を削減することによって達成される高速化は重要です。4層の多層アルファブレンディングは, 224ビットに格納された6つのパワーモーメントを持つ筆者らの手法に近いランタイムを持ちます。しかし,筆者らの手法はより良い品質を提供し,実質的に低いオーバーヘッドで高ダイナミックレンジレンダリングを処理しました。

REFERENCES

  • Christoph Peters and Reinhard Klein. 2015. Moment Shadow Mapping. In Proceedings of the 19th ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games (i3D’15). ACM, 7–14. https://doi.org/10.1145/2699276.2699277
  • Christoph Peters, Cedrick Münstermann, Nico Wetzstein, and Reinhard Klein. 2017. Improved Moment Shadow Maps for Translucent Occluders, Soft Shadows and Single Scattering. Journal of Computer Graphics Techniques (JCGT) 6, 1 (2017), 17–67. http://jcgt.org/published/0006/01/03/

超雑訳 Moment-Based Order-Independent Transparency

こんらみ。Pocolです。
今日は,Alan Wake 2で採用されているという…
[Münstermann 2018] Cedrick Münstermann, Stefan Krumpen, Reinhard Klein, Christophe Peters, “Moment-Based Order-Independent Transparency”, I3D 2018.
を読んでみようと思います。
いつもながら誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。


(さらに…)

超雑訳 Moment Shadow Mapping

こんこよー。Pocolです。
今日は…
[Peters 2015] Christoph Peters, Reinhard Klein, “Moment Shadow Mapping”, I3D 2015.
を読んでみようと思います。
いつもながら、誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。
(さらに…)

超雑訳 Weighted Blended Order-Independent Transparency

どうも、こんちにわ。Pocolです。新年最初の投稿です。
今日は,
[McGuire 2013] Morgan McGuire, Louis Bavoil, “Weighted Blended Order-Independent Transparency”, Journal of Computer Graphics Techniques, Vol.2, No.2, 2013
を読んでみようと思います。
いつもながら、誤字・誤訳があるかと思いますので,ご指摘頂ける場合は正しい翻訳例と共に指摘して頂けるとありがたいです。

(さらに…)

premake5覚書

こんねね~。Pocolです。
premake5の覚書をしておこうと思います。

XBox系のプラットフォームの場合は,Billzard社がpremake-consolesというGithubリポジトリを公開しているので,これを使えばよさそうです。
https://github.com/Blizzard/premake-consoles

で,青いプラットフォームの場合は,これまたBillzard社がソースコードをフォーラムにて公開しているので,それを使えばいいです。

問題はこれらが使えない場合です。既にpremake作っちゃっているやつとか,これから出てくる新規プラットフォームの場合とかですね。
自力で実装する必要があります。ほとんどの場合はSDKがC++だと思われるので,C++のコンパイルは問題なく対応できるでしょう。
じゃ、何が問題か?
そうです。前回やったシェーダ設定ですね。恐らく問題になるでしょう。
SDKでVS拡張が提供されている場合は,プロジェクトファイルに記述すればいいので,premakeを拡張していけばいいです。

やり方としては,
https://github.com/premake/premake-core/blob/master/modules/vstudio/vs2010_vcxproj.lua
のFxCompileおよびfxCompileで記載されている箇所を参考にします。

premake用に公開するAPIは,
https://github.com/premake/premake-core/blob/master/modules/vstudio/_preload.lua
のshadertypeとか,shadermodel,shaderincludedirsあたりを参考にLuaに実装を付け足します。

先ほど言った、VS拡張が提供されている場合はこんな感じでいいのですが,じゃ、提供されていない場合はどうすればいいでしょうか?
残念ながら,自分で作るしかないですね。

作り方については,以前に書いた「カスタムビルドルール!」「Visual Studioと格闘中…。」の記事で紹介しているので,これを参考に実装すれば良いでしょう。

…ということでコンソールプラットフォーム用のpremakeのシェーダビルド対応について,覚書を書いてみました。
殆どの人には,全く役に立たないんですが,自分だけには役立つというしょうもない記事でした。

premake5でシェーダ設定する

こんこよー。Pocolです。

ちと分け合って,premake5でプロジェクトを作成する際に,シェーダのコンパイル設定を含めてしまいたいな~と思いました。
premake5からHLSLがサポートされているようなので,実際に書いてみました。
リポジトリは下記です。
https://github.com/ProjectAsura/premake_test

プロジェクトを生成するluaは下記のような感じで書きます。

local project_dir="project"
location(project_dir)

-- ソリューション名.
workspace "SampleSolution"
    -- 構成名.
    configurations { "Debug", "Develop", "Release" }

-- プロジェクト名.
project "SampleProject"

    kind "ConsoleApp"
    language "C++"

    -- 出力ディレクトリ.
    targetdir "bin/%{cfg.buildcfg}"

    -- インクルードディレクトリ.
    includedirs { "include/**" }
    shaderincludedirs { "$(ProjectDir)../res/shader" }

    -- 対称ファイル
    files { "**.h", "**.cpp", "**.hlsl", "**.hlsli" }

    -- シェーダモデルとエントリー名設定.
    shadermodel "6.5"
    shaderentry "main"

    -- ヘッダ変数名とヘッダーファイル名設定.
    shadervariablename "%%(Filename)"
    shaderheaderfileoutput "$(ProjectDir)../res/shader/Compiled/%%(Filename).inc"

    -- シェーダヘッダファイル設定.
    filter { "files:**.hlsli" }
        flags "ExcludeFromBuild"

    -- 頂点シェーダ設定.
    filter { "files:**_vs.hlsl" }
        shadertype "Vertex"

    -- ピクセルシェーダ設定.
    filter { "files:**_ps.hlsl" }
        shadertype "Pixel"

    -- コンピュートシェーダ設定.
    filter { "files:**_cs.hlsl" }
        shadertype "Compute"

    -- ジオメトリシェーダ設定.
    filter { "files:**_gs.hlsl" }
        shadertype "Geometry"

    -- ドメインシェーダ設定.
    filter { "files:**_ds.hlsl" }
        shadertype "Domain"

    -- ハルシェーダ設定.
    filter { "files:**_hs.hlsl" }
        shadertype "Hull"

    -- 増幅シェーダ設定.
    filter { "files:**_as.hlsl" }
        shadertype "Amplification"

    -- メッシュシェーダ設定.
    filter { "files:**_ms.hlsl" }
        shadertype "Mesh"

    -- 構成設定.
    filter { "configurations:Debug" }
        defines {"DEBUG"}
        shaderdefines {"DEBUG=1"}
        symbols "On"
    filter { "configurations:Develop" }
        defines {"NDEBUG"}
        shaderdefines {"NDEBUG=1"}
        optimize "On"
        symbols "On"
    filter { "configurations:Release" }
        defines {"NDEBUG"}
        shaderdefines {"NDEBUG=1"}
        optimize "On"

このluaファイルを使って,サンプルのリポジトリで下記のコマンドで,プロジェクトを生成します。

.\premake5.exe vs2022 --file=my_premake.lua

プロジェクトを生成すると projectフォルダが出来上がり,そこにソリューションファイルが作成されます。ソリューションファイルを開くと次の画像のような感じになります。

シェーダファイルのプロパティを見ると次のように設定されているはずです。

これで,シェーダのビルドが出来るようになるはずです。
下記のPremakeドキュメントに設定できる項目が記載されているので,より細かい設定をしたい方はドキュメントを参照すると良いと思います。

…ということで,premake5でのシェーダ設定方法について紹介してみました!