UnityでSNNフィルタのポストエフェクトを掛けてレンダリング結果を絵画風にする方法をまとめました。
Unity2019.2.6
SNNフィルタとは?
SNNフィルタとはSymmetric Nearest Neighborフィルタの略で、
もともとは画像のノイズを目立たなくするために考えられたフィルタのようです。
これを強めに適用することで結果を絵画風にすることができます。
本記事ではこのSNNフィルタのアルゴリズムを解説し、実際にシェーダを書いてみます。
アルゴリズムの解説
いま、以下のように中央のピクセルとその周辺のピクセルを考えます。
これからSNNフィルタにより中央のピクセルに適用する色を求めます。
まず一番左上のピクセルと、それと対角線上にあるピクセルに注目します。
これら二つのピクセルを比べて、より中央の色に近い方を「採用」とします。
同様の考え方で対角線にあるピクセル同士を比較していき、「採用」かどうかを判定していきます。
全部判定した結果、こんな感じになったとします。
あとは「採用」となった色の平均値を求め、それを中央ピクセルの色とします。
アルゴリズムは以上です。
シェーダを書く
それではSNNフィルタのシェーダを書いてみます。
Shader "SymmetricNearestNeighbor" { Properties{ _MainTex("Texture", 2D) = "white" {} _SampleCountFactor("Sample Count Factor", float) = 10 _CellScale("Cell Scale", float) = 1 } 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; float2 _MainTex_TexelSize; int _SampleCountFactor; float _CellScale; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = v.uv; return o; } fixed4 frag(v2f i) : SV_Target { float4 color = float4(0, 0, 0, 1); // 解像度が違っても同じ見え方にする float2 cellSize = _CellScale / 1000; cellSize.y *= _MainTex_TexelSize.y / _MainTex_TexelSize.x; half3 color0 = tex2D(_MainTex, i.uv).rgb; int count = 0; float2 offset = -_SampleCountFactor * cellSize; for (int x = 0; x <= _SampleCountFactor; ++x) { offset.y = -_SampleCountFactor * cellSize.y; for (int y = -_SampleCountFactor; y <= _SampleCountFactor; ++y) { if (x == 0 && y <= 0) { continue; } half3 color1 = tex2D(_MainTex, i.uv + offset).rgb; half3 color2 = tex2D(_MainTex, i.uv - offset).rgb; float3 diff1 = color1 - color0.rgb; float3 diff2 = color2 - color0.rgb; // 中心の色に近いほうを採用 color.rgb += dot(diff1, diff1) < dot(diff2, diff2) ? color1 : color2; count++; // cellSize分だけずらしていく offset.y += cellSize.y; } offset.x += cellSize.x; } color.rgb /= count; return color; } ENDCG } } }
コメントにも書きましたが、前節で説明したように「周辺3ピクセル」のように
範囲を決めてしまうと、解像度によって大きく結果が変わってしまいます。
これはポストエフェクトとして好ましくないので、範囲はUV座標ベースで決定しています。
また、中央の色に「近い」かどうかを明度で判定していますが、ここは輝度の方がいいのかもしれません。
あとガンマ空間なのかリニア空間なのかによっても結果が変わってきそうです。
今回はこのあたりはざっくり、シンプルな実装にしています。
ポストエフェクトとして適用する
次にこのシェーダをポストエフェクトとして適用します。
この方法に関しては以下の記事にまとめていますので、必要に応じて参照してください。
結果
さてそれではこのポストエフェクトを反映してみます。
まず適用前のレンダリング結果は以下の通りです。
適用前
これにSNNフィルタを適用すると以下のようなレンダリング結果となります。
Sample Count Factor = 3
Cell Size = 3
絵画風になったことが確認できました。