ユニバーサルレンダーパイプライン(URP)において、複数のカメラの描画を重ね合わせる「カメラスタッキング(Camera Stacking)」は、3Dシーンの手前にUIや特殊な3Dモデル(キャラクター、武器、装飾用アセットなど)を投影するために頻繁に使われます。しかし、この機能を使用すると、手前に重ねたモデルのエッジ(境界線)に激しいジャギー(階段状のアーティファクト)が発生するという重大な描画品質の問題に遭遇することが多々あります。本記事では、このバグがなぜ発生するのか、その内部的なレンダリングの仕組みと、実務で採用すべき完璧な回避コードおよび設定手順を分かりやすく解説します。

1. 根本的な原因:レンダリングパイプラインにおけるMSAAのタイミング問題

まず、なぜBaseカメラでMSAA(Multi-Sample Anti-Aliasing)が有効になっているにもかかわらず、Overlayカメラのオブジェクトには適用されないのでしょうか。その答えは、「MSAA解決(Resolve)の実行タイミング」にあります。

MSAAは、1つのピクセルを複数のサブピクセル(サンプル点)に分割して描画し、最終的にそれらのサンプルを平均化(解決/Resolve)することでエッジを滑らかにする技術です。URPのレンダリングフローでは、以下のような手順で処理が進みます。

  1. Baseカメラの描画: マルチサンプルされたカラーバッファ(MSAAバッファ)に3Dシーンを描画します。
  2. Baseカメラの描画終了(Resolve): シーンの描画がすべて終わった段階で、マルチサンプルされたカラーバッファが単一サンプルのカラーバッファに変換されます(ここでアンチエイリアス処理が確定します)。
  3. Overlayカメラの描画: すでに解決(Resolve)され、MSAA機能がオフになった平坦なカラーバッファに対し、Overlayカメラで設定されたオブジェクトを直接レンダリングして上書きします。

このように、Overlayカメラが処理を開始した時点では、カラーバッファはすでに「解決(Resolve)」された後であり、マルチサンプリングのための追加データはメモリ上に存在しません。そのため、Overlayカメラのレンダリングは強制的に「MSAAなし」の通常描画となり、エッジ部分にギザギザの階段現象が発生します。

2. ポストプロセス併用時のぼやけ問題

さらに問題になるのは、BaseカメラとOverlayカメラの双方でポストプロセス(Post-processing)を有効にした場合です。URPはOverlayカメラにポストプロセスを適用する際、画面全体を一度「中間テクスチャ」にコピーして処理を行います。このコピー処理の過程で、解像度の不一致やサンプリング方法のミスマッチにより、画面全体のシャープネスが失われ、文字や重ね合わせた3Dモデルがぼやける現象が発生します。また、Overlayカメラ側に別個のポストプロセスコントローラーを割り当てても、Baseカメラ側のレンダーパスと衝突し、設定したエフェクト(BloomやColor Gradingなど)が期待通りにブレンドされないトラブルも引き起こされます。

3. 実務で採用すべき2つの解決アプローチ

この問題を解消するためには、レンダリング順序やアロケーションを変更する必要があります。開発の文脈に応じて、以下のいずれかのアプローチを採用してください。

解決法A:Render Feature(カスタムレンダーパス)への移植(推奨)

最も推奨される手法は、Overlayカメラという「第2のカメラ」を使うのをやめ、Baseカメラの単一のレンダーパイプラインの途中にオブジェクトを挿入する方法です。これにより、MSAAが解決される前の純粋なマルチサンプルバッファ内で描画を終えることができます。

具体的な手順:

  1. Overlayカメラに表示させていた3Dオブジェクトのレイヤーを、例えば「`Overlay3D`」という新規レイヤーに割り当てます。
  2. Baseカメラの「Culling Mask」から「`Overlay3D`」を除外します(これにより、通常の描画フローからは除外されます)。
  3. URPの「Forward Renderer Data (Universal Renderer Data)」アセットを開き、インスペクターの下部にある「Add Renderer Feature」から「Render Objects」を追加します。
  4. 追加した「Render Objects」の設定を以下のように調整します。
    • Name: `Overlay3DPass`
    • Event: `After Rendering Transparents`(半透明描画の後、かつMSAA解決前)
    • Filters > Queue: `Opaque` および `Transparent`
    • Filters > Layer Mask: `Overlay3D` のみを選択
    • Overrides > Depth: 「Write Depth」を `True`、「Depth Test」を `Less Equal`(任意でカメラをクリアするために `Always`)に設定

この手法であれば、Overlayカメラ自体のオーバヘッド(カメラの更新処理、カリング計算、バッファの再確保など)を削減できるため、パフォーマンス的にも非常に有利であり、MSAAもBaseカメラ側の設定(4x/8x)がそのまま適用されます。

解決法B:RenderTextureを用いたマルチサンプルの手動解決(個別表示用)

画面構成の都合上、どうしてもOverlayカメラによる別描画が必要な場合(例:UIの中に完全に独立した3Dキャラクターを表示したい場合など)は、C#スクリプトでマルチサンプルRenderTextureを動的に作成し、自前でMSAAを適用してブレンドします。

以下は、MSAAを有効にしたRenderTextureに対してOverlay描画を行い、それをRawImageなどに反映させるための堅牢な制御スクリプトの例です。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[RequireComponent(typeof(Camera))]
public class MsaaOverlayController : MonoBehaviour
{\
    public int msaaSamples = 4; // 2, 4, 8
    private Camera overlayCamera;
    private RenderTexture msaaColorBuffer;
    private RenderTexture resolvedColorBuffer;

    void Start()
    {
        overlayCamera = GetComponent<Camera>();
        InitializeBuffers();
    }

    void InitializeBuffers()
    {
        if (msaaColorBuffer != null) msaaColorBuffer.Release();
        if (resolvedColorBuffer != null) resolvedColorBuffer.Release();

        // 1. MSAAが有効なテンポラリレンダーテクスチャを作成
        RenderTextureDescriptor desc = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.ARGB32, 24);
        desc.msaaSamples = msaaSamples; // MSAAサンプル数を設定
        desc.useMipMap = false;
        desc.sRGB = true;

        msaaColorBuffer = new RenderTexture(desc);
        msaaColorBuffer.name = "MsaaOverlay_ColorBuffer";
        msaaColorBuffer.Create();

        // 2. 解像後の平坦なカラーバッファを作成 (UI描画用)
        RenderTextureDescriptor resolveDesc = desc;
        resolveDesc.msaaSamples = 1; // サンプル数は1にする
        resolvedColorBuffer = new RenderTexture(resolveDesc);
        resolvedColorBuffer.name = "MsaaOverlay_ResolvedBuffer";
        resolvedColorBuffer.Create();

        // カメラの出力先を設定
        overlayCamera.targetTexture = msaaColorBuffer;
    }

    // レンダリング完了時のコールバックでMSAAを解決(Resolve)する
    void OnPostRender()
    {
        if (msaaColorBuffer == null || resolvedColorBuffer == null) return;

        // MSAAテクスチャから非MSAAテクスチャへの「Resolve(解決)」コマンドを発行
        Graphics.Blit(msaaColorBuffer, resolvedColorBuffer);
    }

    public RenderTexture GetResolvedTexture()
    {
        return resolvedColorBuffer;
    }

    void OnDestroy()
    {
        if (msaaColorBuffer != null) msaaColorBuffer.Release();
        if (resolvedColorBuffer != null) resolvedColorBuffer.Release();
    }
}

このスクリプトをOverlayカメラにアタッチし、カメラの出力を取得してUI上のRawImageに割り当てることで、UI内に描画される3Dオブジェクトであっても、ジャギーのない非常に滑らかな輪郭線でレンダリングすることが可能になります。