人生初心者の雑記

すべてにおいてド素人な人がいろんなことを書くよ

シェーダアート_1 球体レンダリング

wgld.org | GLSL: レイマーチングで球体を描く |
GLSL Sandbox Gallery



3Dオブジェクトを使わずに球を描こう。
wgldさんのコードを題材にさせていただきます。

GLSLの知識はほとんどいらない。

wgldさんのリンクの、>フラグメントシェーダのコード を見ていく。
GLSL SandboxのCreate new Effect からGLSLのソースコードを書く場所にコピペして、記事を読みながらコードを書き換えて確認。


上から四行
precision mediump float;//わからん。おまじないと。計算精度について書かれているらしい。
uniform float time;//時間を取得できる特別な変数。今回は使ってない。
uniform vec2 mouse;//マウス座標を取得できる特別な...。今回は使ってない。
uniform vec2 resolution;//スクリーンの幅をry。使う。




本題 main()を見ていく
スクリーンのピクセル一つ一つにmain()が適用されていく
14行目
vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);

gl_FragCoord 四次元ベクトル。.xyとつけると、gl_FragCoord一次元目と二次元目の値をもつ二次元ベクトルを取得できる。
glFragCoordの値は{今mainが適用されているピクセルのx座標,y座標,z,w} である。zはデプス,wはクリップとからしいけどよくわからんし、ここでは使っていない。
gl_FragCoordのx,yの値は

0<=x<=resolution.x
0<=y<=resolution.y

よって p の値は、スクリーンが横に広いとすると、

ーresolution.x/resolution.y <= p.x <= resolution.x/resolution.y
ー1<=p.y<=1

まぁ、この値自体にさほど意味はない。重要なのは、スクリーンの中心の値がp.xy = {0,0}となったことだ*1

17~21行目
カメラの定義である
CPos カメラ位置
CDir カメラの向いている方向
CUp カメラの上方向
CSide カメラから見た座標

CSide = cross(CDir,CUp) に注目。カメラの視線方向のベクトルと、カメラの頭の方向のベクトルとの外積を取ると、カメラの右方向にベクトルができる。f:id:stalagmite:20150607222138p:plain
つまり、カメラから見ると、CSideとCUpがそれぞれx軸、y軸方向のベクトルに見える。

21,24行目
float targetDepth = 1.0;
vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);

targetDepth は、視野角に関係する値である。rayというのは、カメラからでる光の方向である。
ここからの話は、スクリーン上のピクセルの色というのが、3d空間における何なのかについてよく考えていないと難しい(俺もよく理解できていない。(俺もというか俺だけかもしれないが))。

スクリーンのピクセルの色=カメラからスクリーン上のピクセルに光を飛ばしたときに当たった物体の色
targetDepth=スクリーンの3次元空間上の位置 

と考えたら、俺はわかりやすかった
下の二枚が、物体とカメラとtargetDepthの関係を表したもの。大球が物体、小球がカメラ、平行四辺形がスクリーンのつもり

f:id:stalagmite:20150607234752p:plain
f:id:stalagmite:20150607234834p:plain

上はtargetDepthがカメラに近い場合。(具体的にはtargetDepth = 0.5ぐらい)スクリーン上の円が小さい。
下はtargetDepthがカメラに遠い場合。(具体的にはtargetDepth = 1.5ぐらい) スクリーン上の円が大きい。

視野角の話は次の記事にも出そう。

26~34行目
やっと本題だ。レイマーチング法
レイトレーシングの一種だとか。よくしらん。原理はこうだ。

1.カメラから出た光の先端座標を記録する(最初はカメラの位置と同じ)
2.その光の先端と物体との直線最短距離を出す。その長さをLとする。
3.2のLの長さだけ光の進む方向に、光の先端を移動させる。
4.1~3を繰り返して、Lが一定値以下になれば当たったとみなす。

全能感UP! GLSLで進めレイマーチング « demoscene.jp

このリンク先でレイマーチングの処理が1ステップごとに見ることができる。
覚えておきたいレイマーチングの性質がある。
物体外部にある光の先端は、レイマーチング処理中に物体内部に入り込むことはないこと(計算誤差を無視すれば)
光の直線状に物体があれば、Lの値は必ず一定値以下になること(無限回ループできれば)

まぁ直感的にわかると思う。


wgldさんのコードでは、16ループでレイマーチングを終わらせている。
変数の役割はコードに書いてあるので省く。一番重要なのは、distancefuncの処理だ。


31,8~10行目
distanceFuncは、光の先端と物体(この例では半径1、座標{0,0,0}の球)との距離を出すものだ
distanceFuncの中身は超簡単。球の中心座標が原点にあるから、光の先端と球との最短距離は
「原点から光の先端までの距離」-「球の半径」
で求まる


37行以下
ループの最後に得た光の先端と物体との最短距離(distance)の値が、ある値以下の時、当たっているとみなす。

gl_FragColor = vec4(vec3(1.0), 1.0);

gl_FragColor に代入された値が、今mainを適用しているスクリーン上のピクセルの出力色となる。





これで終わった。この記事の内容は、レイマーチング等のアルゴリズムとはあまり関係ない部分も突っ込んだので冗長になったが、次からの記事はもっとアルゴリズムに焦点を絞っていけるようになると思う。
具体的には、影の付け方、視野角についての覚書になるとおもう。

*1:p.x,p.yの値をそれぞれをresolution.x,resolution.yの値で割ってないことに注意。上のコードでは、pの要素を両方とも「resolution.xとresolution.yのうち小さい方」で割っている。ためしに vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / resolution.xy; と書いてみよう。p = {(gl_FragCoord.x*2.0-resolution.x)/resolution.x,(gl_FragCoord.y*2.0-resolution.y)/resolution.y}となるが、この時表示されているのは楕円になるはずだ。これは、スクリーンにおけるx軸、y軸のスケールが異なってしまうためである。この意味は、この場合のスクリーン上のp = {0.5,0.5}の座標が、スクリーン中心から見て水平と45度を為していないことに注意するとわかると思う。