見出し画像

【Unity】PPS v2 カスタムエフェクトを作る

Post Process Stack v2(PPS)には予め用意されているいくつかのエフェクトを追加することで好みの絵作りをすることが可能になっていますが、自分で1からオリジナルのエフェクトを作ることも可能になっています。

PPSで色を調整する利点としては、カメラに映る全てのオブジェクトに対してエフェクトを適用できるため、既存オブジェクトのマテリアルやシェーダーに手を加えなくて良いという点が挙げられます。では早速やっていきましょう!

環境

Unity 2019.4.15f1 Windows 10
Post Processing Version 3.0.3

予めPackage ManagerからPost Processingをインストールしておいてください。

構成

カスタムエフェクトを作るには、PostProcessEffectSettingsとPostProcessEffectRenderer<T>を継承したクラスと、エフェクトの内容を記述したシェーダーを用意します。

PostProcessEffectSettingsでパラメータを保持し、PostProcessEffectRendererでシェーダーを読み込んでレンダリングする流れです。

スクリプトとシェーダー

今回は特定の位置を基準にして球体状に色をモノクロにするエフェクトを作成してみます。以下のスクリプトとシェーダーを作成してください。

Grayscale.cs

using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;

[Serializable]
[PostProcess(typeof(GrayscaleRenderer), PostProcessEvent.BeforeStack, "Custom/Grayscale")]
public sealed class Grayscale : PostProcessEffectSettings {
    [Range(0f, 100f), Tooltip("Grayscale effect range.")]
    public FloatParameter radius = new FloatParameter { value = 0.5f };
    public FloatParameter softness = new FloatParameter { value = 0.5f };
    public Vector3Parameter origin = new Vector3Parameter { value = new Vector3(0, 0, 0) };
}

public sealed class GrayscaleRenderer : PostProcessEffectRenderer<Grayscale> {
    public override void Render(PostProcessRenderContext context) {
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Custom/Grayscale"));
        sheet.properties.SetVector("_Origin", settings.origin);
        sheet.properties.SetFloat("_Radius", settings.radius);
        sheet.properties.SetFloat("_Softness", settings.softness);

        var camera = context.camera;

        var p = GL.GetGPUProjectionMatrix(camera.projectionMatrix, false);
        p[2, 3] = p[3, 2] = 0.0f;
        p[3, 3] = 1.0f;
        var clipToWorld = Matrix4x4.Inverse(p * camera.worldToCameraMatrix) *
                          Matrix4x4.TRS(new Vector3(0, 0, -p[2, 2]), Quaternion.identity, Vector3.one);
        sheet.properties.SetMatrix("_ClipToWorld", clipToWorld);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
}

Grayscale.shader

Shader "Hidden/Custom/Grayscale"
{
    HLSLINCLUDE

    #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
    #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/Colors.hlsl"

    TEXTURE2D_SAMPLER2D(_CameraDepthTexture, sampler_CameraDepthTexture);
    TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);

    uniform float4x4 _ClipToWorld;
    uniform float _Radius;
    uniform float _Softness;
    uniform vector _Origin;

    struct VaryingsExtended
    {
        float4 vertex : SV_POSITION;
        float2 texcoord : TEXCOORD0;
        float2 texcoordStereo : TEXCOORD1;
        float3 worldDirection : TEXCOORD2;
    };

    VaryingsExtended Vert(AttributesDefault v)
    {
        VaryingsExtended o;
        o.vertex = float4(v.vertex.xy, 0.0, 1.0);
        o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);

        #if UNITY_UV_STARTS_AT_TOP
            o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
        #endif

        o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);

        float4 clip = float4(o.texcoord.xy * 2 - 1, 0.0, 1.0);
        o.worldDirection = mul(_ClipToWorld, clip) - _WorldSpaceCameraPos;

        return o;
    }

    float4 Frag(VaryingsExtended i) : SV_Target
    {
        float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.texcoordStereo);
        depth = LinearEyeDepth(depth);
        float3 worldspace = i.worldDirection * depth + _WorldSpaceCameraPos;

        float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
        float4 colGray = float4(Luminance(col.rgb).xxx, 1);

        float d = distance(_Origin.xyz, worldspace);
        float sum = saturate((d - _Radius) / _Softness);
        return lerp(colGray, col, sum);
    }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

            #pragma vertex Vert
            #pragma fragment Frag

            ENDHLSL
        }
    }
}

カスタムエフェクトの適用

まずカメラにPost-Process Layerコンポーネントを追加します。

Post-Process Layerコンポーネントの追加

次にヒエラルキー上にPost-process Volumeコンポーネントがアタッチされたオブジェクトを作成します。「Is Global」にチェックを入れて、Profileを新規に作成しておきます。

Post-process Volumeの設定

[Add effect]ボタンを押して[Unity] > [Custom] > [Grayscale]を押します。

カスタムエフェクトの追加

インスペクタにパラメータ設定が表示されたら、以下のパラメータにチェックを入れて有効化します。

  • Radius:モノクロを適用する半径

  • Softness:カラーとモノクロの境界値の幅

  • Origin:球体モノクロエフェクトの原点

カスタムエフェクトのパラメータ

結果

各パラメータを変更してみてエフェクトが適用されていることを確認します。実行しなくても確認することができます。

カスタムエフェクトの適用

ビルド時の注意点

エディタで確認する場合はこのままでも動作しますが、ビルド時にはカスタムエフェクトのシェーダーが含まれないため、[Project Settings] > [Graphics]のAlways Included Shadersに作成したシェーダーを追加(Sizeを増やすとElementが増えて追加できるようになります。)するか、もしくはシェーダーをResourcesフォルダに入れるようにしてください。

Project Settingsへのシェーダー追加(ビルド時)

おわりに

Post Process Stack v2(PPS)でカスタムエフェクトを作ることができました。カスタムエフェクトを適用する例としてはスクリプトのOnRenderImageを使用する方法もありますが、既存のPPSのエフェクトとの順番を制御できないため、例えばBloomを後に適用するといったことができません。

一方、PPSに沿ったカスタムエフェクトであればエフェクトの順番を柔軟に制御することができるようになります。(今回の例ではPostProcessEvent.BeforeStackを指定して既存のエフェクトより前段でエフェクトが適用するようにしています。)

オリジナルのエフェクトが作れるようになると、さらにオリジナリティあふれた絵作りや演出が可能になるので、この記事が少しでも参考になれば幸いです。🌱


参考


この記事が気に入ったらサポートをしてみませんか?