Edited at

UnityでMSDFを使ったShaderによるベクター画像の描画について

Unity 2018.1のプレビュー版機能としてSVGファイルを読み込む事ができますが、シーンに追加するとSpriteとして扱われたりあまり使い勝手が良いとは言えません。(VRCはUnity 2018対応してないし)

ある時Twitterをボケーと眺めていたときにMSDF(Multi-channel signed distance field)なる存在を知りました。

MSDFってなんなんー?という方にざっくりと説明すると、ベクター画像(SVGなど)のデータを通常の画像にエンコードして表示する際にデコードして、ベクター画像のように見せるアルゴリズムで、Multi-channelとのことでRGBチャンネルを使用することでSingle-channel(グレースケール)を使用した際に比べて鋭い角などの再現性が飛躍的に向上しているそうです。

ベクター画像としてデコードするので通常の画像のように拡大してもジャギジャギにならず、ほぼ無限になめらかで必要な画像のサイズも小さいのでビルド容量の削減に繋がります。また、今回はShaderでデコードを行うので高速です。




msdfgenから引用



というわけで早速SVGファイルを通常の画像にエンコードしてみます。releasesのページからコンパイル済みのバイナリを入手できます。

コマンドプロンプトを立ち上げ、実行ファイルがある場所まで移動します。そこで

msdfgen.exe msdf -svg "C:\test.svg" -o msdf.png -size 64 64 -pxrange 4 -autoframe

を実行するとカレントディレクトリにmsdf.pngが生成されます。



使用するSVGファイルにもよりますが、こんな感じの画像が生成されます。

後はUnityにインポートしてInspectorから画像のインポート設定を以下のように

圧縮を無効にして解像度を合わせます。


次にこの画像ファイルからベクター画像としてデコードするShaderを用意します。


先のgithubにGLSLのサンプルコードが乗っているのでHLSLに書き換えます。

in vec2 pos;

out vec4 color;
uniform sampler2D msdf;
uniform float pxRange;
uniform vec4 bgColor;
uniform vec4 fgColor;
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
void main() {
vec2 msdfUnit = pxRange/vec2(textureSize(msdf, 0));
vec3 sample = texture(msdf, pos).rgb;
float sigDist = median(sample.r, sample.g, sample.b) - 0.5;
sigDist *= dot(msdfUnit, 0.5/fwidth(pos));
float opacity = clamp(sigDist + 0.5, 0.0, 1.0);
color = mix(bgColor, fgColor, opacity);
}

こんな感じで適当に


MSDF.shader

Shader "Unlit/MSDF"

{
Properties
{
[NoScaleOffset]_MainTex("MSDF Texture", 2D) = "white" {}
[HDR]_Color_0("Color 0", Color) = (1,1,1,0)
[HDR]_Color_1("Color 1", Color) = (0,0,0,1)
[Toggle] _Show_Original_Texture("Show Original Texture", Float) = 0
}
SubShader
{
Tags {"Queue"="Transparent" "RenderType"="Transparent"}
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
Cull off
ZWrite off
Pass
{
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag

#pragma shader_feature _SHOW_ORIGINAL_TEXTURE_ON

#include "UnityCG.cginc"

uniform sampler2D _MainTex;
uniform float4 _Color_0, _Color_1;

float median (float3 col)
{
return max(min(col.r, col.g), min(max(col.r, col.g), col.b));
}

float fwidth(float2 p)
{
return abs(ddx(p)) + abs(ddy(p));
}

float4 frag (v2f_img i) : SV_Target
{
float3 tex = tex2D(_MainTex, i.uv);
float dist = median(tex) - .5;
float sigDist = fwidth(dist);
float opacity = smoothstep(-sigDist, sigDist, dist);
float4 o;
#ifdef _SHOW_ORIGINAL_TEXTURE_ON
o = float4(tex, 1);
#else
o = lerp(_Color_0, _Color_1, opacity);
#endif
return o;
}
ENDCG
}
}
}


ShaderをQuadなどに適用してMSDF Textureに先の画像を突っ込むと以下のようになります。



また、"Show Original Texture"にチェックを付けるとそのままの画像を出力します。(右のやつ)

拡大しても元の画像のサイズが64✕64とは思えないほどになめらかです。


VRゲームのUIなどに使えば極端に近づいた際にジャギらないので良いかも?


Link

msdfgen