🌈

そろそろShaderをやるパート66 ポストエフェクトを自作してみる

2022/03/26に公開

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

デモ

色を乗算するポストエフェクトとモノクロのポストエフェクトを試してみました。

仕組み

OnRenderImageというUnityイベントを利用します。
OnRenderImageはカメラのレンダリングが完了したタイミングで呼び出されます。
カメラのレンダリング結果のピクセルに対して、OnRenderImage内でShaderのエフェクトを適用することでポストエフェクトの自作が可能です。

【参考リンク】:MonoBehaviour.OnRenderImage(RenderTexture,RenderTexture)

C#サンプル

using UnityEngine;

/// <summary>
/// 自作ポストエフェクトを適用する
/// ImageEffectAllowedInSceneViewというアトリビュートを使うことでシーンビューにも反映される
/// </summary>
[ExecuteInEditMode, ImageEffectAllowedInSceneView]
public class CustomColorPostEffect : MonoBehaviour
{
    [SerializeField] private Material colorEffectMaterial;
    
    private enum UsePass
    {
        UsePass1,
        UsePass2
    }

    [SerializeField] private UsePass usePass;  
    
    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        Graphics.Blit(src, dest, colorEffectMaterial,(int)usePass);
    }
}

OnRenderImage(RenderTexture src, RenderTexture dest)の2つの引数はともにRenderTextureとなっています。srcはSource、destはDestinationを意味します。

それぞれ、src = 受け取ったレンダリング結果のイメージ、dest = コピー先のイメージ ということになります。

OnRenderImage内でGraphics.Blitを使用することで受け取ったレンダリング結果のイメージに対してマテリアルの効果を適用し、コピー先のイメージ(すなわち最終的なレンダリング結果)に反映しています。
第4引数にIndexを指定することで利用するShaderのパスを選択可能です。

【参考リンク】:Graphics.Blit

Shaderサンプル

Shader "Custom/SimplePostEffect"
{
    Properties
    {
        //_MainTexを定義しておけば勝手に描画結果が入ってくるらしい
        _MainTex ("Texture", 2D) = "white" {}
        //カラー
        _EffectColor("EffectColor",Color) = (0,0,0,0)
    }
    SubShader
    {
        //パスを跨いで利用できる変数や関数をひとまとめにしておく
        CGINCLUDE
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct v2f
        {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
        };

        sampler2D _MainTex;

        v2f vert(appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            return o;
        }
        ENDCG

        //色を変更するポストエフェクト
        Pass
        {
            CGPROGRAM
            float4 _EffectColor;

            float4 frag(v2f i) : SV_Target
            {
                //描画結果をサンプリング
                float4 renderingColor = tex2D(_MainTex, i.uv);
                return renderingColor * _EffectColor;
            }
            ENDCG
        }

        //モノクロになるポストエフェクト
        Pass
        {
            CGPROGRAM

            float4 frag(v2f i) : SV_Target
            {
                //描画結果をサンプリング
                float4 renderingColor = tex2D(_MainTex, i.uv);
                float monochrome = 0.3 * renderingColor.r + 0.6 * renderingColor.g + 0.1 * renderingColor.b;
                float4 monochromeColor = float4(monochrome.xxx, 1);
                return monochromeColor;
            }
            ENDCG
        }
    }
}

_MainTexを定義することでポストエフェクトをかける前の画像が取得できます。
あとはその画像をサンプリングし、フラグメントシェーダー内で加工してあげればポストエフェクトの完成です。

パスを2つ用意して、エフェクトを切り替えられるようにしています。

デモ2

Shaderを変えれば様々な表現が可能になります。
例えば、ノイズやグリッチのかかったホログラムっぽい表現を適用すると以下のようになります。

【参考リンク】:そろそろShaderをやるパート39 グリッチによるホログラムっぽい表現

モノクロの処理も合わせると以下のような感じです。

参考リンク

Unityでポストプロセス描いてみたい
【Unityシェーダ入門】Unityのポストエフェクトでモノクロ画面を作る

Discussion