From ede8bbae3496315e5ef821d4c00cf04022a3d453 Mon Sep 17 00:00:00 2001 From: yum Date: Mon, 23 Feb 2026 22:44:17 -0800 Subject: Implement bent normals --- filamented.cginc | 35 +++++++++++++++++++++++++++++++++++ lighting.cginc | 18 +++++++++++++++++- pbr.cginc | 16 ++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/filamented.cginc b/filamented.cginc index 62ec4c3..6d34123 100755 --- a/filamented.cginc +++ b/filamented.cginc @@ -301,4 +301,39 @@ inline half3 UnityGI_prefilteredRadiance(const UnityGIInput data, const float pe return specular; } +#if defined(_AMBIENT_OCCLUSION) || defined(_BENT_NORMALS) +// Oat and Sander 2007, "Ambient Aperture Lighting" +// Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion" +float sphericalCapsIntersection(float cosCap1, float cosCap2, float cosDistance) { + float r1 = acos(cosCap1); + float r2 = acos(cosCap2); + float d = acos(cosDistance); + + if (min(r1, r2) <= max(r1, r2) - d) { + return 1.0 - max(cosCap1, cosCap2); + } else if (r1 + r2 <= d) { + return 0.0; + } + + float delta = abs(r1 - r2); + float x = 1.0 - saturate((d - delta) / max(r1 + r2 - delta, 1e-4)); + float x2 = x * x; + float area = -2.0 * x2 * x + 3.0 * x2; + return area * (1.0 - max(cosCap1, cosCap2)); +} + +float computeSpecularAO(float NoV, float visibility, float roughness, float3 bent_normal, float3 reflect_dir) { +#if defined(_BENT_NORMALS) + // Jimenez et al. 2016, "Practical Realtime Strategies for Accurate Indirect Occlusion" + float cosAv = sqrt(1.0 - visibility); // aperture from AO + float cosAs = exp2(-3.321928 * roughness * roughness); // aperture from roughness; log(10)/log(2) = 3.321928 + float cosB = dot(bent_normal, reflect_dir); // angle between bent normal and reflection + return sphericalCapsIntersection(cosAv, cosAs, saturate(0.5 * cosB + 0.5)) / (1.0 - cosAs); +#else + // Lagarde and de Rousiers 2014, "Moving Frostbite to Physically Based Rendering" + return saturate(pow(NoV + visibility, exp2(-16.0 * roughness - 1.0)) - 1.0 + visibility); +#endif +} +#endif // _AMBIENT_OCCLUSION || _BENT_NORMALS + #endif // __FILAMENTED_INC diff --git a/lighting.cginc b/lighting.cginc index d5e0be8..e56bb37 100755 --- a/lighting.cginc +++ b/lighting.cginc @@ -174,11 +174,15 @@ float3 yumSH9(float4 n, float3 worldPos, inout LightIndirect light) { float4 getIndirectDiffuse(v2f i, Pbr pbr, inout LightIndirect light) { float4 diffuse = 0; #if defined(FORWARD_BASE_PASS) +#if defined(_BENT_NORMALS) + diffuse.xyz += max(0, yumSH9(float4(pbr.bent_normal, 1.0), i.worldPos, light)); +#else diffuse.xyz += max(0, yumSH9(float4(i.normal, 1.0), i.worldPos, light)); #endif +#endif #if defined(_AMBIENT_OCCLUSION) - diffuse.xyz *= saturate(lerp(1, _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy), _OcclusionStrength)); + diffuse.xyz *= pbr.ao; #endif return diffuse; @@ -233,6 +237,18 @@ void GetLighting(v2f i, Pbr pbr, out LightData data) { data.indirect.diffuse = getIndirectDiffuse(i, pbr, data.indirect); data.indirect.specular = getIndirectSpecular(i, pbr.roughness_perceptual, view_dir, -data.indirect.dir); +#if defined(_AMBIENT_OCCLUSION) || defined(_BENT_NORMALS) + float ao_vis = 1.0; +#if defined(_AMBIENT_OCCLUSION) + ao_vis = pbr.ao; +#endif +#if defined(_BENT_NORMALS) + float3 spec_ao_bent = pbr.bent_normal; +#else + float3 spec_ao_bent = pbr.normal; +#endif + data.indirect.specular *= computeSpecularAO(data.common.NoV, ao_vis, pbr.roughness, spec_ao_bent, -data.indirect.dir); +#endif #if defined(_CLEARCOAT) data.indirect.specular_cc = getIndirectSpecular(i, saturate(sqrt(pbr.cc_roughness)), view_dir, dir_cc); #if defined(_CLEARCOAT_MASK) diff --git a/pbr.cginc b/pbr.cginc index 25d6ece..5e19089 100755 --- a/pbr.cginc +++ b/pbr.cginc @@ -16,6 +16,12 @@ struct Pbr { float roughness_perceptual; float roughness; float metallic; +#if defined(_AMBIENT_OCCLUSION) + float ao; +#endif +#if defined(_BENT_NORMALS) + float3 bent_normal; +#endif #if defined(_CLEARCOAT) float cc_roughness; float cc_strength; @@ -208,6 +214,12 @@ Pbr getPbr(v2f i) { pbr.normal = normalize(mul(normal_tangent, pbr.tbn)); +#if defined(_BENT_NORMALS) + float3 bent_ts = UnpackNormal(_Bent_Normals_Map.Sample(aniso4_trilinear_repeat_s, uv_parallax * _Bent_Normals_Map_ST.xy + _Bent_Normals_Map_ST.zw)); + bent_ts.xy *= _Bent_Normals_Strength; + pbr.bent_normal = normalize(mul(bent_ts, pbr.tbn)); +#endif + float4 metallic_gloss = _MetallicGlossMap.Sample(aniso4_trilinear_repeat_s, uv_parallax * _MetallicGlossMap_ST.xy); pbr.smoothness = metallic_gloss.a * _Glossiness; pbr.metallic = metallic_gloss.r * _Metallic; @@ -219,6 +231,10 @@ Pbr getPbr(v2f i) { #endif propagateSmoothness(pbr); +#if defined(_AMBIENT_OCCLUSION) + pbr.ao = saturate(lerp(1.0, _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy).r, _OcclusionStrength)); +#endif + return pbr; } -- cgit v1.2.3