かっこいシェーダーを描きたいと思って、レイマーチングをやっていて、簡単な球体が完成した!
ここに公開してあるけど、極一般的なソースコード
- Shader - Shadertoy BETA
いろんなサイトを横断してコードを読んで作り上げたが、コードがわからないところを調べたのでメモしておく。
分からなかった箇所。
球体の法線ベクトルを取得するgetNormalの実装の意味が分からなかった。球体の法線方向をベクトルを計算する関数だとはわかっても、なぜこれでよいのか、これを調べた。
float distanceFunc(vec3 pos, float radius) { return length(pos) - radius; } vec3 getNormal(vec3 pos, float radius) { float delta = 0.0001; float a = distanceFunc(vec3(0,0,0), 0.2); float f_x = distanceFunc(pos, radius) - distanceFunc(vec3(pos.x - delta, pos.y, pos.z), radius); float f_y = distanceFunc(pos, radius) - distanceFunc(vec3(pos.x , pos.y - delta, pos.z), radius); float f_z = distanceFunc(pos, radius) - distanceFunc(vec3(pos.x , pos.y, pos.z - delta), radius); return normalize(vec3(f_x, f_y, f_z)); }
法線(normal vector)はこういうものだと思って、考えていく。
まずはdistanceFuncについて見ていく。球が座標中心(0,0,0)にある時について考えているため、球表面との距離を求めている。
distanceFuncをD, radiusをrとして数式で表すとこうなる。
$$ D = \sqrt{x^{2} + y^{2} + z^{2}} - r $$
それでは、本題のgetNormal関数について考えていこう。 getNormalの f_x をDを用いて数式に書き起こしてみる。
$$ \begin{aligned} F_x &= D(x + delta) - D(x) \\ &= (\sqrt{(x + delta)^{2} + y^{2} + z^{2}} - r) - (\sqrt{x^{2} + y^{2} + z^{2}} - r) \\ &= \sqrt{(x + delta)^{2} + y^{2} + z^{2}} - \sqrt{x^{2} + y^{2} + z^{2}} \end{aligned} $$
これはパッとみ何をしているのかわからないが、微分の形にとても似ている。deltaは計算に良さそうな小さい値を取っている。そう考えて、D(x,y,z)をxで偏微分してみよう。
$$ \begin{aligned} \frac{\partial D}{\partial x} &= \frac{\partial }{\partial x} (\sqrt{x^{2} + y^{2} + z^{2}} - r) \\ &= \frac{x}{\sqrt{x^{2} + y^{2} + z^{2}}} \end{aligned} $$
いい感じ、続けて他の成分の偏微分も行ってみよう。
$$ \begin{aligned} \frac{\partial D}{\partial y} &= \frac{\partial }{\partial y} (\sqrt{x^{2} + y^{2} + z^{2}} - r) \\ &= \frac{y}{\sqrt{x^{2} + y^{2} + z^{2}}} \end{aligned} $$
$$ \begin{aligned} \frac{\partial D}{\partial z} &= \frac{\partial }{\partial z} (\sqrt{x^{2} + y^{2} + z^{2}} - r) \\ &= \frac{z}{\sqrt{x^{2} + y^{2} + z^{2}}} \end{aligned} $$
全部対照的な形になるわけだけど、これらを全部合わせてみよう。ちなみにこれが勾配 になる。
$$ \begin{aligned} {\nabla} D &= (\frac{\partial D}{\partial x} ,\frac{\partial D}{\partial y} ,\frac{\partial D}{\partial z} ) \\ &= \frac{1}{\sqrt{x^{2} + y^{2} + z^{2}}} (x,y,z) \end{aligned} $$
こうなるのさ。ここまで求めたら、これは行ったんおいといて、今回本当に求めたかったことに戻ってみる。 知りたいのは球の法線ベクトル。球の方程式はこうなる。
$$ \begin{aligned} x^{2} + y^{2} + z^{2} &= r^{2} \\ x^{2} + y^{2} + z^{2} - r^{2} &= 0 \end{aligned} $$
これは陰関数で、勾配をとれば、法線ベクトルがもとまる。として求める。
$$ \begin{aligned} {\nabla} F &= (\frac{\partial F}{\partial x} ,\frac{\partial F}{\partial y} ,\frac{\partial F}{\partial z} )\\ &= 2(x,y,z) \end{aligned} $$
ここまで色々計算してきて、わかったことが、 DとFのベクトル要素の部分が (x, y, z)で同じ方向を向いているということだ。
つまり、 Fは法線ベクトルを表していて、それと同じ向きにある D も法線ベクトルと同じ方向を表しているといってよい。
シェーダーにおいて、正規化し単位ベクトルにするので、ベクトルの大きさは必要がない。
だからgetNormalではdistanceFuncの勾配(のようなもの)を使っていた。
ざっと自分が理解するために考えたことを書いて、納得することが出来て良かった。
間違えがあったらごめんなさい、教えてください。