LoginSignup
17
12

More than 3 years have passed since last update.

数値を数字として表示するシェーダーを作ったおはなし[Unity]

Last updated at Posted at 2019-12-17

この記事はシェーダーアドベントカレンダー18日目の記事として書かれています。
https://qiita.com/advent-calendar/2019/shader-advent-calender-2019

はじめに

シェーダーの数値って色として見ることは多いのだけど、普通(?)のプログラムのように数字として見ることは少ないと思います。
VR空間でVJをするときに、パラメータの値を数字として目で見たかったので数値を数字として表示するシェーダーを作りました。
今回はその数字シェーダーを何を考えながらどういう風に作っていったか書きたいと思います。

完成したサンプル。機能として桁数指定、マイナス表示など。

-100から100まで表示してみた動画。

完成したコードとテクスチャです。
https://github.com/noriben327/DisplayNumber/blob/master/DisplayNumber.shader

既に数字シェーダーを作っている方々

同じようなことを思っている方はたくさんいて、シェーダーを配布している方もいます。

BUTADIENE WORKS
ブタジエンさん
https://twitter.com/butadiene121/status/1063451198194413568

Position Shader
オノッチさん
https://onotchi.booth.pm/items/996827

少しコードを読んでみたものの自分にはわからなかったので、1から考えながら作ることにしました。

基本の考え

数字を書いたテクスチャを用意して、例えば数値が1ならテクスチャの1が書いてある部分にスクロールして表示する。これだけです。

今回作った数字のテクスチャはこれです。
Y座標を10分割して数字を書いているので、uvfloat2(0, 0.1)1float2(0, 0.7)7のテクスチャにそのままスクロールできます。
numTex 1.png

説明用に2桁で、隠している部分も表示してみたもの。数値にあわせてUVスクロールしてるのがわかるんじゃないかな~と思います。0.0から2.0を表示させています。

数値の抽出

数値そのままではどうにも扱えないので、1桁ずつ切り取って0~9の値にして、1/10にすることで0.0~1.0のUV値として使える値にする。それを全桁に実行することで最終的に数字として表示する。
今回は整数部と小数部を分けてfor文にして処理した。

整数部の抽出

まず1の位だけやってみる。

  • 数値は仮に123.141592とする。

  • 1/10する。

    • 12.3141592
  • fracで小数点のみにする。

    • 0.3141592
  • 10倍する。

    • 3.141592
  • floorで小数点以下を切り捨てる。

    • 3

これで1の位の数値である3のみを取り出せる。
あとは1/10して0.3とすれば0~1の範囲になるのでそのままUVの値として使えるようになる。

実際のコード。

for(j = 1; j < _IntDigits + 1; j++)
                {
                    multi = pow(10, j); 
                    val = numVal;
                    com = val * 1 / multi;
                    val = frac(com) * 10;
                    val = floor(val) * 0.1;
                    uv = i.uv;
                    uv.y += val; //数字移動
                    uv.x += -0.04 + 0.05 * j; //桁移動
                    numCol =  numCol + tex2D(_NumTex, uv);
                }

小数部の抽出

次に小数点第1位だけ取り出してみる。

  • 数値は仮に123.141592とする。

  • fracで小数点のみにする

    • 0.141592
  • 10倍する

    • 1.141592
  • floorで小数点以下を切り捨てる

    • 1

これで小数点第1位の1という数字を取り出せる。
1/10すれば0.1とそのままUVの値として使える。

実際のコード。
おまじないとして元の値に0.00001を足しているが、これは例えば0.30.299999と表示されてしまうときがある場合のとりあえずの対策である:thinking::thinking::thinking:(本来は必要ない)

for(j = 0; j < _DecimalDigits; j++)
                {
                    multi = pow(10, j); 
                    val = numVal + 0.00001;//おまじない
                    com = val * multi;
                    val = frac(com) * 10;
                    val = floor(val) * 0.1;

                    uv = i.uv;
                    uv.y += val; //数字移動
                    uv.x -= 0.06 + 0.05 * j; //桁移動
                    numCol =  numCol + tex2D(_NumTex, uv);
                }

負号

ifでマイナス以外のときは負号を消すようにした。
桁数の増加にあわせて常に先頭に表示するようUV.x値を移動している。(動画参照)
(_IntDigits(整数部の桁数)の1つ上の桁に負号を表示)

float2 muv = i.uv;
muv.x += -0.005 + 0.05 * (_IntDigits + 1);

見た目を整えて完成

カンマを描画したり、見せたくない部分をマスクするなど。

あとがき

プログラムの知識も経験も少ないのですが、それでもシェーダーを書くのは楽しいです。このコードも楽しく書けたのでせっかくなので解説の記事を書こうかな~と思ってアドベントカレンダーに参加しました。
決め打ちの数字が多かったり、数字の表示部分を関数にして他のコードで使いやすくすべきだよなーと反省点はあるものの、コードの一部の数値を確認したり、オブジェクトの座標を表示してみたり、

Unityの時間の流れ(_Time.y)を可視化してみたりと結構実用的なシェーダーとして使っています。

全コード

RenderTextureの値を取得するコードが一部入っていますが、今回は_Testの値を表示するようにしています。


Shader "Noriben/DisplayNumber"
{
    Properties
    {
        _NumTex ("NumTex", 2D) = "white" {}
        _RenderTex ("RenderTex", 2D) = "white" {}
        _Index ("Index", float) = 0
        _IntDigits("Int Digits", int) = 2
        _DecimalDigits("Decimal Digits", int) = 3
        _Test("Test", Range(-100,100)) = 0
        [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="TransparentCutout" "Queue" = "AlphaTest" "DisableBatching" = "True"}
        LOD 100

        Pass
        {
            Cull [_Cull]

            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 _NumTex;
            float4 _NumTex_ST;
            sampler2D _RenderTex;
            float _Test;
            float _Index;
            int _IntDigits;
            int _DecimalDigits;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _NumTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //数値取得
                float4 renderTex = tex2D(_RenderTex, float2((_Index + 0.5) * 0.1, 0.5));

                //表示位置をセンターにオフセット
                i.uv.x += -0.37;

                //カンマの描画
                float left = step(0.076, i.uv.x);
                float right = 1 - step(0.085, i.uv.x);
                float bottom = step(0.0234, i.uv.y);
                float top = 1 - step(0.033, i.uv.y);
                float comma = left * right * top * bottom;

                //数字表示
                //float4 objPos = mul ( unity_ObjectToWorld, float4(0, 0, 0, 1));
                //float minusCheck = renderTex.x;
                float minusCheck = _Test;
                //float minusCheck = -_Time.y; //表示する数値
                float numVal = abs(minusCheck);


                fixed4 numCol = fixed4(0,0,0,0);
                float multi, val, com;
                float2 uv = i.uv;

                //1未満
                int j = 0;
                for(j = 0; j < _DecimalDigits; j++)
                {
                    multi = pow(10, j); 
                    val = numVal + 0.00001;//おまじない
                    com = val * multi;
                    val = frac(com) * 10;
                    val = floor(val) * 0.1;

                    uv = i.uv;
                    uv.y += val; //数字移動
                    uv.x -= 0.06 + 0.05 * j; //桁移動
                    numCol =  numCol + tex2D(_NumTex, uv);
                }

                //1以上
                for(j = 1; j < _IntDigits + 1; j++)
                {
                    multi = pow(10, j); 
                    val = numVal;
                    com = val * 1 / multi;
                    val = frac(com) * 10;
                    val = floor(val) * 0.1;

                    uv = i.uv;
                    uv.y += val;
                    uv.x += -0.04 + 0.05 * j; 
                    numCol =  numCol + tex2D(_NumTex, uv);
                }

                //負号
                float2 muv = i.uv;
                muv.x += -0.005 + 0.05 * (_IntDigits + 1);
                float mleft = step(0.076, muv.x);
                float mright = 1 - step(0.1, muv.x);
                float mbottom = step(0.044, muv.y);
                float mtop = 1 - step(0.054, muv.y);
                float mcol = mleft * mright * mbottom * mtop;
                //0以上のときは負号消す
                if(minusCheck >= 0)
                {
                    mcol = 0;
                }

                //mix
                fixed4 col = numCol;
                col += float4(comma, comma, comma, 1);
                col += float4(mcol, mcol, mcol, 1);
                col = clamp(col, 0, 0.8); //明るさちょっとさげる
                //使わない部分黒塗り
                top = 1 - step(0.1, i.uv.y);
                float3 black = float3(top, top, top);
                col *= float4(black.xyz, 1);

                //黒部分透明化
                clip(col.x - 0.5);
                return col;

            }
            ENDCG
        }
    }
}
17
12
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
17
12