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

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

<a target="blank" href="https://www.youtube.com/watch?v=wUx_Y9BgC7k">Unity道場 2019.2 シェーダを書けるプログラマになろう #1 シェーダを理解しよう</a>
<a target="blank" href="https://www.youtube.com/watch?v=-Ki1Q2CAqEc">Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘</a>

**アーカイブ**
<a target=”_blank” href="https://qiita.com/Nekomasu/items/6620ab19bfdaeb7743f2">Unity、Shaderことはじめ【Shader : 0】</a>
<a target=”_blank” href="https://qiita.com/Nekomasu/items/2fbe8ef5ecfa5f81947a">Unity、UnlitShader/textureのコードを追いかける【Shader : 1】</a>

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

「UVスクロールなんて地味なテクニック興味ないんじゃ！派手派手で盛り盛りな奴くれ！」という方には、そういう導線があるにはありますので、そちらをおすすめします。
<a target=”_blank” href="https://wgld.org/sitemap.html">wgld.org</a>
<a target=”_blank” href="https://indievisuallab.stores.jp/">Unity Graphics Programming</a>

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

![uv.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/40ae0f28-18bf-88d2-5def-73fdf13e3e9b.gif)

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

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

![u1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/b24a3f4b-cb31-f106-5606-0e6195a8bcd8.png)

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

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

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

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

```c
//バーテクスシェーダー
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;
}
```

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

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

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

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

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

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

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

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

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

```c
//バーテクスシェーダー
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によって定義されてる値で、**宣言しなくても使用できる。**
これらの定義済みの値は下記で確認する事ができる。
<a target=”_blank” href="https://docs.unity3d.com/jp/460/Manual/SL-BuiltinValues.html">ShaderLab 定義済みの値</a>
そして、_Timeの中には当然時間が入ってる。

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

![uv.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/40ae0f28-18bf-88d2-5def-73fdf13e3e9b.gif)

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

```c
//フラグメントシェーダー
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の領域をオーバーしてしまいます。
しかし、上記のサンプルでは問題なくループしているように見えるのは何故か？

<a target=”_blank” href="https://youtu.be/-Ki1Q2CAqEc?t=344">Unity道場 2019.2 シェーダを書けるプログラマになろう #2 GPUの神秘</a>
**※5:44から再生します。**

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

## HLSLの関数について

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

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

<a target=”_blank” href="https://docs.microsoft.com/ja-jp/previous-versions/direct-x/bb509611(v=vs.85)">組み込み関数 (DirectX HLSL)</a>
はい、Microsoftの方のリファレンスを見る必要があるわけですね。
ちなみに <a target=”_blank” href="https://docs.microsoft.com/ja-jp/windows/desktop/direct3dhlsl/dx-graphics-hlsl-tex2d">tex2D</a> は察しろよって感じのリファレンスになってます。

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

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

**向きと速度**

![uv2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/2ed12fad-623d-fcff-b1db-672deafb5f80.png)

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

１．Y方向シフトに対するパラメータと、X方向シフトに対するパラメータを持つ。
２．シフトの値は -1～1 の間で選べるようにする。
３．UVのY座標 ＝ Y方向シフト × 時間
　　UVのX座標 ＝ X方向シフト × 時間　で計算してみる…と以下のようになります。
![uvspeed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149449/1af3e1f2-a56c-0e7d-634e-a3c3a4bcac00.gif)

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

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

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

<a target=”_blank” href="https://docs.unity3d.com/ja/2018.1/Manual/SL-Blend.html">ShaderLab: Blending</a>
透明なオブジェクトの作成について。

<a target=”_blank” href="https://docs.unity3d.com/ja/2018.2/ScriptReference/Rendering.RenderQueue.html">RenderQueue</a>
Unityが決めてるレンダリング順の定義

## できあがったShader



```c: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スクロールを題材にしたのは <a target=”_blank” href="https://www.youtube.com/watch?v=UVo5AsUyyG0">Unity道場 2019.1 俺はUVスクロールがしたかっただけなんだ！</a> という話を思い出して、アーティストの人に需要ありそうだとおもったので。

## 次
<a target=”_blank” href="https://qiita.com/Nekomasu/items/5738b94b62a99f487f58">Unity、アウトラインを出してみる【Shader : 3】</a>