【Unity】【シェーダ】より低負荷なエッジ抽出方法(4点サンプリング法)

f:id:halya_11:20180513164741p:plain

以前ソーベルフィルタやプレヴィットフィルタによるエッジの抽出方法を紹介しました(関連の節からリンクしてあります)。
これらの方法はテクスチャサンプリング数が多い(8回)という欠点があります。

そこで、より低負荷なエッジ抽出方法を紹介します。

下記の記事で使っているものと同様の手法となります。

light11.hatenadiary.com

名前がついてるのかよくわからないのでこの記事では4点サンプリング法と呼ぶことにします。

考え方

考え方はシンプルです。

まずエッジかどうかを判定したいピクセルの近隣の4ピクセルをサンプリングします。
そしてそれらの対角線上にあるピクセル同士の差を求め、差が大きい部分をエッジであるとみなします。

f:id:halya_11:20180513164359p:plain

シェーダ

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

Shader "Outline_Simple"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _OutlineThick ("Outline Thick", float) = 1.0
        _OutlineThreshold ("Outline Threshold", float) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        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;
            float4 _MainTex_TexelSize;
            float _OutlineThick;
            float _OutlineThreshold;
            
            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
            {
                // 近隣のテクスチャ色をサンプリング
                half diffU = _MainTex_TexelSize.x * _OutlineThick;
                half diffV = _MainTex_TexelSize.y * _OutlineThick;
                half3 col00 = tex2D(_MainTex, i.uv + half2(-diffU, -diffV));
                half3 col10 = tex2D(_MainTex, i.uv + half2(diffU, -diffV));
                half3 col01 = tex2D(_MainTex, i.uv + half2(-diffU, diffV));
                half3 col11 = tex2D(_MainTex, i.uv + half2(diffU, diffV));
                half3 diff00_11 = col00 - col11;
                half3 diff10_01 = col10 - col01;

                // 対角線の色同士を比較して差分が多い部分をアウトラインとみなす
                half3 outlineValue = diff00_11 * diff00_11 + diff10_01 * diff10_01;
                return half4(outlineValue - _OutlineThreshold, 1);
            }
            ENDCG
        }
    }
}

説明はコメントに記載しています。
色の差分を計算した後に二乗するのはソーベルフィルタのときと同じ考え方です。

またサンプリングする場所については下記のようにしてもいいかもしれません。

half3 col00 = tex2D(_MainTex, i.uv + half2(-diffU, -diffV));
half3 col10 = tex2D(_MainTex, i.uv + half2(diffU, -diffV));
half3 col01 = tex2D(_MainTex, i.uv + half2(-diffU, diffV));
half3 col11 = tex2D(_MainTex, i.uv + half2(diffU, diffV));

きれいにアウトラインが出るほうにすればいいと思います。

結果

下のテクスチャの輪郭を抽出してみます。

f:id:halya_11:20180513160611p:plain

作ったシェーダを適用すると、

f:id:halya_11:20180513164741p:plain

輪郭が抽出されました。

関連

light11.hatenadiary.com

light11.hatenadiary.com