Unityのシェーダでテクスチャを円形にサンプリングする方法です。
Unity2019.2.6
はじめに
UnityのPostProcessingのソースコードを見ていたら、テクスチャを円形にサンプリングするためのカーネルを見つけました。
この記事ではこれを使ってテクスチャを円形にサンプリングしてみます。
また今回はポストエフェクトとして実装しますが、C#のソースコードは省略します。
この辺りは以下の記事にまとめていますので、必要に応じて参照してください。
シェーダ
それではシェーダを書いてみます。
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; }
これでどのような解像度でも同じ見え方になりました。
レンダリング結果
それではこのポストエフェクトを以下の画像に適用してみます。
適用結果は以下のようになります。
アニメーションさせるとこんな感じです。
もっと高精度なカーネルもある
UnityのPostprocessingにはもっとサンプリング数が多くて精度の高いカーネルも用意されています。
サンプリング数:22回のもの
github.com
サンプリング数:43回のもの github.com
サンプリング数:71回のもの github.com
処理負荷と品質を天秤に掛けつつ選択するのがよさそうです。