春休みでShaderを完全に理解する(予定)もくもくスクラップ

VRChatでShaderを使いたいので春休みで頑張ろうのスクラップです。
ほぼ転写ですが、手を動かして覚えていきます

参考資料・あとで読む記事

シェーダーとは?
コンピュータへの指示の集まりで、画面上の全てのピクセルに対して同時に指示を与える方法
ピクセルの位置を入力として色を返す関数として働き、一度に画面全体を書き出す。
(版で本を印刷するような感じ)
シェーダーはなぜ速いのか
CPU:すべてのタスクを順番に受付け、一つずつ終わらせていく
工場の作業レーン:パイプに乗ってタスクは流れていく
(このパイプがスレッドと呼ばれる)
→スクリーンの描画等膨大な処理を行う必要がある(画像コンテンツをピクセルに描画するために、ピクセルごとに計算を必要とするなど)ときかなりの負荷、足りない
そこで並列処理を行う!
大きくて強力なプロセッサー(パイプ)の代わりに、たくさんの小さなプロセッサーを動かす
→GPU(Graphic Processor Unit)
例)800x600本の小さなパイプなら480,000個の処理を行うことができ、大きなデータの流れを扱うことができる。
特定の数学的な関数がハードウェアで高速に処理される(直接チップで処理!)ため、三角関数や行列計算を高速に行うことができる。
GLSLとは
GLSLはopenGL Shading Languageの略、標準化されたシェーダー言語の1つ。

ShaderのHello World!
サンプルコードを読む
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
void main() {
gl_FragColor = vec4(1.0,0.0,1.0,1.0);
}
ショッキングピンク!
このコードから読み取れること
- シェーダー言語は結果として色を返すmain()関数を持っている。
- 最終的なピクセルの色は、予約語として確保されたグローバル変数、gl_FragColorに割り当てられる。
- 組み込みの「変数」や「関数」、「型」がある。
- 不動小数点精度を持つ4次元ベクトル「vec4型」が使われている。
- C言語のように、マクロはコンパイルの前に処理される。→マクロを使える
- グローバル変数を定義(#define)したり、#ifdef・#endifを使って場合分けを行うことができる。
- すべてのマクロの命令は**ハッシュタグ(#)**で始まる。
- コンパイル直前にすべての#defineの呼び出しが置き換えられ、#ifdef・#endifの条件文チェックが行われる。
- サンプルのコードでは、GL_ESが定義されているときのみ、2行目のコードを挿入する。
- GL_ES:モバイル機器やブラウザー上でコンパイルされたことを意味する
-
浮動小数点はシェーダーに不可欠!精度がめっちゃ大事。
- 精度が低いとレンダリングが早くなる代わりに品質が下がる。
- こだわりたいなら、GLSLでは浮動小数点を使うそれぞれの変数ごとに精度を指定できる。
- 2行目の
precision mediump float;
は、すべての浮動小数点型の変数に中レベルの精度を指定している。 - より低い精度(
precision lowp float;
)より高い精度(precision highp float;
)を選ぶこともできる。
-
変数の自動的な型変換は保証されていない。
- 自動的な型変換は最低限の仕様に含まれていない。
- vec4にはfloat型の値が割り当てられる。
- 浮動小数点型の値には小数点(.)を使うことを習慣にしよう。
-
gl_FragColor = vec4(1,0,0,1); // ERROR
は不適切。

ユニフォーム変数
シェーダーではスレッド間の情報のやり取りを行うことはできないが、CPUからそれぞれのスレッドに入力を送ることはできる。
グラフィックカードはすべてのスレッドに全く同じ入力を、読み取り専用に送るように設計されており、
スレ度はその情報を書き換えることはできない。
これらの入力はuniform変数と呼ばれ、GLSLでサポートされているほとんどの型が使用できる。
サポートされている型:float、vec2、vrc3、mat2、mat3、mat4、samplar2D、samplarCubeなど...
uniform変数はシェーダーの冒頭、浮動小数点精度の設定の後に型指定付きで指定する。
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution; // Canvas size (width,height)
uniform vec2 u_mouse; // mouse position in screen pixels
uniform float u_time; // Time in seconds since load
サンプルでは一貫して
- u_time(シェーダーが開始してから経過した秒数)
- u_resolution(シェーダーが描画する領域の大きさ)
- u_mouse(描画領域の中のマウスの位置)
を渡す。
他のサイトなどでは異なる名前が使用されていることがある。

実際にやってみた
下記のコードでは、u_time(シェーダーが実行を始めてからの秒数)をサイン関数と組み合わせて使い、赤い画面の色を変化させています。
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
void main() {
gl_FragColor = vec4(abs(sin(u_time)),0.0,0.0,1.0);
}
GPUの驚くべき機能の1つには、角度や三角関数、指数関数などがハードウェア上で高速に処理されることが挙げられます。
サポートされる関数には
sin()、 cos()、tan()、 asin()、acos()、 atan()、pow()、 exp()、log()、 sqrt()、abs()、 sign()、floor()、 ceil()、fract()、 mod()、min()、 max()、clamp() などがある。

gl_FragCoord
GLSLにはデフォルトの入力として画面上の「フラグメント」、
つまりピクセルの位置を表すvec4 gl_FragCoord
が用意されている。
スレッドが描画領域内のどこで作業をしているかを知ることができる。
このgl_FragCoordはスレッドごとに異なる値を持っているためuniform変数とは呼ばず、
代わりにvarying変数と呼ぶ。
サンプル
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution;
gl_FragColor = vec4(st.x,st.y,0.0,1.0);
}
st.xのとき左から右に行くにつれて0.0~1.0に変化する

ここでエディターを使える

アルゴリズムで絵を描く
シェイピング関数
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
// Plot a line on Y using a value between 0.0-1.0
float plot(vec2 st) {
return smoothstep(0.02, 0.0, abs(st.y - st.x));
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution;
float y = st.x;
vec3 color = vec3(y);
// Plot a line
float pct = plot(st);
color = (1.0-pct)*color+pct*vec3(0.0,1.0,0.0);
gl_FragColor = vec4(color,1.0);
}
vec3 color = vec3(y);
では、vec3 型のコンストラクタに値を1つだけ渡している。
このとき、コンストラクタは3つの色のチャンネルに同じ値を割りてようとしているのだと解釈する。
// 以下2つは同じ意味
vec3 color = vec3(1.0);
vec3 color = vec3(1.0,1.0,1.0);
// 三次元のベクトルともう1つの値(ここではalpha、つまり透明度)で初期化されている
gl_FragColor = vec4(color,1.0)
gl_FragColor = vec4(1.0,1.0,1.0,1.0)

難しすぎか.......??????
挫折しそうなのでこっちに寄り道します