Help us understand the problem. What is going on with this article?

【Unity2019 LWRP】ポストエフェクトを自作してゲームから利用する

環境

Unity2019.1.0f2
LWRP - Version 5.7.2

はじめに

ポストエフェクトを自作する場合、従来のUnityではOnRenderImage()を実装したMonoBehaviourスクリプトをカメラにアタッチすることでこれを実装することができました。

ExampleClass.cs
using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour {
    public Material mat;
    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        Graphics.Blit(src, dest, mat); // ポストエフェクト
    }
}

しかし、この方法はLWRP環境では使うことができません

LWRPのようなSRP環境ではレンダリングパイプラインに自作レンダリングパスを追加することで、ポストエフェクトを実装します。
今回はLWRP環境でポストエフェクトを実装するまでの手順を紹介します。

ポストエフェクトの実装例

Unity1Weekというオンラインイベントで制作したToTheRightというゲームでは2つのポストエフェクトを実装しました。

・死亡時に画面の色を反転させるポストエフェクト
色反転2.gif

・画面を歪ませるグリッチエフェクト
gif_animation_005.gif

今回はこの二つのポストエフェクトの実装について解説します。

ポストエフェクトの実装の流れ

ポストエフェクトを実装するには以下を作成する必要があります。
・CustomForwardRendererアセット
・ScriptableRendererFeatureクラス (パラメータやレンダーパスの追加はここで行う)
・ScriptableRendererPassクラス (レンダーパスの実装。実際の描画処理はここで実装)

処理の流れとしては以下のようになります。
LWRPSettingsへ登録したForwardRendererDataが実行される
-> ScriptableRendererFeatureが実行される
-> ScriptableRendererPassが実行され、描画が行われる。

ポストエフェクトの作成手順

1. CustomForwardRendererアセットの作成

メニューから Rendering > Lightweight Pipeline > Forward Rendererを選択し、CustomForwardRendererを作成します。
image.png

2. Forward Rendererアセットを登録

LWRP SettingsアセットのRendererType をCustomに設定。
Dataの部分に先ほど作成したForward Rendererアセットを登録します。
image.png

これでCustomForwardRendererが実行されるようになります。

補足 : LWRP Settingsアセットについて

Project SettingsのScriptable Render Pipeline Settingsの部分に登録しているアセットがLWRP Settingsアセットになります。
image.png

3. RendererFeatrureスクリプトの作成

ScriptableRendererFeatureの派生クラスを作成します。

GameRendererFeature.cs
using UnityEngine;
using UnityEngine.Rendering.LWRP;

public class GameRendererFeature : ScriptableRendererFeature
{
    [SerializeField] private bool hoge = true; // テキトーな変数
    [SerializeField] private int fuga = 123; // テキトーな変数

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
    }

    public override void Create()
    {
    }
}

4.RendererFeatureの登録

CustomForwardRendererにRendererFeatureを登録し、RendererFeatureが実行されるようにします。

+ボタンをクリックし、RendererFeatureを登録します。
image.png

登録すると以下のようになります。
image.png

Projectビューを見るとCustomForwardRendererの中にRendererFeatureができています。
image.png

RendererFeatureは、通常のScriptableObjectと同じようにInspectorタブからシリアライズ変数の値を編集できます。
レンダリングに関係するパラメータはRendererFeatureの中に定義しておくと良いでしょう。
image.png

ToTheRightで実装したポストエフェクト

1. RendererFeatureクラスの実装

今回のゲームToTheRightでは、色反転Passとグリッチ効果Passの二つを作成しています。
RendererFeatureクラスのコードは以下のようになりました。

GameRendererFeature.cs
using UnityEngine;
using UnityEngine.Rendering.LWRP;

public sealed class GameRendererFeature : ScriptableRendererFeature
{
    [field: SerializeField] public bool ReverseActive { get; set; } = false;
    [field: SerializeField] public bool GlitchActive { get; set; } = false;
    [field: SerializeField, Range(0, 1)] public float GlitchWeight { get; set; } = 1f;
    [field: SerializeField, Range(0, 1)] public float NoiseWeight { get; set; } = 1f;
    private ReverseColorRendererPass reverseColorPass = null;
    private GlitchEffectRendererPass glitchPass = null;

    [SerializeField] private Material reverseColorMaterial;

    // レンダーパスの作成を行う
    public override void Create()
    {
        reverseColorPass = reverseColorPass ?? new ReverseColorRendererPass();
        glitchPass = glitchPass ?? new GlitchEffectRendererPass();
    }

    // レンダーパスの追加を行う
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        // 色反転 ポストエフェクトPass 追加
        reverseColorPass.SetRenderTarget(renderer.cameraColorTarget);
        reverseColorPass.Active = ReverseActive;
        renderer.EnqueuePass(reverseColorPass);

        // グリッチ ポストエフェクトPass 追加
        glitchPass.SetRenderTarget(renderer.cameraColorTarget);
        glitchPass.Active = GlitchActive;
        glitchPass.GlitchWeight = GlitchWeight;
        renderer.EnqueuePass(glitchPass);
    }
}

2. 色反転エフェクトの実装

色を反転するShader

ReverseColor.shader
Shader "Hidden/ReverseColor"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            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;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // just invert the colors
                col.rgb = 1 - col.rgb;
                return col;
            }
            ENDCG
        }
    }
}

色の反転Passの実装

ReverseColorRendererPass.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.LWRP;

public sealed class ReverseColorRendererPass : ScriptableRenderPass
{
    private const string Tag = nameof(ReverseColorRendererPass);

    private RenderTargetIdentifier currentTarget;

    public bool Active { get; set; }

    public ReverseColorRendererPass()
    {
        renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
    }

    public void SetRenderTarget(RenderTargetIdentifier target)
    {
        currentTarget = target;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!Active) { return; }

        var shader = Shader.Find("Hidden/ReverseColor"); // 色を反転するシェーダー取得
        if (!shader) { return; }

        var material = new Material(shader);
        var commandBuffer = CommandBufferPool.Get(Tag);
        var renderTextureId = Shader.PropertyToID("_SampleLWRPScriptableRenderer");
        var cameraData = renderingData.cameraData;
        var w = cameraData.camera.scaledPixelWidth;
        var h = cameraData.camera.scaledPixelHeight;
        int shaderPass = 0;

        commandBuffer.GetTemporaryRT(renderTextureId, w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
        commandBuffer.Blit(currentTarget, renderTextureId);
        commandBuffer.Blit(renderTextureId, currentTarget, material, shaderPass);

        context.ExecuteCommandBuffer(commandBuffer);
        CommandBufferPool.Release(commandBuffer);
    }
}

3. グリッチエフェクトの実装

グリッチエフェクトのシェーダー

今回のグリッチ効果は以下のリンク先のGlitchShader.shaderを利用させていただきました。
https://github.com/staffantan/unityglitch

GlitchShader.shader
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// This work is licensed under a Creative Commons Attribution 3.0 Unported License.
// http://creativecommons.org/licenses/by/3.0/deed.en_GB
//
// You are free:
//
// to copy, distribute, display, and perform the work
// to make derivative works
// to make commercial use of the work


Shader "Hidden/GlitchShader" {
    Properties{
        _MainTex("Base (RGB)", 2D) = "white" {}
        _DispTex("Base (RGB)", 2D) = "bump" {}
        _Intensity("Glitch Intensity", Range(0.1, 1.0)) = 1
        _ColorIntensity("Color Bleed Intensity", Range(0.1, 1.0)) = 0.2
    }

        SubShader{
            Pass {
                ZTest Always Cull Off ZWrite Off
                Fog { Mode off }

                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest 

                #include "UnityCG.cginc"

                uniform sampler2D _MainTex;
                uniform sampler2D _DispTex;
                float _Intensity;
                float _ColorIntensity;
                float _GlitchWeight;

                fixed4 direction;

                float filterRadius;
                float flip_up, flip_down;
                float displace;
                float scale;

                struct v2f {
                    float4 pos : POSITION;
                    float2 uv : TEXCOORD0;
                };

                v2f vert(appdata_img v)
                {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.uv = v.texcoord.xy;

                    return o;
                }

                half4 frag(v2f i) : COLOR
                {
                    half4 normal = tex2D(_DispTex, i.uv.xy * scale);
                    fixed2 uv = i.uv;

                    i.uv.y -= (1 - (i.uv.y + flip_up)) * step(i.uv.y, flip_up) + (1 - (i.uv.y - flip_down)) * step(flip_down, i.uv.y);

                    i.uv.x += (normal.x - 0.5) * displace * _Intensity;

                    i.uv = lerp(uv, i.uv, _GlitchWeight);

#define DirectionMulti 0.005
                    half4 baseColor = tex2D(_MainTex, i.uv.xy);
                    half4 color = baseColor;
                    half4 redcolor = tex2D(_MainTex, i.uv.xy + direction.xy * DirectionMulti * filterRadius * _ColorIntensity);
                    half4 greencolor = tex2D(_MainTex,  i.uv.xy - direction.xy * DirectionMulti * filterRadius * _ColorIntensity);

                    color += fixed4(redcolor.r, redcolor.b, redcolor.g, 1) *  step(filterRadius, -0.001);
                    color *= 1 - 0.5 * step(filterRadius, -0.001);

                    color += fixed4(greencolor.g, greencolor.b, greencolor.r, 1) *  step(0.001, filterRadius);
                    color *= 1 - 0.5 * step(0.001, filterRadius);

                    //return color;
                    return baseColor + color * _GlitchWeight;
                }
                ENDCG
            }
        }

            Fallback off

}

グリッチエフェクトPassの実装

GlitchEffectRendererPass.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.LWRP;

public sealed class GlitchEffectRendererPass : ScriptableRenderPass
{
    private const string Tag = nameof(GlitchEffectRendererPass);

    private RenderTargetIdentifier currentTarget;

    public bool Active { get; set; } 
    public float GlitchWeight { get; set; }
    public Material Material { get; set; }

    private GlitchEffect glitchEffect = null;

    public GlitchEffectRendererPass()
    {
        renderPassEvent = RenderPassEvent.AfterRendering;
    }

    public void SetRenderTarget(RenderTargetIdentifier target)
    {
        currentTarget = target;
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!Active) { return; }

        if (glitchEffect == null || glitchEffect.gameObject == null)
        {
            glitchEffect = Camera.main.gameObject.GetComponent<GlitchEffect>();
        }
        //glitchEffect = glitchEffect ?? Camera.main.gameObject.GetComponent<GlitchEffect>();
        if (glitchEffect == null) { return; }

        glitchEffect.Execute();
        if (glitchEffect.Material == null) { return; }

        var commandBuffer = CommandBufferPool.Get(Tag);
        var renderTextureId = Shader.PropertyToID("_SampleLWRPScriptableRenderer");
        var cameraData = renderingData.cameraData;
        var w = cameraData.camera.scaledPixelWidth;
        var h = cameraData.camera.scaledPixelHeight;
        int shaderPass = 0;

        glitchEffect.Material.SetFloat("_GlitchWeight", GlitchWeight);

        commandBuffer.GetTemporaryRT(renderTextureId, w, h, 0, FilterMode.Point, RenderTextureFormat.Default);
        commandBuffer.Blit(currentTarget, renderTextureId);
        commandBuffer.Blit(renderTextureId, currentTarget, glitchEffect.Material, shaderPass);

        context.ExecuteCommandBuffer(commandBuffer);
        CommandBufferPool.Release(commandBuffer);
    }
}

ポストエフェクトの制御について

今回はGameRendererFeatureで定義したパラメータを変更することでポストエフェクトを制御しました。
・色反転エフェクトのON/OFF
・グリッチエフェクトのかかり具合

ポストエフェクトのON/OFF

色反転エフェクトのON/OFFを切り替えたい場合はReverseActiveの値を変更します。

色反転エフェクトのON/OFF
    /// <summary>
    /// 色を反転
    /// </summary>
    public void SetReverseColor(bool active)
    {
        if (gameRendererFeature == null) return;
        gameRendererFeature.ReverseActive = active;
    }

ReverseActiveはGameRendererFeature.cs内にてReverseColorRendererPassに値をそのまま渡しています。

GameRendererFeature.cs
        // 色反転 ポストエフェクトPass 追加
        reverseColorPass.SetRenderTarget(renderer.cameraColorTarget);
        reverseColorPass.Active = ReverseActive;
        renderer.EnqueuePass(reverseColorPass);

ReverseColorRendererPassではActiveの数値を見て、falseの場合にスキップさせるようにしています。

ReverseColorRendererPass.cs
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        if (!Active) { return; }

グリッチのかかり具合変更

グリッチのかかり具合を変更したい場合はGlitchWeightの数値を変更します。

グリッチエフェクトの重み変更
gameRendererFeature.GlitchWeight = scale * postEffectConfig.EnemyDeathGlitchCurve.Evaluate(time);

GameRendererFeatureのGlitchWeightをGlitchEffectRendererPassへ値を流しています。

GameRendererFeature.cs
        // グリッチPass 追加
        glitchPass.SetRenderTarget(renderer.cameraColorTarget);
        glitchPass.Active = GlitchActive;
        glitchPass.GlitchWeight = GlitchWeight;
        renderer.EnqueuePass(glitchPass);

GlitchEffectRendererPassでは GlitchWeight の値をシェーダーへ流しています。

GlitchEffectRendererPass.cs
        glitchEffect.Material.SetFloat("_GlitchWeight", GlitchWeight);

シェーダー内では _GlitchWeight はディストーションをかけたUVと元のUVの線形補間する際の数値として利用しています。
_GlitchWeight=0.0なら歪みなしUV、_GlitchWeight=1.0の場合は歪みありUVになります。

GlitchShader.shader
    fixed2 uv = i.uv;
    i.uv.y -= (1 - (i.uv.y + flip_up)) * step(i.uv.y, flip_up) + (1 - (i.uv.y - flip_down)) * step(flip_down, i.uv.y);
    i.uv.x += (normal.x - 0.5) * displace * _Intensity;
    i.uv = lerp(uv, i.uv, _GlitchWeight);
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした