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

f:id:halya_11:20180520163528p:plain

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

実装方法

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

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

参考

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