Unity
Shader
0

Unity、UVScroll(UVの処理)をやってみる【Shader : 2】

はじめに

この記事は連載記事です。
記事を読むにあたってレンダリングパイプラインでどのような処理を行っているのか?を知っている事は前提になります。
非常に分かりやすい資料がありますので、レンダリングパイプラインを知らない方は読む前にご覧ください。

Unity道場 2019.2 シェーダを書けるプログラマになろう #1 シェーダを理解しよう
Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘

アーカイブ
Unity、Shaderことはじめ【Shader : 0】
Unity、UnlitShader/textureのコードを追いかける【Shader : 1】

導入

UnlitShaderの動きが分かったところで、GPUパーティクルレイマーチングをごりごり書いてVJに使って「は~~~~~↑↑↑エッッッッモ…」といきたいところだが、コードをコピペせずにあそこら辺ちゃんとやろうと思うと、数学的な計算を色々知らないといけない。内積も死ぬほど出てくる。
なので、まず地に足ついてシェーダーをシェーダーとして使う所から頑張りたい。

「UVスクロールなんて地味なテクニック興味ないんじゃ!派手派手で盛り盛りな奴くれ!」という方には、そういう導線があるにはありますので、そちらをおすすめします。
wgld.org
Unity Graphics Programming

UVスクロールをする という事は?

uv.gif

UVスクロールを端的に表すと「時間経過によってテクスチャの位置をずらしてほしい」である。
言葉で言えばたったこれだけの事で、難しい原因はこれをコンピューターに伝えなきゃならない部分である。
知りたいのは「何を・どこに書けばいいか?」という事だ。

まず、適当な名前でUnityのデフォルトのUnlitShaderを作成してください。
UnlitShaderをベースに改造してゆきます。

u1.png

Assetフォルダで右クリック > Create > Shader > UnlitShader

レンダリングパイプラインの気持ちになるのです。
まず、UnlitShaderの中で扱う情報は空間座標とUV座標しかない。

struct appdata
{
    float4 vertex : POSITION; //頂点1つ1つの空間座標
    float2 uv : TEXCOORD0; //頂点1つ1つのUV空間の座標
};

これが、UnlitShaderを実行するために必要な情報の全てであり…
そして、極論言えば前後の処理がわからずとも「UV座標である float2 uv に何かしらの式で「時間」を足せばいい」だけの話である。
で、「UV座標に時間を足す」という処理を行えそうな箇所としてはフラグメントシェーダーとバーテクスシェーダーの関数の中。

//バーテクスシェーダー
v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

//フラグメントシェーダー
fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

1行1行追いかけてゆくと…

//バーテクスシェーダー
o.uv = TRANSFORM_TEX(v.uv, _MainTex);

//フラグメントシェーダー
fixed4 col = tex2D(_MainTex, i.uv);

バーテクスシェーダーとフラグメントシェーダーの中で v.uvi.uv があるのが分かる。頭文字が違う理由が気になる。

バーテクスシェーダーでは…
v2f vert (appdata v)
この関数の頭の部分で appdate に v という名前を付けて受け取っている。

フラグメントシェーダーでは…
fixed4 frag (v2f i)
この関数の頭の部分で v2f に i という名前を付けて受け取っている。

それぞれの違いは、appdateの時のUV座標と、appdateがクリップに変換された後のv2fのUV座標の違い。
つまり、どちらも 頂点が参照しているUV座標の位置 という点では変わりない。

ここら辺を理解するには…プログラミングの 引数と戻り値 構造体へのアクセス の仕組みを理解する必要がある。

では、UVの値を時間を使って変えればいいとして、すごい安直に書くと…

//バーテクスシェーダー
v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    //v.uv にただ _Time を足しただけ
    o.uv = TRANSFORM_TEX(v.uv + _Time, _MainTex);
    UNITY_TRANSFER_FOG(o,o.vertex);
    return o;
}

クソである。 v.uv に直接 _Time を足すという暴挙。
ちなみに _Time はShaderLabによって定義されてる値で、宣言しなくても使用できる。
これらの定義済みの値は下記で確認する事ができる。
ShaderLab 定義済みの値
そして、_Timeの中には当然時間が入ってる。

それでは + _Time を付け加えただけのこのShaderを実行してみると…

uv.gif

とりあえず、細かい理屈は抜きにして動く。
これで「書くべき事」と「書くべき場所」は大雑把に把握できた。
なら、当然の発想としてフラグメントシェーダーで使ってるUV座標にも _Time を足してやれば動くんじゃないの?という疑問が浮かぶ。
先ほどのバーテクスシェーダーに書いた _Time を消して、以下のように記述する。

//フラグメントシェーダー
fixed4 frag (v2f i) : SV_Target
{
    // i.uv に _Timeを足しただけ。
    fixed4 col = tex2D(_MainTex, i.uv + _Time);
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

そして、動く。 バーテクスシェーダーで処理した時とまったく同じように動く。
ただ動けばいいだけのスクロールシェーダーであれば + _Time を加えるだけで書けてしまった。

UV座標について

UV座標は0~1の2Dベクトルなので、大きくなり続ける _Time をただ足してるだけではあっという間に1を超えた値になってしまい、UVの領域をオーバーしてしまいます。
しかし、上記のサンプルでは問題なくループしているように見えるのは何故か?

Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘
※5:44から再生します。

上記の動画の5:44から非常に分かりやすくUV座標が1を超えた場合の処理が説明されています。
プログラムの形としては綺麗じゃないにしろ…とりあえず _Time は足していいという説明ができました。

HLSLの関数について

ここまで「UVの値に時間足せばいいだろう」という感じで周りの処理を追ってませんが、いくつか関数が使われています。

tex2D(_MainTex, i.uv)
まぁ、察するに「参照したいテクスチャとUV座標を指定すると、指定したUV座標からテクスチャカラーをとってきてくれるやつ」なんでしょうけど、一応は調べておきたい気持ちがあるわけです。
というわけでUnityのリファレンス調べると出てきません。
「おいUnity!」と思うのは早い。
思い出してほしい…HLSL…DirectX…Microsoft…という事を…!

組み込み関数 (DirectX HLSL)
はい、Microsoftの方のリファレンスを見る必要があるわけですね。
ちなみに tex2D は察しろよって感じのリファレンスになってます。

動きを操作したい

しかし、このUVスクロール…
スクロールの早さも操作できないし、スクロール方向も操作できない等の問題を抱えてるわけです。
少なくとも、ある程度の汎用性を持たせたくなります。
それではまずどうするか?

速さ
早さについては簡単で _Time小さい値を掛ければ遅くなりますし、大きな値を掛ければ早くなります。非常にシンプルです。

向きと速度

uv2.png

先ほどのサンプルでは X座標とY座標に直接時間を足していた ので、+Y方向と+X方向に同じだけずれてるのが分かります。
つまり…

1.Y方向シフトに対するパラメータと、X方向シフトに対するパラメータを持つ。
2.シフトの値は -1~1 の間で選べるようにする。
3.UVのY座標 = Y方向シフト × 時間
  UVのX座標 = X方向シフト × 時間 で計算してみる…と以下のようになります。
uvspeed.gif

透過について

透過するかどうか?はプログラマブルではなく、プロパティなのでShaderLab側の設定になります。
UVスクロールでバーニアを出したい!とう需要はあると思うで、やっぱ透過テクスチャに対応してなんぼのもんじゃい。

    SubShader
    {
        Tags
        {
            "Queue"      = "Transparent"
            "RenderType" = "Transparent"
        }
        Blend SrcAlpha OneMinusSrcAlpha 

        Pass
        {
            CGPROGRAM
            //(略)
            ENDCG
        }
    }

上記のように「SubShader内、Passの外」に Blend SrcAlpha OneMinusSrcAlpha を追記します。
そして Tags"Transparent" にします。
一応、処理上はTagsを変更しなくとも、直接RenderQueueを上げればいいのですが、Unityはざっくり スカイボックス→不透明→透明 という風に標準のレンダリング順がきまってます。
なので、透明なテクスチャを使う場合は Tags「透明なものをレンダリングする時に、レンダリングしてね」と伝える事で、レンダリングの不整合を防ぎます。

ShaderLab: Blending
透明なオブジェクトの作成について。

RenderQueue
Unityが決めてるレンダリング順の定義

できあがったShader

UVScroll.shader
Shader "Unlit/UVScroll"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}

        //X方向のシフトとスピードに関するパラメータを追加
        _XShift("Xuv Shift", Range(-1.0, 1.0)) = 0.1
        _XSpeed("X Scroll Speed", Range(1.0, 100.0)) = 10.0

        //Y方向のシフトとスピードに関するパラメータを追加
        _YShift("Yuv Shift", Range(-1.0, 1.0)) = 0.1
        _YSpeed("Y Scroll Speed", Range(1.0, 100.0)) = 10.0
    }
    SubShader
    {
        Tags
        {
            //レンダリング順に関する指示
            "Queue"      = "Transparent"
            "RenderType" = "Transparent"
        }
        //透明なテクスチャを使用する場合に必要なプロパティ
        Blend SrcAlpha OneMinusSrcAlpha 
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            //追加したパラメータを宣言する
            float _XShift;
            float _YShift;
            float _XSpeed;
            float _YSpeed;

            //バーテクスシェーダー(変更なし)
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            //フラグメントシェーダー(変更箇所)
            fixed4 frag (v2f i) : SV_Target
            {
                //Speed
                _XShift = _XShift * _XSpeed;
                _YShift = _YShift * _YSpeed;

                //add Shift
                i.uv.x = i.uv.x + _XShift * _Time;
                i.uv.y = i.uv.y + _YShift * _Time;

                //i.uvの適用
                fixed4 col = tex2D(_MainTex, i.uv);
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

まとめ

ひとまずUVの基本的な処理についてまとめました。
UVスクロールを題材にしたのは Unity道場 2019.1 俺はUVスクロールがしたかっただけなんだ! という話を思い出して、アーティストの人に需要ありそうだとおもったので。

Unity、アウトラインを出してみる【Shader : 3】

Why do not you register as a user and use Qiita more conveniently?
  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
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