topazの遊び場

いろいろやってみる

[GLSL] レイマーチングにおける球体の法線について

かっこいシェーダーを描きたいと思って、レイマーチングをやっていて、簡単な球体が完成した!

ここに公開してあるけど、極一般的なソースコード
- 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} $$

全部対照的な形になるわけだけど、これらを全部合わせてみよう。ちなみにこれが勾配  {\nabla} になる。

$$ \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} $$

これは陰関数で、勾配をとれば、法線ベクトルがもとまる。 F = x^{2} + y^{2} + z^{2} - r^{2} = 0として求める。

$$ \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} $$

ここまで色々計算してきて、わかったことが、  {\nabla}Dと {\nabla}Fのベクトル要素の部分が (x, y, z)で同じ方向を向いているということだ。

つまり、 {\nabla} Fは法線ベクトルを表していて、それと同じ向きにある {\nabla} D も法線ベクトルと同じ方向を表しているといってよい。

シェーダーにおいて、正規化し単位ベクトルにするので、ベクトルの大きさは必要がない。

だからgetNormalではdistanceFuncの勾配(のようなもの)を使っていた。

ざっと自分が理解するために考えたことを書いて、納得することが出来て良かった。

間違えがあったらごめんなさい、教えてください。

参考