軽率にRaymarchingを写経した
最近シェーダープログラムのお勉強をしているのですが
【ゆるぼ】
— さやちゃんぐbot (@songofsaya_) December 27, 2018
これを写経して「どこがわからん」とかフィードバックしてくれる方 https://t.co/yNDeyISmQo
ということなのでいい機会だからやってみることにしました
写経するのはこちらのシェーダープログラム
UnityのUnlitシェーダーで軽率にRaymarchingを始めるハンズオン
まだ途中ですが以下わからんところ
1-1.とりあえずピンクにしてみる
fixed4 frag(v2f_img i) : SV_Target { }わかる
1-2.とりあえず黒にしてみる
fixed4 frag(v2f_img i) : SV_Target { fixed4 col = (0).xxxx; return col; }(0).xxxx 何この書式・・・
1-3.板ポリを3次元っぽく扱えるようにしてみる
fixed4 frag(v2f_img i) : SV_Target { fixed4 col = (0).xxxx; float2 uv = 2 * i.uv - 1; float3 p = float3(uv, 0); return col; }xy座標を-1~1の間に正規化してるっぽいことはわかる。
pが3次元での座標を表す変数ということだろうか?
1-4.ここから先はしばらく絵に変化がなくて寂しいのでなんか背景を描いてみる
fixed4 frag(v2f_img i) : SV_Target { fixed4 col = (1).xxxx; float2 uv = 2 * i.uv - 1; float3 p = float3(uv, 0); col.rg = uv; col.b = dot(uv, uv); return col; }dotわからん・・・内積がなんなのかわからん・・・
1-5.動きが欲しいので背景を動かしてみる
uv = 2 * i.uv - 1 + frac(_Time.y);frac(_Time.y)で時間の小数部を取得するので
座標が斜めに動く→元の位置にワープを
1秒間隔で繰り返す。はず。
1-6.後で使うかもしれない関数を書き足す
float2 rot(float2 p, float a) { return float2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); } float mod(float x, float y) { return x - floor(x / y) * y; }rot なぜこの式で回転する・・・?
mod modは除算の余りを返す関数、実際にx=7,y=3で計算すると1が返る。
1-7.途中経過
// 背景 uv = rot(uv, _Time.y); uv += sin(_Time.y); col.rg = uv; col.b = pow(1 - dot(uv, uv), 0.4); return col;背景がうごいた・・・!
dotの部分はぜんぜんわからん・・・
2-1.カメラを置く
float2 uv = 2 * i.uv - 1; float3 p = float3(uv, 0); // カメラ float3 cam = float3(0, 0, -3);原点(0,0,0)から -3 z方向に動いた場所にカメラを置いたっぽいことはわかる
2-2.3つの軸を作る
float3 cam = float3(0, 0, -3); // 上、前、横 float3 up = float3(0,1,0); float3 fwd = normalize(-cam); float3 side = normalize(cross(up, fwd));upはわかる
fwdはカメラの向きとは逆のベクトル?を作ろうとしてる?normalizeは0~1に正規化する?
side cross??外積??横向きのベクトルを求めようとしているんだと思うけどcrossが何なのかわからん・・・
2-3.レイの初期座標を決める
float3 rayPos = cam;`わかる
2-4.レイが進む向きを決める
float3 rayDir = p.x * side + p.y * up + fwd`平面上(最初に置いた板ポリ)の向こう側に空間があると想定して
板ポリ上の座標(p.x, p.y)の位置が決まるとレイの向きも決まるということ???
2-5. map関数を作って球を描けるようにしてみる
float map(float3 p) { float s = length(p - (1).xxx); return s; }これ知ってる!進研ゼミで習った距離関数ってやつだ!
2-6.レイマーチするループを作る
for(int j=0; j<16; j++) { float d = map(p); if(d < 0.0001) break; rayPos += d * rayDir; }レイの位置がオブジェクトとどれだけ離れているか(d)を取得して
一定の距離以下ならばループを抜ける、そうでなければレイの先端からdの距離分だけレイを進める。だっけ?
2-7.mapの結果を使って白で書いてみる
if(d < 0.0001) { return (1).xxxx; }距離が一定以下(オブジェクトと衝突)したら白を返すということですね。わかります。
3-1.動かしてみる
float map(float3 p) { float3 q = p; q.xy += sin(_Time.y + UNITY_PI / 2); return dSphere(q); }PI / 2を足してる理由は背景の青い円と動きをずらすためっぽい?
3-2.立体感を出すために法線を使ってみる
float3 normal(float3 p) { float EPSILON = 0.0001; return normalize(float3( map(float3(p.x + EPSILON, p.y, p.z)) - map(float3(p.x - EPSILON, p.y, p.z)), map(float3(p.x, p.y + EPSILON, p.z)) - map(float3(p.x, p.y - EPSILON, p.z)), map(float3(p.x, p.y, p.z + EPSILON)) - map(float3(p.x, p.y, p.z - EPSILON)) )); }EPSILONとは・・・?なぜあの式で法線が算出できるのか・・・?
なぜこの式で法線を求めることができるのか???
— ゆでうなぎ (@yudeunagi) December 28, 2018
この場合だと球の真正面と端でどう値が変わるんだ? pic.twitter.com/z2LV6ZMJFB
真ん中の球に影が付かないぞ・・・? pic.twitter.com/ymo2u3JoGm
— ゆでうなぎ (@yudeunagi) December 29, 2018
ループの中で関数に渡す値を間違えていたようだ pic.twitter.com/hniEUPJqgt
— ゆでうなぎ (@yudeunagi) December 29, 2018
なるほどカメラの向きに対して正面に近いほど青くなるってことかな? pic.twitter.com/CH83yAtMIO
— ゆでうなぎ (@yudeunagi) December 29, 2018
3-3.ライトの位置と色を決めてみる
float3 L = normalize(float3(-1, -1, 1));` float3 Lcol = float3(0.9, 0.7, 0.4);`normalizeでライトの位置(向き)を0~1に正規化
Lcolは単純に色を指定している
return の行が原因でエラーになってるっぽい、dotの戻り値って引数がfloat3だったらfloat3になるんじゃなかったっけ? pic.twitter.com/Hfj4RV4444
— ゆでうなぎ (@yudeunagi) December 29, 2018
ここでエラーになる原因に関してはdotが返すのはfloat型の値だからだということを教えてもらいました。 サンキュー!こう変更したら通った、どうして・・・? pic.twitter.com/dnPSLWefWZ
— ゆでうなぎ (@yudeunagi) December 29, 2018
dotはベクトルの内積を返す関数なので、この場合の返り値はfloatです!https://t.co/4ODZi2qoyY
— KILI (@KiliWare) December 29, 2018
2枚目の変更でうまくいったのは、float3 hoge = x;のように初期化すると、自動的にfloat3 hoge = float3(x, x, x);のように読み替えてくれるからではないでしょうか?
3-4.ライトと法線の角度を元にライティングしてみる
if (d < 0.0001) { float3 L = normalize(float3(-1.0, 0.5, -0.2)); float3 Lcol = float3(2.6, 2.3, 1.4); return fixed4(dot(L, N) * 0.5 + 0.5, 1); // Half Lambert }returnの行でエラーになる。どうして・・・?
3-5.そこはかとなくいじっておしまい
float2 uv2 = uv + 0.5 * sin(_Time.y) + 0.5; uv2.x += sin(floor(4 * uv2.y) - _Time.y); uv2.x += cos(floor(4 * uv2.x) + _Time.y);uv2の座標にfloorを使うことで座標を升目状に区切ってるっぽい?
後半の法線が出てきたあたりからは完全についていけなくなったのと内積、外積がほんとにわからんので その辺をちょっとは勉強する必要がありそうだなー。と思いました。まる。