GGX BRDF ImplementationGGX BRDF 实现
Microfacet Theory — The Cook-Torrance Model微表面理论 — Cook-Torrance 模型
NeoX uses the standard Cook-Torrance microfacet specular BRDF, the same family used by UE5, Frostbite, and most modern PBR engines. The specular term is:
NeoX 使用标准的 Cook-Torrance 微表面镜面 BRDF,与 UE5、Frostbite 和大多数现代 PBR 引擎同族。高光项为:
D: GGX Normal Distribution FunctionD: GGX 法线分布函数
The D_GGX function (Trowbridge-Reitz distribution) determines what fraction of microfacets are oriented to reflect light toward the viewer. Higher roughness = wider distribution = blurrier highlights.
D_GGX 函数(Trowbridge-Reitz 分布)决定有多少微表面朝向将光线反射到观察者的方向。粗糙度越高 = 分布越宽 = 高光越模糊。
// [Walter et al. 2007, "Microfacet models for refraction through rough surfaces"] float D_GGX(float a2, float NoH) { NoH = min(0.999, NoH); // prevent singularity at NoH=1 float d = (NoH * a2 - NoH) * NoH + 1; // 2 mad return a2 / (PI * d * d); // 4 mul, 1 rcp } // Optimized version with pre-squared roughness (used in isotropy.hlsl) float GGXTerm_ApproxWithPrecomputedVar(float NdotH, float roughness_pow2) { float a = roughness_pow2 * roughness_pow2; // a = roughness^4 NdotH = min(0.9999, NdotH); float d = (NdotH * a - NdotH) * NdotH + 1.0; return a * rcp(max(PI * d * d, FLT_MIN)); }
min(0.9999, NdotH) clamp prevents this.
精度注意:源码注释警告当粗糙度很小且 NoH 接近 1 时,连续平方可能产生精度损失导致接近零除法。min(0.9999, NdotH) 截断防止了这个问题。
F: Schlick Fresnel ApproximationF: Schlick 菲涅尔近似
The Fresnel term determines how much light is reflected vs transmitted at different viewing angles. At grazing angles, all surfaces become highly reflective.
菲涅尔项决定在不同观察角度下有多少光被反射而非透射。在掠射角度,所有表面都会变得高度反射。
float3 F_Schlick(float3 SpecularColor, float VoH) { float Fc = Pow5(1 - VoH); // (1-cosθ)^5 // 2% minimum: anything below is physically impossible (shadowing) return saturate(50.0 * SpecularColor.g) * Fc + (1 - Fc) * SpecularColor; }
saturate(50.0 * SpecularColor.g) factor ensures that even very dark specular colors (e.g. 0.02 F0 for dielectrics) still produce visible Fresnel at grazing angles. This is the same technique used by UE4/5, taken from Lazarov 2013.
"50 倍绿色"技巧:saturate(50.0 * SpecularColor.g) 因子确保即使非常暗的高光颜色(如电介质 0.02 F0)在掠射角度仍能产生可见菲涅尔。这与 UE4/5 使用的技术相同,来自 Lazarov 2013。
G: Smith Joint GGX VisibilityG: Smith 联合 GGX 可见性
The geometry/visibility term accounts for microfacets blocking each other (masking and shadowing). NeoX implements the height-correlated Smith approximation:
几何/可见性项考虑微表面相互遮挡(遮蔽和阴影)。NeoX 实现了高度相关 Smith 近似:
float SmithJointGGXVisibilityTerm(float NdotL, float NdotV, float roughness_pow2) { float a = roughness_pow2; float lambdaV = NdotL * (NdotV * (1 - a) + a); float lambdaL = NdotV * (NdotL * (1 - a) + a); return 0.5 * rcp(max(lambdaV + lambdaL, FLT_MIN)); } // Alternative: Schlick approximation (used for anisotropy) float Vis_Schlick(float Roughness_pow2, float NoV, float NoL) { float k = Roughness_pow2 * 0.5; float Vis_SchlickV = NoV * (1 - k) + k; float Vis_SchlickL = NoL * (1 - k) + k; return 0.25 / (Vis_SchlickV * Vis_SchlickL); }
Anisotropic GGX各向异性 GGX
For brushed metal and hair, NeoX supports anisotropic specular via D_GGXaniso (Disney's Burley 2012 formulation):
对于拉丝金属和头发,NeoX 通过 D_GGXaniso 支持各向异性高光(Disney Burley 2012 公式):
// [Burley 2012, "Physically-Based Shading at Disney"] float D_GGXaniso(float RoughnessX, float RoughnessY, float NoH, float3 H, float3 X, float3 Y) { float mx = RoughnessX * RoughnessX; float my = RoughnessY * RoughnessY; float XoH = dot(X, H); float YoH = dot(Y, H); float d = XoH*XoH / (mx*mx) + YoH*YoH / (my*my) + NoH*NoH; return 1 / (mx * my * d * d); }
Environment BRDF — Split-Sum Approximation环境 BRDF — 分离求和近似
For IBL (Image-Based Lighting), evaluating the full integral is too expensive. NeoX uses the EnvBRDFApprox from Lazarov 2013 (same as UE4's original implementation):
对于 IBL(基于图像的光照),计算完整积分太昂贵。NeoX 使用来自 Lazarov 2013 的 EnvBRDFApprox(与 UE4 原始实现相同):
// [Lazarov 2013, "Getting More Physical in Call of Duty: Black Ops II"] float3 EnvBRDFApprox(float3 SpecularColor, float Roughness, float NoV) { const float4 c0 = { -1, -0.0275, -0.572, 0.022 }; const float4 c1 = { 1, 0.0425, 1.04, -0.04 }; float4 r = Roughness * c0 + c1; float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; float2 AB = float2(-1.04, 1.04) * a004 + r.zw; return SpecularColor * AB.x + AB.y; }
Full Specular Assembly — How It All Connects完整高光组装 — 如何组合
In the isotropy.hlsl shading model, the final specular term is assembled from D, G, and the Fresnel applied outside:
在 isotropy.hlsl 着色模型中,最终高光项由 D、G 和外部应用的菲涅尔组装:
// From isotropy.hlsl -- LightingSpecular() float3 LightingSpecular(...) { return max(0.0, SmithJointGGXVisibilityTerm(n_dot_l, n_dot_v_abs, roughness_pow2) // G term * GGXTerm_ApproxWithPrecomputedVar(n_dot_h, roughness_pow2) // D term * n_dot_l // N.L ); } // Called from DirectLighting(): light_specular = LightingSpecular(...) * data_rw.fresnel * data_rw.atten; // ^ F_Schlick ^ shadow*light