知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【Unity Shader】Renderer Featureによるポストプロセス #116

はじめに

Renderer Featureとシェーダーグラフを使用して、ポストプロセスを実装します。

ソースコード

ScriptableRendererFeature

using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Day4.Practice1.PostEffect
{
    public class GrayscaleRendererFeature : ScriptableRendererFeature
    {
        [SerializeField]
        private Shader shader;

        private GrayscalePass grayscalePass;

        // 初期化
        public override void Create()
        {
            grayscalePass = new GrayscalePass(shader);
        }

        // 1つ、または複数のパスを追加する
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
            renderer.EnqueuePass(grayscalePass);
        }
    }
}

ScriptableRenderPass

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

namespace Day4.Practice1.PostEffect
{
    public class GrayscalePass: ScriptableRenderPass
    {
        private const string ProfilerTag = nameof(GrayscalePass);

        private readonly Material material;

        // 描画対象をハンドリングする
        private RenderTargetHandle tmpRenderTargetHandle;
        private RenderTargetIdentifier cameraColorTarget;

        public GrayscalePass(Shader shader)
        {
            material = CoreUtils.CreateEngineMaterial(shader);
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            tmpRenderTargetHandle.Init("_TempRT");
        }

        public void SetRenderTarget(RenderTargetIdentifier target)
        {
            cameraColorTarget = target;
        }

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

            // コマンドバッファの生成
            var cmd = CommandBufferPool.Get(ProfilerTag);

            // RenderTextureDescriptorの取得
            var descriptor = renderingData.cameraData.cameraTargetDescriptor;
            // 今回深度は不要なので0に
            descriptor.depthBufferBits = 0;

            cmd.GetTemporaryRT(tmpRenderTargetHandle.id, descriptor);
            cmd.Blit(cameraColorTarget, tmpRenderTargetHandle.Identifier(), material);
            cmd.Blit(tmpRenderTargetHandle.Identifier(), cameraColorTarget);
            cmd.ReleaseTemporaryRT(tmpRenderTargetHandle.id);

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

シェーダー

f:id:soramamenatan:20220327131917p:plain

RendererFeature

ユーザーが独自にカスタマイズした描画パスを追加できるURPで提供されている機能になります。
ポストプロセスや、ブラーによるダウンサンプリング等に使用されます。

独自に定義したパスを追加するScriptableRendererFeature
描画の具体的な内容を記載するScriptableRenderPassの2つを使用することにより、描画パスを追加できます。

ScriptableRendererFeature

// 初期化
public override void Create()
{
    grayscalePass = new GrayscalePass(shader);
}

Createは初期化をする箇所で、monobehaviourでいうStartに近いものになります。
今回はパスの初期化を行っています。

// 1つ、または複数のパスを追加する
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
    grayscalePass.SetRenderTarget(renderer.cameraColorTarget);
    renderer.EnqueuePass(grayscalePass);
}

AddRenderPassesでパスを追加します。
renderer.cameraColorTargetはカメラに写っている対象の色になります。
これをScriptableRenderPass側に渡すことによりポストプロセスを可能にしています。
renderer.EnqueuePassはその名の通り、パスをEnqueueしているものになります。

ScriptableRenderPass

コンストラク

public GrayscalePass(Shader shader)
{
    material = CoreUtils.CreateEngineMaterial(shader);
    renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
    tmpRenderTargetHandle.Init("_TempRT");
}

CoreUtils.CreateEngineMaterial

/// <summary>
/// Creates a Material with the provided shader.
/// hideFlags will be set to HideFlags.HideAndDontSave.
/// </summary>
/// <param name="shader">Shader used for the material.</param>
/// <returns>A new Material instance using the provided shader.</returns>
public static Material CreateEngineMaterial(Shader shader)
{
    if (shader == null)
    {
        Debug.LogError("Cannot create required material because shader is null");
        return null;
    }

    var mat = new Material(shader)
    {
        hideFlags = HideFlags.HideAndDontSave
    };
    return mat;
}

シェーダーを元に、Hierarchyに表示せずシーンに保存しない、オブジェクトによりアンロードしないマテリアルを制作してくれるものになります。

RenderPassEvent

パスを実行するタイミングを制御するものになります。
一覧は公式のドキュメントに記載しています。

docs.unity3d.com

RenderTargetHandle

描画対象をハンドリングするものになります。
FrameDebugからもレンダーテクスチャが確認できます。

f:id:soramamenatan:20220327122009p:plain

Execute

実際にパスを実行する関数になります。

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

    // コマンドバッファの生成
    var cmd = CommandBufferPool.Get(ProfilerTag);

    // RenderTextureDescriptorの取得
    var descriptor = renderingData.cameraData.cameraTargetDescriptor;
    // 今回深度は不要なので0に
    descriptor.depthBufferBits = 0;

    cmd.GetTemporaryRT(tmpRenderTargetHandle.id, descriptor);
    cmd.Blit(cameraColorTarget, tmpRenderTargetHandle.Identifier(), material);
    cmd.Blit(tmpRenderTargetHandle.Identifier(), cameraColorTarget);
    cmd.ReleaseTemporaryRT(tmpRenderTargetHandle.id);

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

CommandBufferPool.Get

var cmd = CommandBufferPool.Get(ProfilerTag);

コマンドバッファを生成する関数になります。
引数にstring型を渡すことで名前をつけてくれます。

Descriptor

var descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.depthBufferBits = 0;

カメラからRenderTextureDescriptorを取得し、今回は不要ですので深度情報を0にしています。
RenderTextureDescriptorはRenderTextureを作成するための全ての情報が含まれた構造体になっています。

RenderTextureに書き込む

cmd.GetTemporaryRT(tmpRenderTargetHandle.id, descriptor);
cmd.Blit(cameraColorTarget, tmpRenderTargetHandle.Identifier(), material);
cmd.Blit(tmpRenderTargetHandle.Identifier(), cameraColorTarget);
cmd.ReleaseTemporaryRT(tmpRenderTargetHandle.id);

以下のことを行っています。

  1. 一時的なレンダーテクスチャを取得
  2. 元のテクスチャから一時的なレンダーテクスチャにエフェクトを適応して描画
  3. 一時的なレンダーテクスチャから元のテクスチャに描画
  4. 一時的なレンダーテクスチャの解放

GetTemporaryRTReleaseTemporaryRTはセットなので解放を忘れないようにしてください。

Identifierはハンドルに登録されたレンダーテクスチャを識別するもので、今回はidが割り振られているのでそのまま返ってきます。

public RenderTargetIdentifier Identifier()
{
    if (id == -1)
    {
        return BuiltinRenderTextureType.CameraTarget;
    }
    if (id == -2)
    {
        return rtid;
    }
    return new RenderTargetIdentifier(id, 0, CubemapFace.Unknown, -1);
}

コマンドバッファの実行

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

ExecuteCommandBufferは引数のコマンドバッファを実行するものになります。
最後にReleaseでコマンドバッファを解放してあげるのを忘れないでください。

結果

制作したRenderFeatureを、Addして今回制作したシェーダーグラフをアタッチしてください。

f:id:soramamenatan:20220327131541p:plain

カメラのRendererに今回のPipelineを選択して準備完了になります。

f:id:soramamenatan:20220327131617p:plain

対応前

f:id:soramamenatan:20220327130238p:plain

対応後

f:id:soramamenatan:20220327130250p:plain

詰まった箇所

画面が青く描画される

シェーダーをUnlitで使用していて、画面一面が青くなってしまっていた。
litに修正することで治ったが、Unlitだと何故起きるのかまでは突き止められなかった。

原因は、DepthNormalsパスとSceneSelectionPassパスで青く描画されていることだとは思うがなんで青いかもいまいち掴めていない。

f:id:soramamenatan:20220327130658p:plain

シェーダーが適応されない

intermediate textureをAlwaysにして強制しないと適応されない時があった。

f:id:soramamenatan:20220327131036p:plain

参考サイト様

light11.hatenadiary.com

edom18.hateblo.jp