🥐

URPでカスタムポストエフェクト実装する

2023/11/14に公開

URPでグレースケールを行うポストエフェクトを作ります

環境

・ Unity2023.3.13f1
・ URP14.0.9

1.シェーダーの用意

URP14環境下でポストエフェクトをRendererFeatureに対応させるにはBlitterAPIを使用する必要があり、合わせてシェーダーも変更する必要があるらしい

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

Shader "PostEffect/GrayScale" {
    Properties
    {
        [HideInspector] _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Cull Off ZWrite Off ZTest Always
        Tags { "RenderType"="Opaque" "Renderpipeline"="UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

            half4 frag (Varyings i) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearRepeat, i.texcoord);
                half gray = dot(color.rgb, half3(0.2126, 0.7152, 0.0722));
                return half4(gray, gray, gray, color.a);
            }
            ENDHLSL
        }
    }
}

グレースケールの処理はここ

half gray = dot(color.rgb, half3(0.2126, 0.7152, 0.0722));
return half4(gray, gray, gray, color.a);

2.RenderPassの実装

ScriptableRenderPassを実装した、グレースケール処理を行う独自パスを実装します。

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

public class GrayScaleRenderPass : ScriptableRenderPass
{
    private readonly Material material;

    public GrayScaleRenderPass(Shader shader)
    {
        material = CoreUtils.CreateEngineMaterial(shader);
        renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (renderingData.cameraData.isSceneViewCamera) { return; }

        var cmd = CommandBufferPool.Get("GrayScaleRenderPass");

        using (new ProfilingScope(cmd, new ProfilingSampler("GrayScaleRenderPass")))
        {
            var handle = renderingData.cameraData.renderer.cameraColorTargetHandle;
            Blitter.BlitCameraTexture(cmd, handle, handle, material, 0);
        }

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

renderPassEventの設定でどのタイミングでこのパスが実行されるかを決定することができます
今回は、不透明の描画後にGrayScaleRenderPassが実行されるようにしました

renderPassEvent = RenderPassEvent.AfterRenderingOpaques;

3.RendererFeatureの実装

ScriptableRenderPipelineに先ほど作成したGrayScaleRenderPassを渡すためのRendererFeatureを実装します

using UnityEngine;
using UnityEngine.Rendering.Universal;

public class GrayScaleRendererFeature : ScriptableRendererFeature
{
    [SerializeField] private Shader _shader;

    private GrayScaleRenderPass grayscalePass;

    public override void Create()
    {
        grayscalePass = new GrayScaleRenderPass(_shader);
    }

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

レンダーパスが生成された時に生成したレンダーパスをレンダラーに渡しています

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

4.UniversalRenderDataの作成

「Create」->「Rendering」->「URP Universal Renderer」でUniversalRenderDataのアセットを生成します

5.RenderFeatureの追加

4.で生成したUniversalRenderDataに3.で作成したRendererFeatureを追加します。

作成したシェーダーの参照もここで設定します

6.UniversalRenderDataをパイプラインに追加

7.カメラの設定でグレースケールのUniversalRenderDataを選択する

グレースケールのポストエフェクトがかかっていることが確認できた

FrameDebuggerでみるとDrawOpaqueObjects(不透明の描画)の後にGrayScaleRenderPassが実行されているのがわかる

レンダーパスの実行タイミングを変えてみる

スカイボックスの描画の前にしてみる

GrayScaleRenderPass
renderPassEvent = RenderPassEvent.AfterRenderingSkybox;

スカイボックスもグレースケールがかかった

FrameDebuggerで見てみるとCamera.RenderSkyboxの後にGrayScaleRenderPassが実行されているのがわかる

Discussion