【Unity】【シェーダ】光芒(グレア)のポストエフェクトを実装する

f:id:halya_11:20180520163528p:plain

光芒とかスターと呼ばれるポストエフェクトを実装してみます。

はじめに

この記事では光芒のポストエフェクトの実装方法を紹介します。
ポストエフェクトの基礎については以下の記事で紹介していますので、必要に応じて参照してください。

light11.hatenadiary.com

実装方法

基本的な流れはブルームと似ています。

light11.hatenadiary.com

ブルームのぼかし方を変える(四方向にぼかす)と光芒になります。
ぼかし方については下記のスライドを参考にしました。

http://www.daionet.gr.jp/~masa/archives/GDC2003_DSTEAL.ppt

実装

まずスクリプトは次のようになります。

using UnityEngine;

[ExecuteInEditMode]
public class Star : MonoBehaviour
{

    [SerializeField]
    private Material _material;

    [SerializeField, Range(0.0f, 1.0f)]
    private float _threshold = 0.5f;
    [SerializeField, Range(0.5f, 0.95f)]
    private float _attenuation = 0.9f;
    [SerializeField, Range(0.0f, 10.0f)]
    private float _intensity = 1.0f;

    private void OnRenderImage(RenderTexture source, RenderTexture dest)
    {
        if (_material == null){
            Graphics.Blit(source, dest);
            return;
        }

        var paramsId = Shader.PropertyToID("_Params");
        _material.SetFloat("_Threshold", _threshold);
        _material.SetFloat("_Attenuation", _attenuation);
        _material.SetFloat("_Intensity", _intensity);
        var tempRT1 = RenderTexture.GetTemporary(source.width, source.height);
        var tempRT2 = RenderTexture.GetTemporary(source.width, source.height);

        // SourceをDestにコピーしておく
        Graphics.Blit(source, dest);

        // 4方向にスターを作るループ
        for (int i = 0; i < 4; i++)
        {
            // まず明度が高い部分を抽出する
            Graphics.Blit(source, tempRT1, _material, 0);

            var currentSrc = tempRT1;
            var currentTarget = tempRT2;
            var parameters = Vector3.zero;

            // x, yにUV座標のオフセットを代入する
            // (-1, -1), (-1, 1), (1, -1), (1, 1)
            parameters.x = i == 0 || i == 1 ? -1 : 1;
            parameters.y = i == 0 || i == 2 ? -1 : 1;

            // 1方向にぼかしを伸ばしていくループ
            for (int j = 0; j < 4; j++)
            {
                // zに描画回数のindexを代入してマテリアルにセット
                parameters.z = j;
                _material.SetVector(paramsId, parameters);

                // 二つのRenderTextureに交互にBlitして効果を足していく
                Graphics.Blit(currentSrc, currentTarget, _material, 1);
                var tmp = currentSrc;
                currentSrc = currentTarget;
                currentTarget = tmp;
            }

            // Destに加算合成する
            Graphics.Blit(currentSrc, dest, _material, 2);
        }

        // RenderTextureを開放する
        RenderTexture.ReleaseTemporary(tempRT1);
        RenderTexture.ReleaseTemporary(tempRT2);
    }
}

※最適化されてません

次にシェーダは次のようにします。

Shader "Star"
{
    Properties{
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off
        ZTest Always
        ZWrite Off

        Tags { "RenderType"="Opaque" }

        // 0: 明度を抽出するパス
        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Threshold;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
                half brightness = max(col.r, max(col.g, col.b));
                half contribution = max(0, brightness - _Threshold);
                contribution /= max(brightness, 0.00001);
                return col * contribution;
            }
            ENDCG
        }

        //1: スターを作るパス
        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;
                half2 uvOffset : TEXCOORD1;
                half pathFactor : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _MainTex_TexelSize;
            // x: offsetU, y: offsetY, z: pathIndex
            int3 _Params;
            float _Attenuation;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.pathFactor = pow(4, _Params.z);
                o.uvOffset = half2(_Params.x, _Params.y) * _MainTex_TexelSize.xy * o.pathFactor;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                half4 col = half4(0, 0, 0, 1);
                
                half2 uv = i.uv;
                for (int j = 0; j < 4; j++) {
                    col.rgb += tex2D(_MainTex, uv) * pow(_Attenuation, j * i.pathFactor);
                    uv += i.uvOffset;
                }
                
                return col;
            }
            ENDCG
        }

        // 2: 加算合成パス
        Pass
        {
            Blend One One
            ColorMask RGB
        
            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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Intensity;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv) * _Intensity;
            }
            ENDCG
        }
    }
}

「スターを作るパス」としているのが、ぼかしている部分です。
前節のスライドの式と見比べてみてください。

結果

結果はこのようになります。

f:id:halya_11:20180520163528p:plain

縮小バッファで処理する

なかなか処理負荷が高そうなので、
縮小バッファを使った場合の見え方を検証してみます。

レンダーテクスチャのサイズを縦横それぞれ1/2にします。

var tempRT1 = RenderTexture.GetTemporary(source.width / 2, source.height / 2);
var tempRT2 = RenderTexture.GetTemporary(source.width / 2, source.height / 2);

レンダリング結果は次のようになりました。

f:id:halya_11:20180520170605p:plain (前節の結果からパラメータは調整してます)

全然いけそうです。 縦横4分の1にしてもいけるかも?

関連

light11.hatenadiary.com

light11.hatenadiary.com

参考

http://www.daionet.gr.jp/~masa/archives/GDC2003_DSTEAL.ppt