知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】Bloomシェーダー #106

前回の成果

3Dモデルを液体のようにするシェーダーについて学んだ。

soramamenatan.hatenablog.com


今回やること

以下のサイト様を参考に、Bloomシェーダーを実装します。

www.shibuya24.info

事前準備

Scene上にSpriteRendererを配置します。

f:id:soramamenatan:20210605182519p:plain

そして、MainCameraに今回使用するScriptをアタッチして事前準備完了となります。

f:id:soramamenatan:20210605182529p:plain

ソースコード

Script

using UnityEngine;

[ExecuteInEditMode]
public class BloomTexture : MonoBehaviour {
    [SerializeField]
    private Shader _shader;
    // 全体の強度
    [SerializeField, Range(0, 1f)]
    public float _strength = 0.3f;
    // ブラーの強度
    [SerializeField, Range(1, 64)]
    public int _blur = 20;
    // 明るさのしきい値
    [SerializeField, Range(0, 1f)]
    public float _threshold = 0.3f;
    // RenderTextureサイズの分母
    [SerializeField, Range(1,12)]
    public int _ratio = 1;

    [SerializeField, Range(1f, 10f)]
    private float _offset = 1f;

    private Material _material;
    private float[] _weights = new float[10];

    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (_material == null) {
            _material = new Material(_shader);
            _material.hideFlags = HideFlags.DontSave;
        }
        _OnRenderImage(src, dest);
    }

    private void _OnRenderImage (RenderTexture src, RenderTexture dest)
    {
        int renderTextureX = src.width / _ratio;
        int renderTextureY = src.height / _ratio;
        RenderTexture tmp  = CreateRenderTexture(renderTextureX, renderTextureY);
        RenderTexture tmp2 = CreateRenderTexture(renderTextureX, renderTextureY);

        // Bloom
        _material.SetFloat ("_Strength", _strength);
        _material.SetFloat ("_Threshold", _threshold);
        _material.SetFloat ("_Blur", _blur);
        _material.SetTexture ("_Tmp", tmp);
        Graphics.Blit (src, tmp, _material, 0);

        // ガウシアンブラー
        UpdateWeights();
        _material.SetFloatArray("_Weights", _weights);
        float x = _offset / tmp2.width;
        float y = _offset / tmp2.height;
        _material.SetVector("_Offset", new Vector4(x, 0, 0, 0));
        Graphics.Blit(src, tmp2, _material, 1);
        _material.SetVector("_Offset", new Vector4(0, y, 0, 0));
        Graphics.Blit(tmp2, dest, _material, 1);

        RenderTexture.ReleaseTemporary(tmp);
        RenderTexture.ReleaseTemporary(tmp2);
    }

    /// <summary>
    /// RenderTextureの生成
    /// </summary>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <returns></returns>
    private RenderTexture CreateRenderTexture(int width, int height)
    {
        RenderTexture renderTexture = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32);
        renderTexture.filterMode = FilterMode.Bilinear;
        return renderTexture;
    }

    /// <summary>
    /// 重みの計算
    /// </summary>
    private void UpdateWeights() {
        float total = 0;
        float d = _blur * _blur * 0.01f;
        for (int i = 0; i < _weights.Length; i++) {
            float x = 1.0f + i * 2f;
            float w = Mathf.Exp(-0.5f * (x * x) / d);
            _weights[i] = w;
            // xとyがあるので2倍
            if (i > 0) {
                w *= 2.0f;
            }
            total += w;
        }
        // 正規化
        for (int i = 0; i < _weights.Length; i++) {
            _weights[i] /= total;
        }
    }
}

シェーダー

Shader "Unlit/BloomTexture" {
    Properties {
        _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader {
        Cull Off
        ZWrite Off
        ZTest Always

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment fragBright
            ENDCG
        }

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment fragGauss
            ENDCG
        }

        CGINCLUDE
        #include "UnityCG.cginc"
        sampler2D _MainTex;
        sampler2D _Tmp;
        float _Strength;
        float _Blur;
        float _Threshold;
        half4 _Offset;
        static const int samplingCount = 10;
        half _Weights[samplingCount];

        fixed4 fragBright (v2f_img i) : SV_Target {
            fixed4 col = tex2D(_MainTex, i.uv);
            // ピクセルの明るさ
            float bright = (col.r + col.g + col.b) / 3;
            // しきい値によって明るさを決める
            float tmp = step(_Threshold, bright);
            return col * tmp * _Strength;
        }

        fixed4 fragGauss (v2f_img i) : SV_Target {
            fixed4 col = 0;
            // メモリサイズを大きくする代わりに高速にする
            [unroll]
            // 左右へのサンプリング
            for (int j = samplingCount - 1; j > 0; j--) {
                col += tex2D(_Tmp, i.uv - (_Offset.xy * j)) * _Weights[j];
            }

            [unroll]
            // 上下へのサンプリング
            for (int j = 0; j < samplingCount; j++) {
                col += tex2D(_Tmp, i.uv + (_Offset.xy * j)) * _Weights[j];
            }
            return col + tex2D(_MainTex, i.uv);
        }

        ENDCG
    }
}


Bloom

Bloomとは

ブルームとは、光源から光があふれ出るようなエフェクトのことを指します。ゲームでは光源は数えきれないほど存在します。太陽の光、反射するミラーやガラス、スポットライトやサーチライトといったオブジェクトの光だけでなく、シューティングゲーム弾幕や、スキルエフェクトなども光源として扱われます。

グラフィック設定のブルームとは | ゲーミングPCなう:より引用

とのことです。
このエフェクトを使用することで、オブジェクトにライティングを付与させてリッチな表現にすることができます。

Bloom適応前

f:id:soramamenatan:20210605183030j:plain

Bloom - Unity マニュアル:より引用

Bloom適応後

f:id:soramamenatan:20210605183037j:plain Bloom - Unity マニュアル:より引用

BloomはPostProcessVolumeコンポーネントから追加することもできます。

光らせる箇所の取得

フラグメントシェーダーでテクセルの色を取得し、スクリプトで渡したしきい値とstep()します。
そうすることで、しきい値以上の明るさを箇所を2Pass目に渡して処理することができます。

// ピクセルの明るさ
float bright = (col.r + col.g + col.b) / 3;
// しきい値によって明るさを決める
float tmp = step(_Threshold, bright);
光らせる箇所

しきい値が0.8の時の2Pass目にわたす箇所になります。
2Pass目で元画像に合成するため、黒くしておくことで計算を楽にできるようにしています。

f:id:soramamenatan:20210605191123p:plain


ブラー処理

1Pass目で取得した箇所にブラー処理をかけて、元の画像に加算合成します。

fixed4 col = 0;
// メモリサイズを大きくする代わりに高速にする
[unroll]
// 左右へのサンプリング
for (int j = samplingCount - 1; j > 0; j--) {
    col += tex2D(_Tmp, i.uv - (_Offset.xy * j)) * _Weights[j];
}

[unroll]
// 上下へのサンプリング
for (int j = 0; j < samplingCount; j++) {
    col += tex2D(_Tmp, i.uv + (_Offset.xy * j)) * _Weights[j];
}
return col + tex2D(_MainTex, i.uv);

今回は以前勉強した、ガウシアンブラーを使用しています。
このブラーを使用することで、より軽量な計算でブラー処理を行うことが出来ます。
詳しくは以下を参考にしてください。

soramamenatan.hatenablog.com

ガウシアンブラーの横方向サンプリング結果

f:id:soramamenatan:20210605191836p:plain

ガウシアンブラーの縦方向サンプリング結果

f:id:soramamenatan:20210605191847p:plain


スクリプト

備忘録程度に記載します。

OnRenderImage

カメラのレンダリングが完了した時に呼ばれる関数になります。
第一引数には、入力画像が、第二引数には出力画像が渡されます。

ExecuteInEditMode

UnityをPlayモードにせず、Editモードのままで実行することが出来ます。
ただし、実行できるのは以下の3つのみになります。

  • Update
  • OnGUI
  • OnRenderObject
Playモード

PlayモードはUnityの右三角が押されている状態のことを指します。

f:id:soramamenatan:20210605192401p:plain


結果

SpriteRendererにBloomエフェクトがかかれば成功です。

f:id:soramamenatan:20210605192608p:plain

また、普通のオブジェクトに対しても使用することができます。

f:id:soramamenatan:20210605192621p:plain


今回は以上となります。
ここまでご視聴ありがとうございました。

ユニティちゃんライセンス

この作品はユニティちゃんライセンス条項の元に提供されています