LoginSignup
72
60

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-02-02

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

72
60
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
60