前回は一番簡単なScriptableRenderPassを実装できましたが、よく見るとレンダリングテスクチャを使っていません。実際このScriptableRenderPassの真価は、おそらく同じ描画工程に、色んなレンダリングテスクチャとシェーダを利用することなので、例えばHLSL本のブラーなどでは、レンダリングテスクチャなしでは無理だろう。
すると今回はレンダリングテスクチャに挑戦しました。もちろん、最もシンプルのコードで。
単純にシェーダを使うだけでは、わざわざRenderTextureを使うのは意味不明です。しかしブラーはちょっとシンプルではない。なので、今回は以下の記事を参考して、表示解像度を落とします:
https://shibuya24.info/entry/unity-urp-add-pass
また、シェーダは前回ではモノクロだが、今回はRGBの値を逆にします。
上記のダウンサンプリング記事では、RenderTextureを利用したものの、RenderTargetHandleを利用していません(そもそもシェーダ使ってない)。よって、こちらではRenderTargetHandleを利用するように変更。
ただし、URP14の問題点では、今までネット記事、そしてバイブルでも使ってたこの「RenderTargetHandle」が非推奨となっていること。なので、こちらを参考して、RenderTargetHandleの呼び出し及び処理は、RTHandleに変更しました:
Sampling.shader
Shader "TestingShader/PostProcess/Sampling"
{
Properties
{
_MainTex ("Main Tex", 2D) = "white" {}
}
SubShader
{
Cull Off
ZWrite Off
ZTest Always
Pass
{
// C#からFindPassで特定できるように名前を指定する
Name "Invert"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
Varyings vert (Attributes IN)
{
Varyings o;
VertexPositionInputs vertexInput = GetVertexPositionInputs(IN.positionOS.xyz);
o.positionCS = vertexInput.positionCS;
o.uv = IN.uv;
return o;
}
half4 frag(Varyings IN) : SV_Target
{
half4 col = tex2D(_MainTex, IN.uv);
col.rgb = 1 - col.rgb;
return col;
}
ENDHLSL
}
}
}
モノクロと違って、今回レンダリングテスクチャはテスクチャだから、普通に_MainTexを使うことです。
この場合は複数パスを同じシェーダファイルに用意し、Blit処理で特定シェーダを処理するから、名前を付くこと。
DownSamplingInvertRenderPass.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class DownSamplingInvertRenderPass : ScriptableRenderPass
{
private const string ProfilerTag = nameof(DownSamplingInvertRenderPass);
private readonly int _downSample = 2;
private RenderTargetIdentifier _source;
private Material _material;
// 以前では RenderTargetHandle
private readonly RTHandle _renderTextureDownSamplingHandle;
private readonly int _invertPass;
public DownSamplingInvertRenderPass(Material material, int downSample, RenderPassEvent renderPassEvent)
{
this.renderPassEvent = renderPassEvent;
this._downSample = downSample;
this._material = material;
// 以前では renderTarget.Init("_DownSamplingInvert");
this._renderTextureDownSamplingHandle = RTHandles.Alloc("_DownSamplingInvert", name: "_DownSamplingInvert");
this._invertPass = this._material.FindPass("Invert");
}
public void Setup(RenderTargetIdentifier source)
{
this._source = source;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (_material == null
|| !renderingData.cameraData.postProcessEnabled
|| renderingData.cameraData.cameraType == CameraType.SceneView
)
{
return;
}
var cameraData = renderingData.cameraData;
var w = cameraData.camera.scaledPixelWidth / _downSample;
var h = cameraData.camera.scaledPixelHeight / _downSample;
var cmd = CommandBufferPool.Get(ProfilerTag);
// 以前では RenderTargetHandle から ID を参照できたが
// 今回では Shader.PropertyToID から ID を取得する(GetRenderTextureId()を参照)
cmd.GetTemporaryRT(GetRenderTextureId(), w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
// 現在のカメラ描画画像を、生成した RenderTexture にコピーし、さらに指定したシェーダ処理を行う
cmd.Blit(_source, GetRenderTextureId(), this._material, this._invertPass);
// 処理済みの RenderTexture をカメラに適用する
cmd.Blit(GetRenderTextureId(), _source);
context.ExecuteCommandBuffer(cmd);
context.Submit();
CommandBufferPool.Release(cmd);
}
public override void FrameCleanup(CommandBuffer cmd)
{
// リリースも同様
cmd.ReleaseTemporaryRT(GetRenderTextureId());
}
private int GetRenderTextureId()
{
return Shader.PropertyToID(_renderTextureDownSamplingHandle.name);
}
}
前回からRTのコードを追加した。RTHandleの変更点はコメントに参考(基本は上記のリンクそのままです)
DownSamplingInvertRenderFeature.cs
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class DownSamplingInvertRenderFeature : ScriptableRendererFeature
{
[Serializable]
public sealed class DownSamplingInvertPassSettings
{
[SerializeField]
public Shader Shader;
public int downSample = 2;
}
[SerializeField]
DownSamplingInvertPassSettings settings = new();
DownSamplingInvertRenderPass pass;
public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
{
pass.Setup(renderer.cameraColorTargetHandle);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
renderer.EnqueuePass(pass);
}
public override void Create()
{
var targetShader = settings.Shader;
if (targetShader == null) targetShader = Shader.Find("TestingShader/PostProcess/Sampling");
var targetMaterial = CoreUtils.CreateEngineMaterial(targetShader);
pass = new DownSamplingInvertRenderPass(targetMaterial, settings.downSample, RenderPassEvent.AfterRenderingTransparents);
}
}
RenderFeatureはとくに面白いことはないです。Settings化は単純にバイブルの真似です。
DownSampleの値を5にしたらこんな感じ:
これで、やっとRenderTextureを扱えることができました。
あれ、もしかして、世の中のいわゆる2.5Dのゲームはこんな感じで表現したかね…?
正直バイブルのChapter6を読んでも、あるいはドキュメントを読んでも、理解できても、結局どこからどうすれば利用できるか、と悩んでいた。僕自身はわりと、どんな機能でも、Hello Worldみたいな、一番簡単なコードから実験するほうが好みなので、いきなり複雑で長いコードを見せられても、感嘆しながらも、いきなり書けそうにないという気持ちが一杯です。
しかも、この ScriptableRenderPass はやはり手間が多く、実際自分で1からなにかを実装してみないと、感覚がわからないだろうか。
バカバカしい実装だが、実はこんなバカバカしい実験を経て、やっと初めて ScriptableRenderPass と RenderTexture を完全理解したとも言える。