【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); } } }
シェーダー
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
パスを実行するタイミングを制御するものになります。
一覧は公式のドキュメントに記載しています。
RenderTargetHandle
描画対象をハンドリングするものになります。
FrameDebugからもレンダーテクスチャが確認できます。
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);
以下のことを行っています。
- 一時的なレンダーテクスチャを取得
- 元のテクスチャから一時的なレンダーテクスチャにエフェクトを適応して描画
- 一時的なレンダーテクスチャから元のテクスチャに描画
- 一時的なレンダーテクスチャの解放
GetTemporaryRT
とReleaseTemporaryRT
はセットなので解放を忘れないようにしてください。
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して今回制作したシェーダーグラフをアタッチしてください。
カメラのRendererに今回のPipelineを選択して準備完了になります。
対応前
対応後
詰まった箇所
画面が青く描画される
シェーダーをUnlitで使用していて、画面一面が青くなってしまっていた。
litに修正することで治ったが、Unlitだと何故起きるのかまでは突き止められなかった。
原因は、DepthNormalsパスとSceneSelectionPassパスで青く描画されていることだとは思うがなんで青いかもいまいち掴めていない。
シェーダーが適応されない
intermediate textureをAlwaysにして強制しないと適応されない時があった。