【LIVE Shader Deconstruction :: happy jumping】iq神のライティングを読み解く
レイマーチングの神さま、iq神によるレイマーチングのメイキングが公開されました。
happy jumpingというタイトルのこの楽しいレイマーチングには、シェーダーでやることができるいろいろな技がたっぷり詰め込まれています。
ライブストリーミングで惜しげもなく公開されたこの技から、ぜひともいろいろと学び取っちゃいましょう。
今回のエントリでは、序盤の50分くらいまでのライティングについて見ていきたいと思います。
それではさっそく行ってみましょう。
このエントリはYouTubeもあわせて観つつ、写経しながら試してみるのが一番です。
写経するにはglslfanが最適ですので、Twitter連携などで使うと良いでしょう。
今回のコードについては、以下のglslfanリンクに置いてあります。
ではライティングの準備をしていきましょう。
カメラはシンプルです。z=1に配置し、レイを飛ばす方向はvec3(p,-1.5)で定義しています。
vec3 ro=vec3(.0,.0,1.)
,rd=normalize(vec3(p,-1.5));
マップ関数もシンプルで、球と床をモデリングしています。
float map(vec3 p){
float d=length(p)-.25;
float d2=p.y+.25;
return min(d,d2);
}
法線算出はライブコーディングではmapを6回呼び出す実装でしたが、この記事で用意したサンプルでは4回呼び出しの法線算出です。
vec3 normal(vec3 p){
vec2 e=vec2(.001,.0);
return normalize(.000001+map(p)-vec3(map(p-e.xyy),map(p-e.yxy),map(p-e.yyx)));
}
レイを飛ばすマーチングループ関数。iq神の実装では100回のループでしたが、ディテールを出したかったので200回に増やしています。
ポイントはroとrdを受け取ってトータルの距離を返すことと、衝突タイミングと、トータル距離が一定以上になったタイミングでbreakすることです。
遠い場所については距離を-1で返してしまうのが、呼び出し元で背景を描けるようにするポイントですね。
float castRay(vec3 ro,vec3 rd){
float t=0.0;
for(int i=0;i<200;++i){
float f=map(ro+t*rd);
if(f<.001) break;
if(t>20.) break;
t+=f;
}
if(t>20.) t=-1.;
return t;
}
空の色は色を扱う変数の初期値にセットしています。レイが衝突しなかった場合にはこれがそのまま表示されます。
この色を加工すれば空を描画できます。
vec3 col=vec3(.6,.7,.8);
さて、本題のライティングについて見ていきます。
少ない行数でいいかんじのライティングになっています。
これを分解して見ていきましょう。
dはレイを飛ばして衝突した距離、Nは法線です。
マテリアルの色に初期値0.18を与えています。
太陽のディフューズ。内積を使った時のありふれたルックです。
このポイントは、太陽から見た裏側をclampで0.0にしているところと、太陽の色が黄色に近いということです。
次は空のディフューズを見てみましょう。
これは覚えたい技です。法線と上方向へのベクトルとで内積をとっているので、上から下に向かって陰のグラデーションがいいかんじにつきます。
また、空は青ですので、陰が青みがかったかんじになります。いいかんじ、いいかんじです。
ハーフランバートのように扱っているので、clmapをしなくとも値の範囲は0.0から1.0になることでしょう。
float sky_dif=.5+.5*dot(N,vec3(.0,1.,.0));
今度は床からの照り返しのような色を載せています。法線と下方向へのベクトルで内積をとっているところが注目するポイントです。
太陽のシェーディング。この箇所が一番面白いところではないでしょうか。マーチングループを関数で切り出した一工夫がここに生きてきています。
これを書き加えるだけで、なんと床に影が描かれるようになります。
何が起きているのでしょうか。
ro+d*rdは衝突した座標です。これを法線方向にほんの少し押し出したものをレイの初期位置として、太陽のベクトル方向にレイを進めます。
この正負を判定すれば影の場所なのかどうなのかが、どうしてわかるのでしょうか?
形状を調べるために最初に行ったマーチングループとはいくつか違いがあることに注目すると良さそうです。
レイの進む方向にはライトベクトルが与えられています。これはパースペクティブではなく、正投影です。ループ回数が増えるほど、どんどん前に進んでいくことになり、一定の距離を越えると-1.0が返る分岐に入ります。
マーチングループを進めて考えてみましょう。
レイの初期位置はマテリアル表面から少しだけ法線方向に押し出した先です。
+N*.001だけ離れているところです。
床の位置からmap関数で距離をはかると、 床までの距離が取れることになります。この距離だけレイはライトベクトルの方向に進みます。次のループでは、レイの位置から垂直な床までの距離が取れることになり、その分だけレイはさらに進みます。
さて、この調子でレイが進んだとして、もし最小の距離が床ではなく球になるとどうでしょうか。ここがポイントになるハズです。
おそらく、ここで飛ばしているレイは球を突き抜け、空の方向に向かってたくさんの距離を進んでしまうことでしょう。レイがとても遠くに飛んだ場合、-1.0が返ることになります。
結果として、影になる場所は色が引き算で扱われることから生まれます。
最後にガンマ補正を行ってライティングはおしまいです。
col=pow(col, vec3(.4545));
ハーフランバートを使って陰を描くライティングに加えて、法線を利用してもう一度飛ばしたレイの正負を判定するトリックを使った影を描く方法がわかってしまいました。
これでレイマーチングがさらにはかどるようになるのではないでしょうか*2。
ではでは。
*2:iq神によるテレインの記事も読むと良いでしょう。
http://www.iquilezles.org/www/articles/terrainmarching/terrainmarching.htm