かりんちゃんの随心日誌

随筆、旅の話、ゲームの話、香港の話、言いたい放題

研究:自作ScriptableRenderPassでPostProcessing RenderTextureを使う

前回は一番簡単なScriptableRenderPassを実装できましたが、よく見るとレンダリングテスクチャを使っていません。実際このScriptableRenderPassの真価は、おそらく同じ描画工程に、色んなレンダリングテスクチャとシェーダを利用することなので、例えばHLSL本のブラーなどでは、レンダリングテスクチャなしでは無理だろう。

すると今回はレンダリングテスクチャに挑戦しました。もちろん、最もシンプルのコードで。

 

単純にシェーダを使うだけでは、わざわざRenderTextureを使うのは意味不明です。しかしブラーはちょっとシンプルではない。なので、今回は以下の記事を参考して、表示解像度を落とします:

https://shibuya24.info/entry/unity-urp-add-pass

 

また、シェーダは前回ではモノクロだが、今回はRGBの値を逆にします。

 

上記のダウンサンプリング記事では、RenderTextureを利用したものの、RenderTargetHandleを利用していません(そもそもシェーダ使ってない)。よって、こちらではRenderTargetHandleを利用するように変更。

ただし、URP14の問題点では、今までネット記事、そしてバイブルでも使ってたこの「RenderTargetHandle」が非推奨となっていること。なので、こちらを参考して、RenderTargetHandleの呼び出し及び処理は、RTHandleに変更しました:

https://forum.unity.com/threads/rendertargethandle-is-obsolete-deprecated-in-favor-of-rthandle.1211052/#post-7737570

 


Sampling.shader


Shader "TestingShader/PostProcess/Sampling"
{
    Properties
    {
        _MainTex ("Main Tex", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off
        ZWrite Off
        ZTest Always

        Pass
        {
            // C#からFindPassで特定できるように名前を指定する
            Name "Invert"

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct Attributes
			{
				float4 positionOS : POSITION;
				float2 uv : TEXCOORD0;
			};            
            struct Varyings
			{
				float4 positionCS : SV_POSITION;
				float2 uv : TEXCOORD0;
			};

            sampler2D _MainTex;

            Varyings vert (Attributes IN)
            {
                Varyings o;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz);
                o.positionCS = vertexInput.positionCS;
                o.uv = IN.uv;
                return o;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 col = tex2D(_MainTex, IN.uv);
                col.rgb = 1 - col.rgb;
                return col;
            }
            ENDHLSL
        }
    }
}

モノクロと違って、今回レンダリングテスクチャはテスクチャだから、普通に_MainTexを使うことです。

この場合は複数パスを同じシェーダファイルに用意し、Blit処理で特定シェーダを処理するから、名前を付くこと。


DownSamplingInvertRenderPass.cs


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

public class DownSamplingInvertRenderPass : ScriptableRenderPass
{
    private const string ProfilerTag = nameof(DownSamplingInvertRenderPass);
    private readonly int _downSample = 2;
    private RenderTargetIdentifier _source;
    private Material _material;
    
    // 以前では RenderTargetHandle
    private readonly RTHandle _renderTextureDownSamplingHandle;
    private readonly int _invertPass;

    public DownSamplingInvertRenderPass(Material material, int downSample, RenderPassEvent renderPassEvent)
    {
        this.renderPassEvent = renderPassEvent;
        this._downSample = downSample;
        this._material = material;

        // 以前では renderTarget.Init("_DownSamplingInvert");
        this._renderTextureDownSamplingHandle = RTHandles.Alloc("_DownSamplingInvert", name: "_DownSamplingInvert");

        this._invertPass = this._material.FindPass("Invert");
    }

    public void Setup(RenderTargetIdentifier source)
    {
        this._source = source;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (_material == null
            || !renderingData.cameraData.postProcessEnabled
            || renderingData.cameraData.cameraType == CameraType.SceneView
            )
        {
            return;
        }

        var cameraData = renderingData.cameraData;
        var w = cameraData.camera.scaledPixelWidth / _downSample;
        var h = cameraData.camera.scaledPixelHeight / _downSample;
        
        var cmd = CommandBufferPool.Get(ProfilerTag);

        // 以前では RenderTargetHandle から ID を参照できたが
        // 今回では Shader.PropertyToID から ID を取得する(GetRenderTextureId()を参照)
        cmd.GetTemporaryRT(GetRenderTextureId(), w, h, 0, FilterMode.Point, RenderTextureFormat.Default);

        // 現在のカメラ描画画像を、生成した RenderTexture にコピーし、さらに指定したシェーダ処理を行う
        cmd.Blit(_source, GetRenderTextureId(), this._material, this._invertPass);

        // 処理済みの RenderTexture をカメラに適用する
        cmd.Blit(GetRenderTextureId(), _source);

        context.ExecuteCommandBuffer(cmd);
        context.Submit();

        CommandBufferPool.Release(cmd);
    }

    public override void FrameCleanup(CommandBuffer cmd)
    {
        // リリースも同様
        cmd.ReleaseTemporaryRT(GetRenderTextureId());
    }

    private int GetRenderTextureId()
    {
        return Shader.PropertyToID(_renderTextureDownSamplingHandle.name);
    }
}

前回からRTのコードを追加した。RTHandleの変更点はコメントに参考(基本は上記のリンクそのままです)


DownSamplingInvertRenderFeature.cs


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

public class DownSamplingInvertRenderFeature : ScriptableRendererFeature
{

    [Serializable]
    public sealed class DownSamplingInvertPassSettings
    {
        [SerializeField]
        public Shader Shader;

        public int downSample = 2;
    }
    
    [SerializeField]
    DownSamplingInvertPassSettings settings = new();
    DownSamplingInvertRenderPass pass;

    public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
    {
        pass.Setup(renderer.cameraColorTargetHandle);
    }

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

    public override void Create()
    {
        var targetShader = settings.Shader;        
        if (targetShader == null) targetShader = Shader.Find("TestingShader/PostProcess/Sampling");

        var targetMaterial = CoreUtils.CreateEngineMaterial(targetShader);

        pass = new DownSamplingInvertRenderPass(targetMaterial, settings.downSample, RenderPassEvent.AfterRenderingTransparents);
    }
}

RenderFeatureはとくに面白いことはないです。Settings化は単純にバイブルの真似です。


DownSampleの値を5にしたらこんな感じ:

 

これで、やっとRenderTextureを扱えることができました。

あれ、もしかして、世の中のいわゆる2.5Dのゲームはこんな感じで表現したかね…?

 

正直バイブルのChapter6を読んでも、あるいはドキュメントを読んでも、理解できても、結局どこからどうすれば利用できるか、と悩んでいた。僕自身はわりと、どんな機能でも、Hello Worldみたいな、一番簡単なコードから実験するほうが好みなので、いきなり複雑で長いコードを見せられても、感嘆しながらも、いきなり書けそうにないという気持ちが一杯です。

しかも、この ScriptableRenderPass はやはり手間が多く、実際自分で1からなにかを実装してみないと、感覚がわからないだろうか。

バカバカしい実装だが、実はこんなバカバカしい実験を経て、やっと初めて ScriptableRenderPass と RenderTexture を完全理解したとも言える。