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(h) * F(v,h) * G(l,v,h) f_specular = ----------------------------------- 4 * (N.L) * (N.V) Where: D(h) = Normal Distribution Function // how many microfacets align with H F = Fresnel reflectance // angle-dependent reflectivity G = Geometry / Visibility term // self-shadowing between microfacets H = half-vector = normalize(L + V) N.L = clamped dot(normal, light_dir) N.V = clamped dot(normal, view_dir)

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));
}
Precision note: The source code comments warn that when roughness is very small and NoH approaches 1, consecutive squaring can produce precision loss leading to division by near-zero. The 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;
}
The "50x green" trick: The 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 近似:

// New engine: lambdaV/L computation vectorized into float2
float SmithJointGGXVisibilityTerm(float NdotL, float NdotV, float roughness_pow2) {
  float a = roughness_pow2;
  float2 lambdaVL = float2(NdotL, NdotV) * (float2(NdotV, NdotL) * (1 - a) + a);
  // NOTE: FLT_MIN changed to FLT_MIN_SAFE in new engine for more robust guard
  return 0.5 * rcp(max(lambdaVL.x + lambdaVL.y, FLT_MIN_SAFE));
}

// 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);
}
New vs replaced: The new brdf.hlsl (152 lines) replaces the old 122-line version. Smith Joint visibility vectorized from separate scalars to float2. Denominator guard changed from FLT_MIN to FLT_MIN_SAFE (slightly larger safe floor defined in function.hlsl). All old BRDF functions remain (D_GGX, F_Schlick, D_GGXaniso, Vis_Schlick). 新版替换:brdf.hlsl(152 行)替换旧版 122 行。Smith Joint 可见性项从独立标量向量化为 float2。分母保护从 FLT_MIN 改为 FLT_MIN_SAFE(在 function.hlsl 中定义的略大安全下限)。所有旧 BRDF 函数保留(D_GGX, F_Schlick, D_GGXaniso, Vis_Schlick)。

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). In the new engine the LUT math is extracted into a separate EnvBRDFLUTApprox helper:

对于 IBL(基于图像的光照),计算完整积分太昂贵。NeoX 使用来自 Lazarov 2013 的 EnvBRDFApprox(与 UE4 原始实现相同)。新引擎将 LUT 数学提取到独立的 EnvBRDFLUTApprox 辅助函数中:

// New engine: LUT math extracted into reusable helper
float2 EnvBRDFLUTApprox(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;
  return float2(-1.04, 1.04) * a004 + r.zw;
}

// [Lazarov 2013] Main function now wraps the LUT helper
float3 EnvBRDFApprox(float3 SpecularColor, float Roughness, float NoV) {
  float2 AB = EnvBRDFLUTApprox(Roughness, NoV);
  return SpecularColor * AB.x + AB.y;
}
New vs replaced: EnvBRDFApprox refactored: core LUT math extracted into reusable EnvBRDFLUTApprox helper (also used by the new cloth sheen LUT function). Functionally equivalent to the old single-function version. 新版替换:EnvBRDFApprox 重构:核心 LUT 数学提取到可复用的 EnvBRDFLUTApprox 辅助函数中(新布料 Sheen LUT 函数也使用它)。功能上与旧版单函数版本等价。

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
Final Specular Per Light = D * G * N.L * F * attenuation * light_color D = GGXTerm_ApproxWithPrecomputedVar(NdotH, roughness²) G = SmithJointGGXVisibilityTerm(NdotL, NdotV, roughness²) // vectorized in new engine F = F_Schlick(specularColor, VdotH) Note: The "/ 4*NdotL*NdotV" denominator from Cook-Torrance is already folded into the SmithJoint visibility term (the 0.5/rcp). The extra NdotL in LightingSpecular cancels with the one in the denominator.

New Functions: D_Quadratic & Cloth Sheen BRDF新增函数:D_Quadratic 与布料 Sheen BRDF

The new engine adds a cheaper quadratic NDF approximation and a Sheen BRDF LUT polynomial specifically for the new sheen.hlsl and cloth.hlsl shading models:

新引擎增加了一个更廉价的二次 NDF 近似和一个专门用于新 sheen.hlslcloth.hlsl 着色模型的 Sheen BRDF LUT 多项式:

// NEW: D_Quadratic — cheap quadratic NDF, cheaper than full GGX
// Used for low-quality approximations or special effects
float D_Quadratic(float a, float NoH) {
  float t = (1 - a * a);
  return lerp(1.0, a, t);  // linearly blends flat vs peaked distribution
}

// NEW: EnvClothSheenLUT3rd — polynomial LUT approximation for cloth sheen IBL
// Replaces a texture LUT lookup with a rational polynomial approximation
float EnvClothSheenLUT3rd(float R, float NoV) {
  // a, b, c, Offset = constants baked from the sheen DFG integral
  return a * R / (b * R + c * NoV + NoV * R) + Offset;
}
Design note: D_Quadratic is not physically-based but offers a useful artistic approximation for soft/velvety surfaces where full GGX is overkill. EnvClothSheenLUT3rd approximates the same sheen DFG integral that UE5's Substrate cloth uses a texture LUT for — NeoX avoids the texture sample entirely via polynomial math. 设计备注:D_Quadratic 不是基于物理的,但对于完整 GGX 过于复杂的柔软/天鹅绒表面提供了有用的艺术化近似。EnvClothSheenLUT3rd 用多项式数学逼近 UE5 Substrate 布料使用纹理 LUT 的 sheen DFG 积分——NeoX 完全避免了纹理采样。
New additions: D_Quadratic, EnvBRDFLUTApprox, and EnvClothSheenLUT3rd are new in the res_upgrade/ version of brdf.hlsl. They support the new Cloth and Sheen shading models (Keys 13, 15). The old version in res/ had no cloth or sheen model support. Since the new brdf.hlsl takes priority, these functions are always available in the new engine. 新增内容:D_QuadraticEnvBRDFLUTApproxEnvClothSheenLUT3rdres_upgrade/brdf.hlsl 中新增的。它们支持新的 Cloth 和 Sheen 着色模型(键 13、15)。res/ 中的旧版没有布料或 Sheen 模型支持。由于新 brdf.hlsl 优先,这些函数在新引擎中始终可用。