Unity6.3からコンパチビリティモードも無くなり、本格的にRenderGraph対応をしていかなければならなくなってきましたね。皆さんRenderGraph対応してますか?
ただ「そんな大仰なRendererFeatureを組みたいわけではなくて、単純にシェーダを1つ嚙ませてPostProcessっぽい事がしたいだけなんだけどなぁ・・・」という時にわざわざRenderGraphの作法から調べるのも面倒なので、Blit()だけを行うシンプルなRendrGraph対応済みRendererFeatureを書いておきます
前提
今回のコードは
- Unity6000.4.2f1
- Windows
環境で記載しています
先にシェーダから
本題のRendererFeatureに入る前に、シェーダのコードを書いておきましょう。
私のQiita記事ではお馴染みのMonochromeシェーダです
Shader "ScreenPocket/URP/PostProcess/Monochrome"
{
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
half4 Frag(Varyings input) : SV_Target
{
half4 col = SAMPLE_TEXTURE2D_X( _BlitTexture, sampler_PointClamp, input.texcoord );
half luminance = Luminance(col.rgb);
return half4(luminance,luminance,luminance,1);
}
ENDHLSL
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline"}
LOD 100
Cull Off ZWrite Off ZTest Always
Pass
{
Name "Monochrome"
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
}
}
たった30行のシンプルなヤツです。
_BlitTextureで色を引いてLuminance()で輝度抽出するだけ。
コード
という事でRendererFeatureのコードです
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace ScreenPocket.Rendering.Runtime
{
/// <summary>
/// ただコピーバッファにBlit()して、マテリアルを当てつつBlit()で書き戻すだけのRendererFeature
/// </summary>
public sealed class SimpleBlitFeature : ScriptableRendererFeature
{
/// <summary>
/// Blit用のPass
/// </summary>
private sealed class Pass : ScriptableRenderPass, IDisposable
{
private const string PassName = "Simple Blit Pass";
private Material _blitMaterial;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="evt"></param>
/// <param name="blitShader"></param>
public Pass(RenderPassEvent evt, Shader blitShader)
{
renderPassEvent = evt;
profilingSampler = new ProfilingSampler(PassName);
if (blitShader != null)
{
_blitMaterial = new Material(blitShader);
}
}
/// <summary>
/// 受け渡し用Data
/// </summary>
private class PassData
{
public Material material;
public TextureHandle inputTexture;
}
/// <summary>
/// Blit()実行
/// </summary>
/// <param name="data"></param>
/// <param name="context"></param>
private static void ExecuteBlit(PassData data, RasterGraphContext context)
{
if (data.material == null)
{
Blitter.BlitTexture(context.cmd, data.inputTexture, new Vector4(1, 1, 0, 0), 0f, false);
return;
}
//Materialの指定があるならこちら
Blitter.BlitTexture(context.cmd, data.inputTexture, new Vector4(1, 1, 0, 0), data.material, 0);
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
var resourceData = frameData.Get<UniversalResourceData>();
var cameraData = frameData.Get<UniversalCameraData>();
//コピー用テクスチャを作る
var colorCopyDescriptor = cameraData.cameraTargetDescriptor;
//色をコピーしたいだけなのでMSAAやらDepthは不要
colorCopyDescriptor.msaaSamples = 1;
colorCopyDescriptor.depthBufferBits = (int)DepthBits.None;
var copiedColorTexture = UniversalRenderer.CreateRenderGraphTexture(renderGraph, colorCopyDescriptor, "_CopyTexture", false);
//先ずフレームバッファを一時バッファにコピー
using (var builder = renderGraph.AddRasterRenderPass<PassData>("SimpleBlit_CopyColor", out var data, profilingSampler))
{
data.inputTexture = resourceData.activeColorTexture;
builder.UseTexture(resourceData.activeColorTexture);
//Blit先の指定 コピー用テクスチャへ
builder.SetRenderAttachment(copiedColorTexture, 0);
builder.SetRenderFunc<PassData>(ExecuteBlit);
}
//一時バッファにコピーしたテクスチャをマテリアルで加工しつつ書き戻す
using (var builder = renderGraph.AddRasterRenderPass<PassData>("SimpleBlit_Main", out var data, profilingSampler))
{
data.material = _blitMaterial;
data.inputTexture = copiedColorTexture;
builder.UseTexture(copiedColorTexture);
//Blit先の指定 もとのActiveColorへ
builder.SetRenderAttachment(resourceData.activeColorTexture, 0);
builder.SetRenderFunc<PassData>(ExecuteBlit);
}
}
/// <summary>
/// 片付け
/// </summary>
public void Dispose()
{
if (_blitMaterial != null)
{
CoreUtils.Destroy(_blitMaterial);
_blitMaterial = null;
}
}
}// end class Pass
/// <summary>
/// 処理タイミング
/// </summary>
[SerializeField] private RenderPassEvent _renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
/// <summary>
/// 処理タイミングのOffset
/// </summary>
[SerializeField] private int _renderPassEventOffset;
/// <summary>
/// 書き戻しの際に適用するシェーダ
/// </summary>
[SerializeField] private Shader _blitShader;
private Pass _pass;
/// <summary>
/// Pass作成
/// </summary>
public override void Create()
{
_pass = new Pass(_renderPassEvent + _renderPassEventOffset, _blitShader);
}
/// <summary>
/// Pass登録
/// </summary>
/// <param name="renderer"></param>
/// <param name="renderingData"></param>
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(_pass);
}
/// <summary>
/// 片付け
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (_pass != null)
{
_pass.Dispose();
_pass = null;
}
base.Dispose(disposing);
}
}
}
コメント込みでの160行程度なので、まぁそこそこシンプルなのではないでしょうか?
やっていることは昔のGetTemporary()でやっていた時よろしく(と言って伝わるのかしら?)、カラーバッファから一度コピー用のテクスチャにBlit()した後で、マテリアルを当てつつコピー用からカラーバッファに書き戻している感じです
使い方
冒頭のMonochorme.shaderをプロジェクトに追加、RendererFeatureをプロジェクトに追加し、URPのRendererにSimpleBlitFeatureを追加してシェーダを当て込めば動作するはずです

FrameDebuggerで確認すると、SimpleBlitPassが追加されていることが見て取れるはずです
終わりに
という事で、手軽?に使いやすいRendererFeatureでした。
ImageEffect的にPostProcess的なシェーダを試す際に役に立つのではないでしょうか?
私がQiitaを書き始めた時はImageEffectだったのが、RendererFeatureになり、RenderGraphになり・・・と、移り変わりを感じますね。日々勉強ですねぇ・・・

