Skip to content

Commit 90b3892

Browse files
committed
clearcoat fixes and improvements
1 parent 295e465 commit 90b3892

File tree

4 files changed

+145
-65
lines changed

4 files changed

+145
-65
lines changed

drivers/gles3/shaders/scene.glsl

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,11 @@ float SchlickFresnel(float u) {
14931493
return m2 * m2 * m; // pow(m,5)
14941494
}
14951495

1496+
float V_Kelemen(float LdotH) {
1497+
// Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling"
1498+
return 0.25 / (LdotH * LdotH + 1e-4);
1499+
}
1500+
14961501
void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_directional, float attenuation, vec3 f0, float roughness, float metallic, float specular_amount, vec3 albedo, inout float alpha, vec2 screen_uv,
14971502
#ifdef LIGHT_BACKLIGHT_USED
14981503
vec3 backlight,
@@ -1531,6 +1536,8 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di
15311536
float NdotV = dot(N, V);
15321537
float cNdotV = max(NdotV, 1e-4);
15331538

1539+
float cc_attenuation = 1.0;
1540+
15341541
#if defined(DIFFUSE_BURLEY) || defined(SPECULAR_SCHLICK_GGX) || defined(LIGHT_CLEARCOAT_USED)
15351542
vec3 H = normalize(V + L);
15361543
#endif
@@ -1543,6 +1550,20 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di
15431550
float cLdotH = clamp(A + dot(L, H), 0.0, 1.0);
15441551
#endif
15451552

1553+
#if defined(LIGHT_CLEARCOAT_USED)
1554+
// Clearcoat ignores normal_map, use vertex normal instead
1555+
float ccNdotH = clamp(A + dot(vertex_normal, H), 0.0, 1.0);
1556+
float cLdotH5 = SchlickFresnel(cLdotH);
1557+
1558+
float Dr = D_GGX(ccNdotH, mix(0.001, 0.1, clearcoat_roughness));
1559+
float Gr = V_Kelemen(cLdotH);
1560+
float Fr = mix(0.04, 1.0, cLdotH5) * clearcoat;
1561+
cc_attenuation = 1.0 - Fr;
1562+
float clearcoat_specular_brdf_NL = clearcoat * Gr * Fr * Dr * cNdotL;
1563+
1564+
specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount;
1565+
#endif // LIGHT_CLEARCOAT_USED
1566+
15461567
if (metallic < 1.0) {
15471568
float diffuse_brdf_NL; // BRDF times N.L for calculating diffuse radiance
15481569

@@ -1564,10 +1585,10 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di
15641585
diffuse_brdf_NL = cNdotL * (1.0 / M_PI);
15651586
#endif
15661587

1567-
diffuse_light += light_color * diffuse_brdf_NL * attenuation;
1588+
diffuse_light += light_color * diffuse_brdf_NL * attenuation * cc_attenuation;
15681589

15691590
#if defined(LIGHT_BACKLIGHT_USED)
1570-
diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation;
1591+
diffuse_light += light_color * (vec3(1.0 / M_PI) - diffuse_brdf_NL) * backlight * attenuation * cc_attenuation;
15711592
#endif
15721593

15731594
#if defined(LIGHT_RIM_USED)
@@ -1609,35 +1630,18 @@ void light_compute(vec3 N, vec3 L, vec3 V, float A, vec3 light_color, bool is_di
16091630
float G = V_GGX(cNdotL, cNdotV, alpha_ggx);
16101631
#endif // LIGHT_ANISOTROPY_USED
16111632
// F
1633+
#if !defined(LIGHT_CLEARCOAT_USED)
16121634
float cLdotH5 = SchlickFresnel(cLdotH);
1635+
#endif
16131636
// Calculate Fresnel using cheap approximate specular occlusion term from Filament:
16141637
// https://google.github.io/filament/Filament.html#lighting/occlusion/specularocclusion
16151638
float f90 = clamp(50.0 * f0.g, 0.0, 1.0);
16161639
vec3 F = f0 + (f90 - f0) * cLdotH5;
16171640

16181641
vec3 specular_brdf_NL = cNdotL * D * F * G;
16191642

1620-
specular_light += specular_brdf_NL * light_color * attenuation * specular_amount;
1643+
specular_light += specular_brdf_NL * light_color * attenuation * cc_attenuation * specular_amount;
16211644
#endif
1622-
1623-
#if defined(LIGHT_CLEARCOAT_USED)
1624-
// Clearcoat ignores normal_map, use vertex normal instead
1625-
float ccNdotL = max(min(A + dot(vertex_normal, L), 1.0), 0.0);
1626-
float ccNdotH = clamp(A + dot(vertex_normal, H), 0.0, 1.0);
1627-
float ccNdotV = max(dot(vertex_normal, V), 1e-4);
1628-
1629-
#if !defined(SPECULAR_SCHLICK_GGX)
1630-
float cLdotH5 = SchlickFresnel(cLdotH);
1631-
#endif
1632-
float Dr = D_GGX(ccNdotH, mix(0.001, 0.1, clearcoat_roughness));
1633-
float Gr = 0.25 / (cLdotH * cLdotH + 1e-4);
1634-
float Fr = mix(.04, 1.0, cLdotH5);
1635-
float clearcoat_specular_brdf_NL = clearcoat * Gr * Fr * Dr * cNdotL;
1636-
1637-
specular_light += clearcoat_specular_brdf_NL * light_color * attenuation * specular_amount;
1638-
// TODO: Clearcoat adds light to the scene right now (it is non-energy conserving), both diffuse and specular need to be scaled by (1.0 - FR)
1639-
// but to do so we need to rearrange this entire function
1640-
#endif // LIGHT_CLEARCOAT_USED
16411645
}
16421646

16431647
#ifdef USE_SHADOW_TO_OPACITY

servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,32 +1680,27 @@ void fragment_shader(in SceneData scene_data) {
16801680
#endif
16811681

16821682
#ifdef LIGHT_CLEARCOAT_USED
1683+
vec3 cc_specular_light = vec3(0.0);
1684+
vec3 cc_ref_vec = vec3(0.0);
16831685

16841686
if (bool(scene_data.flags & SCENE_DATA_FLAGS_USE_REFLECTION_CUBEMAP)) {
1685-
float NoV = max(dot(geo_normal, view), 0.0001); // We want to use geometric normal, not normal_map
1686-
vec3 ref_vec = reflect(-view, geo_normal);
1687-
ref_vec = mix(ref_vec, geo_normal, clearcoat_roughness * clearcoat_roughness);
1688-
// The clear coat layer assumes an IOR of 1.5 (4% reflectance)
1689-
float Fc = clearcoat * (0.04 + 0.96 * SchlickFresnel(NoV));
1690-
float attenuation = 1.0 - Fc;
1691-
ambient_light *= attenuation;
1692-
indirect_specular_light *= attenuation;
1693-
1694-
float horizon = min(1.0 + dot(ref_vec, indirect_normal), 1.0);
1695-
ref_vec = scene_data.radiance_inverse_xform * ref_vec;
1696-
float roughness_lod = mix(0.001, 0.1, sqrt(clearcoat_roughness)) * MAX_ROUGHNESS_LOD;
1687+
cc_ref_vec = reflect(-view, geo_normal);
1688+
cc_ref_vec = mix(cc_ref_vec, geo_normal, mix(0.001, 0.1, clearcoat_roughness));
1689+
1690+
vec3 cc_radiance_ref_vec = scene_data.radiance_inverse_xform * cc_ref_vec;
1691+
float roughness_lod = sqrt(mix(0.001, 0.1, clearcoat_roughness)) * MAX_ROUGHNESS_LOD;
16971692
#ifdef USE_RADIANCE_CUBEMAP_ARRAY
16981693

16991694
float lod, blend;
17001695
blend = modf(roughness_lod, lod);
1701-
vec3 clearcoat_light = texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(ref_vec, lod)).rgb;
1702-
clearcoat_light = mix(clearcoat_light, texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(ref_vec, lod + 1)).rgb, blend);
1696+
vec3 clearcoat_light = texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(cc_radiance_ref_vec, lod)).rgb;
1697+
clearcoat_light = mix(clearcoat_light, texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(cc_radiance_ref_vec, lod + 1)).rgb, blend);
17031698

17041699
#else
1705-
vec3 clearcoat_light = textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), ref_vec, roughness_lod).rgb;
1700+
vec3 clearcoat_light = textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), cc_radiance_ref_vec, roughness_lod).rgb;
17061701

17071702
#endif //USE_RADIANCE_CUBEMAP_ARRAY
1708-
indirect_specular_light += clearcoat_light * horizon * horizon * Fc * scene_data.ambient_light_color_energy.a;
1703+
cc_specular_light += clearcoat_light * scene_data.IBL_exposure_normalization * scene_data.ambient_light_color_energy.a;
17091704
}
17101705
#endif // LIGHT_CLEARCOAT_USED
17111706
#endif // !AMBIENT_LIGHT_DISABLED
@@ -1957,6 +1952,9 @@ void fragment_shader(in SceneData scene_data) {
19571952

19581953
vec4 reflection_accum = vec4(0.0, 0.0, 0.0, 0.0);
19591954
vec4 ambient_accum = vec4(0.0, 0.0, 0.0, 0.0);
1955+
#ifdef LIGHT_CLEARCOAT_USED
1956+
vec3 cc_reflection_accum = vec3(0.0, 0.0, 0.0);
1957+
#endif
19601958

19611959
uint cluster_reflection_offset = cluster_offset + implementation_data.cluster_type_size * 3;
19621960

@@ -2006,7 +2004,12 @@ void fragment_shader(in SceneData scene_data) {
20062004
break;
20072005
}
20082006

2009-
reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, indirect_specular_light, ambient_accum, reflection_accum);
2007+
reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, indirect_specular_light,
2008+
#ifdef LIGHT_CLEARCOAT_USED
2009+
cc_specular_light, cc_ref_vec, mix(0.001, 0.1, clearcoat_roughness), cc_reflection_accum,
2010+
#endif
2011+
ambient_accum, reflection_accum
2012+
);
20102013
}
20112014
}
20122015

@@ -2020,6 +2023,9 @@ void fragment_shader(in SceneData scene_data) {
20202023

20212024
if (reflection_accum.a > 0.0) {
20222025
indirect_specular_light = reflection_accum.rgb;
2026+
#ifdef LIGHT_CLEARCOAT_USED
2027+
cc_specular_light = cc_reflection_accum.rgb;
2028+
#endif
20232029
}
20242030

20252031
#if !defined(USE_LIGHTMAP)
@@ -2109,7 +2115,22 @@ void fragment_shader(in SceneData scene_data) {
21092115
// cheap luminance approximation
21102116
float f90 = clamp(50.0 * f0.g, metallic, 1.0);
21112117
indirect_specular_light *= energy_compensation * ((f90 - f0) * envBRDF.x + f0 * envBRDF.y);
2118+
2119+
#ifdef LIGHT_CLEARCOAT_USED
2120+
float geo_NdotV = max(dot(geo_normal, view), 0.0001); // We want to use geometric normal, not normal_map
2121+
// The clearcoat layer assumes an IOR of 1.5 (4% reflectance).
2122+
// Attenuate underlying diffuse/specular by clearcoat fresnel (ONLY fresnel, hence we don't just invert the BRDF below).
2123+
float NdotV5 = SchlickFresnel(geo_NdotV);
2124+
float F = mix(0.04, 1.0, NdotV5) * clearcoat;
2125+
float cc_attenuation = 1.0 - F;
2126+
2127+
ambient_light *= cc_attenuation;
2128+
indirect_specular_light *= cc_attenuation;
2129+
2130+
indirect_specular_light += cc_specular_light * (F * clearcoat);
21122131
#endif
2132+
2133+
#endif // DIFFUSE_TOON
21132134
}
21142135

21152136
#endif // !AMBIENT_LIGHT_DISABLED

servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,33 +1596,28 @@ void main() {
15961596
ambient_light = mix(ambient_light, custom_irradiance.rgb, custom_irradiance.a);
15971597
#endif // CUSTOM_IRRADIANCE_USED
15981598
#ifdef LIGHT_CLEARCOAT_USED
1599+
hvec3 cc_specular_light = hvec3(0.0);
1600+
hvec3 cc_ref_vec = hvec3(0.0);
15991601

16001602
if (sc_scene_use_reflection_cubemap()) {
1601-
half NoV = max(dot(geo_normal, view), half(0.0001));
1602-
hvec3 ref_vec = reflect(-view, geo_normal);
1603-
ref_vec = mix(ref_vec, geo_normal, clearcoat_roughness * clearcoat_roughness);
1604-
// The clear coat layer assumes an IOR of 1.5 (4% reflectance)
1605-
half Fc = clearcoat * (half(0.04) + half(0.96) * SchlickFresnel(NoV));
1606-
half attenuation = half(1.0) - Fc;
1607-
ambient_light *= attenuation;
1608-
indirect_specular_light *= attenuation;
1603+
cc_ref_vec = reflect(-view, geo_normal);
1604+
cc_ref_vec = mix(cc_ref_vec, geo_normal, mix(0.001, 0.1, clearcoat_roughness));
16091605

1610-
half horizon = min(half(1.0) + dot(ref_vec, indirect_normal), half(1.0));
1611-
ref_vec = hvec3(scene_data.radiance_inverse_xform * vec3(ref_vec));
1612-
float roughness_lod = mix(0.001, 0.1, sqrt(float(clearcoat_roughness))) * MAX_ROUGHNESS_LOD;
1606+
hvec3 cc_radiance_ref_vec = hvec3(scene_data.radiance_inverse_xform * vec3(cc_ref_vec));
1607+
float roughness_lod = sqrt(mix(0.001, 0.1, clearcoat_roughness)) * MAX_ROUGHNESS_LOD;
16131608
#ifdef USE_RADIANCE_CUBEMAP_ARRAY
16141609

16151610
float lod;
16161611
half blend = half(modf(roughness_lod, lod));
1617-
hvec3 clearcoat_sample_a = hvec3(texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(ref_vec, lod)).rgb);
1618-
hvec3 clearcoat_sample_b = hvec3(texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(ref_vec, lod + 1)).rgb);
1612+
hvec3 clearcoat_sample_a = hvec3(texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(cc_radiance_ref_vec, lod)).rgb);
1613+
hvec3 clearcoat_sample_b = hvec3(texture(samplerCubeArray(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(cc_radiance_ref_vec, lod + 1)).rgb);
16191614
hvec3 clearcoat_light = mix(clearcoat_sample_a, clearcoat_sample_b, blend);
16201615

16211616
#else
1622-
hvec3 clearcoat_light = hvec3(textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec3(ref_vec), roughness_lod).rgb);
1617+
hvec3 clearcoat_light = hvec3(textureLod(samplerCube(radiance_cubemap, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec3(cc_radiance_ref_vec), roughness_lod).rgb);
16231618

16241619
#endif //USE_RADIANCE_CUBEMAP_ARRAY
1625-
indirect_specular_light += clearcoat_light * horizon * horizon * Fc * half(scene_data.ambient_light_color_energy.a);
1620+
cc_specular_light += clearcoat_light * half(scene_data.IBL_exposure_normalization) * half(scene_data.ambient_light_color_energy.a);
16261621
}
16271622
#endif // LIGHT_CLEARCOAT_USED
16281623
#endif // !AMBIENT_LIGHT_DISABLED
@@ -1714,6 +1709,9 @@ void main() {
17141709
if (reflection_probe_count > 0) {
17151710
hvec4 reflection_accum = hvec4(0.0);
17161711
hvec4 ambient_accum = hvec4(0.0);
1712+
#ifdef LIGHT_CLEARCOAT_USED
1713+
hvec3 cc_reflection_accum = hvec3(0.0, 0.0, 0.0);
1714+
#endif
17171715

17181716
#ifdef LIGHT_ANISOTROPY_USED
17191717
// https://google.github.io/filament/Filament.html#lighting/imagebasedlights/anisotropy
@@ -1739,7 +1737,12 @@ void main() {
17391737
break;
17401738
}
17411739

1742-
reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, indirect_specular_light, ambient_accum, reflection_accum);
1740+
reflection_process(reflection_index, vertex, ref_vec, normal, roughness, ambient_light, indirect_specular_light,
1741+
#ifdef LIGHT_CLEARCOAT_USED
1742+
cc_specular_light, cc_ref_vec, mix(0.001, 0.1, clearcoat_roughness), cc_reflection_accum,
1743+
#endif
1744+
ambient_accum, reflection_accum
1745+
);
17431746
}
17441747

17451748
if (ambient_accum.a < half(1.0)) {
@@ -1752,6 +1755,9 @@ void main() {
17521755

17531756
if (reflection_accum.a > half(0.0)) {
17541757
indirect_specular_light = reflection_accum.rgb;
1758+
#ifdef LIGHT_CLEARCOAT_USED
1759+
cc_specular_light = cc_reflection_accum.rgb;
1760+
#endif
17551761
}
17561762

17571763
#if !defined(USE_LIGHTMAP)
@@ -1810,7 +1816,22 @@ void main() {
18101816
hvec2 env = hvec2(-1.04, 1.04) * a004 + r.zw;
18111817

18121818
indirect_specular_light *= env.x * f0 + env.y * clamp(half(50.0) * f0.g, metallic, half(1.0));
1819+
1820+
#ifdef LIGHT_CLEARCOAT_USED
1821+
half geo_NdotV = max(dot(geo_normal, view), half(0.0001)); // We want to use geometric normal, not normal_map
1822+
// The clearcoat layer assumes an IOR of 1.5 (4% reflectance).
1823+
// Attenuate underlying diffuse/specular by clearcoat fresnel (ONLY fresnel, hence we don't just invert the BRDF below).
1824+
half NdotV5 = SchlickFresnel(geo_NdotV);
1825+
half F = mix(half(0.04), half(1.0), NdotV5) * clearcoat;
1826+
half cc_attenuation = half(1.0) - F;
1827+
1828+
ambient_light *= cc_attenuation;
1829+
indirect_specular_light *= cc_attenuation;
1830+
1831+
indirect_specular_light += cc_specular_light * (F * clearcoat);
18131832
#endif
1833+
1834+
#endif // DIFFUSE_TOON
18141835
}
18151836

18161837
#endif // !AMBIENT_LIGHT_DISABLED

0 commit comments

Comments
 (0)