【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値 を変更することでボヤァっと、ガウスが効く様になります。