Unity에서 MSDF를 사용하여 Shader로 벡터 이미지를 그리는 방법

14460 단어 ShaderUnity
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 등에 사용하면 극단적으로 접근했을 때 쟈기하지 않기 때문에 좋을지도?

링크



msdfgen

좋은 웹페이지 즐겨찾기