自己皇帝感

をください

シェーダアート_2 視野角と影

wgld.org | GLSL: 視野角を考慮したレイの定義 |
wgld.org | GLSL: 法線の算出と簡単なライティング |


wgldさんのコードをみつつやっていきます。
wgldさんの、視野角を考慮したレイの定義 >フラグメントシェーダのコード 



シェーダアート_1 球体レンダリング - 人生初心者の雑記
視野角については第一回で触れた。targetDepthを使わずに直接視野角を出したい。
mainの中
28,31行目

vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
vec3 ray = normalize(vec3(sin(fov) * p.x, sin(fov) * p.y, -cos(fov)));

pの話は第一回でやった。
ray ここで視野角を使っている。fov は コードの 7,8行目に書かれている。angleを 60°として fov はその半分の角度のラジアンだ。f:id:stalagmite:20150609234651p:plain

ー1<=p.y<=1
-resolution.x/resolution.y <= p.x <= resolution.x/resolution.y//スクリーンの横幅が縦幅より広いとき。

というのは、第一回で説明した。よって、y座標の視野角(図の、黒矢印の動く範囲)が確かに60度でおさまっていることがわかる。x座標については、60度よりも少し視野角が大きい。これはスクリーンの縦横比問題なのであんまり気にしない。normalizeはベクトルの大きさを1にするだけだ。
これで視野角とピクセル座標から、飛ばすべきrayの方向が計算できる。*1




33~41行目
レイマーチング。第一回で説明した。distancefuncが、物体との最短距離を表すものだということを確認しよう。





法線の計算
45,17~24行目

vec3 normal = getNormal(rPos);


「光が当たった」時、物体の法線を出している。17~24行目に定義した getNormal 関数 が法線を取るための関数である。原理を見ていく。

vec3 getNormal(vec3 p){
float d = 0.0001;
return normalize(vec3(
distanceFunc(p + vec3( d, 0.0, 0.0)) - distanceFunc(p + vec3( -d, 0.0, 0.0)),
distanceFunc(p + vec3(0.0, d, 0.0)) - distanceFunc(p + vec3(0.0, -d, 0.0)),
distanceFunc(p + vec3(0.0, 0.0, d)) - distanceFunc(p + vec3(0.0, 0.0, -d))
));
}

発想はこうだ。f:id:stalagmite:20150610004609p:plain
法線のx軸成分について考えてみよう。
p(main内ではrPosを入れている、つまり物体表面にとても近い座標)のベクトルを、x軸方向へ+d,-d動かしたベクトルを用意する。(このdは極めて小さい数。コードでは0.0001となっている。)。それぞれのベクトルをA,Bとよぶ。Aについてdistancefunc,Bについてdistancefuncを呼ぶ。
それぞれの戻り値をAd,Bdとする。|Ad-Bd|の大きさ、そしてAd-Bdの向きが何を表す指標となるだろうか。*2
|Ad-Bd|が小さいとき、つまり「pを少しずらしても,物体との最短距離はさほど変わらない」ということだ。これは、その物体表面がx軸に対して平行、すなわちx軸方向の法線成分が少ないということである。

また図のように,|Ad-Bd|が大きいとき,逆にx軸方向の法線成分が多いのだ。

Ad-Bdの引き算の順序と符号に注意しよう。この符号はそのまま、x軸方向の法線成分の正負を表す

同様のことがy,z方向にも言える。

たとえばpの座標が円の真上だった場合、法線は、xとy方向の成分はほとんどなく、z方向の成分だけが残るだろうし、計算してみればgetNormal関数もそのように返すのがわかる。

これで法線が取得できた。


光の計算
46行目

float diff = clamp(dot(lightDir, normal), 0.1, 1.0);


clampは、第一引数の値の範囲を 第二引数~第三引数 にする。はみ出す場合は、はみ出した側の境界の値になる。
物体に光が当たった場合、「光の方向に対して遮るように面をなしている部分」が明るい。言い換えると、「光の方向と法線の内積が-1に近い」部分が明るい。


だから、wgldさんのコードではそのままdotを取ってしまっているので間違いだと思う。そもそも,lightDirと物体、カメラの位置を考えると、逆光になっているはずなので、物体は全体的に暗くなるはずだが、そう,,,だよな?


float diff = clamp(-dot(lightDir, normal), 0.1, 1.0);

これで逆光であることが正しく表示されるはずだ。逆光を直すべく、光の方向を//11行目
const vec3 lightDir = vec3(-0.577, 0.577, -0.577);

とすると、いい感じに光が当たる。







こんな感じだ。wgldさんのところでも書いてるように、法線の出し方は他にもある(俺はこれしか知らんが。)まだまだ勉強することがあるなぁ。

次は オブジェクトの複製 かなぁ。俺全然理解してないのに。

*1:本当なら,カメラの方向を決めて行列をかけるなりしなければならないが、物体の位置を原点、カメラの位置を{0,0,2}と決めているので、rayのz方向を単純に-cos(fov)としている

*2:distancefunc は、物体表面への最短距離を符号付きで返すようにしている。つまり、図のp+dのようにめり込んでいる場合は、一番近い表面への距離をマイナスで返す