Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1#define_import_path bevy_pbr::lighting
3#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::PI
9const LAYER_BASE: u32 = 0;
10const LAYER_CLEARCOAT: u32 = 1;
12// From the Filament design doc
13// https://google.github.io/filament/Filament.html#table_symbols
14// Symbol Definition
15// v View unit vector
16// l Incident light unit vector
17// n Surface normal unit vector
18// h Half unit vector between l and v
19// f BRDF
20// f_d Diffuse component of a BRDF
21// f_r Specular component of a BRDF
22// α Roughness, remapped from using input perceptualRoughness
23// σ Diffuse reflectance
24// Ω Spherical domain
25// f0 Reflectance at normal incidence
26// f90 Reflectance at grazing angle
27// χ+(a) Heaviside function (1 if a>0 and 0 otherwise)
28// nior Index of refraction (IOR) of an interface
29// ⟨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 material
33// 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 specular
37// 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) dm
38//
39// In which:
40// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets
41// G models the visibility (or occlusion or shadow-masking) of the microfacets
42// f_m is the microfacet BRDF and differs between specular and diffuse components
43//
44// The above integration needs to be approximated.
46// Input to a lighting function for a single layer (either the base layer or the
47// 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,
62// Input to a lighting function (`point_light`, `spot_light`,
63// `directional_light`).
64struct LightingInput {
65#ifdef STANDARD_MATERIAL_CLEARCOAT
66 layers: array<LayerLightingInput, 2>,
67#else // STANDARD_MATERIAL_CLEARCOAT
68 layers: array<LayerLightingInput, 1>,
69#endif // STANDARD_MATERIAL_CLEARCOAT
71 // 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` in
86 // <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_CLEARCOAT
91 // The strength of the clearcoat layer.
92 clearcoat_strength: f32,
93#endif // STANDARD_MATERIAL_CLEARCOAT
95#ifdef STANDARD_MATERIAL_ANISOTROPY
96 // The anisotropy strength, reflecting the amount of increased roughness in
97 // the tangent direction.
98 anisotropy: f32,
99 // The tangent direction for anisotropy: i.e. the direction in which
100 // roughness increases.
101 Ta: vec3<f32>,
102 // The bitangent direction, which is the cross product of the normal with
103 // the tangent direction.
104 Ba: vec3<f32>,
105#endif // STANDARD_MATERIAL_ANISOTROPY
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 view
111 // 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,
121// distanceAttenuation is simply the square falloff of light intensity
122// combined with a smooth attenuation at the edge of the light radius
123//
124// light radius is a non-physical construct for efficiency purposes,
125// because otherwise every light affects every fragment in the scene
126fn 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);
133// Normal distribution function (specular D)
134// Based on https://google.github.io/filament/Filament.html#citation-walter07
136// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 }
138// Simple implementation, has precision problems when using fp16 instead of fp32
139// see https://google.github.io/filament/Filament.html#listing_speculardfp16
140fn D_GGX(roughness: f32, NdotH: 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;
148// An approximation of the anisotropic GGX distribution function.
149//
150// 1
151// 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;
172// Visibility function (Specular G)
173// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) }
174// such that f_r becomes
175// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0)
176// where
177// 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_approximatedspecularv
179fn 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;
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);
204// Probability-density function that matches the bounded VNDF sampler
205// https://gpuopen.com/download/Bounded_VNDF_Sampling_for_Smith-GGX_Reflections.pdf (Listing 2)
206fn ggx_vndf_pdf(i: vec3<f32>, NdotH: f32, roughness: f32) -> f32 {
207 let ndf = D_GGX(roughness, NdotH);
209 // Common terms
210 let ai = roughness * i.xy;
211 let len2 = dot(ai, ai);
212 let t = sqrt(len2 + i.z * i.z);
213 if i.z >= 0.0 {
214 let a = roughness;
215 let s = 1.0 + length(i.xy);
216 let a2 = a * a;
217 let s2 = s * s;
218 let k = (1.0 - a2) * s2 / (s2 + a2 * i.z * i.z);
219 return ndf / (2.0 * (k * i.z + t));
220 }
222 // Backfacing case
223 return ndf * (t - i.z) / (2.0 * len2);
226// https://gpuopen.com/download/Bounded_VNDF_Sampling_for_Smith-GGX_Reflections.pdf (Listing 1)
227fn sample_visible_ggx(
228 xi: vec2<f32>,
229 roughness: f32,
230 normal: vec3<f32>,
231 view: vec3<f32>,
232) -> vec3<f32> {
233 let n = normal;
234 let alpha = roughness;
236 // Decompose view into components parallel/perpendicular to the normal
237 let wi_n = dot(view, n);
238 let wi_z = -n * wi_n;
239 let wi_xy = view + wi_z;
241 // Warp view vector to the unit-roughness configuration
242 let wi_std = -normalize(alpha * wi_xy + wi_z);
244 // Compute wi_std.z once for reuse
245 let wi_std_z = dot(wi_std, n);
247 // Bounded VNDF sampling
248 // Compute the bound parameter k (Eq. 5) and the scaled z–limit b (Eq. 6)
249 let s = 1.0 + length(wi_xy);
250 let a = clamp(alpha, 0.0, 1.0);
251 let a2 = a * a;
252 let s2 = s * s;
253 let k = (1.0 - a2) * s2 / (s2 + a2 * wi_n * wi_n);
254 let b = select(wi_std_z, k * wi_std_z, wi_n > 0.0);
256 // Sample a spherical cap in (-b, 1]
257 let z = 1.0 - xi.y * (1.0 + b);
258 let sin_theta = sqrt(max(0.0, 1.0 - z * z));
259 let phi = 2.0 * PI * xi.x - PI;
260 let x = sin_theta * cos(phi);
261 let y = sin_theta * sin(phi);
262 let c_std = vec3f(x, y, z);
264 // Reflect the sample so that the normal aligns with +Z
265 let up = vec3f(0.0, 0.0, 1.0);
266 let wr = n + up;
267 let c = dot(wr, c_std) * wr / wr.z - c_std;
269 // Half-vector in the standard frame
270 let wm_std = c + wi_std;
271 let wm_std_z = n * dot(n, wm_std);
272 let wm_std_xy = wm_std_z - wm_std;
274 // Unwarp back to original roughness and compute microfacet normal
275 let H = normalize(alpha * wm_std_xy + wm_std_z);
277 // Reflect view to obtain the outgoing (light) direction
278 return reflect(-view, H);
281// Smith geometric shadowing function
282fn G_Smith(NdotV: f32, NdotL: f32, roughness: f32) -> f32 {
283 let k = roughness / 2.0;
284 let GGXL = NdotL / (NdotL * (1.0 - k) + k);
285 let GGXV = NdotV / (NdotV * (1.0 - k) + k);
286 return GGXL * GGXV;
289// A simpler, but nonphysical, alternative to Smith-GGX. We use this for
290// clearcoat, per the Filament spec.
291//
292// https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel#toc4.9.1
293fn V_Kelemen(LdotH: f32) -> f32 {
294 return 0.25 / (LdotH * LdotH);
297// Fresnel function
298// see https://google.github.io/filament/Filament.html#citation-schlick94
299// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5
300fn F_Schlick_vec(f0: vec3<f32>, f90: f32, VdotH: f32) -> vec3<f32> {
301 // not using mix to keep the vec3 and float versions identical
302 return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);
305fn F_Schlick(f0: f32, f90: f32, VdotH: f32) -> f32 {
306 // not using mix to keep the vec3 and float versions identical
307 return f0 + (f90 - f0) * pow(1.0 - VdotH, 5.0);
310fn fresnel(f0: vec3<f32>, LdotH: f32) -> vec3<f32> {
311 // f_90 suitable for ambient occlusion
312 // see https://google.github.io/filament/Filament.html#lighting/occlusion
313 let f90 = saturate(dot(f0, vec3<f32>(50.0 * 0.33)));
314 return F_Schlick_vec(f0, f90, LdotH);
317// Given distribution, visibility, and Fresnel term, calculates the final
318// specular light.
319//
320// Multiscattering approximation:
321// <https://google.github.io/filament/Filament.html#listing_energycompensationimpl>
322fn specular_multiscatter(
323 D: f32,
324 V: f32,
325 F: vec3<f32>,
326 F0: vec3<f32>,
327 F_ab: vec2<f32>,
328 specular_intensity: f32,
329) -> vec3<f32> {
330 var Fr = (specular_intensity * D * V) * F;
331 Fr *= 1.0 + F0 * (1.0 / F_ab.x - 1.0);
332 return Fr;
335// Specular BRDF
336// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf
338// N, V, and L must all be normalized.
339fn derive_lighting_input(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>) -> DerivedLightingInput {
340 var input: DerivedLightingInput;
341 var H: vec3<f32> = normalize(L + V);
342 input.H = H;
343 input.NdotL = saturate(dot(N, L));
344 input.NdotH = saturate(dot(N, H));
345 input.LdotH = saturate(dot(L, H));
346 return input;
349// Returns L in the `xyz` components and the specular intensity in the `w` component.
350fn compute_specular_layer_values_for_point_light(
351 input: ptr<function, LightingInput>,
352 layer: u32,
353 V: vec3<f32>,
354 light_to_frag: vec3<f32>,
355 light_position_radius: f32,
356) -> vec4<f32> {
357 // Unpack.
358 let R = (*input).layers[layer].R;
359 let a = (*input).layers[layer].roughness;
361 // Representative Point Area Lights.
362 // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16
363 var LtFdotR = dot(light_to_frag, R);
365 // HACK: the following line is an amendment to fix a discontinuity when a surface
366 // intersects the light sphere. See https://github.com/bevyengine/bevy/issues/13318
367 //
368 // This sentence in the reference is crux of the problem: "We approximate finding the point with the
369 // smallest angle to the reflection ray by finding the point with the smallest distance to the ray."
370 // This approximation turns out to be completely wrong for points inside or near the sphere.
371 // Clamping this dot product to be positive ensures `centerToRay` lies on ray and not behind it.
372 // Any non-zero epsilon works here, it just has to be positive to avoid a singularity at zero.
373 // However, this is still far from physically accurate. Deriving an exact solution would help,
374 // but really we should adopt a superior solution to area lighting, such as:
375 // Physically Based Area Lights by Michal Drobot, or
376 // Polygonal-Light Shading with Linearly Transformed Cosines by Eric Heitz et al.
377 LtFdotR = max(0.0001, LtFdotR);
379 let centerToRay = LtFdotR * R - light_to_frag;
380 let closestPoint = light_to_frag + centerToRay * saturate(
381 light_position_radius * inverseSqrt(dot(centerToRay, centerToRay)));
382 let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint));
383 let normalizationFactor = a / saturate(a + (light_position_radius * 0.5 * LspecLengthInverse));
384 let intensity = normalizationFactor * normalizationFactor;
386 let L: vec3<f32> = closestPoint * LspecLengthInverse; // normalize() equivalent?
387 return vec4(L, intensity);
390// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m
391// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) }
392fn specular(
393 input: ptr<function, LightingInput>,
394 derived_input: ptr<function, DerivedLightingInput>,
395 specular_intensity: f32,
396) -> vec3<f32> {
397 // Unpack.
398 let roughness = (*input).layers[LAYER_BASE].roughness;
399 let NdotV = (*input).layers[LAYER_BASE].NdotV;
400 let F0 = (*input).F0_;
401 let NdotL = (*derived_input).NdotL;
402 let NdotH = (*derived_input).NdotH;
403 let LdotH = (*derived_input).LdotH;
405 // Calculate distribution.
406 let D = D_GGX(roughness, NdotH);
407 // Calculate visibility.
408 let V = V_SmithGGXCorrelated(roughness, NdotV, NdotL);
409 // Calculate the Fresnel term.
410 let F = fresnel(F0, LdotH);
412 // Calculate the specular light.
413 let Fr = specular_multiscatter(D, V, F, F0, (*input).F_ab, specular_intensity);
414 return Fr;
417// Calculates the specular light for the clearcoat layer. Returns Fc, the
418// Fresnel term, in the first channel, and Frc, the specular clearcoat light, in
419// the second channel.
420//
421// <https://google.github.io/filament/Filament.html#listing_clearcoatbrdf>
422fn specular_clearcoat(
423 input: ptr<function, LightingInput>,
424 derived_input: ptr<function, DerivedLightingInput>,
425 clearcoat_strength: f32,
426 specular_intensity: f32,
427) -> vec2<f32> {
428 // Unpack.
429 let roughness = (*input).layers[LAYER_CLEARCOAT].roughness;
430 let NdotH = (*derived_input).NdotH;
431 let LdotH = (*derived_input).LdotH;
433 // Calculate distribution.
434 let Dc = D_GGX(roughness, NdotH);
435 // Calculate visibility.
436 let Vc = V_Kelemen(LdotH);
437 // Calculate the Fresnel term.
438 let Fc = F_Schlick(0.04, 1.0, LdotH) * clearcoat_strength;
439 // Calculate the specular light.
440 let Frc = (specular_intensity * Dc * Vc) * Fc;
441 return vec2(Fc, Frc);
444#ifdef STANDARD_MATERIAL_ANISOTROPY
446fn specular_anisotropy(
447 input: ptr<function, LightingInput>,
448 derived_input: ptr<function, DerivedLightingInput>,
449 L: vec3<f32>,
450 specular_intensity: f32,
451) -> vec3<f32> {
452 // Unpack.
453 let roughness = (*input).layers[LAYER_BASE].roughness;
454 let NdotV = (*input).layers[LAYER_BASE].NdotV;
455 let V = (*input).V;
456 let F0 = (*input).F0_;
457 let anisotropy = (*input).anisotropy;
458 let Ta = (*input).Ta;
459 let Ba = (*input).Ba;
460 let H = (*derived_input).H;
461 let NdotL = (*derived_input).NdotL;
462 let NdotH = (*derived_input).NdotH;
463 let LdotH = (*derived_input).LdotH;
465 let TdotL = dot(Ta, L);
466 let BdotL = dot(Ba, L);
467 let TdotH = dot(Ta, H);
468 let BdotH = dot(Ba, H);
469 let TdotV = dot(Ta, V);
470 let BdotV = dot(Ba, V);
472 let ab = roughness * roughness;
473 let at = mix(ab, 1.0, anisotropy * anisotropy);
475 let Da = D_GGX_anisotropic(at, ab, NdotH, TdotH, BdotH);
476 let Va = V_GGX_anisotropic(at, ab, NdotL, NdotV, BdotV, TdotV, TdotL, BdotL);
477 let Fa = fresnel(F0, LdotH);
479 // Calculate the specular light.
480 let Fr = specular_multiscatter(Da, Va, Fa, F0, (*input).F_ab, specular_intensity);
481 return Fr;
484#endif // STANDARD_MATERIAL_ANISOTROPY
486// Diffuse BRDF
487// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf
488// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm
489//
490// simplest approximation
491// float Fd_Lambert() {
492// return 1.0 / PI;
493// }
494//
495// vec3 Fd = diffuseColor * Fd_Lambert();
496//
497// Disney approximation
498// See https://google.github.io/filament/Filament.html#citation-burley12
499// minimal quality difference
500fn Fd_Burley(
501 input: ptr<function, LightingInput>,
502 derived_input: ptr<function, DerivedLightingInput>,
503) -> f32 {
504 // Unpack.
505 let roughness = (*input).layers[LAYER_BASE].roughness;
506 let NdotV = (*input).layers[LAYER_BASE].NdotV;
507 let NdotL = (*derived_input).NdotL;
508 let LdotH = (*derived_input).LdotH;
510 let f90 = 0.5 + 2.0 * roughness * LdotH * LdotH;
511 let lightScatter = F_Schlick(1.0, f90, NdotL);
512 let viewScatter = F_Schlick(1.0, f90, NdotV);
513 return lightScatter * viewScatter * (1.0 / PI);
516// Scale/bias approximation
517// https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
518// TODO: Use a LUT (more accurate)
519fn F_AB(perceptual_roughness: f32, NdotV: f32) -> vec2<f32> {
520 let c0 = vec4<f32>(-1.0, -0.0275, -0.572, 0.022);
521 let c1 = vec4<f32>(1.0, 0.0425, 1.04, -0.04);
522 let r = perceptual_roughness * c0 + c1;
523 let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
524 return vec2<f32>(-1.04, 1.04) * a004 + r.zw;
527fn EnvBRDFApprox(F0: vec3<f32>, F_ab: vec2<f32>) -> vec3<f32> {
528 return F0 * F_ab.x + F_ab.y;
531fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 {
532 // clamp perceptual roughness to prevent precision problems
533 // According to Filament design 0.089 is recommended for mobile
534 // Filament uses 0.045 for non-mobile
535 let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);
536 return clampedPerceptualRoughness * clampedPerceptualRoughness;
539// this must align with CubemapLayout in decal/clustered.rs
540const CUBEMAP_TYPE_CROSS_VERTICAL: u32 = 0;
541const CUBEMAP_TYPE_CROSS_HORIZONTAL: u32 = 1;
542const CUBEMAP_TYPE_SEQUENCE_VERTICAL: u32 = 2;
543const CUBEMAP_TYPE_SEQUENCE_HORIZONTAL: u32 = 3;
545const X_PLUS: u32 = 0;
546const X_MINUS: u32 = 1;
547const Y_PLUS: u32 = 2;
548const Y_MINUS: u32 = 3;
549const Z_MINUS: u32 = 4;
550const Z_PLUS: u32 = 5;
552fn cubemap_uv(direction: vec3<f32>, cubemap_type: u32) -> vec2<f32> {
553 let abs_direction = abs(direction);
554 let max_axis = max(abs_direction.x, max(abs_direction.y, abs_direction.z));
556 let face_index = select(
557 select(X_PLUS, X_MINUS, direction.x < 0.0),
558 select(
559 select(Y_PLUS, Y_MINUS, direction.y < 0.0),
560 select(Z_PLUS, Z_MINUS, direction.z < 0.0),
561 max_axis != abs_direction.y
562 ),
563 max_axis != abs_direction.x
564 );
566 var face_uv: vec2<f32>;
567 var divisor: f32;
568 var corner_uv: vec2<u32> = vec2(0, 0);
569 var face_size: vec2<f32>;
571 switch face_index {
572 case X_PLUS: { face_uv = vec2<f32>(direction.z, -direction.y); divisor = direction.x; }
573 case X_MINUS: { face_uv = vec2<f32>(-direction.z, -direction.y); divisor = -direction.x; }
574 case Y_PLUS: { face_uv = vec2<f32>(direction.x, -direction.z); divisor = direction.y; }
575 case Y_MINUS: { face_uv = vec2<f32>(direction.x, direction.z); divisor = -direction.y; }
576 case Z_PLUS: { face_uv = vec2<f32>(direction.x, direction.y); divisor = direction.z; }
577 case Z_MINUS: { face_uv = vec2<f32>(direction.x, -direction.y); divisor = -direction.z; }
578 default: {}
579 }
580 face_uv = (face_uv / divisor) * 0.5 + 0.5;
582 switch cubemap_type {
583 case CUBEMAP_TYPE_CROSS_VERTICAL: {
584 face_size = vec2(1.0/3.0, 1.0/4.0);
585 corner_uv = vec2<u32>((0x111102u >> (4 * face_index)) & 0xFu, (0x132011u >> (4 * face_index)) & 0xFu);
586 }
587 case CUBEMAP_TYPE_CROSS_HORIZONTAL: {
588 face_size = vec2(1.0/4.0, 1.0/3.0);
589 corner_uv = vec2<u32>((0x131102u >> (4 * face_index)) & 0xFu, (0x112011u >> (4 * face_index)) & 0xFu);
590 }
591 case CUBEMAP_TYPE_SEQUENCE_HORIZONTAL: {
592 face_size = vec2(1.0/6.0, 1.0);
593 corner_uv.x = face_index;
594 }
595 case CUBEMAP_TYPE_SEQUENCE_VERTICAL: {
596 face_size = vec2(1.0, 1.0/6.0);
597 corner_uv.y = face_index;
598 }
599 default: {}
600 }
602 return (vec2<f32>(corner_uv) + face_uv) * face_size;
605fn point_light(
606 light_id: u32,
607 input: ptr<function, LightingInput>,
608 enable_diffuse: bool,
609 enable_texture: bool,
610) -> vec3<f32> {
611 // Unpack.
612 let diffuse_color = (*input).diffuse_color;
613 let P = (*input).P;
614 let N = (*input).layers[LAYER_BASE].N;
615 let V = (*input).V;
617 let light = &view_bindings::clusterable_objects.data[light_id];
618 let light_to_frag = (*light).position_radius.xyz - P;
619 let L = normalize(light_to_frag);
620 let distance_square = dot(light_to_frag, light_to_frag);
621 let rangeAttenuation = getDistanceAttenuation(distance_square, (*light).color_inverse_square_range.w);
623 // Base layer
625 let specular_L_intensity = compute_specular_layer_values_for_point_light(
626 input,
627 LAYER_BASE,
628 V,
629 light_to_frag,
630 (*light).position_radius.w,
631 );
632 var specular_derived_input = derive_lighting_input(N, V, specular_L_intensity.xyz);
634 let specular_intensity = specular_L_intensity.w;
636#ifdef STANDARD_MATERIAL_ANISOTROPY
637 let specular_light = specular_anisotropy(input, &specular_derived_input, L, specular_intensity);
638#else // STANDARD_MATERIAL_ANISOTROPY
639 let specular_light = specular(input, &specular_derived_input, specular_intensity);
640#endif // STANDARD_MATERIAL_ANISOTROPY
642 // Clearcoat
644#ifdef STANDARD_MATERIAL_CLEARCOAT
645 // Unpack.
646 let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
647 let clearcoat_strength = (*input).clearcoat_strength;
649 // Perform specular input calculations again for the clearcoat layer. We
650 // can't reuse the above because the clearcoat normal might be different
651 // from the main layer normal.
652 let clearcoat_specular_L_intensity = compute_specular_layer_values_for_point_light(
653 input,
654 LAYER_CLEARCOAT,
655 V,
656 light_to_frag,
657 (*light).position_radius.w,
658 );
659 var clearcoat_specular_derived_input =
660 derive_lighting_input(clearcoat_N, V, clearcoat_specular_L_intensity.xyz);
662 // Calculate the specular light.
663 let clearcoat_specular_intensity = clearcoat_specular_L_intensity.w;
664 let Fc_Frc = specular_clearcoat(
665 input,
666 &clearcoat_specular_derived_input,
667 clearcoat_strength,
668 clearcoat_specular_intensity
669 );
670 let inv_Fc = 1.0 - Fc_Frc.r; // Inverse Fresnel term.
671 let Frc = Fc_Frc.g; // Clearcoat light.
672#endif // STANDARD_MATERIAL_CLEARCOAT
674 // Diffuse.
675 // Comes after specular since its N⋅L is used in the lighting equation.
676 var derived_input = derive_lighting_input(N, V, L);
677 var diffuse = vec3(0.0);
678 if (enable_diffuse) {
679 diffuse = diffuse_color * Fd_Burley(input, &derived_input);
680 }
682 // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation
683 // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩
684 // where
685 // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color
686 // Φ is luminous power in lumens
687 // our rangeAttenuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius
689 // For a point light, luminous intensity, I, in lumens per steradian is given by:
690 // I = Φ / 4 π
691 // The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower
693 // NOTE: (*light).color.rgb is premultiplied with (*light).intensity / 4 π (which would be the luminous intensity) on the CPU
695 var color: vec3<f32>;
696#ifdef STANDARD_MATERIAL_CLEARCOAT
697 // Account for the Fresnel term from the clearcoat darkening the main layer.
698 //
699 // <https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel/integrationinthesurfaceresponse>
700 color = (diffuse + specular_light * inv_Fc) * inv_Fc + Frc;
701#else // STANDARD_MATERIAL_CLEARCOAT
702 color = diffuse + specular_light;
703#endif // STANDARD_MATERIAL_CLEARCOAT
705 var texture_sample = 1f;
707#ifdef LIGHT_TEXTURES
708 if enable_texture && (*light).decal_index != 0xFFFFFFFFu {
709 let relative_position = (view_bindings::clustered_decals.decals[(*light).decal_index].local_from_world * vec4(P, 1.0)).xyz;
710 let cubemap_type = view_bindings::clustered_decals.decals[(*light).decal_index].tag;
711 let decal_uv = cubemap_uv(relative_position, cubemap_type);
712 let image_index = view_bindings::clustered_decals.decals[(*light).decal_index].image_index;
714 texture_sample = textureSampleLevel(
715 view_bindings::clustered_decal_textures[image_index],
716 view_bindings::clustered_decal_sampler,
717 decal_uv,
718 0.0
719 ).r;
720 }
721#endif
723 return color * (*light).color_inverse_square_range.rgb *
724 (rangeAttenuation * derived_input.NdotL) * texture_sample;
727fn spot_light(
728 light_id: u32,
729 input: ptr<function, LightingInput>,
730 enable_diffuse: bool
731) -> vec3<f32> {
732 // reuse the point light calculations
733 let point_light = point_light(light_id, input, enable_diffuse, false);
735 let light = &view_bindings::clusterable_objects.data[light_id];
737 // reconstruct spot dir from x/z and y-direction flag
738 var spot_dir = vec3<f32>((*light).light_custom_data.x, 0.0, (*light).light_custom_data.y);
739 spot_dir.y = sqrt(max(0.0, 1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z));
740 if ((*light).flags & POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u {
741 spot_dir.y = -spot_dir.y;
742 }
743 let light_to_frag = (*light).position_radius.xyz - (*input).P.xyz;
745 // calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight
746 // spot_scale and spot_offset have been precomputed
747 // note we normalize here to get "l" from the filament listing. spot_dir is already normalized
748 let cd = dot(-spot_dir, normalize(light_to_frag));
749 let attenuation = saturate(cd * (*light).light_custom_data.z + (*light).light_custom_data.w);
750 let spot_attenuation = attenuation * attenuation;
752 var texture_sample = 1f;
754#ifdef LIGHT_TEXTURES
755 if (*light).decal_index != 0xFFFFFFFFu {
756 let local_position = (view_bindings::clustered_decals.decals[(*light).decal_index].local_from_world *
757 vec4((*input).P, 1.0)).xyz;
758 if local_position.z < 0.0 {
759 let decal_uv = (local_position.xy / (local_position.z * (*light).spot_light_tan_angle)) * vec2(-0.5, 0.5) + 0.5;
760 let image_index = view_bindings::clustered_decals.decals[(*light).decal_index].image_index;
762 texture_sample = textureSampleLevel(
763 view_bindings::clustered_decal_textures[image_index],
764 view_bindings::clustered_decal_sampler,
765 decal_uv,
766 0.0
767 ).r;
768 }
769 }
770#endif
772 return point_light * spot_attenuation * texture_sample;
775fn directional_light(
776 light_id: u32,
777 input: ptr<function, LightingInput>,
778 enable_diffuse: bool
779) -> vec3<f32> {
780 // Unpack.
781 let diffuse_color = (*input).diffuse_color;
782 let NdotV = (*input).layers[LAYER_BASE].NdotV;
783 let N = (*input).layers[LAYER_BASE].N;
784 let V = (*input).V;
785 let roughness = (*input).layers[LAYER_BASE].roughness;
787 let light = &view_bindings::lights.directional_lights[light_id];
789 let L = (*light).direction_to_light.xyz;
790 var derived_input = derive_lighting_input(N, V, L);
792 var diffuse = vec3(0.0);
793 if (enable_diffuse) {
794 diffuse = diffuse_color * Fd_Burley(input, &derived_input);
795 }
797#ifdef STANDARD_MATERIAL_ANISOTROPY
798 let specular_light = specular_anisotropy(input, &derived_input, L, 1.0);
799#else // STANDARD_MATERIAL_ANISOTROPY
800 let specular_light = specular(input, &derived_input, 1.0);
801#endif // STANDARD_MATERIAL_ANISOTROPY
803#ifdef STANDARD_MATERIAL_CLEARCOAT
804 let clearcoat_N = (*input).layers[LAYER_CLEARCOAT].N;
805 let clearcoat_strength = (*input).clearcoat_strength;
807 // Perform specular input calculations again for the clearcoat layer. We
808 // can't reuse the above because the clearcoat normal might be different
809 // from the main layer normal.
810 var derived_clearcoat_input = derive_lighting_input(clearcoat_N, V, L);
812 let Fc_Frc =
813 specular_clearcoat(input, &derived_clearcoat_input, clearcoat_strength, 1.0);
814 let inv_Fc = 1.0 - Fc_Frc.r;
815 let Frc = Fc_Frc.g;
816#endif // STANDARD_MATERIAL_CLEARCOAT
818 var color: vec3<f32>;
819#ifdef STANDARD_MATERIAL_CLEARCOAT
820 // Account for the Fresnel term from the clearcoat darkening the main layer.
821 //
822 // <https://google.github.io/filament/Filament.html#materialsystem/clearcoatmodel/integrationinthesurfaceresponse>
823 color = (diffuse + specular_light * inv_Fc) * inv_Fc * derived_input.NdotL +
824 Frc * derived_clearcoat_input.NdotL;
825#else // STANDARD_MATERIAL_CLEARCOAT
826 color = (diffuse + specular_light) * derived_input.NdotL;
827#endif // STANDARD_MATERIAL_CLEARCOAT
829 var texture_sample = 1f;
831#ifdef LIGHT_TEXTURES
832 if (*light).decal_index != 0xFFFFFFFFu {
833 let local_position = (view_bindings::clustered_decals.decals[(*light).decal_index].local_from_world *
834 vec4((*input).P, 1.0)).xyz;
835 let decal_uv = local_position.xy * vec2(-0.5, 0.5) + 0.5;
837 // if tiled or within tile
838 if (view_bindings::clustered_decals.decals[(*light).decal_index].tag != 0u)
839 || all(clamp(decal_uv, vec2(0.0), vec2(1.0)) == decal_uv)
840 {
841 let image_index = view_bindings::clustered_decals.decals[(*light).decal_index].image_index;
843 texture_sample = textureSampleLevel(
844 view_bindings::clustered_decal_textures[image_index],
845 view_bindings::clustered_decal_sampler,
846 decal_uv - floor(decal_uv),
847 0.0
848 ).r;
849 } else {
850 texture_sample = 0f;
851 }
852 }
853#endif
855 return color * (*light).color.rgb * texture_sample;
Contents