diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | brdf.cginc | 91 | ||||
| -rw-r--r-- | filamented.cginc | 33 | ||||
| -rw-r--r-- | pbr.cginc | 3 |
4 files changed, 78 insertions, 50 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6bb293 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.meta @@ -17,36 +17,91 @@ float F_Schlick(float LoH, float f0, float f90) { return f0 + (f90 - f0) * term5; } +// Walter "Microfacet Models for Refraction through Rough Surfaces" +// Equation 33. +// In the paper: +// - m = microsurface normal +// - n = macrosurface normal +// - theta_m = angle between micro- & macrosurface normals +// - alpha = roughness +// - cos(theta_m) = NoH +// Per sohcahtoa: +// tan(theta) = sin(theta) / cos(theta) +// tan^2(theta) = sin^2(theta) / cos^2(theta) +// = (1 - cos^2(theta)) / cos^2(theta) +// = -1 + 1 / cos^2(theta) +float D_GGX(float roughness, float NoH) { + float r2 = roughness * roughness; + float NoH2 = NoH * NoH; + float NoH4 = NoH2 * NoH2; + + float k = rcp(NoH2) - 1; + float r2_plus_k = r2 + k; + // Not sure why, but not using the factor of PI here makes the specular match + // the Unity standard much more closely. Maybe the author was just folding + // the 4.0 (historically used to be a PI) into the GGX calculation? + float denom = NoH4 * r2_plus_k * r2_plus_k; + + return r2 / denom; +} + +// Hammon "PBR Diffuse Lighting for GGX+Smith Microsurfaces" +// Slide 84. Note that we remove the (4 * NoL * NoV) from the +// denominator of the specular lobe because of some cancellations. +// The original, un-optimized equation is: +// 2 * NoL * NoV / lerp(2 * NoL * NoV, NoL + NoV, roughness) +float V_GGXSmith(float roughness, float NoL, float NoV) { + float denom = 2.0f * lerp(2.0f * NoL * NoV, NoL + NoV, roughness); + return rcp(denom); +} + float4 brdf(Pbr pbr, LightData data) { float3 specular = 0; float3 diffuse = 0; + float f0 = 0.04f; + const float f90 = 1.0f; + +//#define FURNACE_TEST_DIRECT +#if defined(FURNACE_TEST_DIRECT) + // Create the conditions for the standard BRDF furnace test. + // Only applies to the direct lighting stage. The only variable left over is + // NoV. + f0 = 1; + data.direct.color = 1; + data.direct.NoL = 1; + data.direct.NoH = 1; + data.direct.LoH = 1; + data.direct.LoV = 1; +#endif + // Direct if (true) { - float F = F_Schlick(data.direct.LoH, 0.04f, 1.0f); - float D = D_GGX(pbr.roughness, data.direct.NoH, data.direct.H); - float V = V_SmithGGXCorrelated_Fast(pbr.roughness, data.common.NoV, data.direct.NoL); - - float denom = 4.0f * data.common.NoV * data.direct.NoL; - float FDV = denom > _BRDF_Specular_Min_Denom ? F * D * V / denom : 0.0f; - specular += FDV * data.direct.color * data.direct.NoL; - - float Fd = Fd_OrenNayar(pbr.roughness, data.common.NoV, data.direct.NoL, data.direct.LoV); - float3 remainder = (1.0f - F); - diffuse += (Fd / PI) * remainder * pbr.albedo.xyz * data.direct.color; - remainder *= (1.0f - (Fd / PI) * pbr.albedo); + float F = F_Schlick(data.direct.LoH, f0, f90); + float D = D_GGX(pbr.roughness, data.direct.NoH); + float G = V_GGXSmith(pbr.roughness, data.direct.NoL, data.common.NoV); + + float FDG = F * D * G; + float3 direct_specular = FDG * data.direct.color * data.direct.NoL * lerp(1.0f, pbr.albedo.xyz, pbr.metallic); + specular += direct_specular; + + float3 remainder = (1.0f - direct_specular); + float Fd = Fd_OrenNayar(pbr.roughness, data.common.NoV, data.direct.NoL, data.direct.LoV) / PI; + float3 direct_diffuse = Fd * remainder * (1.0f - pbr.metallic) * pbr.albedo.xyz * data.direct.color; + diffuse += direct_diffuse; } // Indirect - if (true) { - float F = F_Schlick(data.indirect.LoH, 0.04f, 1.0f); + if (false) { + float F = F_Schlick(data.indirect.LoH, f0, f90); - specular += F * data.indirect.specular; + float3 indirect_specular = F * data.indirect.specular; + specular += indirect_specular; float Fd = 1.0f; // Lambertian divide is baked into SH - float3 remainder = (1.0f - F); - diffuse += Fd * remainder * pbr.albedo.xyz * data.indirect.diffuse; - remainder *= (1.0f - Fd * pbr.albedo); + float3 remainder = (1.0f - indirect_specular); + float3 indirect_diffuse = Fd * remainder * pbr.albedo.xyz * data.indirect.diffuse; + diffuse += indirect_diffuse; } return float4(diffuse + specular, 1); diff --git a/filamented.cginc b/filamented.cginc index 0e27bbc..fb019cb 100644 --- a/filamented.cginc +++ b/filamented.cginc @@ -213,37 +213,6 @@ #include "UnityCG.cginc" #include "UnityImageBasedLightingMinimal.cginc" -#define MIN_PERCEPTUAL_ROUGHNESS 0.045f -#define MIN_ROUGHNESS 0.002025f - -float D_GGX(float roughness, float NoH, const float3 h) { - // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces" - - // In mediump, there are two problems computing 1.0 - NoH^2 - // 1) 1.0 - NoH^2 suffers floating point cancellation when NoH^2 is close to 1 (highlights) - // 2) NoH doesn't have enough precision around 1.0 - // Both problem can be fixed by computing 1-NoH^2 in highp and providing NoH in highp as well - - // However, we can do better using Lagrange's identity: - // ||a x b||^2 = ||a||^2 ||b||^2 - (a . b)^2 - // since N and H are unit vectors: ||N x H||^2 = 1.0 - NoH^2 - // This computes 1.0 - NoH^2 directly (which is close to zero in the highlights and has - // enough precision). - // Overall this yields better performance, keeping all computations in mediump - // Not available without reworking to pass NxH to the function - float oneMinusNoHSquared = 1.0 - NoH * NoH; - float a = NoH * roughness; - float k = roughness / (oneMinusNoHSquared + a * a); - float d = k * k * (1.0 / PI); - return d; -} - -float V_SmithGGXCorrelated_Fast(float roughness, float NoV, float NoL) { - // Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces" - float v = 0.5 / lerp(2.0 * NoL * NoV, NoL + NoV, roughness); - return v; -} - float normalFiltering(float perceptualRoughness, const float3 worldNormal) { // Kaplanyan 2016, "Stable specular highlights" // Tokuyoshi 2017, "Error Reduction and Simplification for Shading Anti-Aliasing" @@ -275,7 +244,7 @@ half3 Unity_GlossyEnvironment_local (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_G // Workaround for issue where objects are blurrier than they should be // due to specular AA. float roughnessAdjustment = 1-perceptualRoughness; - roughnessAdjustment = MIN_PERCEPTUAL_ROUGHNESS * roughnessAdjustment * roughnessAdjustment; + roughnessAdjustment = 1e-6f * roughnessAdjustment * roughnessAdjustment; perceptualRoughness = perceptualRoughness - roughnessAdjustment; // Unity derivation @@ -14,6 +14,9 @@ struct Pbr { float metallic; }; +#define MIN_PERCEPTUAL_ROUGHNESS 5e-2f +#define MIN_ROUGHNESS 2e-3f + // TODO consider normal filtering like filamented void propagateSmoothness(inout Pbr pbr) { pbr.roughness_perceptual = max(MIN_PERCEPTUAL_ROUGHNESS, 1.0f - pbr.smoothness); |
