【Unity】 URP10,URP12,URP14でのMRTの実装の方法の違い

2023/06/25に公開

はじめに

URPのバージョンが変わると、MRT (Multi Render Targets) を実装する方法が変わります。

今回はURP10, URP12, URP14でのMRTの実装方法について比較してみます。

環境

  • Unity2020.3.23f1 (URP 10.7.0)
  • Unity2021.3.16f1 (URP 12.1.8)
  • Unity2022.2.20f1 (URP 14.0.7)

GitHub サンプル

https://github.com/rngtm/Unity_MRT_Sample

Chapter1. MRTの実装

MRT (Multi Render Targets)とは

MRTを利用することで、オブジェクトを同時に複数のレンダーターゲット(テクスチャ)に対して一度に描画することができます。

MRT向けのシェーダーの書き方

MRT向けのシェーダーでは、fragにて構造体をreturnします。
こちらはURP10とURP14の両方で動作します。

struct BufferOutput
{
    half4 color : COLOR0; // RT0
    half3 normal : COLOR1; // RT1
};

BufferOutput frag (v2f i) : SV_Target
{
    BufferOutput o = (BufferOutput)0;
    o.color = tex2D(_MainTex, i.uv);
    o.normal = i.normal;
    return o;
}
シェーダーコード
Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : TEXCOORD2;
            };
            
            struct BufferOutput
            {
                half4 color;
                half3 normal; 
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            BufferOutput frag (v2f i) : SV_Target
            {
                BufferOutput o = (BufferOutput)0;
                o.color = tex2D(_MainTex, i.uv);
                o.normal = i.normal;
                return o;
            }
            ENDCG
        }
    }
}

レンダーターゲットの作り方の違い

URPでは、プログラマーが自分でレンダーターゲット(テクスチャ)を作成することになります。

URP12以前

URP12以前(Unity2021.3系)では、 一時的に使用するレンダーターゲットを作成するには、
CommandBuffer.GetTemporaryRT を使用する形になっていました。

var desc = renderingData.cameraData.cameraTargetDescriptor;
var colorTexture =  new RenderTargetIdentifier("_CustomPassHandle");
commandBuffer.GetTemporaryRT(colorTexture, desc);

テクスチャを使い終わったら、CommandBuffer.ReleaseTemporaryRT を呼び出してテクスチャを解放します。

URP14

URP14 では、 レンダーターゲットを管理する仕組みRTHandlesが追加されました。
RenderingUtils.ReAllocateIfNeededRTHandles.Alloc
を使用し、RTHandleを作成します。

RenderingUtils.ReAllocateIfNeeded を使うと、RTHandleがテクスチャを確保していた場合にテクスチャを自動で解放してくれます。

var desc = renderingData.cameraData.cameraTargetDescriptor;
// Then using RTHandles, the color and the depth properties must be separate
desc.depthBufferBits = 0;
RenderingUtils.ReAllocateIfNeeded(ref m_Handle, desc, FilterMode.Point,
                                    TextureWrapMode.Clamp, name: "_CustomPassHandle");
var desc = renderingData.cameraData.cameraTargetDescriptor;
desc.depthBufferBits = 0;
m_Handle = RTHandles.Alloc(desc, name: "_CustomPassHandle");

テクスチャが不要になったタイミングでは、RTHandles.Releaseを呼び、テクスチャを解放することができます。

参考 : https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/upgrade-guide-2022-2.html

MRTを実装してみる

URP10.7.0 での MRT

URP10.7.0のRenderObjects.csの実装を参考に、
オブジェクトを Color, Normal, Depth へのレンダリングを行うRendererFeatureを作成します。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2020.3.23/Assets/Rendering/Scripts/MyDrawObjectsFeature.cs

URP12.1.8 での MRT

URP12.1.8のRenderObjects.csの実装を参考に、
オブジェクトを Color, Normal, Depth へのレンダリングを行うRendererFeatureを作成します。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2021.3.16/Assets/Rendering/Scripts/MyDrawObjectsFeature.cs

URP10.7.0との差分

RenderPassEvent.BeforeRenderingPrepasses が BeforeRenderingPrePasses に変更されました。

Assets/Rendering/Scripts/MyDrawObjectsFeature.cs
- if (settings.Event < RenderPassEvent.BeforeRenderingPrepasses)
+ if (settings.Event < RenderPassEvent.BeforeRenderingPrePasses)
-     settings.Event = RenderPassEvent.BeforeRenderingPrepasses;
+     settings.Event = RenderPassEvent.BeforeRenderingPrePasses;

...

- setupRenderPass = new SetupRenderPass(RenderPassEvent.BeforeRenderingPrepasses);
+ setupRenderPass = new SetupRenderPass(RenderPassEvent.BeforeRenderingPrePasses);

URP14.0.7 での MRT

URP14のRenderObjects.csの実装を参考に、
オブジェクトを Color, Normal, Depth へのレンダリングを行うRendererFeatureを作成します。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2022.2.20/Assets/Rendering/Scripts/MyRenderObjectsFeature.cs

URP12.1.8との差分

差分1: レンダーターゲット指定方法の違い

URP12以前では、RenderTargetIdentifier を使用していましたが、
URP14では、RTHandle を使う形になりました。

ScriptableRenderPass.cs
- public void ConfigureTarget(RenderTargetIdentifier[] colorAttachments, RenderTargetIdentifier depthAttachment)
+ public void ConfigureTarget(RTHandle[] colorAttachments, RTHandle depthAttachment)

差分2: テクスチャ作成方法の違い

URP12以前では、SciriptableRenderPass内でテクスチャを作成しています。

// URP10.7.0, URP12.1.8
public class SetupRenderPass : ScriptableRenderPass
{
    ....
    public override void Configure(CommandBuffer cmd, RenderTextureDescriptor desc)
    {
        base.Configure(cmd, desc);

        var colorDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.ARGB32, 0);
        var depthDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.Depth, 8);
        var normalDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.ARGB32, 0);
        cmd.GetTemporaryRT(ShaderPropertyId.MyColorTexture, colorDesc);
        cmd.GetTemporaryRT(ShaderPropertyId.MyDepthTexture, depthDesc);
        cmd.GetTemporaryRT(ShaderPropertyId.MyNormalTexture, normalDesc);

URP14では、SciriptableRendererFeature内でテクスチャを作成しています。

// URP14
public static class MyRenderTargetBuffer
{
    static RTHandle m_MyColorTexture;
    static RTHandle m_MyDepthTexture;
    static RTHandle m_MyNormalTexture;
    ....
    public static void Setup(RenderTextureDescriptor desc)
    {
        ....
        var colorDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.ARGB32, 0);
        var depthDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.Depth, 8);
        var normalDesc = new RenderTextureDescriptor(desc.width, desc.height, RenderTextureFormat.ARGB32, 0);

        // URP14
        RenderingUtils.ReAllocateIfNeeded(ref m_MyColorTexture, colorDesc, FilterMode.Point, TextureWrapMode.Clamp, name: "_MyColorTexture");
        RenderingUtils.ReAllocateIfNeeded(ref m_MyDepthTexture, depthDesc, FilterMode.Point, TextureWrapMode.Clamp, name: "_MyDepthTexture");
        RenderingUtils.ReAllocateIfNeeded(ref m_MyNormalTexture, normalDesc, FilterMode.Point, TextureWrapMode.Clamp, name: "_MyNormalTexture");

RTHandleについて

RTHandleには、RenderTextureとRenderTargetIdentifierが定義されています。
RTHandleはレンダーターゲットIDとレンダーテクスチャの実体を抽象化しており、
これらを1つのものとして扱うことができます。

com.unity.render-pipelines.core@14.0.7/Runtime/Textures/RTHandle.cs
public class RTHandle
{
    internal RTHandleSystem m_Owner;
    internal RenderTexture m_RT;
    internal Texture m_ExternalTexture;
    internal RenderTargetIdentifier m_NameID;
    ...
}
暗黙的な型変換

RTHandle は暗黙的な型変換が定義されています。

public static implicit operator RenderTargetIdentifier(RTHandle handle) { ... }
public static implicit operator Texture(RTHandle handle) { ... }


暗黙的な型変換のおかげで、CommandBufferのSetGlobalTextureで、
RenderTargetIdentifier の代わりにRTHandleを渡すことができるなど、取り回しが良くなっています。

public void SetGlobalTexture(int nameID, RenderTargetIdentifier value) 

Chapter2. Deferred Lighting を実装してみる

今回は_MyColorTexture, _MyNormalTextureをテクスチャに書き出しました。
これらのテクスチャを使ってライティングを実装してみます。

シェーダー

MyDeferredLighting.shader
MyDeferredLighting
Shader "Hidden/MyDeferredLighting"
{
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MyColorTexture;
            sampler2D _MyNormalTexture;

            half4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D(_MyColorTexture, i.uv);
                float3 normal = tex2D(_MyNormalTexture, i.uv).xyz;
                Light light = GetMainLight();
                half lambert = saturate(dot(normalize(normal), light.direction));
                return half4(col.rgb * lambert * light.color.rgb, col.a);
            }
            ENDHLSL
        }
    }
}

URP 10.7.0

URP10.7.0でのRendererFeatureは以下になります。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2020.3.23/Assets/Rendering/Scripts/MyDeferredLightingFeature.cs

URP 12.1.8

URP12.1.8でのRendererFeatureは以下になります。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2021.3.16/Assets/Rendering/Scripts/MyDeferredLightingFeature.cs

URP 14.0.7

URP14.0.7でのRendererFeatureは以下になります。

https://github.com/rngtm/Unity_MRT_Sample/blob/main/MRT_Unity2022.2.20/Assets/Rendering/Scripts/MyDeferredLightingFeature.cs

まとめ

URP12以前とURP14の実装方法の違いについてまとめます。

差分1: レンダーターゲットの作成

URP12以前

CommandBuffer.GetTemporaryRT() でレンダーターゲットを作成し、
CommandBuffer.ReleaseTemporaryRT() でレンダーターゲットを解放します。

public void GetTemporaryRT(int nameID, RenderTextureDescriptor desc) {...}
public extern void ReleaseTemporaryRT(int nameID);

URP14

RTHandles.Alloc() でレンダーテクスチャ(レンダーターゲット)を作成し、
RTHandles.Release() でレンダーテクスチャ(レンダーターゲット)を解放します。

com.unity.render-pipelines.core@14.0.7/Runtime/Textures/RTHandles.cs
public static RTHandle Alloc(RenderTargetIdentifier tex, string name) { ... }
public static void Release(RTHandle rth) { ... }

RTHandles.Alloc を安全に使えるようにラップした、
RenderingUtils.ReAllocateIfNeeded が用意されています。

com.unity.render-pipelines.universal@14.0.7/Runtime/RenderingUtils.cs
public static bool ReAllocateIfNeeded(
    ref RTHandle handle,
    in RenderTextureDescriptor descriptor,
    FilterMode filterMode = FilterMode.Point,
    TextureWrapMode wrapMode = TextureWrapMode.Repeat,
    bool isShadowMap = false,
    int anisoLevel = 1,
    float mipMapBias = 0,
    string name = "")
{
    if (RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, false))
    {
        handle?.Release();
        handle = RTHandles.Alloc(descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
        return true;
    }
    return false;
}

差分2: レンダーターゲットの指定

URP12以前

ConfigureTarget でレンダーターゲットを指定します。

public void ConfigureTarget(RenderTargetIdentifier[] colorAttachments, RenderTargetIdentifier depthAttachment) { ... }

URP14

URP14では、RTHandleを使用します。

public void ConfigureTarget(RTHandle[] colorAttachments, RTHandle depthAttachment)

関連

https://zenn.dev/sakutaro/articles/bliter_api_change

https://zenn.dev/sakutaro/articles/convert_blitter

Discussion