Microfacet入門(2)
スロープ空間
先のMicrofacet入門(1)でおざなりにしていた、 とは何かについて、見ていきます。それを整理するために、まずはスロープ空間について考えます。
スロープ空間とは、マイクロファセット法線を変数変換してできる空間です。マイクロファセット法線を、を使って表すと、
となり、スロープ空間は二次元であり、
と表すことにすると、変数変換は、
と表すことができます。一見直観的でないように思われますが、 である状況を考えてみましょう。
なんと、x が になりました。これが"スロープ空間"のゆえんであります。 これには幾何学的解釈もでき、以下のようにスロープ空間がxy平面だとすると、z=1の面に投射した、と考えることができます。
※75 ページ Earl Hammon, Jr, GDC, "PBR Diffuse Lighting for GGX+Smith Microsurfaces" より
なぜかといえば、法線のxyは、zで割り算されているためで、二次元的には以下のような状態だからです。
ちなみに逆変換も可能で、
であるため、法線 は、
ただ、 は未知であり、
とすると、求めることができるため、最終的には、
となります。法線が逆側の場合はどうか?というのもありますが、実際ミクロ表面は裏返らないため、問題ありません。
スロープ空間とヤコビアン
変換方法は明らかになりましたが、元の"法線分布"を引き継ぐため、この変数変換のヤコビアンを求めておきたいです。 もとの変数は であり、ここから への変換は、ヤコビ行列は、
と求めることができます。 ヤコビアンは微小な成分の拡大率であるため、以下のように積分の辻褄があったまま変数変換することができます。
これを使って、
を満たすような を考えます。 これには、
であり、
であることから、
のようにDから機械的に変換することができることがわかります。
具体的なP
そろそろ具体的なDを考えて、そこからPを出しておきましょう。有名な法線分布として今回は、Beckmann分布と、GGX(Trowbridge-Reitz)分布を取り上げます。 まずBeckmann分布ですが、これの具体的な形は をパラメーターとして、
ここからは、
となります。 はスロープ空間上での ゼロ地点からの距離の二乗であり、それは結局 であることを利用しています。
そう、なんとBeckmann分布はスロープ空間において、標準的な2次元ガウス関数だったのです。
続いて、GGX(Trowbridge-Reitz)分布です。
こちらも同様に、
となります。
P2
では Microfacet入門(1)での は結局何かと言えば、片方を単純に積分して一次元に落としたものです。
これは実際のところ、Beckmann分布とGGX(Trowbridge-Reitz)分布で解析的に解くことができ、
Beckmann分布の場合は、
となります。
GGX分布の場合は...すみません、自分の積分力の欠如により、Symbolab Math Solver - Step by Step calculator で自動で解くと、
のようになります。
Λ (Beckmann分布)
先のMicrofacet入門(1)では、もう一つおざなりにしていたのは です。
こちらも、なんと、解析的に解くことができます。 Beckmann分布の場合、
この後、左と右の項をそれぞれまた Symbolab Math Solver - Step by Step calculatorの力を借りて、
と置きながら...
左の項 www.symbolab.com
右の項
したがって、まとめると、
ただし、
と求めることができました。
Λ (GGX分布)
GGX分布の場合のΛは、
ここでまたまたSymbolab Math Solver - Step by Step calculatorの力を借りて、
ただし、
と求めることができました。
実装例
inline double chi_plus(double x) { return x < 0.0 ? 0.0 : 1.0; } inline double D_Beckman(const Vec3 &n, const Vec3 &h, double alpha) { double cosTheta = glm::dot(n, h); if (cosTheta < 1.0e-9) { return 0.0; } double cosTheta2 = cosTheta * cosTheta; double cosTheta4 = cosTheta2 * cosTheta2; double alpha2 = alpha * alpha; double chi = chi_plus(cosTheta); // \tan { \theta } =\pm \frac { \sqrt { 1-\cos { \theta } } }{ \cos { \theta } } \\ \tan ^{ 2 }{ \theta } =\frac { 1-\cos ^{ 2 }{ \theta } }{ \cos ^{ 2 }{ \theta } } double tanTheta2 = (1.0 - cosTheta2) / cosTheta2; return chi * std::exp(-tanTheta2 / alpha2) / (glm::pi<double>() * alpha2 * cosTheta4); } inline double D_GGX(const Vec3 &n, const Vec3 &h, double alpha) { double cosTheta = glm::dot(n, h); if (cosTheta < 1.0e-9) { return 0.0; } double cosTheta2 = cosTheta * cosTheta; double cosTheta4 = cosTheta2 * cosTheta2; double alpha2 = alpha * alpha; double chi = chi_plus(cosTheta); // \tan { \theta } =\pm \frac { \sqrt { 1-\cos { \theta } } }{ \cos { \theta } } \\ \tan ^{ 2 }{ \theta } =\frac { 1-\cos ^{ 2 }{ \theta } }{ \cos ^{ 2 }{ \theta } } double tanTheta2 = (1.0 - cosTheta2) / cosTheta2; return chi / (glm::pi<double>() * alpha2 * cosTheta4 * Sqr(1.0 + tanTheta2 / alpha2)); } inline double lambda_beckmann(double cosTheta, double alpha) { double tanThetaO = std::sqrt(1.0 - cosTheta * cosTheta) / cosTheta; double a = 1.0 / (alpha * tanThetaO); return (std::erf(a) - 1.0) * 0.5 + std::exp(-a * a) / (2.0 * a * std::sqrt(glm::pi<double>())); } inline double lambda_ggx(double cosTheta, double alpha) { double tanThetaO = std::sqrt(1.0 - cosTheta * cosTheta) / cosTheta; double a = 1.0 / (alpha * tanThetaO); return -1.0 + std::sqrt(1.0 + 1.0 / (a * a)) * 0.5; } inline double G_2_beckmann(const Vec3 &omega_i, const Vec3 &omega_o, const Vec3 &omega_h, const Vec3 &n, double alpha) { double numer = chi_plus(glm::dot(omega_o, omega_h)) * chi_plus(glm::dot(omega_i, omega_h)); double denom = (1.0 + lambda_beckmann(glm::dot(omega_o, n), alpha) + lambda_beckmann(glm::dot(omega_i, n), alpha)); return numer / denom; } inline double G_2_ggx(const Vec3 &omega_i, const Vec3 &omega_o, const Vec3 &omega_h, const Vec3 &n, double alpha) { double numer = chi_plus(glm::dot(omega_o, omega_h)) * chi_plus(glm::dot(omega_i, omega_h)); double denom = (1.0 + lambda_ggx(glm::dot(omega_o, n), alpha) + lambda_ggx(glm::dot(omega_i, n), alpha)); return numer / denom; }
まとめ
マイクロファセットBRDFの理論的背景は、驚くほどに緻密で、なかなかとっつきづらいものでした。しかしひとつひとつ紐解けば、その洞察の一つ一つが結構刺激的で面白いことに気づきます。発展手法を追いかけるためにも、一度その深みに触れてみるのもよいかもしれません。
参考文献
Eric Heitz "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
Earl Hammon, Jr, GDC, "PBR Diffuse Lighting for GGX+Smith Microsurfaces"