【Unity】ガウス処理

DEVELOP, Unity

画面全体にシンプルなガウス処理を適用してみます。

目次

確認バージョン

2019.2.10f1

ファイル構成

  • ImageEffectGaussian
    • Gaussian.shader
    • Gaussian.mat
    • ImageEffectGaussian.cs
    • ImageEffectGaussianController.cs

それぞれのソースコードはこんな感じです。

Gaussian.shader

Shader "Custom/Gaussian"{   
	Properties {   
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("_MainTex", 2D) = "white" { }
	}   
	SubShader {
 	   
		Tags { "RenderType"="Opaque" }
 		
		pass {   
			Cull Off
			Lighting Off
			ZWrite Off
			Blend SrcAlpha  OneMinusSrcAlpha   
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
 			
			#include "UnityCG.cginc"
 			   
			fixed4 _Color;
			float4 _MainTex_ST;
			sampler2D _MainTex;   
			float  _Width;      // テクスチャ横幅
            float  _Height;     // テクスチャ縦幅
            float  _Weight[8];  // ガウスぼかし
 			
			struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
 
            struct v2f {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
                float2 uv1 : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
                float2 uv3 : TEXCOORD2;
                float2 uv4 : TEXCOORD3;
                float2 uv5 : TEXCOORD4;
                float2 uv6 : TEXCOORD5;
                float2 uv7 : TEXCOORD6;
                float2 uv8 : TEXCOORD7;
            };
 			
			v2f vert (appdata_t v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                float2 uv = TRANSFORM_TEX (v.texcoord, _MainTex);
 
                o.uv1 = uv + float2(-1.0f  / _Width, 0);
                o.uv2 = uv + float2(-3.0f  / _Width, 0);
                o.uv3 = uv + float2(-5.0f  / _Width, 0);
                o.uv4 = uv + float2(-7.0f  / _Width, 0);
                o.uv5 = uv + float2(-9.0f  / _Width, 0);
                o.uv6 = uv + float2(-11.0f / _Width, 0);
                o.uv7 = uv + float2(-13.0f / _Width, 0);
                o.uv8 = uv + float2(-15.0f / _Width, 0);
 
                o.color = v.color;
                return o;
            }
 
            float4 frag (v2f i) : COLOR
            {
                float4 base;
                float2 offset = float2(16.0f / _Width, 0);
                base  = ( tex2D (_MainTex, i.uv1) + tex2D (_MainTex, i.uv8 + offset) ) * _Weight[0];
                base += ( tex2D (_MainTex, i.uv2) + tex2D (_MainTex, i.uv7 + offset) ) * _Weight[1];
                base += ( tex2D (_MainTex, i.uv3) + tex2D (_MainTex, i.uv6 + offset) ) * _Weight[2];
                base += ( tex2D (_MainTex, i.uv4) + tex2D (_MainTex, i.uv5 + offset) ) * _Weight[3];
                base += ( tex2D (_MainTex, i.uv5) + tex2D (_MainTex, i.uv4 + offset) ) * _Weight[4];
                base += ( tex2D (_MainTex, i.uv6) + tex2D (_MainTex, i.uv3 + offset) ) * _Weight[5];
                base += ( tex2D (_MainTex, i.uv7) + tex2D (_MainTex, i.uv2 + offset) ) * _Weight[6];
                base += ( tex2D (_MainTex, i.uv8) + tex2D (_MainTex, i.uv1 + offset) ) * _Weight[7];
 
                base *= i.color;
                base.a = i.color.a;
                return base;
            }
            ENDCG
        }
         
        GrabPass{}
 
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
 
            #include "UnityCG.cginc"
            #include "UnityUI.cginc"
 
            sampler2D _GrabTexture;
            float4 _GrabTexture_ST;
            float4 _GrabTexture_TexelSize;
             
            float  _Width;
            float  _Height;
            float  _Weight[8];
            int    _SnapShot;
 
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
 
            struct v2f {
                float4 pos : SV_POSITION;
                float4 color : COLOR;
                float2 uv1 : TEXCOORD0;
                float2 uv2 : TEXCOORD1;
                float2 uv3 : TEXCOORD2;
                float2 uv4 : TEXCOORD3;
                float2 uv5 : TEXCOORD4;
                float2 uv6 : TEXCOORD5;
                float2 uv7 : TEXCOORD6;
                float2 uv8 : TEXCOORD7;
            };
 
            v2f vert (appdata_t v)
            {
                v2f o;
//                o.pos = UnityObjectToClipPos(v.vertex);
				o.pos = ComputeGrabScreenPos(v.vertex);	// 天地がひっくり返るのでこちら。
 
                float2 uv = TRANSFORM_TEX (v.texcoord, _GrabTexture);
 
                o.uv1 = uv + float2(0, -1.0f  / _Height);
                o.uv2 = uv + float2(0, -3.0f  / _Height);
                o.uv3 = uv + float2(0, -5.0f  / _Height);
                o.uv4 = uv + float2(0, -7.0f  / _Height);
                o.uv5 = uv + float2(0, -9.0f  / _Height);
                o.uv6 = uv + float2(0, -11.0f / _Height);
                o.uv7 = uv + float2(0, -13.0f / _Height);
                o.uv8 = uv + float2(0, -15.0f / _Height);
 
                o.color = v.color;
 
                return o;
            }
 
            float4 frag (v2f i) : COLOR
            {
                float4 base;
                float2 offset = float2(0, 16.0f / _Height);
                base  = ( tex2D (_GrabTexture, i.uv1) + tex2D (_GrabTexture, i.uv8 + offset) ) * _Weight[0];
                base += ( tex2D (_GrabTexture, i.uv2) + tex2D (_GrabTexture, i.uv7 + offset) ) * _Weight[1];
                base += ( tex2D (_GrabTexture, i.uv3) + tex2D (_GrabTexture, i.uv6 + offset) ) * _Weight[2];
                base += ( tex2D (_GrabTexture, i.uv4) + tex2D (_GrabTexture, i.uv5 + offset) ) * _Weight[3];
                base += ( tex2D (_GrabTexture, i.uv5) + tex2D (_GrabTexture, i.uv4 + offset) ) * _Weight[4];
                base += ( tex2D (_GrabTexture, i.uv6) + tex2D (_GrabTexture, i.uv3 + offset) ) * _Weight[5];
                base += ( tex2D (_GrabTexture, i.uv7) + tex2D (_GrabTexture, i.uv2 + offset) ) * _Weight[6];
                base += ( tex2D (_GrabTexture, i.uv8) + tex2D (_GrabTexture, i.uv1 + offset) ) * _Weight[7];
 
                base *= i.color;
                base.a = i.color.a;
                return base;
            }
            ENDCG
        }
	}
	fallback "Standard"
}

Gaussian.mat

Gaussian.shader を割り当てただけで他は変更なし。

ImageEffectGaussian.cs

using UnityEngine;
 
namespace mira
{
    [RequireComponent(typeof(Camera))]
    public class ImageEffectGaussian : MonoBehaviour
    {
        [SerializeField]
        private Material m_ImageEffect;
        
        public Material imageEffect
        {
            get { return m_ImageEffect; }
        }
        
        private void OnRenderImage(RenderTexture _source, RenderTexture destination)
        {   
            Graphics.Blit(_source, destination, m_ImageEffect);
        }
    }
}

ImageEffectGaussianController.cs

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
 
namespace mira
{
    [RequireComponent(typeof(ImageEffectGaussian))]
    public class ImageEffectGaussianController : MonoBehaviour
    {
        [SerializeField, Range(0f, 20f)]
        private float m_Deviation = 7f;
         
        public float deviation
        {
            get { return m_Deviation; }
            set
            {
                if (m_Deviation != value)
                {
                    m_Deviation = value;
                    UpdateMaterial();
                }
            }
        }
        
        private const int WeightNum = 8;
        private const float deviationMin = 0.1f;
 
        private List<float> GetWeightList( float _stDeviation )
        {
            var stDeviation = Mathf.Max(deviationMin, _stDeviation);
            
            var list = new List<float> ();
  
            // 1次元ガウス関数
            var d = Mathf.Pow (stDeviation, 2);
            var a = 1 / Mathf.Sqrt (2 * Mathf.PI * d);
            for (int i = 0; i < WeightNum; ++i)
            {
                var x = 1f + 2f * i;
                list.Add ( a * Mathf.Exp (-0.5f * Mathf.Pow (x, 2) / d));
            }
 
            // 全体で1になるようにする
            var total = list.Sum();
            for (int i = 0; i < list.Count; ++i) 
            {
                list [i] /= total;
                list [i] *= 0.5f;
            }
             
            return list;
        }
 
        private void OnEnable()
        {
            UpdateMaterial();
        }
 
        private void UpdateMaterial()
        {
            var imageEffectGaussian = GetComponent<ImageEffectGaussian>();
            var imageEffect =  imageEffectGaussian.imageEffect;
            if (imageEffect == null)
            {
                return;
            }
             
            imageEffect.SetFloat ("_Width" , Screen.width);
            imageEffect.SetFloat ("_Height", Screen.height);
            imageEffect.SetFloatArray("_Weight", GetWeightList(deviation) );
             
            if (imageEffectGaussian.enabled != !deviation.IsZero(deviationMin))
            {
                imageEffectGaussian.enabled = !deviation.IsZero(deviationMin);
            }
        }
 
#if UNITY_EDITOR
        private void OnValidate()
        {
            UpdateMaterial();
        }        
#endif
    }
 
    public static class NumExtension
    {
        public static bool IsZero(this float val)
        {
            return Mathf.Abs(val) < Mathf.Epsilon;
        }
 
        public static bool IsZero(this float val, float _epsilon)
        {
            return Mathf.Abs(val) < _epsilon;
        }
    }
}

使い方

  • Camera コンポーネントに ImageEffectGaussian と ImageEffectGaussianController をAddComponent します。
  • Image Effect にGaussian マテリアルを割り当てます。

これでスライドバーの Deviation値 を変更することでボヤァっと、ガウスが効く様になります。

Posted by kazupon