1#define_import_path bevy_pbr::lighting3#import bevy_pbr::{4 mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE,5 mesh_view_bindings as view_bindings,6}7#import bevy_render::maths::PI9const LAYER_BASE: u32 = 0;10const LAYER_CLEARCOAT: u32 = 1;12// From the Filament design doc13// https://google.github.io/filament/Filament.html#table_symbols14// Symbol Definition15// v View unit vector16// l Incident light unit vector17// n Surface normal unit vector18// h Half unit vector between l and v19// f BRDF20// f_d Diffuse component of a BRDF21// f_r Specular component of a BRDF22// α Roughness, remapped from using input perceptualRoughness23// σ Diffuse reflectance24// Ω Spherical domain25// f0 Reflectance at normal incidence26// f90 Reflectance at grazing angle27// χ+(a) Heaviside function (1 if a>0 and 0 otherwise)28// nior Index of refraction (IOR) of an interface29// ⟨n⋅l⟩ Dot product clamped to [0..1]30// ⟨a⟩ Saturated value (clamped to [0..1])32// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material33// and consists of two components, the diffuse component (f_d) and the specular component (f_r):34// f(v,l) = f_d(v,l) + f_r(v,l)35//36// The form of the microfacet model is the same for diffuse and specular37// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm38//39// In which:40// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets41// G models the visibility (or occlusion or shadow-masking) of the microfacets42// f_m is the microfacet BRDF and differs between specular and diffuse components43//44// The above integration needs to be approximated.46// Input to a lighting function for a single layer (either the base layer or the47// clearcoat layer).48struct LayerLightingInput {49 // The normal vector.50 N: vec3<f32>,51 // The reflected vector.52 R: vec3<f32>,53 // The normal vector ⋅ the view vector.54 NdotV: f32,56 // The perceptual roughness of the layer.57 perceptual_roughness: f32,58 // The roughness of the layer.59 roughness: f32,60}62// Input to a lighting function (`point_light`, `spot_light`,63// `directional_light`).64struct LightingInput {65#ifdef STANDARD_MATERIAL_CLEARCOAT66 layers: array<LayerLightingInput, 2>,67#else // STANDARD_MATERIAL_CLEARCOAT68 layers: array<LayerLightingInput, 1>,69#endif // STANDARD_MATERIAL_CLEARCOAT71 // The world-space position.72 P: vec3<f32>,73 // The vector to the view.74 V: vec3<f32>,76 // The diffuse color of the material.77 diffuse_color: vec3<f32>,79 // Specular reflectance at the normal incidence angle.80 //81 // This should be read F₀, but due to Naga limitations we can't name it that.82 F0_: vec3<f32>,83 // Constants for the BRDF approximation.84 //85 // See `EnvBRDFApprox` in86 // <https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile>.87 // What we call `F_ab` they call `AB`.88 F_ab: vec2<f32>,90#ifdef STANDARD_MATERIAL_CLEARCOAT91 // The strength of the clearcoat layer.92 clearcoat_strength: f32,93#endif // STANDARD_MATERIAL_CLEARCOAT95#ifdef STANDARD_MATERIAL_ANISOTROPY96 // The anisotropy strength, reflecting the amount of increased roughness in97 // the tangent direction.98 anisotropy: f32,99 // The tangent direction for anisotropy: i.e. the direction in which100 // roughness increases.101 Ta: vec3<f32>,102 // The bitangent direction, which is the cross product of the normal with103 // the tangent direction.104 Ba: vec3<f32>,105#endif // STANDARD_MATERIAL_ANISOTROPY106}108// Values derived from the `LightingInput` for both diffuse and specular lights.109struct DerivedLightingInput {110 // The half-vector between L, the incident light vector, and V, the view111 // vector.112 H: vec3<f32>,113 // The normal vector ⋅ the incident light vector.114 NdotL: f32,115 // The normal vector ⋅ the half-vector.116 NdotH: f32,117 // The incident light vector ⋅ the half-vector.118 LdotH: f32,119}121// distanceAttenuation is simply the square falloff of light intensity122// combined with a smooth attenuation at the edge of the light radius123//124// light radius is a non-physical construct for efficiency purposes,125// because otherwise every light affects every fragment in the scene126fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 {127 let factor = distanceSquare * inverseRangeSquared;128 let smoothFactor = saturate(1.0 - factor * factor);129 let attenuation = smoothFactor * smoothFactor;130 return attenuation * 1.0 / max(distanceSquare, 0.0001);131}133// Normal distribution function (specular D)134// Based on https://google.github.io/filament/Filament.html#citation-walter07136// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 }138// Simple implementation, has precision problems when using fp16 instead of fp32139// see https://google.github.io/filament/Filament.html#listing_speculardfp16140fn D_GGX(roughness: f32, NdotH: f32, h: vec3<f32>) -> f32 {141 let oneMinusNdotHSquared = 1.0 - NdotH * NdotH;142 let a = NdotH * roughness;143 let k = roughness / (oneMinusNdotHSquared + a * a);144 let d = k * k * (1.0 / PI);145 return d;146}148// An approximation of the anisotropic GGX distribution function.149//150// 1151// D(𝐡) = ───────────────────────────────────────────────────152// παₜα_b((𝐡 ⋅ 𝐭)² / αₜ²) + (𝐡 ⋅ 𝐛)² / α_b² + (𝐡 ⋅ 𝐧)²)²153//154// * `T` = 𝐭 = the tangent direction = the direction of increased roughness.155//156// * `B` = 𝐛 = the bitangent direction = the direction of decreased roughness.157//158// * `at` = αₜ = the alpha-roughness in the tangent direction.159//160// * `ab` = α_b = the alpha-roughness in the bitangent direction.161//162// This is from the `KHR_materials_anisotropy` spec:163// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md#individual-lights>164fn D_GGX_anisotropic(at: f32, ab: f32, NdotH: f32, TdotH: f32, BdotH: f32) -> f32 {165 let a2 = at * ab;166 let f = vec3(ab * TdotH, at * BdotH, a2 * NdotH);167 let w2 = a2 / dot(f, f);168 let d = a2 * w2 * w2 * (1.0 / PI);169 return d;170}172// Visibility function (Specular G)173// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) }174// such that f_r becomes175// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0)176// where177// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) }178// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv179fn V_SmithGGXCorrelated(roughness: f32, NdotV: f32, NdotL: f32) -> f32 {180 let a2 = roughness * roughness;181 let lambdaV = NdotL * sqrt((NdotV - a2 * NdotV) * NdotV + a2);182 let lambdaL = NdotV * sqrt((NdotL - a2 * NdotL) * NdotL + a2);183 let v = 0.5 / (lambdaV + lambdaL);184 return v;185}187// The visibility function, anisotropic variant.188fn V_GGX_anisotropic(189 at: f32,190 ab: f32,191 NdotL: f32,192 NdotV: f32,193 BdotV: f32,194 TdotV: f32,195 TdotL: f32,196 BdotL: f32,197) -> f32 {198 let GGX_V = NdotL * length(vec3(at * TdotV, ab * BdotV, NdotV));199 let GGX_L = NdotV * length(vec3(at * TdotL, ab * BdotL, NdotL));200 let v = 0.5 / (GGX_V + GGX_L);201 return saturate(v);202}204// A simpler, but nonphysical, alternative to Smith-GGX. We use this for205// clearcoat, per the Filament spec.206//207// https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel#toc4.9.1208fn V_Kelemen(LdotH: f32) -> f32 {209 return 0.25 / (LdotH * LdotH);210}212// Fresnel function213// see https://google.github.io/filament/Filament.html#citation-schlick94214// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5215fn F_Schlick_vec(f0: vec3<f32>, f90: f32, VdotH: f32) -> vec3<f32> {216 // not using mix to keep the vec3 and float versions identical217 return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);218}220fn F_Schlick(f0: f32, f90: f32, VdotH: f32) -> f32 {221 // not using mix to keep the vec3 and float versions identical222 return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);223}225fn fresnel(f0: vec3<f32>, LdotH: f32) -> vec3<f32> {226 // f_90 suitable for ambient occlusion227 // see https://google.github.io/filament/Filament.html#lighting/occlusion228 let f90 = saturate(dot(f0, vec3<f32>(50.0 * 0.33)));229 return F_Schlick_vec(f0, f90, LdotH);230}232// Given distribution, visibility, and Fresnel term, calculates the final233// specular light.234//235// Multiscattering approximation:236// <https://google.github.io/filament/Filament.html#listing_energycompensationimpl>237fn specular_multiscatter(238 input: ptr<function, LightingInput>,239 D: f32,240 V: f32,241 F: vec3<f32>,242 specular_intensity: f32,243) -> vec3<f32> {244 // Unpack.245 let F0 = (*input).F0_;246 let F_ab = (*input).F_ab;248 var Fr = (specular_intensity * D * V) * F;249 Fr *= 1.0 + F0 * (1.0 / F_ab.x - 1.0);250 return Fr;251}253// Specular BRDF254// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf256// N, V, and L must all be normalized.257fn derive_lighting_input(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> DerivedLightingInput {258 var input: DerivedLightingInput;259 var H: vec3<f32> = normalize(L + V);260 input.H = H;261 input.NdotL = saturate(dot(N, L));262 input.NdotH = saturate(dot(N, H));263 input.LdotH = saturate(dot(L, H));264 return input;265}267// Returns L in the `xyz` components and the specular intensity in the `w` component.268fn compute_specular_layer_values_for_point_light(269 input: ptr<function, LightingInput>,270 layer: u32,271 V: vec3<f32>,272 light_to_frag: vec3<f32>,273 light_position_radius: f32,274) -> vec4<f32> {275 // Unpack.276 let R = (*input).layers[layer].R;277 let a = (*input).layers[layer].roughness;279 // Representative Point Area Lights.280 // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16281 var LtFdotR = dot(light_to_frag, R);283 // HACK: the following line is an amendment to fix a discontinuity when a surface284 // intersects the light sphere. See https://github.com/bevyengine/bevy/issues/13318285 //286 // This sentence in the reference is crux of the problem: "We approximate finding the point with the287 // smallest angle to the reflection ray by finding the point with the smallest distance to the ray."288 // This approximation turns out to be completely wrong for points inside or near the sphere.289 // Clamping this dot product to be positive ensures `centerToRay` lies on ray and not behind it.290 // Any non-zero epsilon works here, it just has to be positive to avoid a singularity at zero.291 // However, this is still far from physically accurate. Deriving an exact solution would help,292 // but really we should adopt a superior solution to area lighting, such as:293 // Physically Based Area Lights by Michal Drobot, or294 // Polygonal-Light Shading with Linearly Transformed Cosines by Eric Heitz et al.295 LtFdotR = max(0.0001, LtFdotR);297 let centerToRay = LtFdotR * R - light_to_frag;298 let closestPoint = light_to_frag + centerToRay * saturate(299 light_position_radius * inverseSqrt(dot(centerToRay, centerToRay)));300 let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint));301 let normalizationFactor = a / saturate(a + (light_position_radius * 0.5 * LspecLengthInverse));302 let intensity = normalizationFactor * normalizationFactor;304 let L: vec3<f32> = closestPoint * LspecLengthInverse; // normalize() equivalent?305 return vec4(L, intensity);306}308// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m309// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) }310fn specular(311 input: ptr<function, LightingInput>,312 derived_input: ptr<function, DerivedLightingInput>,313 specular_intensity: f32,314) -> vec3<f32> {315 // Unpack.316 let roughness = (*input).layers[LAYER_BASE].roughness;317 let NdotV = (*input).layers[LAYER_BASE].NdotV;318 let F0 = (*input).F0_;319 let H = (*derived_input).H;320 let NdotL = (*derived_input).NdotL;321 let NdotH = (*derived_input).NdotH;322 let LdotH = (*derived_input).LdotH;324 // Calculate distribution.325 let D = D_GGX(roughness, NdotH, H);326 // Calculate visibility.327 let V = V_SmithGGXCorrelated(roughness, NdotV, NdotL);328 // Calculate the Fresnel term.329 let F = fresnel(F0, LdotH);331 // Calculate the specular light.332 let Fr = specular_multiscatter(input, D, V, F, specular_intensity);333 return Fr;334}336// Calculates the specular light for the clearcoat layer. Returns Fc, the337// Fresnel term, in the first channel, and Frc, the specular clearcoat light, in338// the second channel.339//340// <https://google.github.io/filament/Filament.html#listing_clearcoatbrdf>341fn specular_clearcoat(342 input: ptr<function, LightingInput>,343 derived_input: ptr<function, DerivedLightingInput>,344 clearcoat_strength: f32,345 specular_intensity: f32,346) -> vec2<f32> {347 // Unpack.348 let roughness = (*input).layers[LAYER_CLEARCOAT].roughness;349 let H = (*derived_input).H;350 let NdotH = (*derived_input).NdotH;351 let LdotH = (*derived_input).LdotH;353 // Calculate distribution.354 let Dc = D_GGX(roughness, NdotH, H);355 // Calculate visibility.356 let Vc = V_Kelemen(LdotH);357 // Calculate the Fresnel term.358 let Fc = F_Schlick(0.04, 1.0, LdotH) * clearcoat_strength;359 // Calculate the specular light.360 let Frc = (specular_intensity * Dc * Vc) * Fc;361 return vec2(Fc, Frc);362}364#ifdef STANDARD_MATERIAL_ANISOTROPY366fn specular_anisotropy(367 input: ptr<function, LightingInput>,368 derived_input: ptr<function, DerivedLightingInput>,369 L: vec3<f32>,370 specular_intensity: f32,371) -> vec3<f32> {372 // Unpack.373 let roughness = (*input).layers[LAYER_BASE].roughness;374 let NdotV = (*input).layers[LAYER_BASE].NdotV;375 let V = (*input).V;376 let F0 = (*input).F0_;377 let anisotropy = (*input).anisotropy;378 let Ta = (*input).Ta;379 let Ba = (*input).Ba;380 let H = (*derived_input).H;381 let NdotL = (*derived_input).NdotL;382 let NdotH = (*derived_input).NdotH;383 let LdotH = (*derived_input).LdotH;385 let TdotL = dot(Ta, L);386 let BdotL = dot(Ba, L);387 let TdotH = dot(Ta, H);388 let BdotH = dot(Ba, H);389 let TdotV = dot(Ta, V);390 let BdotV = dot(Ba, V);392 let ab = roughness * roughness;393 let at = mix(ab, 1.0, anisotropy * anisotropy);395 let Da = D_GGX_anisotropic(at, ab, NdotH, TdotH, BdotH);396 let Va = V_GGX_anisotropic(at, ab, NdotL, NdotV, BdotV, TdotV, TdotL, BdotL);397 let Fa = fresnel(F0, LdotH);399 // Calculate the specular light.400 let Fr = specular_multiscatter(input, Da, Va, Fa, specular_intensity);401 return Fr;402}404#endif // STANDARD_MATERIAL_ANISOTROPY406// Diffuse BRDF407// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf408// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm409//410// simplest approximation411// float Fd_Lambert() {412// return 1.0 / PI;413// }414//415// vec3 Fd = diffuseColor * Fd_Lambert();416//417// Disney approximation418// See https://google.github.io/filament/Filament.html#citation-burley12419// minimal quality difference420fn Fd_Burley(421 input: ptr<function, LightingInput>,422 derived_input: ptr<function, DerivedLightingInput>,423) -> f32 {424 // Unpack.425 let roughness = (*input).layers[LAYER_BASE].roughness;426 let NdotV = (*input).layers[LAYER_BASE].NdotV;427 let NdotL = (*derived_input).NdotL;428 let LdotH = (*derived_input).LdotH;430 let f90 = 0.5 + 2.0 * roughness * LdotH * LdotH;431 let lightScatter = F_Schlick(1.0, f90, NdotL);432 let viewScatter = F_Schlick(1.0, f90, NdotV);433 return lightScatter * viewScatter * (1.0 / PI);434}436// Scale/bias approximation437// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile438// TODO: Use a LUT (more accurate)439fn F_AB(perceptual_roughness: f32, NdotV: f32) -> vec2<f32> {440 let c0 = vec4<f32>(-1.0, -0.0275, -0.572, 0.022);441 let c1 = vec4<f32>(1.0, 0.0425, 1.04, -0.04);442 let r = perceptual_roughness * c0 + c1;443 let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;444 return vec2<f32>(-1.04, 1.04) * a004 + r.zw;445}447fn EnvBRDFApprox(F0: vec3<f32>, F_ab: vec2<f32>) -> vec3<f32> {448 return F0 * F_ab.x + F_ab.y;449}451fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 {452 // clamp perceptual roughness to prevent precision problems453 // According to Filament design 0.089 is recommended for mobile454 // Filament uses 0.045 for non-mobile455 let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);456 return clampedPerceptualRoughness * clampedPerceptualRoughness;457}459fn point_light(460 light_id: u32,461 input: ptr<function, LightingInput>,462 enable_diffuse: bool463) -> vec3<f32> {464 // Unpack.465 let diffuse_color = (*input).diffuse_color;466 let P = (*input).P;467 let N = (*input).layers[LAYER_BASE].N;468 let V = (*input).V;470 let light = &view_bindings::clusterable_objects.data[light_id];471 let light_to_frag = (*light).position_radius.xyz - P;472 let L = normalize(light_to_frag);473 let distance_square = dot(light_to_frag, light_to_frag);474 let rangeAttenuation = getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w);476 // Base layer478 let specular_L_intensity = compute_specular_layer_values_for_point_light(479 input,480 LAYER_BASE,481 V,482 light_to_frag,483 (*light).position_radius.w,484 );485 var specular_derived_input = derive_lighting_input(N, V, specular_L_intensity.xyz);487 let specular_intensity = specular_L_intensity.w;489#ifdef STANDARD_MATERIAL_ANISOTROPY490 let specular_light = specular_anisotropy(input, &specular_derived_input, L, specular_intensity);491#else // STANDARD_MATERIAL_ANISOTROPY492 let specular_light = specular(input, &specular_derived_input, specular_intensity);493#endif // STANDARD_MATERIAL_ANISOTROPY495 // Clearcoat497#ifdef STANDARD_MATERIAL_CLEARCOAT498 // Unpack.499 let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;500 let clearcoat_strength = (*input).clearcoat_strength;502 // Perform specular input calculations again for the clearcoat layer. We503 // can't reuse the above because the clearcoat normal might be different504 // from the main layer normal.505 let clearcoat_specular_L_intensity = compute_specular_layer_values_for_point_light(506 input,507 LAYER_CLEARCOAT,508 V,509 light_to_frag,510 (*light).position_radius.w,511 );512 var clearcoat_specular_derived_input =513 derive_lighting_input(clearcoat_N, V, clearcoat_specular_L_intensity.xyz);515 // Calculate the specular light.516 let clearcoat_specular_intensity = clearcoat_specular_L_intensity.w;517 let Fc_Frc = specular_clearcoat(518 input,519 &clearcoat_specular_derived_input,520 clearcoat_strength,521 clearcoat_specular_intensity522 );523 let inv_Fc = 1.0 - Fc_Frc.r; // Inverse Fresnel term.524 let Frc = Fc_Frc.g; // Clearcoat light.525#endif // STANDARD_MATERIAL_CLEARCOAT527 // Diffuse.528 // Comes after specular since its N⋅L is used in the lighting equation.529 var derived_input = derive_lighting_input(N, V, L);530 var diffuse = vec3(0.0);531 if (enable_diffuse) {532 diffuse = diffuse_color * Fd_Burley(input, &derived_input);533 }535 // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation536 // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩537 // where538 // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color539 // Φ is luminous power in lumens540 // our rangeAttenuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius542 // For a point light, luminous intensity, I, in lumens per steradian is given by:543 // I = Φ / 4 π544 // The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower546 // NOTE: (*light).color.rgb is premultiplied with (*light).intensity / 4 π (which would be the luminous intensity) on the CPU548 var color: vec3<f32>;549#ifdef STANDARD_MATERIAL_CLEARCOAT550 // Account for the Fresnel term from the clearcoat darkening the main layer.551 //552 // <https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel/integrationinthesurfaceresponse>553 color = (diffuse + specular_light * inv_Fc) * inv_Fc + Frc;554#else // STANDARD_MATERIAL_CLEARCOAT555 color = diffuse + specular_light;556#endif // STANDARD_MATERIAL_CLEARCOAT558 return color * (*light).color_inverse_square_range.rgb *559 (rangeAttenuation * derived_input.NdotL);560}562fn spot_light(563 light_id: u32,564 input: ptr<function, LightingInput>,565 enable_diffuse: bool566) -> vec3<f32> {567 // reuse the point light calculations568 let point_light = point_light(light_id, input, enable_diffuse);570 let light = &view_bindings::clusterable_objects.data[light_id];572 // reconstruct spot dir from x/z and y-direction flag573 var spot_dir = vec3<f32>((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y);574 spot_dir.y = sqrt(max(0.0, 1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z));575 if ((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u {576 spot_dir.y = -spot_dir.y;577 }578 let light_to_frag = (*light).position_radius.xyz - (*input).P.xyz;580 // calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight581 // spot_scale and spot_offset have been precomputed582 // note we normalize here to get "l" from the filament listing. spot_dir is already normalized583 let cd = dot(-spot_dir, normalize(light_to_frag));584 let attenuation = saturate(cd * (*light).light_custom_data.z + (*light).light_custom_data.w);585 let spot_attenuation = attenuation * attenuation;587 return point_light * spot_attenuation;588}590fn directional_light(591 light_id: u32,592 input: ptr<function, LightingInput>,593 enable_diffuse: bool594) -> vec3<f32> {595 // Unpack.596 let diffuse_color = (*input).diffuse_color;597 let NdotV = (*input).layers[LAYER_BASE].NdotV;598 let N = (*input).layers[LAYER_BASE].N;599 let V = (*input).V;600 let roughness = (*input).layers[LAYER_BASE].roughness;602 let light = &view_bindings::lights.directional_lights[light_id];604 let L = (*light).direction_to_light.xyz;605 var derived_input = derive_lighting_input(N, V, L);607 var diffuse = vec3(0.0);608 if (enable_diffuse) {609 diffuse = diffuse_color * Fd_Burley(input, &derived_input);610 }612#ifdef STANDARD_MATERIAL_ANISOTROPY613 let specular_light = specular_anisotropy(input, &derived_input, L, 1.0);614#else // STANDARD_MATERIAL_ANISOTROPY615 let specular_light = specular(input, &derived_input, 1.0);616#endif // STANDARD_MATERIAL_ANISOTROPY618#ifdef STANDARD_MATERIAL_CLEARCOAT619 let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;620 let clearcoat_strength = (*input).clearcoat_strength;622 // Perform specular input calculations again for the clearcoat layer. We623 // can't reuse the above because the clearcoat normal might be different624 // from the main layer normal.625 var derived_clearcoat_input = derive_lighting_input(clearcoat_N, V, L);627 let Fc_Frc =628 specular_clearcoat(input, &derived_clearcoat_input, clearcoat_strength, 1.0);629 let inv_Fc = 1.0 - Fc_Frc.r;630 let Frc = Fc_Frc.g;631#endif // STANDARD_MATERIAL_CLEARCOAT633 var color: vec3<f32>;634#ifdef STANDARD_MATERIAL_CLEARCOAT635 // Account for the Fresnel term from the clearcoat darkening the main layer.636 //637 // <https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel/integrationinthesurfaceresponse>638 color = (diffuse + specular_light * inv_Fc) * inv_Fc * derived_input.NdotL +639 Frc * derived_clearcoat_input.NdotL;640#else // STANDARD_MATERIAL_CLEARCOAT641 color = (diffuse + specular_light) * derived_input.NdotL;642#endif // STANDARD_MATERIAL_CLEARCOAT644 return color * (*light).color.rgb;645}