💦

【URP】MRTのブレンドステートの個別変更

2024/03/20に公開

はじめに

今回の記事では、MRT(Multi Render Targets)でのブレンドステートを
RTごとに変更する方法についてまとめてみようと思います。
例えば、RT0 は Blend One Zero で、 RT1 は Blend One One にする、といったことが可能となります。


RT0不透明ブレンドで、RT1は加算ブレンド

関連記事 (MRT)
https://zenn.dev/r_ngtm/articles/unity-mrt-urp10-urp14

環境

今回は、以下の2つの環境にて動作確認しました。

  • UniversalRP 10.8.1 / Unity 2020.3.32f1
  • UniversalRP 14.0.10 / Unity 2022.3.21f1

サンプル (GitHub)

https://github.com/rngtm/Unity_MRT_Sample

Unity 2020.3.32f1のサンプルは以下にあります

https://github.com/rngtm/Unity_MRT_Sample/tree/main/MRT_BlendStateSample_2020.3.32

Unity2022.3.21f1のサンプルは以下にあります

https://github.com/rngtm/Unity_MRT_Sample/tree/main/MRT_BlendStateSample_2022.3.21

Chapter0. ブレンドステートについて

シェーダー上で Blend One Zero と記述した場合、
そのシェーダーが実行される時のブレンドステートは Blend One Zero となります

Pass
{
    Blend One Zero
    
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    ENDCG
}

ブレンドステートの確認

ブレンドステートはFrameDebugger上で確認できます。

URP10.8.1の場合

URP14.0.10の場合

Chapter1. MRT実行する環境をセットアップする

つぎに、MRTを実行するための環境を整える手順を紹介します。
ブレンドステートを変更する方法を手っ取り早く知りたい方は、
Chapter2まで読み飛ばしていただいて構いません。

MRT対応シェーダー

今回は、シェーダープロパティ _Color1 を RT0へ、 _Color2 をRT1 へ出力するシンプルなシェーダーを使用します。

このシェーダーを実行してMRTへ出力するためには、ScriptableRenderPassScriptableRendererFeature を作成する必要があります。

NewUnlitShader.shader
Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _Color0 ("Color 0", Color) = (0.4, 0.4, 0.4, 1)
        _Color1 ("Color 1", Color) = (0, 0.4, 0, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Transparent" }
        LOD 100
        
        CGINCLUDE
        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
        };

        struct v2f
        {
            float4 vertex : SV_POSITION;
        };

        // MRT出力用の構造体
        struct MRTOutput
        {
            half4 color0 : SV_Target0;
            half4 color1 : SV_Target1; 
        };

        half4 _Color0;
        half4 _Color1;

        v2f vert (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            return o;
        }
        
        half4 frag (v2f i) : SV_Target
        {
            return _Color0;
        }

        MRTOutput fragMRT (v2f i) : SV_Target
        {
            MRTOutput output = (MRTOutput)0;
            output.color0 = _Color0;
            output.color1 = _Color1;
            return output;
        }
        ENDCG

        Pass
        {
            Blend One One
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }

        Pass
        {
            Name "Render MRT"
            
            Blend One Zero
            ZWrite Off

            // MRT用のパス
            Tags { "LightMode"="MyTag" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragMRT
            ENDCG
        }
    }
}

ScriptableRenderPass

2枚のテクスチャ _ColorTexture1_ColorTexture2 をレンダーターゲット指定し、
描画を行うScriptableRenderPassを以下に示します。

Unity2020.3.32f1 の場合 (URP10.8.1)

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_BlendStateSample_2020.3.32/Assets/Rendering/MyRenderObjectsPass.cs

Unity2022.3.21f1 の場合 (URP14.0.10)

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_BlendStateSample_2022.3.21/Assets/Rendering/MyRenderObjectsPass.cs

ScriptableRendererFeature

上記のMyRenderObjectsPassを実行し、描画を行うScriptableRendererFeatureを以下に示します。

こちらは Unity2020とUnity2022の両方で動作します。

MyRenderObjects.cs
using UnityEngine.Experimental.Rendering.Universal;
using UnityEngine.Rendering.Universal;

namespace MyRendering
{
    public class MyRenderObjects : ScriptableRendererFeature
    {
        MyRenderObjectsPass renderObjectsPass;

        public override void Create()
        {
            var lightModeTags = new string[]
            {
                "MyTag"
            };

            renderObjectsPass = new MyRenderObjectsPass(
                "MyTag",
                RenderPassEvent.AfterRenderingTransparents,
                lightModeTags,
                RenderQueueType.Opaque,
                -1);
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            renderer.EnqueuePass(renderObjectsPass);
        }
    }
}

シーンのセットアップ

次に、Sphereを2つ作成してシーンに配置し、先ほどのシェーダーをSphereにアタッチします。

FrameDebuggerを見ると、RT0 へ _Color0 が、 RT1 へ _Color1 が書きこまれていることが確認できます。
どちらもブレンドステートはシェーダー上で設定したBlend One Zero となっています。

Chapter2. 2番目のレンダーターゲットだけ加算ブレンドにする

RenderStateBlock

オブジェクトをレンダリングする時、ScriptableRenderContext.DrawRendererを実行しています。
4番目の引数の RenderStateBlock を使用することで、ブレンドステートをオーバーライドできます。

MyRenderObjectsPass.cs
context.DrawRenderers(
    renderingData.cullResults, 
    ref drawingSettings, 
    ref m_FilteringSettings,
    ref m_RenderStateBlock);

以下の4つがオーバーライド可能です。

  • ブレンド
  • ラスタライズ
  • デプス
  • ステンシル
public struct RenderStateBlock : IEquatable<RenderStateBlock>
{
    private BlendState m_BlendState;
    private RasterState m_RasterState;
    private DepthState m_DepthState;
    private StencilState m_StencilState;
    private int m_StencilReference;
    ...
}

https://docs.unity3d.com/2022.2/Documentation/ScriptReference/Rendering.RenderStateBlock.html

ブレンドステートの変更

ブレンドステートを変更する場合、以下のように記述します。

m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Blend)
{
    blendState = new BlendState(separateMRTBlend: true)
    {
        // RT0 : Blend One Zero
        blendState0 = RenderTargetBlendState.defaultValue, 
        
        // RT1 : Blend One One
        blendState1 = new RenderTargetBlendState(
            sourceColorBlendMode: BlendMode.One,
            destinationColorBlendMode: BlendMode.One)
    }
};

以下のように書くこともできます。

m_RenderStateBlock.mask |= RenderStateMask.Blend;
m_RenderStateBlock.blendState = new BlendState(separateMRTBlend: true)
{
    // RT0 : Blend One Zero
    blendState0 = RenderTargetBlendState.defaultValue,

    // RT1 : Blend One One
    blendState1 = new RenderTargetBlendState(
        sourceColorBlendMode: BlendMode.One,
        destinationColorBlendMode: BlendMode.One)
};

ここで作成したBlendStateBlockは、ScriptableRenderContext.DrawRendererの引数として渡しています

MyRenderObjectsPass.cs
context.DrawRenderers(
    renderingData.cullResults, 
    ref drawingSettings, 
    ref m_FilteringSettings,
    ref m_RenderStateBlock);

結果

RT1 だけが加算ブレンド Blend One One, One Zero になっていることが確認できます。


URP 10.8.1

Discussion