はじめに
この記事は連載記事です。
記事を読むにあたって**レンダリングパイプラインでどのような処理を行っているのか?**を知っている事は前提になります。
非常に分かりやすい資料がありますので、レンダリングパイプラインを知らない方は読む前にご覧ください。
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スクロールを端的に表すと**「時間経過によってテクスチャの位置をずらしてほしい」である。
言葉で言えばたったこれだけの事で、難しい原因はこれをコンピューターに伝えなきゃならない部分である。
知りたいのは「何を・どこに書けばいいか?」**という事だ。
まず、適当な名前でUnityのデフォルトのUnlitShaderを作成してください。
UnlitShaderをベースに改造してゆきます。
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.uv
と i.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座標にも _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
に**小さい値を掛ければ遅くなりますし、大きな値を掛ければ早くなります。**非常にシンプルです。
向きと速度
先ほどのサンプルでは X座標とY座標に直接時間を足していた ので、+Y方向と+X方向に同じだけずれてるのが分かります。
つまり…
1.Y方向シフトに対するパラメータと、X方向シフトに対するパラメータを持つ。
2.シフトの値は -1~1 の間で選べるようにする。
3.UVのY座標 = Y方向シフト × 時間
UVのX座標 = X方向シフト × 時間 で計算してみる…と以下のようになります。
透過について
**透過するかどうか?**はプログラマブルではなく、プロパティなので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
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スクロールがしたかっただけなんだ! という話を思い出して、アーティストの人に需要ありそうだとおもったので。