Help us understand the problem. What is going on with this article?

uGUIにシェーダーを適用しよう、あるいは、UnityはいかにしてuGUI.Textを表示しているのか

はじめに

こんにちは、アドベントカレンダー6日目の避雷です。この前uGUIを弄っていたらTextとかImageとかにもmaterialを入れるserializefieldがあるのを見てオ!と思いました。遊べそうですね…

uGUIに対するシェーダーの適用について

他のシェーダーと比べてあまり意識されることは少ないですが、UGUIコンポーネントにもマテリアルを適用することが出来ます。例えばIN.texcoord.xyに沿ってラインやノイズを走らせるようなシェーダーを用意すれば以下のようなレンダリング結果となります。
993c07919ac21066e717679a26503781.gif

Imageなどについては通常のUVと同じような処理が行われます。ここで興味深いのはtextにシェーダーを適用した際の挙動です。

f50db799985bc686ff63ea9dbec368f7.gif

ノイズの入るタイミングとか太い線が走る向きが文字ごとに違うのがわかるでしょうか?どうしてこんなことになるのかと言うと、Textが表示される仕組みに原因があります。

フォントアトラスについて

知っている人には当然の挙動なのですが、UGUIでテキストを表示する際、実際には一度フォントデータからアトラスに焼いたものが使用されます。
この際、画面上の一文字は複数個の文字が敷き詰められたアトラスから切り出されたものなのでUVの取りうる値は0~1ではありません。
更にいうとアトラスに焼きこまれる向きも不定なので、場合によってはxとyが逆になることもあります。
詳しくはCyberAgentさんのこの記事が詳しいです。↓
https://creator.game.cyberagent.co.jp/?p=6402

確認してみよう

実際の挙動を確認したいので試しにuvの座標に応じた色を上塗りするシェーダーを書いて適用してみましょう。

Shader "UI/UVUI"
{
     Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255

        _ColorMask ("Color Mask", Float) = 15

        [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask [_ColorMask]

        Pass
        {
            Name "Default"
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            #pragma multi_compile_local _ UNITY_UI_CLIP_RECT
            #pragma multi_compile_local _ UNITY_UI_ALPHACLIP

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                float2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            float4 _MainTex_ST;

            v2f vert(appdata_t v)
            {
                v2f OUT;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
                OUT.worldPosition = v.vertex;
                OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

                OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

                OUT.color = v.color * _Color;
                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
                half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                color.xyz = half3(IN.texcoord.x,IN.texcoord.y,0.);

                #ifdef UNITY_UI_CLIP_RECT
                color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                #endif

                #ifdef UNITY_UI_ALPHACLIP
                clip (color.a - 0.001);
                #endif

                return color;
            }
        ENDCG
        }
    }
}

試しに適当なImageにくっつけてみます。
image.png

よくあるUVグラデーションですね。これをTextにつけてみると、
image.png

こんな風になります。UV座標がバラバラになっていることがわかります。このことを考えると文字ごとに同じ処理をする、みたいな実装はなかなか難しいところがありそうです。やるとしたら等間隔に配置したBitMapFontを使うとかでしょうか、アトラスを前もって用意しておけばUV座標を制御できるというアイデアですね。漢字を含める場合はあまり現実的ではありませんが。

こんなことができます

制御不能な要素が増えることはもちろん不便なことではあるのですが、その一方で適当なコードでも絶妙なランダム性が発生したりします。例としてはこのようなグリッチシェーダーを作ることができます。
グリッチそれ自体は上から下にノイズが走るようにしているだけなのですが、フォントの配置によってノイズの入るタイミングが違ったり、フォントの向きによってノイズの入る向きも違って、一見するとランダム要素が発生しています。

終わりに

UIにもシェーダーを適用可能なことがわかりました。UIへのシェーダー適用は容易に高級感を付与できる一方で過剰に用いるとUI本来の用途である意思伝達が上手く行かなくなるので何事もほどほどに、と言うことで!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした