はじめに
Universal Render Pipeline(以下URP)におけるポスプロセスは今までのPost Processing Stack v2ではなく、新しいクラスを使って実装されたものとなっています。相変わらずPost Processing Stack v2を利用することはできるようですが、そのうち使えなくなるという警告が表示されますので、遅かれ早かれ移行することが必要みたいです。
URPにポストプロセスを導入する
まずURPのプロジェクトを作って試していきます。URPでポストプロセスを使うことは簡単にできます。Unity 2019.4でUniversal Render Pipelineのプロジェクトを作成します。
こんなシーンがデフォルトで作られるのでここにポストプロセスを導入します。
PostProccessing Stack V3を使う
ヒエラルキーに適当なゲームオブジェクトを作成し、AddComponentからVolumeを付けます。VolumeのProfileに適当なProfileを設定します。ここにあるnewボタンを押せば作成され、セットされます。
確認するだけなので、Add Override から Channnel Mixer あたりを入れます。これで設定ファイル側の導入は終わりです。
次にカメラの設定を行います。カメラに Post Proccessing というチェックボックスがあるのでこれにチェックを入れます。
これでポストエフェクトが利くようになります。
PostProccessing Stack V3をカスタムポストエフェクトを実装する
経緯と方法
PostProccessing Stack V2 では組み込みのポストエフェクト効果以外に、ユーザーが拡張用のクラスを継承したC#スクリプトとシェーダーコードを実装することによって、新しいポストエフェクトを作ることができました。この仕組みはPostProccessing Stack V3ではサポートされていません。(※Unity2019.4.1環境。将来的にサポート予定はある。)
URPにはSRPという仕組みが導入されています。これは既存のレンダリングパイプラインに(ある程度)自由にユーザーカスタムできる仕組みとなっています。これを利用して「カスタムポストエフェクト用描画パス」を追加することで今のURPでもカスタムポストエフェクトを実装することができるようになります。
新しい描画パスを追加する
描画パスの追加は非常に民主化されているため、とても簡単に行うことができます。まずメニューから「Create >> Universal Render Pipeline > Render Feature」を選択し、Render Featureを作成します。
すると下記のようなスクリプトが生成されます。ScriptableRenderPassを継承したクラスに処理を記述することで独自のパスで好きな描画処理を行うことができます。ScriptableRenderFeatureはパスの設定や追加タイミングの定義を行うクラスです。
public class CustomRenderPassFeature : ScriptableRendererFeature { public class CustomRenderPass : ScriptableRenderPass { private const string k_RenderTag = "CustomRenderPass"; public CustomRenderPass(RenderPassEvent evt) { renderPassEvent = evt; var shader = Shader.Find("Hidden/Custom/GrayScale"); if (shader == null) { return; } material = CoreUtils.CreateEngineMaterial(shader); } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { ....
今回は確認のためだけなので、グレースケールをかけるポストエフェクトを作成してみます。グレースケールをかけるためのシェーダーコードを書き、上記の手順で作成したRender Featureを下記のように書き換えます。
Shader "Hidden/Custom/GrayScale"
{
Properties
{
_MainTex("Texture", 2D) = "white" { }
}
SubShader
{
Cull Off
ZWrite Off
ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
float _InfluencePower;
fixed4 frag(v2f_img i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
float gray = dot(color.rgb, fixed3(0.3, 0.6, 0.1));
fixed3 grayColor = fixed3(gray, gray, gray);
fixed3 fixedColor = lerp(color.rgb, grayColor, _InfluencePower);
color = fixed4(fixedColor, 1);
return color;
}
ENDCG
}
}
Fallback off
}
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class CustomRenderPassFeature : ScriptableRendererFeature
{
public class CustomRenderPass : ScriptableRenderPass
{
private const string k_RenderTag = "CustomRenderPass";
public CustomRenderPass(RenderPassEvent evt)
{
renderPassEvent = evt;
var shader = Shader.Find("Hidden/Custom/GrayScale");
if (shader == null)
{
return;
}
material = CoreUtils.CreateEngineMaterial(shader);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (material == null)
{
Debug.LogError("Material not created.");
return;
}
if (!renderingData.cameraData.postProcessEnabled)
{
return;
}
var stack = VolumeManager.instance.stack;
grayScale = stack.GetComponent();
if (grayScale == null)
{
return;
}
if (!grayScale.IsActive()) {
return;
}
var cmd = CommandBufferPool.Get(k_RenderTag);
Render(cmd, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void Setup(in RenderTargetIdentifier currentTarget)
{
currentTargetId = currentTarget;
}
void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
ref var cameraData = ref renderingData.cameraData;
var w = cameraData.camera.scaledPixelWidth;
var h = cameraData.camera.scaledPixelHeight;
material.SetFloat(influencePower, grayScale.influence.value);
cmd.GetTemporaryRT(TempTargetId, w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
cmd.Blit(currentTargetId, TempTargetId);
cmd.Blit(TempTargetId, currentTargetId, material);
}
private GrayScaleComponent grayScale;
private Material material;
private RenderTargetIdentifier currentTargetId;
private int TempTargetId = Shader.PropertyToID("_TempTarget");
private int influencePower = Shader.PropertyToID("_InfluencePower");
}
public override void Create()
{
_scriptablePass = new CustomRenderPass(RenderPassEvent.BeforeRenderingPostProcessing);
// Configures where the render pass should be injected.
_scriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
}
// Here you can inject one or multiple render passes in the renderer.
// This method is called when setting up the renderer once per-camera.
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
_scriptablePass.Setup(renderer.cameraColorTarget);
renderer.EnqueuePass(_scriptablePass);
}
private CustomRenderPass _scriptablePass;
}
次に上記で作成した、描画パスを画面に反映させるためにRendererに登録します。ちなみにRendererはカメラから指定することで利用され、URPでは複数のRendererを簡単に切り替えることができるようになっています。
パスの登録はRendererを表すScriptableObjectがあるはずなので、そのファイルのAdd Render Featureを押すことで行います。
これで新しい描画パスの追加ができました。VolumeのAdd OverrideからGray Scaleを追加します。
これでグレースケールをかけるカスタムポストエフェクトを実装することができました。
まとめ
描画パスを自由に追加できるということは、一部分だけアウトラインを付けるといった機能もできそうな気がします。