Help us understand the problem. What is going on with this article?

URPで無理やり Custom Post Processing する

URPとCustom Post Processing

URP (Universal Render Pipeline)における公式実装によるCustom Post Processingは現時点(2020/06/27)で非対応です。しかし公式ブログには今後対応予定と記載されているので、この記事を読む前に最新の情報をチェックしてくださいね!

非対応だってよ、どうするよ

とりあえずURPのソースを見てみました。PostProcessPass.csにビルトインのポストエフェクトのパスが定義されています。
PostProcessPass.cs
はい、どう見ても決め打ちでカスタムエフェクトを挟む隙などありません。PostProcessPassのことはいったん忘れます。

今回の主役はRendererFeatureという機能になります。これはURPのForward Rendererのセットアップ中に任意の処理を挟み込める機能で、これを使えば任意のパスを設定することが可能です。今回はこいつを使って独自にポストプロセスのパスを挟み込むという算段です。

なお今回のコードはUnity公式のURPサンプルをベースにしていますのでそちらも参考にどうぞ。

RendererFeature

RendererFeatureを使って任意のパスを作成するには、主にふたつのクラスが必要です。ひとつはScriptableRendererFeatureを継承したクラス、もうひとつはScriptableRenderPassを継承したクラスです。前者はパスのセットアップと挟み込みを担当し、後者は実際のパスの動作を定義します。

とりあえず今回は画面をグレイスケール化するポストエフェクトを挟んでみます。

CustomPostProcess.cs
using UnityEngine;
using UnityEngine.Rendering.Universal;

public class CustomPostProcess : ScriptableRendererFeature
{
    [System.Serializable]
    public class CustomPostProcessSettings
    {
        //パスの実行タイミング
        public RenderPassEvent Event = RenderPassEvent.BeforeRenderingPostProcessing;
        //使用するシェーダー
        public Shader GrayScaleShader;
    }

    public CustomPostProcessSettings settings = new CustomPostProcessSettings();

    private CustomPostProcessPass pass;

    // ScriptableRendererFeatureはScriptableObjectとしてRendererData内部に格納される。
    // ScriptableObjectのシリアライズのタイミングで呼ばれる。
    public override void Create()
    {
        this.name = "Custom PostProcess";
        pass = new CustomPostProcessPass(settings.Event, settings.GrayScaleShader);
    }

    //パスの差し込み。URPのSetupで呼ばれる。
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //入出力を指定してパスを初期化
        pass.Setup(renderer.cameraColorTarget, RenderTargetHandle.CameraTarget);
        //パスの差し込み
        renderer.EnqueuePass(pass);
    }
}
CustomPostProcessPass.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class CustomPostProcessPass : ScriptableRenderPass
{
    //CommandBufferの取得に使用する名前
    const string k_RenderCustomPostProcessingTag =
        "Render Custom PostProcessing Effects";

    //入出力
    private RenderTargetIdentifier passSource;
    private RenderTargetHandle passDestination;

    //Blitに使用するマテリアル
    private Material grayScaleMaterial;

    //一時的なレンダーターゲット(パスの入出力が同一の場合、いちど中間バッファを挟んでBlitする必要があるため)
    RenderTargetHandle m_TemporaryColorTexture;

    public CustomPostProcessPass(RenderPassEvent renderPassEvent, Shader grayScaleShader)
    {
        //パスの実行タイミングを指定
        this.renderPassEvent = renderPassEvent;
        if (grayScaleShader)
            grayScaleMaterial = new Material(grayScaleShader);

        //一時バッファの設定
        m_TemporaryColorTexture.Init("_TemporaryColorTexture");
    }

    public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
    {
        this.passSource = source;
        this.passDestination = destination;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        //レンダリング情報(画面サイズなど)。一時バッファの作成に使用。
        RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
        opaqueDesc.depthBufferBits = 0;

        var cmd = CommandBufferPool.Get(k_RenderCustomPostProcessingTag);

        Render(cmd, ref renderingData, opaqueDesc);

        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    void Render(CommandBuffer cmd, ref RenderingData renderingData, RenderTextureDescriptor opaqueDesc)
    {
        cmd.GetTemporaryRT(m_TemporaryColorTexture.id, opaqueDesc, FilterMode.Bilinear);

        //エフェクトの適用
        DoEffectGrayScale(cmd, passSource, m_TemporaryColorTexture, opaqueDesc);

        //出力先へコピー
        if (passDestination == RenderTargetHandle.CameraTarget)
        {
            Blit(cmd, m_TemporaryColorTexture.Identifier(), passSource);
        }
        else
        {
            Blit(cmd, m_TemporaryColorTexture.Identifier(), passDestination.Identifier());
        }
    }

    private void DoEffectGrayScale(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetHandle destination,
        RenderTextureDescriptor opaqueDesc)
    {
        Blit(cmd, source, destination.Identifier(), grayScaleMaterial, 0);
    }

    public override void FrameCleanup(CommandBuffer cmd)
    {
        if (passDestination == RenderTargetHandle.CameraTarget)
            cmd.ReleaseTemporaryRT(m_TemporaryColorTexture.id);
    }
}

これでRendererFeatureが定義できました。

RendererFeatureの登録

次に、このRendererFeatureをURPに登録します。
URP Assetに設定したRenderer AssetのInspectorを開きます。
image.png
下部にAdd Renderer Featureというボタンがあるので押します。出現したメニューに先ほど作成したCustom Post Processの項目があるので、選択して追加します。
image.png
これでRendererFeatureが設定できました。

シェーダーの作成

あとは実際に画面をグレイスケール化するシェーダーを書きましょう。
URP (というかSRP)ではシェーダーはHLSLで書きます。ShaderLab (Cg/HLSL)と基本的な文法は同じですが、いろいろとお作法に相違点があるので注意が必要です。
URPに含まれているビルトインのポストエフェクトシェーダーを見ながら書くと手っ取り早いのでオススメ。

GrayScale.shader
Shader "Hidden/Universal Render Pipeline/Custom/GrayScale"
{
    Properties
    {
        _MainTex("Source", 2D) = "white" {}
    }

    HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"

        TEXTURE2D_X(_MainTex);

        half4 Frag(Varyings input) : SV_Target
        {
            float4 source = SAMPLE_TEXTURE2D_X(_MainTex, sampler_LinearClamp, input.uv);
            float y = 0.299 * source.r + 0.587 * source.g + 0.144 * source.b;
            return half4(y, y, y, 1.0);
        }

    ENDHLSL

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
        LOD 100
        ZTest Always ZWrite Off Cull Off

        Pass
        {
            Name "GrayScale"

            HLSLPROGRAM
                #pragma vertex Vert
                #pragma fragment Frag
            ENDHLSL
        }
    }
}

あとはこのシェーダーをRendererFeatureにセットして……

image.png

image.png

見慣れた画面がグレイスケールになりました。

おしまい

今回はサンプルとしてグレイスケール化をやってみましたが、スクリプト側でやってることはシェーダーによるBlitだけなので、適当に拡張できると思います。
あとは普通のポストプロセスと同様にVolumeからエフェクトの設定ができれば最高なのですが、そのへんはめんどくさいので公式対応に任せておくとして、こんなところで終わりにしたいと思います。

ruccho_vector
Unityをメインで扱っている大学生です。
https://ruccho.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away