UnityでShaderをはじめる
2018/12/16のハンズオンを終えて、こんな絵ができました!
https://gyazo.com/2295484ee8788dbd1e93eeeafe7e0d9a
code:HLSL
// ハンズオン結果!
float3 scgHUE2RGB(float h)
{
float r = abs(h * 6 - 3) - 1;
float g = 2 - abs(h * 6 - 2);
float b = 2 - abs(h * 6 - 4);
return saturate(float3(r, g, b));
}
float scgLiner2(float2 p, float2 a, float2 b, float r)
{
float2 pa = p - a;
float2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0, 1);
return length(pa - ba * h) - r;
}
float scgPentagram(float2 p, float r) {
float2 st = 20 * (p - 0.5);
float d = 1;
for (int i = 0; i < 5; i++) {
float rad1 = 2 * UNITY_PI * i / 5;
float rad2 = 2 * UNITY_PI * (i + 2) / 5;
float2 v1 = float2(cos(rad1), sin(rad1)) * r;
float2 v2 = float2(cos(rad2), sin(rad2)) * r;
d = min(d, scgLiner2(st, v1, v2, 0.2));
}
return d;
}
fixed4 frag (v2f i) : SV_Target
{
float d = scgPentagram(i.uv, 8);
return fixed4(scgHUE2RGB(d), 1);
}
私家版Shader勉強のてびき。 では、くどいくらい「フラグメントシェーダーから始めましょう」と書きました。
このページでは、Unityでフラグメントシェーダーを始めるやり方を書いていきます。
https://gyazo.com/0efecbeee81de08401b9efd19d057eda
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
このページのゴール
読みながらUnityで手を動かして、実際にフラグメントシェーダーを動かすこと。
これはハンズオン向けの資料です。手を動かしましょう!
https://gyazo.com/9420ce835c5245ede79df4b68505bb6e
極力詳細に噛み砕いて「どうやるの?」を記載しました。シェーダーを書き始める一助になれたらと思います。
目的地
以下のような資料を読むためのとっかかりを手に入れる(環境の作り方と手を動かし始める方法、前提になる知識)
楽しい!Unityシェーダーお絵描き入門!/setchi 2018/04/10 サポーターズCoLab勉強会
この資料は、とても良いシェーダーについての楽しい資料です。でも、私は最初これを読んで何がどうなっているのかさっぱりわからず、楽しいとは程遠い境地でした。
本ハンズオンで「楽しい」に到達するための手がかりを手に入れましょう。
ところで、シェーダーをやり続けるとどんなところに到達するのですか??
「UnityでShaderをはじめる」で扱わないこと
謝辞
数学とシェーダーの偉大な先人たちに、ここで敬意を表します。お陰でこの資料を書くことができました。
資料集
シェーダーについて役に立つリンクを集めて整理してみたよ。
Shader - さやちゃんぐbotスクラップス
シェーダーに関わる用語を集めてみたよ。一度も見たことがない用語がないかチェックしてみよう。
Shader Technical Terms
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
もくじ
なにをするの?
シェーダーができるとなにができるの?
私家版Shader勉強のてびき。のなにができるかをもう少し
開発環境を作る
Unityをダウンロードする
Unityとエディタをインストールする
Visual Studioの拡張プラグインをインストールする
Shader Unity Support
Unityのプロジェクトを始める
板ポリを配置してついでにカメラ位置を調整する
Unlitのシェーダーファイルとマテリアルを作る
座標とは
デカルト座標
xyzw、そしてuv
トランスフォームの行列
いよいよ本番!シェーダーファイルをVisual Studioで開く
Unlitのシェーダーファイルをダブルクリックする
実践!フラグメントシェーダー
frag()とは
座標
3つの要素「形、色、時間」
座標を色で可視化する
座標で0.0から1.0までの数字を作る
フラグメントシェーダーの部品
変数と関数
入力と出力
入力→座標と時間、出力→形と色
組み込みの関数
シェーダー関数(Unityで使うCg/HLSL)
共通ファイル(cginc)を作る
Sayachang.cginc
※ハンズオン用のコンパクトな SayachangKompakt.cginc を使います
cginc使い方サンプル Sayachang.cginc sample
丸を描く
時間で動かす
トランスフォームの行列再び
色をつける
RGB
色で0.0から1.0までの数字を作る再び
時間で動かす
時間で0.0から1.0までの数字を作るさらに再び
数字をグラフで見て数式の気持ちを知る
Desmos
インスペクターのGUIからルックを調整する
いろいろな図形を描いてみる
モーフィングの魔法
奥義ディスタンスファンクション
distance functions
終わりに:次に進むために
私家版Shader勉強のてびき。のなにができるかをもう少し再び
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
なにをするの?
3Dコンピュータグラフィクスプログラミングの入り口のところをやるよ。
もし、このページを読んで、Unityを開いてプログラムを書いたなら、君も3Dグラフィックプログラマーの入門者だよ。
シェーダーができるとなにができるの?
私家版Shader勉強のてびき。のなにができるかをもう少しに書いてみました。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
Unityをダウンロードする
Unity HUBを使いましょう。お手軽に最新バージョンや安定バージョン、ベータ版のUnityをインストールできます。
Unity をダウンロード
もしまだなら、この機会にUnityのユーザ登録をしましょう。
Unityとエディタをインストールする
https://gyazo.com/ac58bc25dc64e36dac9a1ccee43d667f
Unity HUBではボタンをDownloadボタンをクリックするだけで自動的にUnityをインストールできます。
もし、スクリプトのエディタをまだインストールしていないなら、Windows使いならひとまずVisual Studioの無料版をインストールしましょう。(Macについてはお任せします)
Visual Studioを初めてインストールするなら、この機会にMicrosoftの開発者アカウントを登録しましょう。
Visual Studioの拡張プラグインをインストールする
WindowsでVisual Studioを使ってシェーダーを書くなら、プラグインを使うとキーワードがハイライトされ、コード補完も使えるようになります。
Shader Unity Support
https://gyazo.com/0ad27f3b63432ae1d2d2a4b7b7baf921
このプラグインは、ダウンロードしたファイルを起動するだけでインストールできます。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
Unityのプロジェクトを始める
Unity HUBから新しいプロジェクトを作成します。
https://gyazo.com/443dbeaec6cee9443e69264fadf1f470
板ポリを配置してついでにカメラ位置を調整する
Quad(またはPlane)を選んで正方形の板をシーンに配置し、位置を動かして回転させます。これがシェーダーを作るための土台です。
https://gyazo.com/f8282b5343b633ba330e463b18f0a2b6
裏表があることに注意します。表から見ると板が見えますが、裏からだと向こうが透けてしまいます。
興味がある人は、カリングということばを調べてみましょう。
https://gyazo.com/272108a501703edbd7beecf896654576
シェーダーを使って表からも裏からも見えるように変更した例。
カメラは板が見えるよう、同様に位置と回転を調整します。
https://gyazo.com/e4a4ee3680c0e818e780b7e3600c57e7
Positionが座標、Rotationが回転です。XYZのどれかが縦横奥行きのいずれかに対応しています。
まだ不慣れなのであれば、考えずに感じましょう。マウスでXやYの文字を左右にドラッグすると、数字を増やしたり減らしたりできます。
もし画面に見えないどこかにいってしまい、どうしても戻すことができなくなったなら、キーボードからすべての数字をゼロにしてみましょう。
https://gyazo.com/178f2be5c13d2dfe03389ba7d5e10fd6
Unlitのシェーダーファイルとマテリアルを作る
Unlitを選んでシェーダーファイルを作成します。続けて、作られたファイルを右クリックしてマテリアルを作成します。
このマテリアルを板に適用すれば、準備は完了です。
※Unlitとは、Un-Lighting(ライティングを使わない)を省略した名前です。
https://gyazo.com/c64d6274161a939e9e88e71d4e74b70c
シェーダーファイルにはいいかんじの名前を付けておきましょう!
迷ったらtest1とかtest2みたいな名前にしておきましょう!!
https://gyazo.com/4e2a1246fd69c0ab6fb133e728ccd667
https://gyazo.com/bca6bde8e458ff322fb051ed48f0d220
座標とは
ここまで「位置を動かし」たり「回転させ」たりという操作をいくつかしました。
シェーダーの世界でこれらを扱うには、座標を意識することになります。
デカルト座標
ここでいくつか用語を挙げてみます。ユークリッド空間、デカルト座標、アフィン写像、直交座標系、クォータニオン…。
数学の用語について、厳密な定義の話はしません!平面をxとyで、空間をxyzであらわし、それぞれの軸が垂直になっているということだけを覚えておきましょう!!
雑に進めます!
https://gyazo.com/4852b10d72561a2250cf3fd7231a970a
xyzw、そしてuv
3次元の座標を扱うには、4x4の行列を使います!「3」次元を扱うのに、「4」つ目のwってやつはどこからきた?!ここでは華麗にスルーして進みます!!
シェーダープログラの世界ではwが登場しますが、今日はたぶん使いません。頭の片隅にとどめておきましょう。アルファベットの並びで、xyzのひとつ前がwです。
そしてモデリングの世界ではuv展開、uv座標といった用語が出てきます。驚くべきことに、uとvはアルファベットでwの前です。
これでもうみんなuvマスターです!
(座標と言えばMVP!ではありますが、複雑にしないためここではその話はしません。)
トランスフォームの行列
座標を動かすという操作には、平行移動、回転、拡大縮小があります。
これらは行列の足し算と掛け算で表現できます。
これらの平行移動、回転、拡大縮小は、行列を使うといっぺんに計算できます。そして、その時に使う行列は4x4なのです。
もっと深く知りたい人は、Unityでもおなじみクォータニオンについて調べてみましょう。
安原神の動画とスライドがオススメです。
https://www.youtube.com/watch?v=uKWLPU8gfIY
【Unity道場スペシャル 2017博多】クォータニオン完全マスター
このハンズオンが対象とするのは、クォータニオンの行列を見て「うっ!!!」となる人です!
できるだけ数学の話はしない!!どうしても必要なら最低限の部分だけします!
https://gyazo.com/ba1f15a5f4fd67f18134e36395284035
※行列は座標やベクトルの計算に良く使われますが、「こみいった計算をビジュアル化してわかりやすくするもの」というところが真骨頂です。
以下の書籍を読むのもオススメです。
マンガでわかる線形代数
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
いよいよ本番!シェーダーファイルをVisual Studioで開く
Unlitのシェーダーファイルをダブルクリックする
シェーダーファイルをダブルクリックすると、エディタで開かれます。
https://gyazo.com/70ab73d63d5bc25958a910b088f94bd2
Unlitシェーダーの先頭の一部。
実践!フラグメントシェーダー
frag()とは
https://gyazo.com/66920aeab6e59cd431727f51b190e19b
frag関数で色を出します!今日はここを中心にいじっていきます。
最初から書かれている内容はほとんど使いません。
思い切って{から}までの中身をごっそり消します。何もないと不安な人は、例えば以下のように書いてみてください。
https://gyazo.com/47ac537f9327467e9ee7e93035ee3d31
※i.uv.xxxxはfixed4(i.uv.x, i.uv.x, i.uv.x, i.uv.x)と書くのと同じ動きをする
座標
ここで扱う座標は、縦と横に垂直にまじわった2本の軸です。
左から右に向かうのがu、下から上に向かうのがvだととらえてください。
原点が(0, 0)、右上の正方形の端が(1, 1)です。
https://gyazo.com/9fb36bce4d924241dc21d79e31feda59
この図が、Unityに出した正方形の板に対応します。
3つの要素「形、色、時間」
正方形に座標があることはわかりました。ただ、いったいここからどうすれば絵が描けるのでしょう?
シェーダーには、座標以外に、形、色、時間というさらに3つの要素があります。
正方形の中身は、たくさんの座標の集まりです。例えば、(0, 0), (0.001, 0.234), (0.765, 0.765), (1, 1)といった座標の、とてもたくさんの点があると想像してください。
このそれぞれの点に色を決めれば、正方形に絵を描くことができます。
座標は0.0という数字から、1.0という数字の範囲であらわすことを覚えてください。
上で例に挙げたi.uv.xxxxという絵は、以下のようになります。
https://gyazo.com/0807b25805929519aac0b1510c9b4ebe
なぜこうなるのか?という説明には、色を数字でどう表現するのかという話が必要です。
https://gyazo.com/6845c308fc3c1d64d04db1b1a93ab67f
Unityのカラーピッカーを開くと、色というものがRGBAの4つの項目で表現されていることがわかります。Rが赤、Gが緑、Bが青で、最後のAはアルファ値(透明度)です。これはシェーダーでも同様です。
数字の範囲については異なります。カラーピッカーでは0から255の範囲で扱います。0は暗く(黒)、255は明るい(白)色をあらわします。RGBAの値をそれぞれ決めて、混ぜ合わせた結果が我々の眼に見える色です。
シェーダーでは、この0から255という値のかわりに、0から1のあいだで数字を扱います。
例えば(0.987, 0.876, 0.012, 1.000)という数字を割り当てると、シェーダーで黄色を表示できます。RとGに大きな数字を当てていることに注目してください。
このプログラムは座標がどこであるかに関わらず、「常に点を同じ色で塗る」という処理になります。
https://gyazo.com/72b214d648b762166f95c462681307b4
https://gyazo.com/fff89b8ece8feddd9e56f78e3ccedc91
「i.uv.xxxx」の説明に戻りましょう。これには少しプログラムの読み方の説明が必要です。
下記のリンクを読んでみましょう。
構造体、引数、uv、そしてスィズル
座標を色で可視化する
先の話をフラグメントシェーダーで試してみましょう。
※ここから先にあるプログラムは、特に断りがなければfrag()の{と}で囲まれた本文を記載します。
code:HLSL
return fixed4(i.uv, 0, 1);
// これはfixed4(i.uv.x, i.uv.y, 0, 1)と書くのと同じ動きをします
https://gyazo.com/266ba0ef5b82b11db40e47bce3eebfc4
座標は0から1のあいだの数字であらわすことを思い出してみましょう。
そして、色はRGBAの4つそれぞれで0から1の数字を決めてあげるものでした。
書いたプログラムが、「座標を元に色を決めた」ものになっていることを確認してみてください。
ここでは、正方形の中の各部分が座標によって数字が変わっていくので、グラデーションという「形」を描くことができています。
処理の概念
もし、ここまでの説明の前に、以下のようなプログラムを想像していたなら、フラグメントシェーダーに違和感を持つかもしれません。
code:calcXY
for ( int x = 0.0, x <= 1.0; x = x + 0.0000001) {
for ( int y = 0.0, y <= 1.0; y = y + 0.0000001 ) {
上のようなプログラムを書く必要はありません。
フラグメントシェーダーでは、すべてのピクセル(フラグメントと呼びます)を自動的に、かつ効率的に(並列処理で)計算してくれるのです。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
質問と確認
うまく飲み込めない人はここで質問をしてみてください。
オンラインならTwitterでこれを書いた人(@songofsaya_)に質問を投げてみるのもいいでしょうし、Discordのチャンネル(なんもわからん部屋)で誰かに答えてもらうこともできるでしょう。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
座標で0.0から1.0までの数字を作る
ここまでで、座標という0から1までの範囲にある数字を入力として、0から1までの範囲の色を作るということがわかりました。
ところで、前に出た「形」と「時間」についてはまだあまり説明していません。
時間は「_Time.y」という書き方で使えます。
先ほどのプログラムに少し手を加えて、どう変わるのか見てみましょう。
code:HLSL
return fixed4(i.uv, sin(_Time.y) * 0.5 + 0.5, 1);
// fixed4(i.uv.x, i.uv.y, sin(_Time.y) * 0.5 + 0.5, 1)と同じです
https://gyazo.com/241aefac21888d89ee6ba57efdc45dc1
フラグメントシェーダーのゴールは、計算を行った結果が0.0から1.0のあいだの数字になることです。
突き詰めれば、シェーダープログラミングはこの数字を作り出すことになります。
組み込み関数には、0.0-1.0の数字を作り出す便利なものがたくさんあります。
sin、frac、clampなどを使い慣れていきましょう。
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
形については、いくつかの図形の書き方を Shader Theory に載せてあります。
ここでは円を描いてみます。
code:HLSL
float2 st = i.uv - 0.5; // i.uv.xy - float2(0.5, 0.5)。
// ここでは座標の範囲を(0,0) - (1,1)から(-0.5, -0,5) - (0.5, 0.5)にずらして、正方形の真ん中を原点に変更しています。
return step(distance(st, 0), 0.2);
// これは step(distance(st, 0), 0.2).xxxx や
// fixed4(step(distance(st, 0), 0.2), step(distance(st, 0), 0.2), step(distance(st, 0), 0.2), step(distance(st, 0), 0.2))
// と書くのと同じです
https://gyazo.com/df7ada2387979bf7309c0f2ce956943b
ここでsin()やstep()、distance()といったものが登場しました。
また、なぜこれらの数式とプログラムでこんな図形になるのか、まだピンと来ないかもしれません。
次を見ていきましょう。
フラグメントシェーダーの部品
ここまでいくつかの道具が登場しました。変数と関数を利用して、座標と時間から形と色を導き出していたのです。
フラグメントシェーダーで利用する、これら部品の使い方を見ていきましょう。
変数と関数
変数は「計算途中の数字」を格納しておくための入れ物です。先に出てきた、座標から色を決めたプログラムを思い出してみましょう。
code:HLSL
float2 st = i.uv - 0.5;
「st」は小数を扱うことができ、配列で2つの値を使うことができる変数です。
例えば、座標uvが(0.123, 0.234)だとしましょう。右辺を計算して、左辺に代入します。
その結果、stには[-4.23][-3.34]という2つの数字が入ることになります。
同様に、float3では3つの小数点がついた数字を、float4やfixed4では4つの数字を持つことができます。
関数について見てみましょう。
code:HLSL
float t = sin(_Time.y);
sin()はサイン関数です。
入力と出力
関数とは、すごくおおざっぱに説明すると、ある数字を入力として渡すと、計算した結果を出力として返してくれる仕組みです。
ここまでで組み込み関数をいくつか見てきました。sin()、step()、distance()は組み込み関数です。
入力のパラメータの型と変数を自由に組み合わせて、それらを加工する計算式を書き、指定したパラメータを出力する関数を自分で作ることもできます。
ここでいちどfrag()関数について振り返ると、座標を入力して色を出力するというものでした。
入力→座標と時間、出力→形と色
フラグメントシェーダーというくくりで考えると、入力と出力には4つの要素があります。
入力には座標と時間が利用できます。
座標と時間を使ってさまざまな計算を行うと、出力として形と色が決まります。
シェーダープログラミングでは、この4つの要素をどう扱うかというさまざまなセオリーやテクニックが存在します。
組み込みの関数
組み込み関数についてもう少し見ていきます。
以下のリンクにシェーダーでよく使う関数をいくつか説明しました。
これまで出てきた関数について、ここから探してみてください。
シェーダー関数(Unityで使うCg/HLSL)
https://gyazo.com/6c4e53bb3465d9ddf45ab1bf696385f2.png
共通ファイル(cginc)を作る
ひとつのシェーダーファイルにすべての処理を書いても構いませんが、書いたプログラムを再利用するためにcgincというファイルが利用できます。
拡張子をcgincとしたファイルを用意すれば、シェーダーからファイルを取り込んで定数や関数を使いまわすことができます。
Unity上からではなく、OSのエクスプローラーやファインダーから直接ファイルを作成してみてください。
code:include cginc
#include "UnityCG.cginc"
#include "Sayachang.cginc"
Sayachang.cginc
※ハンズオン用のコンパクトな SayachangKompakt.cginc を使います
cginc使い方サンプル Sayachang.cginc sample
丸を描く
code:HLSL
fixed4 frag(v2f i) {
return scgCircles2(i.uv);
// これは省略した書き方です。すべてを省略せずに書くと以下のようになります。
// return fixed4(i.uv.x, i.uv.y, 0, 0);
}
時間で動かす
code:HLSL
return scgCircles2(float2(i.uv.x + 0.3 * sin(_Time.y) , i.uv.y));
トランスフォームの行列再び
code:HLSL
float2 uv = 2.0 * i.uv - 1.0;
uv = mul(float2x2(cos(_Time.y), -sin(_Time.y), sin(_Time.y), cos(_Time.y)), uv);
return distance(uv, float2(0.2, 0.4)) - 0.3;
色をつける
RGB
code:HLSL
fixed4 frag(v2f i) {
float c = scgCircles2(i.uv);
return scgRed(c);
}
例題: 緑や青に変更してみましょう。黄色や紫にできるかも試してみてください。
色で0.0から1.0までの数字を作る再び
code:HLSL
// 計算式を書いてみましょう
時間で動かす
code:HLSL
// 計算式を書いてみましょう
時間から色を決める
時間で0.0から1.0までの数字を作るさらに再び
code:HLSL
// 計算式を書いてみましょう
数字をグラフで見て数式の気持ちを知る
Desmos
https://gyazo.com/7a3f98bc64868515e25771264befecf5
インスペクターのGUIからルックを調整する
code:HLSL
// Propertiesのところに1行書き足します
_Speed("Speed", Range(0.0, 100.0)) = 1.0
// 対応する変数の行を増やします
// float4 _MainTex_ST;の下の行に書けばOK
float _Speed;
// frag()で変数を使います
float uv = sin(_Time.x * _Speed) * (2.0 * i.uv - 1.0);
いろいろな図形を描いてみる
code:HLSL
return scgTriangle(i.uv);
モーフィングの魔法
上で図形はひとつのfloatの数字で扱う例をいくつか確認しました。
もし、座標から複数の図形を示す数字をいくつか作り出し、それを時間で切り替えれば表示する形を切り替えることができます。
ここではシンプルなふたつの図形をモーフィングのように表示してみます。
code:HLSL
float scgRound2(float2 p, float rad)
{
float2 st = p - 0.5;
return step(distance(st, 0), rad);
}
float scgTriangle(float2 p){
float2 st = p - 1;
st = scgRotate(st, PI / 6);
// Number of sides of your shape
int N = 3;
// Angle and radius from the current pixel
float a = atan2(st.y, st.x) + PI;
float r = 2 * PI / float(N);
// Shaping function that modulate the distance
float d = cos(floor(0.5 + a / r) * r - a) * length(st);
return 1.0 - smoothstep(0.4, 0.41, d);
}
float scgMorph(float a, float b) {
return lerp(a, b, sin(_Time.y) * 0.5 + 0.5);
}
// frag()を以下のように書き換えます
float r = scgRound2(i.uv, sin(_Time.y));
float t = scgTriangle(i.uv);
return scgColorful3(scgMorph(r, t));
奥義ディスタンスファンクション
ディスタンスファンクション(距離関数)は、レイトレーシングの分野で役に立つ関数です。
ある場所(例えば原点)から、任意のuvの場所までの長さがわかれば、それを利用して円・球が描けます。
なんと、これを応用・発展させると、さまざまな図形を描くことが可能になります。(実はこれまでで出てきた図形はディスタンスファンクションを使って描かれています)
シェーダーの世界では神さまのような存在であるiq神が、ディスタンスファンクションを使って様々な図形を描く方法をまとめています。
終わりに:次に進むために
私家版Shader勉強のてびき。のなにができるかをもう少し再び
このハンズオンでは、シェーダーを使うためのいろいろな「基礎」のツールを紹介し、実際にそれに触れてもらいました。
シェーダーを使ってさまざまな分野で効果を発揮することができるのは、他の資料で紹介しています。
しかし、シェーダーに触り始めてもすぐにそこに到達するには遠いかもしれません。
どのような高度な、複雑なものも、土台の部分にシンプルな基礎があります。
ある程度のレベルまでは、それら基礎のしっかりとした理解と組み合わせで到達できるハズです。
本ハンズオンで手に入れた基礎の技から、さらに追加でいろいろと調べ、手を動かしてみてください。
時間がかかっても、いつの日か必ず実現できるようになります。