uGUIシェーダー - Imageの色を反転する

UnityのuGUIのシェーダーのカスタマイズをしてみます。

以下の記事を参考にしました。

tsubakit1.hateblo.jp

 

ビルドインシェーダーのダウンロード

まずは、組み込みシェーダーをダウンロードします。

過去のアーカイブをダウンロードできるページから、

unity3d.com

 

使用しているUnityのバージョンのビルドインシェーダーを選択します。今回はUnity 2019.1.1f1、OSはMacで試しました。

f:id:ninagreen:20190602033646p:plain

 

ダウンロードした builtin_shaders-2019.1.1f1.zip を解凍して、DefaultResourcesExtra/UI/UI-Default.shaderをUnityプロジェクトの適当な箇所にドラッグ&ドロップしてインポートします。

f:id:ninagreen:20190602034057p:plain

 

カスタマイズの準備は完了しました。

 

シェーダーの変更

uGUIのImageの色を反転させます。インポートしたUI-Defaultシェーダーを複製して任意の名前にします(ここではCustomUI-Negaとしました)

 

まずはUI-Negaシェーダーを開いて3行目のシェーダー名を変更します

Shader "UI/Default"

Shader "CustomUI/Nega"

に変更します。

 

次に101行目の色を決定している箇所の下に

half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

この行を追加します。

color.rgb = 1 - color.rgb;

 

これでソースコードの変更は終了です。

 

シェーダーの適用

CustomUI-Negaを右クリックし、Create > Material とするとシェーダーが適用済のマテリアルが作成されます(知らなかった)。作成したマテリアルを適当なImageのMaterial欄にセットすれば完了です。

結果はこうなりました。

f:id:ninagreen:20190602041849p:plain

 

Unityシェーダーお絵描き入門 - 円をアニメーションさせる

以前、フラグメントシェーダーで黒い円を描きました。

game-graphics.hatenablog.com


 

今回は黒い円をアニメーションさせたいと思います。参考記事はこちらです。

docs.google.com

 

まずは黒い円を描くコードのおさらい。

float4 frag_test (v2f_img i) : SV_Target
{
float value = distance(float2(0.5, 0.5), i.uv);
return step(0.4, value);
}

この0.4を直接指定している箇所が円の大きさになります(0に近くにつれ円が小さくなる)

 

float4 frag_test (v2f_img i) : SV_Target
{
float value = distance(float2(0.5, 0.5), i.uv);
float a = abs(sin(_Time.y)) * 0.4;
return step(a, value);
}

今回はこの0.4と直接固定値を指定していたところを時間経過で値が変更されるようにすれば完成です。黒い円が小さくなっては大きくなるを繰り返します。

 f:id:ninagreen:20190607035857g:plain

 

ここからはコードの解説。

まず _Time はUnityシェーダーの定義済みの値です。

docs.unity3d.com

 

マニュアルにも_Timeはシェーダーの中でアニメーションするのに使用とあります。

Name Type Value
_Time float4 時間 (t/20,t,t×2,t×3),シェーダの中でアニメーションするのに使用。

なお、_Time.y は時間 t が返ってきます。ちなみに _Time.x だと t/20 が返されます。

 

また、sin関数は-1〜1の値を返すので、absで絶対値を取ると abs(_sin(Time.y)) は0〜1の値になります。つまり、時間経過に応じて変数aの値は0〜0.4の値を往復するため、黒い円が小さくなったり大きくなったりするようになります。

 

Unityシェーダー - 四角形を描く

 

四角形を描きます。

 

前回のstep関数を応用すればできそう。 

 float4 frag_test (v2f_img i) : SV_Target
{
        float d = step(i.uv.x, 0.2) + step (0.8, i.uv.x) 

                   + step(i.uv.y, 0.2) + step (0.8, i.uv.y);
        return d;
}

 

四角形の周囲の領域 (x <= 0.2 || x >= 0.8|| y <= 0.2 || y >= 8) が 1以上(白)になるように足してみました。一応うまくいってるっぽい。

f:id:ninagreen:20180920193857p:plain

 

あ、いやよく見ると、隅の部分がなんかはみ出してて汚い。clamp しないとダメですかね。

float4 frag_test (v2f_img i) : SV_Target
{
    float d = clamp(step(i.uv.x, 0.2) + step (0.8, i.uv.x)

                          + step(i.uv.y, 0.2) + step (0.8, i.uv.y), 0, 1);
    return d;
}

綺麗になった!

f:id:ninagreen:20180920195227p:plain

 

もう少し簡潔に書けそうです。

 step関数にはfloatNのvector型を渡すこともできて、それぞれのpropertyごとに計算してくれるっぽいです。

            float4 frag_test (v2f_img i) : SV_Target
            {
                float2 d = step(i.uv, float2(0.2, 0.2)) + step (float2(0.8, 0.8), i.uv);
                return clamp(d.x + d.y, 0, 1);
            } 

 というか、xもyも同じ値であればこうするほうがもっと簡潔かも。

            float4 frag_test (v2f_img i) : SV_Target
            {
                float2 d = step(i.uv, 0.2) + step (0.8, i.uv);
                return clamp(d.x + d.y, 0, 1);
            }

 

黒い四角じゃなくて青い四角もできますね

            float4 frag_test (v2f_img i) : SV_Target
            {
                float2 d = step(i.uv, 0.2) + step (0.8, i.uv);
                return d.x + d.y >= 1 ? 1 : float4(0, 0, 1, 1);
            }

f:id:ninagreen:20180921184223p:plain

 

 調べてたら、四角形を書いているコードを他にも発見

nn-hokuson.hatenablog.com

 

まったく別のアプローチですね。まず、こういう画像を作って、

            float4 frag_test (v2f_img i) : SV_Target
            {

                // 中心の四角形のサイズ(横、縦の長さ)を定義
                fixed2 size = fixed2(0.3,0.1);

 

                // 四角形の左下の座標を取得

                //(中心座標からサイズの半分をマイナスした箇所)
                fixed2 leftbottom = fixed2(0.5,0.5) - size * 0.5;

 

                // leftbottom <= i.uv ならば1

                //(四角形の左下座標より右上にいる場合は白にする)
                fixed2 uv = step(leftbottom, i.uv); 

 

                return uv.x*uv.y; // x=1&&y=1の場合のみ塗りつぶす
            }

f:id:ninagreen:20180921185437p:plain

 

こういうコードで上下左右した画像ができるから、

           float4 frag_test (v2f_img i) : SV_Target
            {
                fixed2 size = fixed2(0.3,0.1);
                fixed2 leftbottom = fixed2(0.5,0.5) - size * 0.5;

                // 1 - i.uv にすると上下左右反転できる
                fixed2 uv = step(leftbottom, 1 - i.uv);
                return uv.x*uv.y;
            } 

f:id:ninagreen:20180921185626p:plain

 

それを掛け算で掛け合わせると四角ができますってことだと思う

            float4 frag_test (v2f_img i) : SV_Target
            {
                fixed2 size = fixed2(0.3,0.1);
                fixed2 leftbottom = fixed2(0.5,0.5) - size * 0.5;
                fixed2 uv = step(leftbottom, i.uv);
                uv *= step(leftbottom, 1 - i.uv);
                return uv.x*uv.y;
            }

f:id:ninagreen:20180921185834p:plain

 

最後反転すれば白い背景の黒い四角になりますね

            float4 frag_test (v2f_img i) : SV_Target
            {
                fixed2 size = fixed2(0.3,0.1);
                fixed2 leftbottom = fixed2(0.5,0.5) - size * 0.5;
                fixed2 uv = step(leftbottom, i.uv);
                uv *= step(leftbottom, 1 - i.uv);
                return 1 - uv.x*uv.y;
            }

f:id:ninagreen:20180921190017p:plain

 

Unityシェーダー - 黒い円を描く

シェーダーで下記のような黒い円を描きます。

f:id:ninagreen:20180919192749p:plain

 

前回、下記のような画像を作成したので、それを応用して距離が一定の閾値内は黒、それ以上は白にすればできそう。

f:id:ninagreen:20180919193001p:plain

 

 

最初に思いつたコードはこれでしたが、エラーになりました。数字のfloatを表すfはいらないらしい。

            float4 frag_test (v2f_img i) : SV_Target
            {
                float d = distance(float2(0.5, 0.5), i.uv);
                return d > 0.5 ? 1f : 0f;
            }

 

修正後はこんな感じ。

            float4 frag_test (v2f_img i) : SV_Target
            {
                float d = distance(float2(0.5, 0.5), i.uv);
                return d > 0.5 ? 1 : 0;
            }

結果はこうなりました。簡単。シェーダーも三項演算子使えるんですね。

f:id:ninagreen:20180919193527p:plain

 

if文で書き直そうかと思ったけど、どうもシェーダーだとif分は遅いらしい。C#の世界では想像もつかない・・・

light11.hatenadiary.com

qiita.com

 

if文の代わりにstep関数を使うほうがいいらしい。

step (DirectX HLSL)

 

step(edge, value)

value が edge 以上の場合は1、それ以外は0

 

なので、先ほどのを書き換えるとこうですかね。

            float4 frag_test (v2f_img i) : SV_Target
            {
                float value = distance(float2(0.5, 0.5), i.uv);
                return 1 - step(value, 0.5);
            }

 

わかりにくい・・・

x < edge が step (x,edge) であるほうが個人的にはわかりやすいですが、逆なんですよね・・この対応表がわかりやすいですが、覚えられる自信がなく・・何回も調べてしまいそうです・・

 

x >= edge x <= edge x > edge x < edge
step(edge,x) step(x,edge) 1.0 - step(edge,x) 1.0 - step(x,edge)

 

Unityシェーダー - float4とは?

C#だと見慣れない float4 って何?

 

調べてみると float4 はベクトル型らしいです。

ベクトル型 (DirectX HLSL)

2 つの部分で構成される単一の名前。1 つめの部分では、スカラー型のいずれかを指定します。2 つ目の部分では、成分の数を指定します。その値は 1 ~ 4 の範囲内である必要があります。

つまり、float4は、floatというスカラー型に成分の数(4)を付加したベクトル型ってことみたい。

 

宣言は

float4 fVector = { 0.2f, 0.3f, 0.4f, 1.0f };

でも

vector <float, 4> fVector = { 1.0f, 0.3f, 0.4f, 1.0f };

でもいいらしい(後者は書かないだろうけど)

 

あと、初期化は

float4 fVector = { 0.2f, 0.3f, 0.4f, 1.0f };

でもいいし、

float4 fVector = float4(1.0f, 0.5f, 1.0f, 1.0f);

とも書けるらしい。ちなみに、C#の勢いで new float4 って書くとエラーでした。

 

あと、ベクトル型へのアクセスは、xyzwだけじゃなく、rgbaでもアクセスできるらしい。独特ですねー

float4 data = float4(1.0f, 0.5f, 0.0f, 1.0f);

 

// result1 と result2 は同じ結果になる
float4 result1 = float4(data.x, data.y, data.z, data.w);
float4 result2 = float4(data.r, data.g, data.b, data.a);

 

あとは抜き出したい成分だけを連続で順序も自由に記述できる。これは便利。C#にもあればいいのに。

float4 pos = float4(1,2,3,4);

float2 f_2D;

f_2D = pos.xy; // float2(1, 2)と同じ

f_2D = pos.xz; // float2(1, 3)と同じ

f_2D = pos.zx; // float2(3, 1)と同じ

f_2D = pos.xx; // float2(1, 1)と同じ

あとはこんなのも書けるらしい。

float2 p;
p.xy = 0;

こう書くと 変数 p の x と y の両方に同時に 0 を入れることができるっぽい。C#的には xy という変数に 0 を代入してるようにしか見えないですね・・・

 

こんなこともできるみたい

float4 pos = float4(1,2,3,4);

float4 f_4D;

f_4D = pos; // float4(1,2,3,4)と同じ

f_4D.xz = pos.xz; // float4(1,0,3,0)と同じ

f_4D.zx = pos.xz; // float4(3,0,1,0)と同じ

 

あ、こんなのもある

f_4D.xyzw = pos.w; // float4(3,3,3,3)と同じ

 

ここからは推測だけど、↑がありなら

f_4D.xyzw = 3;

と書くこともできて、

f_4D = 3;

とxyzwを省略して書くこともできるってことじゃないだろうか・・?

 

つまり、仮に関数の戻り値の型がfloat4であってもfloatで省略して返すことができ、 float値がxyzwに代入されたfloat4を返してるの同義なんじゃないだろうか。

 

Unity の シェーダー言語 のベースとなる HLSL 言語では、基本的な文法はC/C++に準ずるが、グラフィックスプログラムを記述するのに適した専用のベクトル・行列型や関数を備えているそうで、C#しかやってない私のような人には見慣れないものが多いですね。

Unityシェーダーお絵描き入門 - 中心に向かって色を黒くする

今日はDistanceを使ったお絵描き!

docs.google.com

 

前回のfrag_testの中身をこういう感じに書き換えると

            float4 frag_test (v2f_img i) : SV_Target
            {
                float d = distance(float2(0.50.5), i.uv);
                return d;
            }

 

こうなる。

f:id:ninagreen:20180912190223p:plain

 

distanceは中心座標(0.5, 0.5)と引数で渡されたuv座標の距離を取得してると思われる。

 

dには何が入ってくるの?

dの値を直接見たいんだけど、そもそもシェーダーでコンソールでログ出力ってどうするの?

 

Unity フォーラムで質問してる人発見

https://forum.unity.com/threads/how-to-print-shaders-var-please.26052/

 

I guess that sfkdkjjj want to print value of variable inside vertex/pixel shader.

Usually in shaders You use colours or similar tricks to debbug your variables. It is not possible to see value directly.

You can use Pix to see exact values. But it will not be what you expect. This is not even close to usual debbuging.

え、待って、値を直接見ることは not possible !?まじかー

色とかを表示して、擬似的にデバッグするって、まじかー

 

色から推測すると、dは中心は黒(0)で、遠くなるほど値が大きくなって白(1)に近づいているってことだと思う。たぶん。各頂点は、a x a + b x b = c x c に当てはめると c = √(0.5x0.5 + 0.5x0.5) = 0.7ぐらい。たぶん。

 

 

SV_Targetって何?

メソッド名の隣にあるSV_Targetはセマンティクスなるものらしい

qiita.com

セマンティクスとは、シェーダープログラムの入力や出力の値が、何を意味するかを表すためのもの。型が、値の表現の幅(最大値は何か、実数か小数か、スカラーかベクトルか、…)を表すのに対して、セマンティクスは、値の用途(位置か、法線か、色か…)を表すイメージ

つまり同じfloat4を返すにしても位置なのか色なのか等を定義できるってことのよう。ちなみに、SV_TARGET は ピクセルの色 だそうです。

 

Passブロックって何?

前回、Passがいまいちわからなかったのですが、わかりやすい説明を見つけました。

Passは、オブジェクトのレンダリング処理を記述する部分です。

複数パスを定義することも可能で、その場合には複数回のレンダリング処理が走ることとなります。 

blog.applibot.co.jp