【Unity】【シェーダテクニック】テクスチャを円形にサンプリングする

Unityのシェーダでテクスチャを円形にサンプリングする方法です。

Unity2019.2.6

はじめに

UnityのPostProcessingのソースコードを見ていたら、テクスチャを円形にサンプリングするためのカーネルを見つけました。

github.com

この記事ではこれを使ってテクスチャを円形にサンプリングしてみます。

また今回はポストエフェクトとして実装しますが、C#ソースコードは省略します。
この辺りは以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

シェーダ

それではシェーダを書いてみます。

Shader "PostEffect"
{
    Properties{
        _MainTex("Texture", 2D) = "white" {}
        _Scale("Scale", float) = 1.0
    }
    SubShader
    {
        Cull Off
        ZTest Always
        ZWrite Off

        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;
            float2 _MainTex_TexelSize;
            float _Scale;

            // rings = 2
            // points per ring = 5
            static const int SAMPLE_COUNT = 16;
            static const float2 DISK_KERNEL[SAMPLE_COUNT] = {
                float2(0,0),
                float2(0.54545456,0),
                float2(0.16855472,0.5187581),
                float2(-0.44128203,0.3206101),
                float2(-0.44128197,-0.3206102),
                float2(0.1685548,-0.5187581),
                float2(1,0),
                float2(0.809017,0.58778524),
                float2(0.30901697,0.95105654),
                float2(-0.30901703,0.9510565),
                float2(-0.80901706,0.5877852),
                float2(-1,0),
                float2(-0.80901694,-0.58778536),
                float2(-0.30901664,-0.9510566),
                float2(0.30901712,-0.9510565),
                float2(0.80901694,-0.5877853),
            };

            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
            {
                float4 col = 0;
                for (int j = 0; j < SAMPLE_COUNT; j++) {
                    // カーネルに応じてサンプリングする位置をずらす
                    float2 uv = i.uv + DISK_KERNEL[j] * _MainTex_TexelSize.xy * _Scale;
                    col += tex2D(_MainTex, uv);
                }
                // 最後にサンプリング数で割る
                col.rgb /= SAMPLE_COUNT;
                col.a = 1;
                return col;
            }
            ENDCG
        }
    }
}

実装自体はシンプルです。
カーネルの値に応じてテクスチャをサンプリングする位置を変えています。

解像度の影響を受けないようにする

さて前節の実装では_MainTex_TexelSizeを使っているため、解像度を変えると見え方が変わってしまいます。
マルチ解像度に対応するにはこれでは不適切なので、解像度の影響を受けないようにソースコードを一部変更します。

fixed4 frag(v2f i) : SV_Target
{
    // 解像度が違っても同じ見え方にする
    float2 uvScale = _Scale / 1000;
    uvScale.y *= _MainTex_TexelSize.y / _MainTex_TexelSize.x;
    float4 col = 0;
    for (int j = 0; j < SAMPLE_COUNT; j++) {
        float2 uv = i.uv + DISK_KERNEL[j] * uvScale;
        col += tex2D(_MainTex, uv);
    }
    col.rgb /= SAMPLE_COUNT;
    col.a = 1;
    return col;
}

これでどのような解像度でも同じ見え方になりました。

レンダリング結果

それではこのポストエフェクトを以下の画像に適用してみます。

f:id:halya_11:20191022123621p:plain

適用結果は以下のようになります。

f:id:halya_11:20191022123649p:plain

アニメーションさせるとこんな感じです。

f:id:halya_11:20191022123710g:plain

もっと高精度なカーネルもある

UnityのPostprocessingにはもっとサンプリング数が多くて精度の高いカーネルも用意されています。

サンプリング数:22回のもの
github.com

サンプリング数:43回のもの github.com

サンプリング数:71回のもの github.com

処理負荷と品質を天秤に掛けつつ選択するのがよさそうです。

関連

light11.hatenadiary.com#### 参考 github.com