LoginSignup
48
39

More than 3 years have passed since last update.

【Unity】UniversalRPでカスタムポストプロセスを作る【ZoomBlur】

Last updated at Posted at 2019-12-14

概要

本記事はUnity #2 Advent Calendar 2019 の15日目の記事です。
今回UnityのUniversalRenderPipeline(以下、UniversalRP)を使用してZoomBlur【咆哮のポストエフェクト】を実装しました。
ZoomBlur.gif

この素晴らしいドラゴンはアセットストアから無料でダウンロードしました。感謝:relaxed:

環境

下記の環境で実装しております。

  • Unity2019.3.0b12
  • Windows10
  • UniversalRP 7.1.5

UniversalRPのVolumeとPostProcessingStackv2の比較

ppsv2に相当する機能が、UniversalRPではVolumeという名前に変わっていました。
他にもクラス名を比較すると下記のような対応関係になっています。

ppsv2 UniversalRenderPipeline
PostProcessEffectSettings VolumeComponent
PostProcessProfile VolumeProfile
PostProcessManager VolumeManager

ppsv2ではPostProcessEffectSettingsPostProcessEffectRendererを継承してカスタムエフェクトを作成しました。
参考:Writing-Custom-Effects

UniversalRPでは公式的にカスタムエフェクトの実装方法は提供されていませんが
下記のような手法で実装できました。

実装

まずはVolumeComponentを継承してカスタムエフェクトのパラメータを定義してみましょう。

VolumeComponent

VolumeComponentを継承してParameterを定義します。

ZoomBlur.cs
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つのクラスとシェーダーを実装します。

ZoomBlurRenderFeature.cs
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);
    }
}
ZoomBlurPass.cs
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用にカスタムして使わせていただきました。

ZoomBlur.shader
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はチェックを忘れずに入れておきます。

2019-12-08_23h36_51.png

Volumeの追加

ヒエラルキーの右クリック/Volume/Global Volumeでオブジェクトを追加します。
2019-12-09_11h30_53.png

Add OverrideにてZoom Blurを追加します。
プロファイルが設定されていなければNewボタンでプロファイルアセットを作成します。
2019-12-08_23h32_33.png
(Bloomは趣味でつけてます)

RenderPipelineアセットの作成とRenderFeatureの登録

UniversalRenderPipelineAssetの作成をします。
Create/Rendering/Universal Render Pipeline/Pipeline Asset(Forward Renderer)
2019-12-09_11h33_41.png

同時に作成されるUniversalRenderPipelineAsset_Renderer+ボタンでZoom Blur Render Featureを登録します。
2019-12-09_11h36_24.png

ProjectSettings/Graphics/Scriptable Render Pipeline SettingsUniversalRenderPipelineAssetをセットします。
2019-12-09_11h41_03.png

これで独自のポストプロセス描画パスが反映されるようになりました。

アニメーター連携

アニメーターから連携してZoomBlurのパラメータを操作できるように
コントローラとなるコンポーネントを作成します。

ZoomBlurController.cs
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;
    }
}

2019-12-14_17h23_50.png

プロジェクトデータ

サンプルプロジェクトデータをGitHubにアップしましたので
ご自由にお使いください。

参考サイト様

追記1(2020/03/08)

環境

  • Unity2019.3.3f1
  • UniversalRP 7.2.1

ppv2互換性について

  • UniversalRenderPipelineAssetPost-processingIntegratedのほかに、Post Processing V2を選択可能です。
    2020-03-08_17h26_29.png
    2020-03-08_17h27_08.png

  • Post Processing V2を選択すると警告がでています。将来的には廃止するのであくまで互換性維持のために用いるべきとでています。

  • ppv2を使用すると、UniversalRP 7.2.0から同じく使用可能になった Camera Stackingが 使用不可 になるようです。
    2020-03-08_17h38_58.png

追記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 を乗算して解像度の違いの吸収をします。

こちらのコミットで修正しました。

48
39
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
39