前言
GGX作为描述高光形状的函数,非常重要,但同时它也比较消耗性能,适合PC端使用,虽然Walter也提出一种适合移动端的GGX,但并没有解决fp16精度下导致的问题
本文将介绍Walter GGX存在的问题,并分享一种可以解决Walter GGX问题的GGX算法
三种GGX
- 最经典的GGX实现如下:
// GGX / Trowbridge-Reitz // [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"] float D_GGX(float a2, float NoH) { float d = (NoH * a2 - NoH) * NoH + 1.f; // 2 mad return a2 / (PI * d * d + 1e-4); // 4 mul, 1 rcp }- 特点
-
利用指令优化。通过多项式展开规避了标准公式中的 (1−NoH2) 这种需要多次减法的运算
-
缺点
-
α2依赖高精度。当
a2非常小,分母的 d 会趋向于 0,若是fp16,会有可能得到NaN当roughness非常小如0.01,a2 = 0.0001,且normal与half vec刚好对齐——NoH = 1,d = 0.0001,分母 = $3.14 * 10^{-8}$
问题就在于分母,由于fp16的最小值为6.1×10−5,分母已经远远超过fp16的精度范围
-
Walter Mobile GGX
float D_GGX_Mobile(float Roughness, float NoH) { // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces" float OneMinusNoHSqr = 1.0 - NoH * NoH; float a = Roughness * Roughness; float n = NoH * a; float p = a / (OneMinusNoHSqr + n * n); float d = (1.0 / PI) * p * p; // clamp to avoid overlfow in a bright env return min(d, 2048.0); }- 特点:针对FP16做了保护低精度溢出的设计
-
缺点:牺牲了高光细节
因为做了min(d, 2048.0)
-
Filament GGX
#define MEDIUMP_FLT_MAX 65504.0 #define saturateMediump(x) min(x, MEDIUMP_FLT_MAX) float D_GGX(float roughness, float NoH, const vec3 n, const vec3 h) { vec3 NxH = cross(n, h); float a = NoH * roughness; float k = roughness / (dot(NxH, NxH) + a * a); float d = k * k * (1.0 / PI); return saturateMediump(d); }- 为什么cross可以代替1- NoH^2
使用到了拉格朗日等式:|a×b|^2 = |a|^2|b|^2 - (a·b)^2
又因为a、b都是单位向量,那么|a×b|^2 = 1 - (a·b)^2
- 特点:支持FP16,且保留高光细节
-
缺点:计算消耗比Walter Mobile GGX略高一些
总结
| 方案 | 优点 | 缺点 |
|---|---|---|
| Walter GGX | 速度快、高光细节高 | fp16下高光不稳定 |
| Walter Mobile GGX | 比GGX性能更好 | fp16下损失高光细节 |
| Filament GGX | fp16下保留高光细节且稳定 | 性能比Walter Mobile GGX略高 |




Comments | NOTHING