大規模プロジェクトにおける MSAA のシナリオ リファレンス

1. MSAA の紹介

エイリアシングの原因や主流のアンチエイリアシング技術である MSAA についてはインターネット上に多くの情報があり、ゲーム開発についてはある程度の理解があるので、ここでは詳しく説明しません。次の記事を直接参照できます。

  1. 最新のグラフィックス API の MSAAUE4 MSAA & 深さ
  2. Zhihu の MASS とアンチエイリアスに関する質問と回答
  3. 最も単純な OpenGL アンチエイリアシング主流のアンチエイリアシング スキームの詳細な説明

ランダム ゲームを例にとると、MSAA N サンプルの効果の比較は次のようになります。

1.1 モバイル プラットフォームでの MSAA

鋸歯の理由と MSAA ソリューションは以前に紹介しましたが、ここでは主に、MSAA の各ステップがいつどこで行われるかを紹介し、パフォーマンスの問題を簡単に説明し、主にモバイル プラットフォームの TBRD アーキテクチャを検討します。

最初に記事を参照することもできます。

  1. MSAA の詳細な分析
  2. モバイル TBDR アーキテクチャ GPU 機能のレンダリング最適化TBDR アーキテクチャ基盤

1.1.1 プロセスについて

トピックに直行します。MSAA はまずラスター化段階でカバレッジ情報を生成し、次にピクセルの色を計算し、カバレッジ情報と深度情報に従ってサブサンプリング ポイントを書き込むかどうかを決定し、フィルターを介してダウンサンプリングして最終的な画像を取得します。全体のプロセスが完了した後、一般的なプロセスは次のとおりです。

ほとんどの場合、オンチップ FrameBuffer は NxMSAA 形式であり、最終的な MSAA 解決/ダウンサンプリング結果のみが必要です。この時点で、ハードウェアと API はチップ上で最も直接的に解決操作を完了します。最も理想的な状況とオンチップ MSAA ルール: 現在の RenderTarget が単一のサンプリング形式である限り

//像 Unity 中我们自己写的 RenderPass,需要 or 写入的 RT 都是不开 MSAA 的
rtDescriptor = new RenderTextureDescriptor(width, height, format, depthBufferBits)
{
    dimension = TextureDimension.Tex2D,
    msaaSamples = 1,
    sRGB = false
};

Unity FrameDebug は、各 MSAA 解決のタイミングも追跡できます。

1.1.2 ハードウェアが対処しているように見えますが、それほど単純ではありません

前のプロセスからも知ることができます。ハードウェア サポートにより、テクスチャの保存と複数のサンプルの解決操作は、赤い矢印の部分であるオンチップ (オンチップ) で行われるため、デプス テストに似ています。および Alpha Test、MSAA オンチップ キャッシュと対話するだけで済みます。これにより、GPU メモリの直接的な読み取りと書き込みが直接回避され、帯域幅の消費が削減されます。

GPU のオンチップ キャッシュのストレージ スペースは非常に限られているため、タイルをレンダリングした後、結果を FrameBuffer にコピーする必要があります (#Unity RenderBufferStoreAction ) . 同様に、1 つのフレームで複数のレンダリングを行うために RenderTarget を変更する必要がある場合、タイルへの書き込み入力時に、そのタイルに対応する古いデータを FrameBuffer からオンチップ キャッシュに読み込む必要がある場合があります (# Unity RenderBufferLoadAction、Tile が復元された場合)

しかし、上記はすべて理想的な条件です. MSAAのパフォーマンスとさまざまなグラフィックカードプラットフォームのサポートは楽観的ではありません. そのうちの1つは: 4xMSAAのように, 4倍のブロックバッファメモリが必要です. チップ上のブロックバッファメモリを考慮すると.ブロックのサイズを小さくすることで、この問題を解決します. たとえば、デフォルトのタイル レンダリング サイズが 32x32 であるとします. 2xMSAA を有効にすると、メモリのボトルネックがなければ問題ありません.オンチップ メモリが許容できる限界を超えた場合、タイルは 16x16 領域のみをレンダリングできます。

それだけでなく、大規模なゲームの後続のエフェクト処理に対する depthTexture の依存、基礎となるエンジン/グラフィックス API のサポートの欠如、オンチップでハードウェア MSAA を一度に完了することはできません。追加のロード/解決操作を手動で実行して、追加のオーバーヘッドを生成します。理解していなくてもかまいません。MSAA 深度解決問題を解決するときに後で説明します。

1.2 Unity URP が MSAA を有効にする

実際、それは非常に単純で、設定です:

 次に、ゲーム中に動的に構成できるフォームにする方法を見つけます。

static URPAssetRuntimeParams assetRuntimeParams;
public UniversalRenderPipeline(UniversalRenderPipelineAsset asset)
{
    //修改下 URP 源码……
    UniversalRenderPipeline.assetRuntimeParams.Init(asset);
}
static void InitializeStackedCameraData(Camera baseCamera, UniversalAdditionalCameraData baseAdditionalCameraData, ref CameraData cameraData)
{
    var assetRuntimeParams = UniversalRenderPipeline.assetRuntimeParams;
    if (baseCamera.allowMSAA && assetRuntimeParams.msaaSampleCount > 1)
        msaaSamples = (baseCamera.targetTexture != null) ? 
        baseCamera.targetTexture.antiAliasing : assetRuntimeParams.msaaSampleCount;
}

public bool IsOpenMSAA
{
    get { return _isOpenMSAA; }
    set
    {
        _isOpenMSAA = value;
        UserDataManager.SetData(IS_OPEN_MSAA, GLOBAL_SETING_GROUP, value);
        if (IsOpenMSAA)
            UniversalRenderPipeline.assetRuntimeParams.msaaSampleCount = 2;
        else
            UniversalRenderPipeline.assetRuntimeParams.msaaSampleCount = 1;
    }
}

しかし、これはほんの始まりにすぎません. 大規模なプロジェクトの場合、おそらく 3 つの問題に直面する必要があります。

  1. 後処理ストロークなど、depthTexture を使用するすべてのレンダリングが間違っています。
  2. Win + D3D11 は良いですが、さまざまな携帯電話/プラットフォームは MSAA をうまくサポートできませんか?
  3. パフォーマンス?もう少し節約できますか?

1.3 AlphaToCoverage 及び HDR リゾルブ

参考記事:

  1. 知っておくべきこと: アルファからカバレッジへの方法をソートする必要がない理由
  2. 様式化されたクラウド レンダリングのいくつかの試み
  3. カバレッジへのアルファ

アルファからカバレッジへ: 草や木などの AlphaTest オブジェクトのテクスチャのエッジが滑らかで、それらが特に薄くない (<1 ピクセル) 場合は、アルファからカバレッジを有効にする必要はありません。

Unity では、次のタグをシェーダーに追加して AtoC を有効にすることができます。

AlphaToMask On

HDR 解決の場合: エイリアシングの問題を解決する必要がある強調表示された領域がない場合は、無視することもできます

2. URP MSAA とその深度が問題を解決する

何も考えずに MSAA をオンにすると、深度検出に基づくストロークの後処理など、depthTexture を使用するすべてのシェーディングが例外なく壊れる可能性があります。

2.1 MSAA の解決

なぜこれが起こるのか、MSAA リゾルブ アルゴリズムから始めましょう. 色については、リゾルブ アルゴリズムは複数のサンプリング ポイントを平均化する必要があります: そうしないと、アンチエイリアシング効果を得ることができません

しかし、これはハードウェアの要因によるものです。MSAAが同じ RT の下にある場合、Depth Stencil も MultiSample でなければならず、 Samples の数が Color の数と同じであるため、深度フォーマットも NxMSAA でなければなりません。アンチエイリアシングをまったく必要としない

しかし、これは問題の鍵ではありません. 鍵は: depthTexture も colorTexture と同じ解決アルゴリズム、つまり平均を採用しているため、エッジ深度情報は完全に間違っています(そして、意味がありません)。また、上記の問題の主な理由

この点がわかれば、実際に解決するのは非常に簡単です。頭に浮かぶ最初の方法は、depthTexture の解決アルゴリズムを平均値から最大値に変更することですが、残念ながら、前述のように、これはハードウェアなので、解決アルゴリズムを変更したいので、最初のステップはハードウェア プラットフォーム API からのみ開始できます。

2.1.1 ハードウェア解決のサポート

最初にこの記事について話しましょう。これは実際には非常に優れています。小さな BUG からの MSAA 深度解決を見て、大まかに要約します。IOS メタルのように、カスタム解決アルゴリズムの使用を直接サポートしており、非常に簡単です。また、特に主流の Andriod OpenGLES 3.1/3.0 は十分に対応できず、フレームバッファフェッチ拡張機能を利用するなどの解決策もありますが、例外なく エンジンのソースコードに移行する必要があるかどうかできるかどうかは別問題(特にUnity)

2.1.2 ソフトウェア解決

この観点から、ソースコードを変更しない前提で考えられる最も簡単な方法は、ハードウェア解決をスキップすることです.これは実際には非常に簡単に処理できます. 前回の記事の内容を理解していれば、簡単です解決策の 1 つを考えてみましょう: レンダー テクスチャを、解析せずにシェーダーでマルチサンプリング テクスチャとして直接バインドします。

if (m_ActiveCameraDepthAttachment != RenderTargetHandle.CameraTarget)
{
    var depthDescriptor = descriptor;
    depthDescriptor.colorFormat = RenderTextureFormat.Depth;
    depthDescriptor.depthBufferBits = k_DepthStencilBufferBits;
    depthDescriptor.bindMS = msaaSamples > 1 && (SystemInfo.supportsMultisampledTextures != 0);
    cmd.GetTemporaryRT(m_ActiveCameraDepthAttachment.id, depthDescriptor, FilterMode.Point);
}

このようにして、取得した depthTexture が解決前であることを確認でき、深度サンプリングを直接実行するか、単純にコピーして手動で解決できます (次のコードは URP CopyDepthPass からのものです)。 sampleIndex を指定する texelFetch 関数 対応するサンプルの色を取得してから、ソフト解決ソリューションであるカスタム解決操作を実行します

 #define DEPTH_TEXTURE_MS(name, samples) Texture2DMS<float, samples> name
 #if MSAA_SAMPLES == 1
    DEPTH_TEXTURE(_CameraDepthAttachment);
    SAMPLER(sampler_CameraDepthAttachment);
#else
    DEPTH_TEXTURE_MS(_CameraDepthAttachment, MSAA_SAMPLES);
    float4 _CameraDepthAttachment_TexelSize;
#endif

float SampleDepth(float2 uv)
{
    #if MSAA_SAMPLES == 1
        return SAMPLE(uv);
    #else
        int2 coord = int2(uv * _CameraDepthAttachment_TexelSize.zw);
        float outDepth = DEPTH_DEFAULT_VALUE;
    
        UNITY_UNROLL
        for (int i = 0; i < MSAA_SAMPLES; ++i)
            outDepth = DEPTH_OP(LOAD(coord, i), outDepth);
        return outDepth;
    #endif
}

さて、深度解決の問題はこの時点で解決されるはずですが、ソフトウェア解決のコストはどうでしょうか? まず第一に、nxMSAA のメモリ使用量は複数回です。今回はメモリに直接ロードされます。光帯域幅の問題は小さくありません。結局、ハードウェアの解決をあきらめ、同時にオンチップ MSAA をあきらめるとき

2.1.3 余談: Deferred Rendering が MSAA をサポートしていないのはなぜですか

実際、調べることができるソースのほとんどは、この作品をあいまいに説明しており、まったく正しくありません。

実際、グラフィックス ハードウェアとドライバーの開発のタイムラインによると、およそ 2 つの期間があります。

  • DX9 / OpenGL 2.0以前: 遅延レンダリングにはMRTが必要なため、ドライバーとグラフィックス インターフェイスはMRT MSAAをサポートしていないため、当然MSAAをサポートしません。
  • DX10.1 以降: MRT は MSAA をサポートします. この場合、遅延レンダリング理論は MSAA をサポートできますが、通常の考え方では、GBuffer で ColorRT に対して MultiSample を有効にしたい場合は、GBuffer で MRT のすべてのターゲット テクスチャを有効にします。 (RT) MultiSample をオンにする必要があります。つまり、深度バッファーの問題を解決するだけでなく、法線や照明などの複数の追加テクスチャの問題を解決するか、中央のサンプリング結果を保持する必要があります。これらのテクスチャのポイント. しかし、それがどのように処理されても、複数のメモリ使用量と帯域幅の消費を直接もたらすため、従来の MSAA スキームはここで本当に無力です.

2.2 Unity-URP の解決方法

URP MSAA と 2 つの深度パス

前提の 1 つは: シェーダーで depthTexture を使用する必要がある場合は、最初に DepthTexture 構成項目を確認する必要があります。これにより、URP は CopyDepth を通じて現在のカメラの depthTexture を取得します。

対応するソース コードを参照してください。

  • createDepthTexture は、深度テクスチャを作成するかどうかを決定します。それ以外の場合は、カメラのターゲットを直接使用します
  • 深度のコピーを担当する特別な CopyDepthPass があり、シェーダーで使用するグローバル _CameraDepthTexture を設定します。
if (cameraData.renderType == CameraRenderType.Base)
{
    m_ActiveCameraColorAttachment = (createColorTexture) ? m_CameraColorAttachment : RenderTargetHandle.CameraTarget;
    m_ActiveCameraDepthAttachment = (createDepthTexture) ? m_CameraDepthAttachment : RenderTargetHandle.CameraTarget;
    bool intermediateRenderTexture = createColorTexture || createDepthTexture;

    // Doesn't create texture for Overlay cameras as they are already overlaying on top of created textures.
    bool createTextures = intermediateRenderTexture;
    if (createTextures)
        CreateCameraRenderTarget(context, ref renderingData.cameraData);
}
if (!requiresDepthPrepass && renderingData.cameraData.requiresDepthTexture && createDepthTexture)
{
    m_CopyDepthPass.Setup(m_ActiveCameraDepthAttachment, m_DepthTexture);
    EnqueuePass(m_CopyDepthPass);
}

2.2.1 MSAA が有効な場合、URP はこの方法で深さを処理します

現在の URP 7.5.1 バージョンでは、MSAA が有効になっている場合、DepthCopy は内部的に実行されず、代わりにDepthPrePassが使用されます。これは事前にレンダリングされた深度です。

このパスは、すべてのオブジェクトが実際に描画される前に不透明なオブジェクトを描画しますが、深度値 (DepthOnly) を書き込むだけであり、ターゲット テクスチャは MSAA を有効にせず、カメラ レンダリング ターゲットとは関係ありません。 RT 、この RT は、後で使用するために DepthTexture として使用されます


// If camera requires depth and there's no depth pre-pass we create a depth texture that can be read later by effect requiring it.
bool createDepthTexture = cameraData.requiresDepthTexture && !requiresDepthPrepass;
createDepthTexture |= (cameraData.renderType == CameraRenderType.Base && !cameraData.resolveFinalTarget);
if (requiresDepthPrepass)
{
    m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);
    EnqueuePass(m_DepthPrepass);
}

明らかに、この方法で MSAA 深度解決に問題はありません. 結局、書かれた RT は複数の MSAA サンプラーをまったく有効にしませんが、すべての不透明なオブジェクトに DepthOnly パスを追加する必要があります。このパスでは計算は必要ありませんが、多くの余分なドローコール

要約すると、これは実行可能な方法の 1 つとしか言えません: より多くのドローコールを犠牲にして MSAA の深さの問題を解決し、同時にソフトウェアを EarlyZ にすることもできますが、これは現在のURPアプローチのみですが、明らかに唯一のソリューションではありません

2.2.1 他に解決策はありますか?

もちろんできますが、URP のソース コードを少し変更する必要があります。つまり、不透明なオブジェクトをレンダリングした後、DepthCopyPass を実行すると、ソフトウェアが深度を直接解決します。

矢面に立つ: 一番下の DepthCopyPass が有効かどうかを判断するためのロジックに、msaa 判断を追加します。

bool CanCopyDepth(ref CameraData cameraData)
{
    bool msaaEnabledForCamera = cameraData.cameraTargetDescriptor.msaaSamples > 1;
    bool supportsTextureCopy = SystemInfo.copyTextureSupport != CopyTextureSupport.None;
    bool supportsDepthTarget = RenderingUtils.SupportsRenderTextureFormat(RenderTextureFormat.Depth);
    bool supportsDepthCopy = !msaaEnabledForCamera && (supportsDepthTarget || supportsTextureCopy);
    // TODO:  We don't have support to highp Texture2DMS currently and this breaks depth precision.
    // currently disabling it until shader changes kick in.
    depthDescriptor.bindMS = msaaSamples > 1 && (SystemInfo.supportsMultisampledTextures != 0) && !SystemInfo.supportsMultisampleAutoResolve;
    //bool msaaDepthResolve = false;
    return supportsDepthCopy || msaaDepthResolve;
}

ただし、これだけ変更すると、ゲームに入るときにエラーが発生します. 理由 (エラー内容): マルチサンプリングされていないテクスチャがマルチサンプリングされたサンプラーにバインドされています. 未定義の動作を回避するために無効にしています. bindMS フラグを有効にしてくださいテクスチャ

文字通りの意味: サンプリング テクスチャ フォーマットとサンプラーが一致しないため、レンダリング テクスチャの bindMS 属性を設定して、m_ActiveCameraDepthAttachment がサンプリング テクスチャを解決 (解決) せず、そのフォーマットと複数のサンプルのコンテンツを維持することも必要です。

depthDescriptor.bindMS = msaaSamples > 1 && (SystemInfo.supportsMultisampledTextures != 0);

わかりました。DepthCopyPass 設定による DepthTexture 入力は、複数のサンプルのテクスチャです。シェーダーで解決をカスタマイズできます。この URP は既にそれを行っています。

この部分の内容は、実際に上記のソフトウェア解決のプロセスであり、コードを直接参照することもできます

ここで落とし穴があります: DepyCopyPass は、最大値を見つけるためにマルチサンプリングによって深さを解決するのに役立ちます. このアルゴリズムは問題ありませんが、サンプリングポイントの位置が異なるため、それが確実であるとは限りません.テクスチャの中心点を直接サンプリングした結果と同じです. それらの間には小さな違いがあります. 小さなエラー, このコピーされたテクスチャに単一サンプリングされた深度結果を書き続けたい場合, equal ( Equal or LessEqual) 深さを判断する

これは実行可能な MSAA ソリューションでもあるのに、なぜ URP はそれを行わなかったのでしょうか?

実はURPは以前にもこれを行っていたのですが、一部のOpenGLデバイスのブラックスクリーン問題を解決するために、対応する関数がキャストされました. 詳細については、CHANGE.LOGファイルと対応するgit提出を参照するか、このリンクを参照しください.

したがって、MSAA を実装した可能性も否定できず、一部のデバイスでブラック スクリーンの元の問題が引き続き発生します. したがって、この解決策を採用する場合は、モデルのスクリーニングを行う必要があります: または、一部のデバイスでのみ MSAA をサポートする必要があります。ハイエンドモデル。

3. ハードウェア プラットフォームとパフォーマンス

3.1 ハードウェア RenderTexture.ResolveAA RBI

MSAA 分析のほとんどはハードウェアによって行われ、Unity3D FrameDebug は最下層のドットのために分析を追跡できますが、前提があります: 現在のドライバーが MSAA の自動分析をオフにしているかどうか、そうでない場合は Unity3D 自体が追跡できません

これにより、PC D3D11 プラットフォームの MSAA は問題ない可能性がありますが、PC + OpenGLES3.0 に切り替える限り、resolveAA を追跡できないことがわかりますが、MSAA が追跡されていることを恐れないでください。まだ有効です, ゲーム画面を直接チェックして確認できます, 読む URP / Unity ソース コードについて少し知ることもできます. マルチサンプリングされたテクスチャが自動的に解決されるかどうかを決定するプロパティは SystemInfo.supportsMultisampleAutoResolveです:

bool PlatformRequiresExplicitMsaaResolve()
{
    return !SystemInfo.supportsMultisampleAutoResolve &&
           SystemInfo.graphicsDeviceType != GraphicsDeviceType.Metal;
}

ただし、MSAA の自動解析がオフになっている場合は、エンジンの最下層を手動でトリガーする必要がありますが、Conscience Unity は対応するインターフェイスRenderTexture.ResolveAntiAliasedSurfaceを引き続き提供しており、最下層もこれを行うのに役立つ必要があります


2つ目はResolveAAのタイミングと頻度ですが、もちろん以下の議論の前提はSystemInfo.supportsMultisampleAutoResolve =falseです。

ハードウェア分析をトリガーする Unity の基本的な方法は比較的暴力的です: つまり、renderTarget を切り替えるたびに、切り替えた renderTarget で resolveAA を強制します. この操作は実際には冗長な場合があります. たとえば、2 つの renderTarget は異なりますが、MSTexture はシェーダー リソースが行うのと同じです。明らかに、現時点では、MSTexture を解決する必要があるのは 1 回だけですが、実際には 2 つ存在します。

最適化ソリューションが必要です. インターネット上には既に良い例があります. 直接githubを参照できます: 一般的な考え方として, targetHandle は最大で 2 つのテクスチャを直接保持し, そのうちの 1 つは MSTexture フォーマットであり, もう 1 つは解決後のものです.次に、元の Get/SetTemporary および Idfentifier メソッドが書き直されます。

  • GetTemporaryRT が RenderTexture を使用して renderTarget を直接作成することを直接サポートする場合
  • 解決は特別に PASS で行われ、不透明なオブジェクトがレンダリングされた後、深度コピーの前に一度だけ行われます。これは、その後のすべてのレンダリングに AA がないことも意味します。
  • 識別子を書き換え、MSTexture の取得のみをサポートするか、解決するかどうかに応じて結果を直接取得します

全体のプロセスはrenderTextureを追加するように見えますが、unity自体がbindMSを閉じても、内部に2つのrenderTextureハンドルが存在するため、概念的には同等です

3.2 メモリーレスの設定

このセクションは、このセクションの拡張と見なすことができます。オンチップ MSAA は帯域幅を節約するだけで、実際にはシステム メモリを節約しません。つまり、MSTexture + Resolve Texture と同じサイズの領域に適用されます。次のプリミティブがリフレッシュされるときに削除されるまで、システム メモリ

ただし、解決の結果のみが必要な場合は、MSTexture メモリのこの部分を申請する必要はありません.IOS メタルおよびバルカン プラットフォームでは、RT のストレージ モードをメモリレスに設定して、メモリのオーバーヘッドをさらに削減することがサポートされています.また、MSAA を最適化する Apple の手段によっても強く推奨されています。

テクスチャがメモリレスの場合、このテクスチャはレンダリング プロセス中の一時的なリソースであり、レンダリングの開始時にテクスチャのコンテンツをロードすることも、レンダリングの終了時にそのコンテンツを保存することもできないことに注意してください。

3.3 すべてのモデルが MSAA を完全にサポートできるわけではありません

ハードウェア Caps (Unity ソース コードの GraphicsCaps または QualitySetting を参照) を使用して、ハードウェアで MSAA をサポートしない一部のデバイスを排除することはできますが、ハードウェア プロパティで MSAA をサポートするすべてのデバイスが以前の言った黒い画面の問題を含め、エラーはありません

たとえば、複数の Iphone デバイスをテストした結果、MSAA をオンにすると iPad Air 2 だけが黒い画面になることがわかりました.このデバイスは基本的にローエンドのデバイスですが、ハイエンドによって判断されることはありません.デバイス。

したがって、プロジェクトで MSAA を有効にするかどうかは、モデルのハードウェア インデックスに応じて決定する必要があります. 一般に、ハイエンド マシンのみが MSAA の有効化をサポートしています. 理論的には、将来的により包括的なクラウド テストを実施して、市場のほとんどの主流のデバイス/ドライバーをカバーする MSAA を取得できること. さらなるスクリーニングと判断のための機能およびパフォーマンス関連のテスト レポート

さらに、ソフトウェア解決深度に使用するソリューション ( preDepth と DepthCopy ) のどちらを使用するかについては、まだ議論されていません. 現時点では、どちらのパフォーマンスが優れているかを判断することは不可能であり、どのデバイスがそれをサポートしているかを判断することはさらに困難です.先は長いとしか言​​いようがない。

その他の参照:

おすすめ

転載: blog.csdn.net/Jaihk662/article/details/126752896