🔢

変数の値(数値)を表示するShaderの作り方

2023/06/03に公開

まえがき

UnityのC# (VRChatだとUdonSharp)では、

Debug.Log (Num);

と書くことで、Numという変数の値をプログラムの途中で確認することができます。

しかし、Shaderではプログラムの途中で変数の値をDebug.Logのように確認する方法がなく(私が知らないだけかも)、代わりに、出力される色などで値を確認したりするのですが、おおよその値しかわかりません。

Shaderを始めたての頃や、凝ったことをしていると、プログラムの途中で変数の値を確認したくなることが結構あります。

自分だけでなく他の人も、今後同じような経験をしそうだなと勝手に思ったので、
これを機に、変数の値を表示するShaderをなるべく簡単に作ってみます。

作りたいもの

ある変数の整数1~4桁、小数第1~6位までの値を表示するShaderを作ります。
例えば-0.123456を表示させるとこんな感じ。(0埋めでダサいですが目をつぶってください)

事前知識

uv座標値は基本的に0.0~1.0の範囲で扱われるそうです
テクスチャ解像度がどんな大きさであろうと、アスペクト比がどんなであろうと関係ありません

uv座標値が1.0を超えた1.2とかを指定すると、テクスチャのWrap Modeが標準のRepeatの場合、0.2の場所を参照してくれます。(ループしてくれる)

準備

  1. 下図のような数字の書かれたテクスチャを用意する


  2. UnityのProjectタブ内を右クリック > Create > Shader > Unlit Shaderを押して
    新しくシェーダーを作る。
    →今回はDisplayNumShaderという名前にしました。

  3. UnityのProjectタブ内を右クリック > Create > Materialを押して新しくマテリアルを作る。
    →今回はMatDisplayNumという名前にしました。

  4. MatDisplayNumにDisplayNumShaderとテクスチャを割り当てPlaneにアタッチ。
    (DisplayNumShaderはUnlitの階層にあるはずです)


  5. 頭の中にテクスチャ番地TとPlane上での表示番地Hを定義します




チュートリアル 1/3

表示番地H=0の位置にテクスチャ番地T=5に描かれているテクスチャを表示してみます。

今回使用するテクスチャには文字が11文字等間隔に描かれています。
(以降、1文字分を1ブロックと呼ぶことにします)

tex2Dでテクスチャから色を拾う際に、5ブロック進んだ場所を参照するようにしてみます。

テクスチャ番地T=5を定義。
新しいuv座標としてMyUvを定義し、MyUv.xにはi.uv.xに(T / 11.0)を足した値を入れることで、
テクスチャの参照位置をTブロック分ずらします。
MyUv.yにはそのままi.uv.yを代入します。

フラグメントシェーダの中身はこんな感じになります。

int T = 0;                          // テクスチャ番地
float2 MyUv = 0.0;                  // 自分で変更したUV座標値
fixed4 TempTex = 0.0;               // 表示色 (一時使用)

T = 5; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0);       // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                    // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);    // テクスチャから色を取ってくる
return TempTex;

実行してみると
表示番地H=0の位置にテクスチャ番地T=5に描かれているテクスチャを表示できました

このままだと、表示番地H=0以外の場所にもテクスチャが表示されてしまっているので、
stepをつかって不要な部分を黒色で塗りつぶしてみます。
(stepはstep(a, x)と書くと、xがa未満なら0。xがa以上なら1を出力してくれます)

ちょっとかわいそうですが、表示番地Hに+1を足した番地以降(今回はH=1~10の範囲)は、
tex2Dがテクスチャから拾ってきてくれた色TempTexに0を掛けて、黒色にしてしまいます。

フラグメントシェーダの中身はこんな感じになります。(追記した部分に★を付けました)

int H = 0;                                  // ★表示番地
int T = 0;                                  // テクスチャ番地
float2 MyUv = 0.0;                          // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                       // 表示色 (一時使用)

H = 0; // ★表示番地
T = 5; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0);               // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // ★表示番地の右側を黒塗り
return TempTex;

実行してみると
表示番地H=0の位置にテクスチャ番地T=5に描かれているテクスチャ"だけ"を表示できました!!
T = 5の部分を変えると、表示される値も変わります。チュートリアル1/3クリア!!

チュートリアル 2/3

表示番地H=1の位置にテクスチャ番地T=5に描かれているテクスチャを表示してみます。

今回はチュートリアル1と同様にテクスチャ番地はT=5のままですが、
表示番地H=1になることで、表示するブロックが1ずれるので、
チュートリアル1のMyUv.xの計算式に対して(H / 11.0)をさらに引くことで、
テクスチャの参照位置をHブロック分ずらします。

フラグメントシェーダの中身はこんな感じになります。(変更した部分に★を付けました)

int H = 0;                                  // 表示番地
int T = 0;                                  // テクスチャ番地
float2 MyUv = 0.0;                          // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                       // 表示色 (一時使用)

H = 1; // 表示番地
T = 5; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // ★X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
return TempTex;

実行してみると
表示番地H=1の位置にテクスチャ番地T=5に描かれているテクスチャを表示できました

例のごとく、今度は今回の表示番地H=1よりも左側にテクスチャが表示されてしまっているので、黒塗りします。

フラグメントシェーダの中身はこんな感じになります。(追記した部分に★を付けました)

int H = 0;                                  // 表示番地
int T = 0;                                  // テクスチャ番地
float2 MyUv = 0.0;                          // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                       // 表示色 (一時使用)

H = 1; // 表示番地
T = 5; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step((H / 11.0), i.uv.x);        // ★表示番地の左側を黒塗り
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
return TempTex;

実行してみると
表示番地H=1の位置にテクスチャ番地T=5に描かれているテクスチャ"だけ"を表示できました!!
H = 1やT = 5の部分を変えると、表示される場所や値も変わります。チュートリアル2/3クリア!!

チュートリアル 3/3

表示番地H=0の位置にテクスチャ番地T=7に描かれているテクスチャ、
表示番地H=1の位置にテクスチャ番地T=2に描かれているテクスチャを表示してみます。

考え方としては下図のように2枚のテクスチャの色を足し合わせる感じ。
既に着いている色に黒色を足しても、黒色の値はR=0,G=0,B=0なので、
既に着いている色は変わりません。なので数字の7の部分も消えません。

フラグメントシェーダの中身はこんな感じになります。
最終的な表示色としてTexを定義し、そこに表示番地Hごとに生成した色を足し算していく感じ。
「H = 0部分の処理」と「H = 1部分の処理」の違いはHとTの値の違いだけで計算式は一緒です。
(Texに関連する部分に★を付けました)

int H = 0;                                  // 表示番地
int T = 0;                                  // テクスチャ番地
float2 MyUv = 0.0;                          // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                       // 表示色 (一時使用)
fixed4 Tex = 0.0;                           // ★最終的な表示色

// H = 0部分の処理 --------------------------------------------------------------
H = 0; // 表示番地
T = 7; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step((H / 11.0), i.uv.x);        // 表示番地の左側を黒塗り
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
Tex += TempTex;                             // ★色を足す

// H = 1部分の処理 --------------------------------------------------------------
H = 1; // 表示番地
T = 2; // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step((H / 11.0), i.uv.x);        // 表示番地の左側を黒塗り
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
Tex += TempTex;                             // ★色を足す

return Tex;

実行してみると
表示番地H=0の位置にテクスチャ番地T=7に描かれているテクスチャ、
表示番地H=1の位置にテクスチャ番地T=2に描かれているテクスチャを表示できました!!
チュートリアル3/3クリア!!



変数の値(数値)を表示するShaderを作る

STEP 1/3
実数から整数1~4桁、小数第1~6位の値を抽出する処理を考えます (マイナスは考慮しない)

float Input = 1234.123456;              // 入力値 (プラスの値だけ)

int N[10];
N[0] = (int)(Input * 0.001) % 10;       // 整数4桁目を求める
N[1] = (int)(Input * 0.01) % 10;        // 整数3桁目を求める
N[2] = (int)(Input * 0.1) % 10;         // 整数2桁目を求める
N[3] = (int)(Input * 1) % 10;           // 整数1桁目を求める
N[4] = (int)(Input * 10) % 10;          // 小数第1位を求める
N[5] = (int)(Input * 100) % 10;         // 小数第2位を求める
N[6] = (int)(Input * 1000) % 10;        // 小数第3位を求める
N[7] = (int)(Input * 10000) % 10;       // 小数第4位を求める
N[8] = (int)(Input * 100000) % 10;      // 小数第5位を求める
N[9] = (int)(Input * 1000000) % 10;     // 小数第6位を求める

以下のサイトで実行できます。
https://paiza.io/projects/DORLYEEs7xjQhHuYQuDsWg

私の計算方法が間違っているだけかもしれませんが、多分floatの浮動小数点誤差で小数第5,6位の値が変わってます。Inputの型をdoubleにすれば問題なく表示されます。



STEP 2/3
とりあえず表示番地H=1~10に各桁ごとに分解した入力値をそのまま表示してみます。

for文内1行目のT = N[H-1]がややこしいかもしれませんが、
表示番地H=1にはN[0]の値、表示番地H=2にはN[1]の値を表示するって感じです。

float Input = 1234.123456;              // 入力値 (プラスの値だけ)

// 入力値の処理 ----------------------------------------------------------------------
int N[10];
N[0] = (int)(Input * 0.001) % 10;       // 整数4桁目を求める
N[1] = (int)(Input * 0.01) % 10;        // 整数3桁目を求める
N[2] = (int)(Input * 0.1) % 10;         // 整数2桁目を求める
N[3] = (int)(Input * 1) % 10;           // 整数1桁目を求める
N[4] = (int)(Input * 10) % 10;          // 小数第1位を求める
N[5] = (int)(Input * 100) % 10;         // 小数第2位を求める
N[6] = (int)(Input * 1000) % 10;        // 小数第3位を求める
N[7] = (int)(Input * 10000) % 10;       // 小数第4位を求める
N[8] = (int)(Input * 100000) % 10;      // 小数第5位を求める
N[9] = (int)(Input * 1000000) % 10;     // 小数第6位を求める


// 表示部処理 ------------------------------------------------------------------------
int H = 0;                                      // 表示番地
int T = 0;                                      // テクスチャ番地
float2 MyUv = 0.0;                              // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                           // 表示色 (一時使用)
fixed4 Tex = 0.0;                               // 最終的な表示色

// 表示番地H=1~10の処理
for (H = 1; H <= 10; H++)
{
    T = N[H-1]; // テクスチャ番地
    MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
    MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
    TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
    TempTex *= step((H / 11.0), i.uv.x);        // 表示番地の左側を黒塗り
    TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
    Tex += TempTex;                             // 色を足す
}

return Tex;

実行してみるとこんな感じ。

入力値は1234.123456なので、1234123456となってほしいところですが、
おそらくfloat誤差で小数第5,6位の値が4と2になってしまっています。




STEP 3/3
符号表示部と小数点の点を表示する部分を作って完成です!!

符号表示部では表示番地H=0の位置にテクスチャ番地T=10の-(ハイフン)を表示させる処理を書いておき、IsMinusを掛けることで、入力値がマイナスではなかった場合に黒塗りをします。

小数点の点を表示部では、distanceを使ってuv.xと5ブロック目との距離(絶対値)を測り、0.005より離れていたら黒塗り。uv.yが0.1より上側だった場合も黒塗り処理をしています。

float Input = 1234.123456;              // ★入力値

// 入力値の処理 ----------------------------------------------------------------------
int IsMinus = 1 - step(0, Input);       // ★マイナスなら1になる
Input = abs(Input);                     // ★入力値を絶対値にする

int N[10];
N[0] = (int)(Input * 0.001) % 10;       // 整数4桁目を求める
N[1] = (int)(Input * 0.01) % 10;        // 整数3桁目を求める
N[2] = (int)(Input * 0.1) % 10;         // 整数2桁目を求める
N[3] = (int)(Input * 1) % 10;           // 整数1桁目を求める
N[4] = (int)(Input * 10) % 10;          // 小数第1位を求める
N[5] = (int)(Input * 100) % 10;         // 小数第2位を求める
N[6] = (int)(Input * 1000) % 10;        // 小数第3位を求める
N[7] = (int)(Input * 10000) % 10;       // 小数第4位を求める
N[8] = (int)(Input * 100000) % 10;      // 小数第5位を求める
N[9] = (int)(Input * 1000000) % 10;     // 小数第6位を求める


// 表示部処理 ------------------------------------------------------------------------
int H = 0;                                      // 表示番地
int T = 0;                                      // テクスチャ番地
float2 MyUv = 0.0;                              // 自分で変更したUV座標値
fixed4 TempTex = 0.0;                           // 表示色 (一時使用)
fixed4 Tex = 0.0;                               // 最終的な表示色

// 表示番地H=1~10の処理
for (H = 1; H <= 10; H++)
{
    T = N[H-1]; // テクスチャ番地
    MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
    MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
    TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
    TempTex *= step((H / 11.0), i.uv.x);        // 表示番地の左側を黒塗り
    TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
    Tex += TempTex;                             // 色を足す
}

// ★符号表示
H = 0;                                      // 表示番地
T = 10;                                     // テクスチャ番地
MyUv.x = i.uv.x + (T / 11.0) - (H / 11.0);  // X方向のテクスチャの参照位置をずらす
MyUv.y = i.uv.y;                            // Y方向のテクスチャの参照位置はそのまま
TempTex = tex2D(_MainTex, MyUv);            // テクスチャから色を取ってくる
TempTex *= step((H / 11.0), i.uv.x);        // 表示番地の左側を黒塗り
TempTex *= step(i.uv.x, ((H + 1) / 11.0));  // 表示番地の右側を黒塗り
TempTex *= IsMinus;                         // マイナスじゃなかったら黒塗りする
Tex += TempTex;                             // 色を足す

// ★小数点の点を表示
// 現在のuv.xと5ブロック目との距離を測る
float Distance = distance((5 / 11.0), i.uv.x);
TempTex = step(Distance, 0.005);            // 距離が0.005より離れていたら黒塗り
TempTex *= step(i.uv.y, 0.1);               // uv.yが0.1より上側は黒塗り
Tex += TempTex;                             // 色を足す

return Tex; 



Inputを-1234.123456にして実行してみるとこんな感じ。
(float誤差で小数第5,6位の値が変わってる)



Inputを-0.123456にして実行してみるとこんな感じ。
(今度は小数第6位までバッチリ表示されている)



以上で終わりです。お疲れさまでした!!

完成品 (UnityPackage)

Shader, Material, Texture, PlaneをPrefabにまとめたunitypackageです。
Unity 2019.4.31f1 (64-bit)です。
https://drive.google.com/file/d/1foiHOz4G_1eIlK9UiUhmhK98m2ozbMiq/view?usp=sharing

Discussion