概要
本記事はUnity #2 Advent Calendar 2019 の15日目の記事です。
今回UnityのUniversalRenderPipeline(以下、UniversalRP)を使用してZoomBlur【咆哮のポストエフェクト】を実装しました。
この素晴らしいドラゴンはアセットストアから無料でダウンロードしました。感謝
環境
下記の環境で実装しております。
- Unity2019.3.0b12
- Windows10
- UniversalRP 7.1.5
UniversalRPのVolumeとPostProcessingStackv2の比較
ppsv2に相当する機能が、UniversalRP
ではVolume
という名前に変わっていました。
他にもクラス名を比較すると下記のような対応関係になっています。
ppsv2 | UniversalRenderPipeline |
---|---|
PostProcessEffectSettings | VolumeComponent |
PostProcessProfile | VolumeProfile |
PostProcessManager | VolumeManager |
ppsv2ではPostProcessEffectSettings
とPostProcessEffectRenderer
を継承してカスタムエフェクトを作成しました。
参考:Writing-Custom-Effects
UniversalRPでは公式的にカスタムエフェクトの実装方法は提供されていませんが
下記のような手法で実装できました。
実装
まずはVolumeComponent
を継承してカスタムエフェクトのパラメータを定義してみましょう。
VolumeComponent
VolumeComponent
を継承してParameterを定義します。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class ZoomBlur : VolumeComponent, IPostProcessComponent
{
[Range(0f, 100f), Tooltip("強くすることで強いBlurがかかります。")]
public FloatParameter focusPower = new FloatParameter(0f);
[Range(0, 10), Tooltip("値が大きいほど綺麗にでますが負荷が高まるので注意してください。")]
public IntParameter focusDetail = new IntParameter(5);
[Tooltip("ブラーの中心座標。スクリーンの中心を(0,0)としています。")]
public Vector2Parameter focusScreenPosition = new Vector2Parameter(Vector2.zero);
[Tooltip("基準となる横幅解像度です。")]
public IntParameter referenceResolutionX = new IntParameter(1334);
public bool IsActive() => focusPower.value > 0f;
public bool IsTileCompatible() => false;
}
ScriptableRendererFeatureとScriptableRenderPass
次に独自の描画パスを差し込めるようにするために2つのクラスとシェーダーを実装します。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class ZoomBlurRenderFeature : ScriptableRendererFeature
{
ZoomBlurPass zoomBlurPass;
public override void Create()
{
zoomBlurPass = new ZoomBlurPass(RenderPassEvent.BeforeRenderingPostProcessing);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
zoomBlurPass.Setup(renderer.cameraColorTarget);
renderer.EnqueuePass(zoomBlurPass);
}
}
public class ZoomBlurPass : ScriptableRenderPass
{
static readonly string k_RenderTag = "Render ZoomBlur Effects";
static readonly int MainTexId = Shader.PropertyToID("_MainTex");
static readonly int TempTargetId = Shader.PropertyToID("_TempTargetZoomBlur");
static readonly int FocusPowerId = Shader.PropertyToID("_FocusPower");
static readonly int FocusDetailId = Shader.PropertyToID("_FocusDetail");
static readonly int FocusScreenPositionId = Shader.PropertyToID("_FocusScreenPosition");
static readonly int ReferenceResolutionXId = Shader.PropertyToID("_ReferenceResolutionX");
ZoomBlur zoomBlur;
Material zoomBlurMaterial;
RenderTargetIdentifier currentTarget;
public ZoomBlurPass(RenderPassEvent evt)
{
renderPassEvent = evt;
var shader = Shader.Find("PostEffect/ZoomBlur");
if (shader == null)
{
Debug.LogError("Shader not found.");
return;
}
zoomBlurMaterial = CoreUtils.CreateEngineMaterial(shader);
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (zoomBlurMaterial == null)
{
Debug.LogError("Material not created.");
return;
}
if (!renderingData.cameraData.postProcessEnabled) return;
var stack = VolumeManager.instance.stack;
zoomBlur = stack.GetComponent<ZoomBlur>();
if (zoomBlur == null) { return; }
if (!zoomBlur.IsActive()) { return; }
var cmd = CommandBufferPool.Get(k_RenderTag);
Render(cmd, ref renderingData);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void Setup(in RenderTargetIdentifier currentTarget)
{
this.currentTarget = currentTarget;
}
void Render(CommandBuffer cmd, ref RenderingData renderingData)
{
ref var cameraData = ref renderingData.cameraData;
var source = currentTarget;
int destination = TempTargetId;
var w = cameraData.camera.scaledPixelWidth;
var h = cameraData.camera.scaledPixelHeight;
zoomBlurMaterial.SetFloat(FocusPowerId, zoomBlur.focusPower.value);
zoomBlurMaterial.SetInt(FocusDetailId, zoomBlur.focusDetail.value);
zoomBlurMaterial.SetVector(FocusScreenPositionId, zoomBlur.focusScreenPosition.value);
zoomBlurMaterial.SetInt(ReferenceResolutionXId, zoomBlur.referenceResolutionX.value);
int shaderPass = 0;
cmd.SetGlobalTexture(MainTexId, source);
cmd.GetTemporaryRT(destination, w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
cmd.Blit(source, destination);
cmd.Blit(destination, source, zoomBlurMaterial, shaderPass);
}
}
シェーダー
シェーダーはShaderToyをUnity用にカスタムして使わせていただきました。
Shader "PostEffect/ZoomBlur"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Cull Off ZWrite Off ZTest Always
Tags { "RenderPipeline" = "UniversalPipeline"}
Pass
{
CGPROGRAM
#pragma vertex Vert
#pragma fragment Frag
sampler2D _MainTex;
float2 _FocusScreenPosition;
float _FocusPower;
int _FocusDetail;
int _ReferenceResolutionX;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
v2f Vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
float4 Frag (v2f i) : SV_Target
{
float2 screenPoint = _FocusScreenPosition + _ScreenParams.xy/2;
float2 uv = i.uv;
float2 mousePos = (screenPoint.xy / _ScreenParams.xy);
float2 focus = uv - mousePos;
fixed aspectX = _ScreenParams.x/_ReferenceResolutionX;
float4 outColor = float4(0, 0, 0, 1);
for (int i=0; i<_FocusDetail; i++) {
float power = 1.0 - _FocusPower * (1.0/_ScreenParams.x * aspectX) * float(i);
outColor.rgb += tex2D(_MainTex , focus * power + mousePos).rgb;
}
outColor.rgb *= 1.0 / float(_FocusDetail);
return outColor;
}
ENDCG
}
}
}
セットアップ
Main CameraのPost Processingはチェックを忘れずに入れておきます。
Volumeの追加
ヒエラルキーの右クリック/Volume/Global Volume
でオブジェクトを追加します。
Add Override
にてZoom Blur
を追加します。
プロファイルが設定されていなければNew
ボタンでプロファイルアセットを作成します。
(Bloomは趣味でつけてます)
RenderPipelineアセットの作成とRenderFeatureの登録
UniversalRenderPipelineAsset
の作成をします。
Create/Rendering/Universal Render Pipeline/Pipeline Asset(Forward Renderer)
同時に作成されるUniversalRenderPipelineAsset_Renderer
に+
ボタンでZoom Blur Render Feature
を登録します。
ProjectSettings/Graphics/Scriptable Render Pipeline Settings
にUniversalRenderPipelineAsset
をセットします。
これで独自のポストプロセス描画パスが反映されるようになりました。
アニメーター連携
アニメーターから連携してZoomBlurのパラメータを操作できるように
コントローラとなるコンポーネントを作成します。
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteAlways]
public class ZoomBlurController : MonoBehaviour
{
public VolumeProfile volumeProfile; // プロジェクトに作ったPostProcessVolume Profileをアタッチします
[Range(0f, 100f)]
public float focusPower = 10f;
[Range(0, 10)]
public int focusDetail = 5;
public int referenceResolutionX = 1334;
public Vector2 focusScreenPosition = Vector2.zero;
ZoomBlur zoomBlur;
void Update()
{
if (volumeProfile == null) return;
if (zoomBlur == null) volumeProfile.TryGet<ZoomBlur>(out zoomBlur);
if (zoomBlur == null) return;
zoomBlur.focusPower.value = focusPower;
zoomBlur.focusDetail.value = focusDetail;
zoomBlur.focusScreenPosition.value = focusScreenPosition;
zoomBlur.referenceResolutionX.value = referenceResolutionX;
}
}
プロジェクトデータ
サンプルプロジェクトデータをGitHubにアップしましたので
ご自由にお使いください。
参考サイト様
追記1(2020/03/08)
環境
- Unity2019.3.3f1
- UniversalRP 7.2.1
ppv2互換性について
-
UniversalRenderPipelineAsset
のPost-processing
でIntegrated
のほかに、Post Processing V2
を選択可能です。
-
Post Processing V2
を選択すると警告がでています。将来的には廃止するのであくまで互換性維持のために用いるべきとでています。 -
ppv2を使用すると、
UniversalRP 7.2.0
から同じく使用可能になった Camera Stackingが 使用不可 になるようです。
追記2(2020/09/21)
端末の解像度の違いによって見栄えに違いが出ないようにする修正を加えました。
-
ZoomBlur.shader
に_ReferenceResolutionX
: ターゲット横幅解像度 を追加しました。 - あわせて
_ReferenceResolutionX
をスクリプト側から渡せるように修正してます。 -
float power = 1.0 - _FocusPower * (1.0/_ScreenParams.x) * float(i);
-
-
float power = 1.0 - _FocusPower * (1.0/_ScreenParams.x * aspectX) * float(i);
- ↑
aspectX
を乗算して解像度の違いの吸収をします。
- ↑
-
※こちらのコミットで修正しました。