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::pbr_functions
3#import bevy_pbr::{
4 pbr_types,
5 pbr_bindings,
6 mesh_view_bindings as view_bindings,
7 mesh_view_types,
8 lighting,
9 lighting::{LAYER_BASE, LAYER_CLEARCOAT},
10 transmission,
11 clustered_forward as clustering,
12 shadows,
13 ambient,
14 irradiance_volume,
15 view_transformations,
16 mesh_types::{MESH_FLAGS_SHADOW_RECEIVER_BIT, MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT},
18#import bevy_render::maths::{E, powsafe}
20#ifdef MESHLET_MESH_MATERIAL_PASS
21#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
22#else ifdef PREPASS_PIPELINE
23#import bevy_pbr::prepass_io::VertexOutput
24#else // PREPASS_PIPELINE
25#import bevy_pbr::forward_io::VertexOutput
26#endif // PREPASS_PIPELINE
28#ifdef ENVIRONMENT_MAP
29#import bevy_pbr::environment_map
30#endif
32#ifdef TONEMAP_IN_SHADER
33#import bevy_core_pipeline::tonemapping::{tone_mapping, screen_space_dither}
34#endif
37// Biasing info needed to sample from a texture. How this is done depends on
38// whether we're rendering meshlets or regular meshes.
39struct SampleBias {
40#ifdef MESHLET_MESH_MATERIAL_PASS
41 ddx_uv: vec2<f32>,
42 ddy_uv: vec2<f32>,
43#else // MESHLET_MESH_MATERIAL_PASS
44 mip_bias: f32,
45#endif // MESHLET_MESH_MATERIAL_PASS
48// This is the standard 4x4 ordered dithering pattern from [1].
49//
50// We can't use `array<vec4<u32>, 4>` because they can't be indexed dynamically
51// due to Naga limitations. So instead we pack into a single `vec4` and extract
52// individual bytes.
53//
54// [1]: https://en.wikipedia.org/wiki/Ordered_dithering#Threshold_map
55const DITHER_THRESHOLD_MAP: vec4<u32> = vec4(
56 0x0a020800,
57 0x060e040c,
58 0x09010b03,
59 0x050d070f
60);
62// Processes a visibility range dither value and discards the fragment if
63// needed.
64//
65// Visibility ranges, also known as HLODs, are crossfades between different
66// levels of detail.
67//
68// The `dither` value ranges from [-16, 16]. When zooming out, positive values
69// are used for meshes that are in the process of disappearing, while negative
70// values are used for meshes that are in the process of appearing. In other
71// words, when the camera is moving backwards, the `dither` value counts up from
72// -16 to 0 when the object is fading in, stays at 0 while the object is
73// visible, and then counts up to 16 while the object is fading out.
74// Distinguishing between negative and positive values allows the dither
75// patterns for different LOD levels of a single mesh to mesh together properly.
76#ifdef VISIBILITY_RANGE_DITHER
77fn visibility_range_dither(frag_coord: vec4<f32>, dither: i32) {
78 // If `dither` is 0, the object is visible.
79 if (dither == 0) {
80 return;
81 }
83 // If `dither` is less than -15 or greater than 15, the object is culled.
84 if (dither <= -16 || dither >= 16) {
85 discard;
86 }
88 // Otherwise, check the dither pattern.
89 let coords = vec2<u32>(floor(frag_coord.xy)) % 4u;
90 let threshold = i32((DITHER_THRESHOLD_MAP[coords.y] >> (coords.x * 8)) & 0xff);
91 if ((dither >= 0 && dither + threshold >= 16) || (dither < 0 && 1 + dither + threshold <= 0)) {
92 discard;
93 }
95#endif
97fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4<f32>) -> vec4<f32> {
98 var color = output_color;
99 let alpha_mode = material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
100 if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
101 // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
102 color.a = 1.0;
103 }
105#ifdef MAY_DISCARD
106 // NOTE: `MAY_DISCARD` is only defined in the alpha to coverage case if MSAA
107 // was off. This special situation causes alpha to coverage to fall back to
108 // alpha mask.
109 else if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK ||
110 alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE {
111 if color.a >= material.alpha_cutoff {
112 // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
113 color.a = 1.0;
114 } else {
115 // NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
116 discard;
117 }
118 }
119#endif
121 return color;
124fn prepare_world_normal(
125 world_normal: vec3<f32>,
126 double_sided: bool,
127 is_front: bool,
128) -> vec3<f32> {
129 var output: vec3<f32> = world_normal;
130#ifndef VERTEX_TANGENTS
131#ifndef STANDARD_MATERIAL_NORMAL_MAP
132 // NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided
133 // material, the normal needs to be inverted. This is a branchless version of that.
134 output = (f32(!double_sided || is_front) * 2.0 - 1.0) * output;
135#endif
136#endif
137 return output;
140// Calculates the three TBN vectors according to [mikktspace]. Returns a matrix
141// with T, B, N columns in that order.
142//
143// [mikktspace]: http://www.mikktspace.com/
144fn calculate_tbn_mikktspace(world_normal: vec3<f32>, world_tangent: vec4<f32>) -> mat3x3<f32> {
145 // NOTE: The mikktspace method of normal mapping explicitly requires that the world normal NOT
146 // be re-normalized in the fragment shader. This is primarily to match the way mikktspace
147 // bakes vertex tangents and normal maps so that this is the exact inverse. Blender, Unity,
148 // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code
149 // unless you really know what you are doing.
150 // http://www.mikktspace.com/
151 var N: vec3<f32> = world_normal;
153 // NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be
154 // normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the
155 // vertex tangent! Do not change this code unless you really know what you are doing.
156 // http://www.mikktspace.com/
157 var T: vec3<f32> = world_tangent.xyz;
158 var B: vec3<f32> = world_tangent.w * cross(N, T);
160#ifdef MESHLET_MESH_MATERIAL_PASS
161 // https://www.jeremyong.com/graphics/2023/12/16/surface-gradient-bump-mapping/#a-note-on-mikktspace-usage
162 let inverse_length_n = 1.0 / length(N);
163 T *= inverse_length_n;
164 B *= inverse_length_n;
165 N *= inverse_length_n;
166#endif
168 return mat3x3(T, B, N);
171fn apply_normal_mapping(
172 standard_material_flags: u32,
173 TBN: mat3x3<f32>,
174 double_sided: bool,
175 is_front: bool,
176 in_Nt: vec3<f32>,
177) -> vec3<f32> {
178 // Unpack the TBN vectors.
179 var T = TBN[0];
180 var B = TBN[1];
181 var N = TBN[2];
183 // Nt is the tangent-space normal.
184 var Nt = in_Nt;
185 if (standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u {
186 // Only use the xy components and derive z for 2-component normal maps.
187 Nt = vec3<f32>(Nt.rg * 2.0 - 1.0, 0.0);
188 Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y);
189 } else {
190 Nt = Nt * 2.0 - 1.0;
191 }
192 // Normal maps authored for DirectX require flipping the y component
193 if (standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u {
194 Nt.y = -Nt.y;
195 }
197 if double_sided && !is_front {
198 Nt = -Nt;
199 }
201 // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from
202 // the normal map texture in this way to be an EXACT inverse of how the normal map baker
203 // calculates the normal maps so there is no error introduced. Do not change this code
204 // unless you really know what you are doing.
205 // http://www.mikktspace.com/
206 N = Nt.x * T + Nt.y * B + Nt.z * N;
208 return normalize(N);
211#ifdef STANDARD_MATERIAL_ANISOTROPY
213// Modifies the normal to achieve a better approximate direction from the
214// environment map when using anisotropy.
215//
216// This follows the suggested implementation in the `KHR_materials_anisotropy` specification:
217// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md#image-based-lighting
218fn bend_normal_for_anisotropy(lighting_input: ptr<function, lighting::LightingInput>) {
219 // Unpack.
220 let N = (*lighting_input).layers[LAYER_BASE].N;
221 let roughness = (*lighting_input).layers[LAYER_BASE].roughness;
222 let V = (*lighting_input).V;
223 let anisotropy = (*lighting_input).anisotropy;
224 let Ba = (*lighting_input).Ba;
226 var bent_normal = normalize(cross(cross(Ba, V), Ba));
228 // The `KHR_materials_anisotropy` spec states:
229 //
230 // > This heuristic can probably be improved upon
231 let a = pow(2.0, pow(2.0, 1.0 - anisotropy * (1.0 - roughness)));
232 bent_normal = normalize(mix(bent_normal, N, a));
234 // The `KHR_materials_anisotropy` spec states:
235 //
236 // > Mixing the reflection with the normal is more accurate both with and
237 // > without anisotropy and keeps rough objects from gathering light from
238 // > behind their tangent plane.
239 let R = normalize(mix(reflect(-V, bent_normal), bent_normal, roughness * roughness));
241 (*lighting_input).layers[LAYER_BASE].N = bent_normal;
242 (*lighting_input).layers[LAYER_BASE].R = R;
245#endif // STANDARD_MATERIAL_ANISOTROPY
247// NOTE: Correctly calculates the view vector depending on whether
248// the projection is orthographic or perspective.
249fn calculate_view(
250 world_position: vec4<f32>,
251 is_orthographic: bool,
252) -> vec3<f32> {
253 var V: vec3<f32>;
254 if is_orthographic {
255 // Orthographic view vector
256 V = normalize(vec3<f32>(view_bindings::view.clip_from_world[0].z, view_bindings::view.clip_from_world[1].z, view_bindings::view.clip_from_world[2].z));
257 } else {
258 // Only valid for a perspective projection
259 V = normalize(view_bindings::view.world_position.xyz - world_position.xyz);
260 }
261 return V;
264// Diffuse strength is inversely related to metallicity, specular and diffuse transmission
265fn calculate_diffuse_color(
266 base_color: vec3<f32>,
267 metallic: f32,
268 specular_transmission: f32,
269 diffuse_transmission: f32
270) -> vec3<f32> {
271 return base_color * (1.0 - metallic) * (1.0 - specular_transmission) *
272 (1.0 - diffuse_transmission);
275// Remapping [0,1] reflectance to F0
276// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
277fn calculate_F0(base_color: vec3<f32>, metallic: f32, reflectance: vec3<f32>) -> vec3<f32> {
278 return 0.16 * reflectance * reflectance * (1.0 - metallic) + base_color * metallic;
281#ifndef PREPASS_FRAGMENT
282fn apply_pbr_lighting(
283 in: pbr_types::PbrInput,
284) -> vec4<f32> {
285 var output_color: vec4<f32> = in.material.base_color;
287 let emissive = in.material.emissive;
289 // calculate non-linear roughness from linear perceptualRoughness
290 let metallic = in.material.metallic;
291 let perceptual_roughness = in.material.perceptual_roughness;
292 let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
293 let ior = in.material.ior;
294 let thickness = in.material.thickness;
295 let reflectance = in.material.reflectance;
296 let diffuse_transmission = in.material.diffuse_transmission;
297 let specular_transmission = in.material.specular_transmission;
299 let specular_transmissive_color = specular_transmission * in.material.base_color.rgb;
301 let diffuse_occlusion = in.diffuse_occlusion;
302 let specular_occlusion = in.specular_occlusion;
304 // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
305 let NdotV = max(dot(in.N, in.V), 0.0001);
306 let R = reflect(-in.V, in.N);
308#ifdef STANDARD_MATERIAL_CLEARCOAT
309 // Do the above calculations again for the clearcoat layer. Remember that
310 // the clearcoat can have its own roughness and its own normal.
311 let clearcoat = in.material.clearcoat;
312 let clearcoat_perceptual_roughness = in.material.clearcoat_perceptual_roughness;
313 let clearcoat_roughness = lighting::perceptualRoughnessToRoughness(clearcoat_perceptual_roughness);
314 let clearcoat_N = in.clearcoat_N;
315 let clearcoat_NdotV = max(dot(clearcoat_N, in.V), 0.0001);
316 let clearcoat_R = reflect(-in.V, clearcoat_N);
317#endif // STANDARD_MATERIAL_CLEARCOAT
319 let diffuse_color = calculate_diffuse_color(
320 output_color.rgb,
321 metallic,
322 specular_transmission,
323 diffuse_transmission
324 );
326 // Diffuse transmissive strength is inversely related to metallicity and specular transmission, but directly related to diffuse transmission
327 let diffuse_transmissive_color = output_color.rgb * (1.0 - metallic) * (1.0 - specular_transmission) * diffuse_transmission;
329 // Calculate the world position of the second Lambertian lobe used for diffuse transmission, by subtracting material thickness
330 let diffuse_transmissive_lobe_world_position = in.world_position - vec4<f32>(in.world_normal, 0.0) * thickness;
332 let F0 = calculate_F0(output_color.rgb, metallic, reflectance);
333 let F_ab = lighting::F_AB(perceptual_roughness, NdotV);
335 var direct_light: vec3<f32> = vec3<f32>(0.0);
337 // Transmitted Light (Specular and Diffuse)
338 var transmitted_light: vec3<f32> = vec3<f32>(0.0);
340 // Pack all the values into a structure.
341 var lighting_input: lighting::LightingInput;
342 lighting_input.layers[LAYER_BASE].NdotV = NdotV;
343 lighting_input.layers[LAYER_BASE].N = in.N;
344 lighting_input.layers[LAYER_BASE].R = R;
345 lighting_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness;
346 lighting_input.layers[LAYER_BASE].roughness = roughness;
347 lighting_input.P = in.world_position.xyz;
348 lighting_input.V = in.V;
349 lighting_input.diffuse_color = diffuse_color;
350 lighting_input.F0_ = F0;
351 lighting_input.F_ab = F_ab;
352#ifdef STANDARD_MATERIAL_CLEARCOAT
353 lighting_input.layers[LAYER_CLEARCOAT].NdotV = clearcoat_NdotV;
354 lighting_input.layers[LAYER_CLEARCOAT].N = clearcoat_N;
355 lighting_input.layers[LAYER_CLEARCOAT].R = clearcoat_R;
356 lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = clearcoat_perceptual_roughness;
357 lighting_input.layers[LAYER_CLEARCOAT].roughness = clearcoat_roughness;
358 lighting_input.clearcoat_strength = clearcoat;
359#endif // STANDARD_MATERIAL_CLEARCOAT
360#ifdef STANDARD_MATERIAL_ANISOTROPY
361 lighting_input.anisotropy = in.anisotropy_strength;
362 lighting_input.Ta = in.anisotropy_T;
363 lighting_input.Ba = in.anisotropy_B;
364#endif // STANDARD_MATERIAL_ANISOTROPY
366 // And do the same for transmissive if we need to.
367#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
368 var transmissive_lighting_input: lighting::LightingInput;
369 transmissive_lighting_input.layers[LAYER_BASE].NdotV = 1.0;
370 transmissive_lighting_input.layers[LAYER_BASE].N = -in.N;
371 transmissive_lighting_input.layers[LAYER_BASE].R = vec3(0.0);
372 transmissive_lighting_input.layers[LAYER_BASE].perceptual_roughness = 1.0;
373 transmissive_lighting_input.layers[LAYER_BASE].roughness = 1.0;
374 transmissive_lighting_input.P = diffuse_transmissive_lobe_world_position.xyz;
375 transmissive_lighting_input.V = -in.V;
376 transmissive_lighting_input.diffuse_color = diffuse_transmissive_color;
377 transmissive_lighting_input.F0_ = vec3(0.0);
378 transmissive_lighting_input.F_ab = vec2(0.1);
379#ifdef STANDARD_MATERIAL_CLEARCOAT
380 transmissive_lighting_input.layers[LAYER_CLEARCOAT].NdotV = 0.0;
381 transmissive_lighting_input.layers[LAYER_CLEARCOAT].N = vec3(0.0);
382 transmissive_lighting_input.layers[LAYER_CLEARCOAT].R = vec3(0.0);
383 transmissive_lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = 0.0;
384 transmissive_lighting_input.layers[LAYER_CLEARCOAT].roughness = 0.0;
385 transmissive_lighting_input.clearcoat_strength = 0.0;
386#endif // STANDARD_MATERIAL_CLEARCOAT
387#ifdef STANDARD_MATERIAL_ANISOTROPY
388 transmissive_lighting_input.anisotropy = in.anisotropy_strength;
389 transmissive_lighting_input.Ta = in.anisotropy_T;
390 transmissive_lighting_input.Ba = in.anisotropy_B;
391#endif // STANDARD_MATERIAL_ANISOTROPY
392#endif // STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
394 let view_z = dot(vec4<f32>(
395 view_bindings::view.view_from_world[0].z,
396 view_bindings::view.view_from_world[1].z,
397 view_bindings::view.view_from_world[2].z,
398 view_bindings::view.view_from_world[3].z
399 ), in.world_position);
400 let cluster_index = clustering::fragment_cluster_index(in.frag_coord.xy, view_z, in.is_orthographic);
401 var clusterable_object_index_ranges =
402 clustering::unpack_clusterable_object_index_ranges(cluster_index);
404 // Point lights (direct)
405 for (var i: u32 = clusterable_object_index_ranges.first_point_light_index_offset;
406 i < clusterable_object_index_ranges.first_spot_light_index_offset;
407 i = i + 1u) {
408 let light_id = clustering::get_clusterable_object_id(i);
410 // If we're lightmapped, disable diffuse contribution from the light if
411 // requested, to avoid double-counting light.
412#ifdef LIGHTMAP
413 let enable_diffuse =
414 (view_bindings::clusterable_objects.data[light_id].flags &
415 mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u;
416#else // LIGHTMAP
417 let enable_diffuse = true;
418#endif // LIGHTMAP
420 var shadow: f32 = 1.0;
421 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
422 && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
423 shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal);
424 }
426 let light_contrib = lighting::point_light(light_id, &lighting_input, enable_diffuse, true);
427 direct_light += light_contrib * shadow;
429#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
430 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
431 // world position, inverted normal and view vectors, and the following simplified
432 // values for a fully diffuse transmitted light contribution approximation:
433 //
434 // roughness = 1.0;
435 // NdotV = 1.0;
436 // R = vec3<f32>(0.0) // doesn't really matter
437 // F_ab = vec2<f32>(0.1)
438 // F0 = vec3<f32>(0.0)
439 var transmitted_shadow: f32 = 1.0;
440 if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
441 && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
442 transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal);
443 }
445 let transmitted_light_contrib =
446 lighting::point_light(light_id, &transmissive_lighting_input, enable_diffuse, true);
447 transmitted_light += transmitted_light_contrib * transmitted_shadow;
448#endif
449 }
451 // Spot lights (direct)
452 for (var i: u32 = clusterable_object_index_ranges.first_spot_light_index_offset;
453 i < clusterable_object_index_ranges.first_reflection_probe_index_offset;
454 i = i + 1u) {
455 let light_id = clustering::get_clusterable_object_id(i);
457 // If we're lightmapped, disable diffuse contribution from the light if
458 // requested, to avoid double-counting light.
459#ifdef LIGHTMAP
460 let enable_diffuse =
461 (view_bindings::clusterable_objects.data[light_id].flags &
462 mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u;
463#else // LIGHTMAP
464 let enable_diffuse = true;
465#endif // LIGHTMAP
467 var shadow: f32 = 1.0;
468 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
469 && (view_bindings::clusterable_objects.data[light_id].flags &
470 mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
471 shadow = shadows::fetch_spot_shadow(
472 light_id,
473 in.world_position,
474 in.world_normal,
475 view_bindings::clusterable_objects.data[light_id].shadow_map_near_z,
476 );
477 }
479 let light_contrib = lighting::spot_light(light_id, &lighting_input, enable_diffuse);
480 direct_light += light_contrib * shadow;
482#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
483 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
484 // world position, inverted normal and view vectors, and the following simplified
485 // values for a fully diffuse transmitted light contribution approximation:
486 //
487 // roughness = 1.0;
488 // NdotV = 1.0;
489 // R = vec3<f32>(0.0) // doesn't really matter
490 // F_ab = vec2<f32>(0.1)
491 // F0 = vec3<f32>(0.0)
492 var transmitted_shadow: f32 = 1.0;
493 if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
494 && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
495 transmitted_shadow = shadows::fetch_spot_shadow(
496 light_id,
497 diffuse_transmissive_lobe_world_position,
498 -in.world_normal,
499 view_bindings::clusterable_objects.data[light_id].shadow_map_near_z,
500 );
501 }
503 let transmitted_light_contrib =
504 lighting::spot_light(light_id, &transmissive_lighting_input, enable_diffuse);
505 transmitted_light += transmitted_light_contrib * transmitted_shadow;
506#endif
507 }
509 // directional lights (direct)
510 let n_directional_lights = view_bindings::lights.n_directional_lights;
511 for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
512 // check if this light should be skipped, which occurs if this light does not intersect with the view
513 // note point and spot lights aren't skippable, as the relevant lights are filtered in `assign_lights_to_clusters`
514 let light = &view_bindings::lights.directional_lights[i];
516 // If we're lightmapped, disable diffuse contribution from the light if
517 // requested, to avoid double-counting light.
518#ifdef LIGHTMAP
519 let enable_diffuse =
520 ((*light).flags &
521 mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) !=
522 0u;
523#else // LIGHTMAP
524 let enable_diffuse = true;
525#endif // LIGHTMAP
527 var shadow: f32 = 1.0;
528 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
529 && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
530 shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z);
531 }
533 var light_contrib = lighting::directional_light(i, &lighting_input, enable_diffuse);
535#ifdef DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES
536 light_contrib = shadows::cascade_debug_visualization(light_contrib, i, view_z);
537#endif
538 direct_light += light_contrib * shadow;
540#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
541 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
542 // world position, inverted normal and view vectors, and the following simplified
543 // values for a fully diffuse transmitted light contribution approximation:
544 //
545 // roughness = 1.0;
546 // NdotV = 1.0;
547 // R = vec3<f32>(0.0) // doesn't really matter
548 // F_ab = vec2<f32>(0.1)
549 // F0 = vec3<f32>(0.0)
550 var transmitted_shadow: f32 = 1.0;
551 if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
552 && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
553 transmitted_shadow = shadows::fetch_directional_shadow(i, diffuse_transmissive_lobe_world_position, -in.world_normal, view_z);
554 }
556 let transmitted_light_contrib =
557 lighting::directional_light(i, &transmissive_lighting_input, enable_diffuse);
558 transmitted_light += transmitted_light_contrib * transmitted_shadow;
559#endif
560 }
562#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
563 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
564 // world position, inverted normal and view vectors, and the following simplified
565 // values for a fully diffuse transmitted light contribution approximation:
566 //
567 // perceptual_roughness = 1.0;
568 // NdotV = 1.0;
569 // F0 = vec3<f32>(0.0)
570 // diffuse_occlusion = vec3<f32>(1.0)
571 transmitted_light += ambient::ambient_light(diffuse_transmissive_lobe_world_position, -in.N, -in.V, 1.0, diffuse_transmissive_color, vec3<f32>(0.0), 1.0, vec3<f32>(1.0));
572#endif
574 // Diffuse indirect lighting can come from a variety of sources. The
575 // priority goes like this:
576 //
577 // 1. Lightmap (highest)
578 // 2. Irradiance volume
579 // 3. Environment map (lowest)
580 //
581 // When we find a source of diffuse indirect lighting, we stop accumulating
582 // any more diffuse indirect light. This avoids double-counting if, for
583 // example, both lightmaps and irradiance volumes are present.
585 var indirect_light = vec3(0.0f);
586 var found_diffuse_indirect = false;
588#ifdef LIGHTMAP
589 indirect_light += in.lightmap_light * diffuse_color;
590 found_diffuse_indirect = true;
591#endif
593#ifdef IRRADIANCE_VOLUME
594 // Irradiance volume light (indirect)
595 if (!found_diffuse_indirect) {
596 let irradiance_volume_light = irradiance_volume::irradiance_volume_light(
597 in.world_position.xyz,
598 in.N,
599 &clusterable_object_index_ranges,
600 );
601 indirect_light += irradiance_volume_light * diffuse_color * diffuse_occlusion;
602 found_diffuse_indirect = true;
603 }
604#endif
606 // Environment map light (indirect)
607#ifdef ENVIRONMENT_MAP
608 // If screen space reflections are going to be used for this material, don't
609 // accumulate environment map light yet. The SSR shader will do it.
610#ifdef SCREEN_SPACE_REFLECTIONS
611 let use_ssr = perceptual_roughness <=
612 view_bindings::ssr_settings.perceptual_roughness_threshold;
613#else // SCREEN_SPACE_REFLECTIONS
614 let use_ssr = false;
615#endif // SCREEN_SPACE_REFLECTIONS
617 if (!use_ssr) {
618#ifdef STANDARD_MATERIAL_ANISOTROPY
619 var bent_normal_lighting_input = lighting_input;
620 bend_normal_for_anisotropy(&bent_normal_lighting_input);
621 let environment_map_lighting_input = &bent_normal_lighting_input;
622#else // STANDARD_MATERIAL_ANISOTROPY
623 let environment_map_lighting_input = &lighting_input;
624#endif // STANDARD_MATERIAL_ANISOTROPY
626 let environment_light = environment_map::environment_map_light(
627 environment_map_lighting_input,
628 &clusterable_object_index_ranges,
629 found_diffuse_indirect,
630 );
632 indirect_light += environment_light.diffuse * diffuse_occlusion +
633 environment_light.specular * specular_occlusion;
634 }
635#endif // ENVIRONMENT_MAP
637 // Ambient light (indirect)
638 // If we are lightmapped, disable the ambient contribution if requested.
639 // This is to avoid double-counting ambient light. (It might be part of the lightmap)
640#ifdef LIGHTMAP
641 let enable_ambient = view_bindings::lights.ambient_light_affects_lightmapped_meshes != 0u;
642#else // LIGHTMAP
643 let enable_ambient = true;
644#endif // LIGHTMAP
645 if (enable_ambient) {
646 indirect_light += ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);
647 }
649 // we'll use the specular component of the transmitted environment
650 // light in the call to `specular_transmissive_light()` below
651 var specular_transmitted_environment_light = vec3<f32>(0.0);
653#ifdef ENVIRONMENT_MAP
655#ifdef STANDARD_MATERIAL_DIFFUSE_OR_SPECULAR_TRANSMISSION
656 // NOTE: We use the diffuse transmissive color, inverted normal and view vectors,
657 // and the following simplified values for the transmitted environment light contribution
658 // approximation:
659 //
660 // diffuse_color = vec3<f32>(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color`
661 // NdotV = 1.0;
662 // R = T // see definition below
663 // F0 = vec3<f32>(1.0)
664 // diffuse_occlusion = 1.0
665 //
666 // (This one is slightly different from the other light types above, because the environment
667 // map light returns both diffuse and specular components separately, and we want to use both)
669 let T = -normalize(
670 in.V + // start with view vector at entry point
671 refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point
672 ); // normalize to find exit point view vector
674 var transmissive_environment_light_input: lighting::LightingInput;
675 transmissive_environment_light_input.diffuse_color = vec3(1.0);
676 transmissive_environment_light_input.layers[LAYER_BASE].NdotV = 1.0;
677 transmissive_environment_light_input.P = in.world_position.xyz;
678 transmissive_environment_light_input.layers[LAYER_BASE].N = -in.N;
679 transmissive_environment_light_input.V = in.V;
680 transmissive_environment_light_input.layers[LAYER_BASE].R = T;
681 transmissive_environment_light_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness;
682 transmissive_environment_light_input.layers[LAYER_BASE].roughness = roughness;
683 transmissive_environment_light_input.F0_ = vec3<f32>(1.0);
684 transmissive_environment_light_input.F_ab = vec2(0.1);
685#ifdef STANDARD_MATERIAL_CLEARCOAT
686 // No clearcoat.
687 transmissive_environment_light_input.clearcoat_strength = 0.0;
688 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].NdotV = 0.0;
689 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].N = in.N;
690 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].R = vec3(0.0);
691 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].perceptual_roughness = 0.0;
692 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].roughness = 0.0;
693#endif // STANDARD_MATERIAL_CLEARCOAT
695 let transmitted_environment_light = environment_map::environment_map_light(
696 &transmissive_environment_light_input,
697 &clusterable_object_index_ranges,
698 false,
699 );
701#ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
702 transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color;
703#endif // STANDARD_MATERIAL_DIFFUSE_TRANSMISSION
704#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION
705 specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color;
706#endif // STANDARD_MATERIAL_SPECULAR_TRANSMISSION
708#endif // STANDARD_MATERIAL_SPECULAR_OR_DIFFUSE_TRANSMISSION
710#endif // ENVIRONMENT_MAP
712 var emissive_light = emissive.rgb * output_color.a;
714 // "The clearcoat layer is on top of emission in the layering stack.
715 // Consequently, the emission is darkened by the Fresnel term."
716 //
717 // <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md#emission>
718#ifdef STANDARD_MATERIAL_CLEARCOAT
719 emissive_light = emissive_light * (0.04 + (1.0 - 0.04) * pow(1.0 - clearcoat_NdotV, 5.0));
720#endif
722 emissive_light = emissive_light * mix(1.0, view_bindings::view.exposure, emissive.a);
724#ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION
725 transmitted_light += transmission::specular_transmissive_light(in.world_position, in.frag_coord.xyz, view_z, in.N, in.V, F0, ior, thickness, perceptual_roughness, specular_transmissive_color, specular_transmitted_environment_light).rgb;
727 if (in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ATTENUATION_ENABLED_BIT) != 0u {
728 // We reuse the `atmospheric_fog()` function here, as it's fundamentally
729 // equivalent to the attenuation that takes place inside the material volume,
730 // and will allow us to eventually hook up subsurface scattering more easily
731 var attenuation_fog: mesh_view_types::Fog;
732 attenuation_fog.base_color.a = 1.0;
733 attenuation_fog.be = pow(1.0 - in.material.attenuation_color.rgb, vec3<f32>(E)) / in.material.attenuation_distance;
734 // TODO: Add the subsurface scattering factor below
735 // attenuation_fog.bi = /* ... */
736 transmitted_light = bevy_pbr::fog::atmospheric_fog(
737 attenuation_fog, vec4<f32>(transmitted_light, 1.0), thickness,
738 vec3<f32>(0.0) // TODO: Pass in (pre-attenuated) scattered light contribution here
739 ).rgb;
740 }
741#endif
743 // Total light
744 output_color = vec4<f32>(
745 (view_bindings::view.exposure * (transmitted_light + direct_light + indirect_light)) + emissive_light,
746 output_color.a
747 );
749 output_color = clustering::cluster_debug_visualization(
750 output_color,
751 view_z,
752 in.is_orthographic,
753 clusterable_object_index_ranges,
754 cluster_index,
755 );
757 return output_color;
759#endif // PREPASS_FRAGMENT
761#ifdef DISTANCE_FOG
762fn apply_fog(fog_params: mesh_view_types::Fog, input_color: vec4<f32>, fragment_world_position: vec3<f32>, view_world_position: vec3<f32>) -> vec4<f32> {
763 let view_to_world = fragment_world_position.xyz - view_world_position.xyz;
765 // `length()` is used here instead of just `view_to_world.z` since that produces more
766 // high quality results, especially for denser/smaller fogs. we get a "curved"
767 // fog shape that remains consistent with camera rotation, instead of a "linear"
768 // fog shape that looks a bit fake
769 let distance = length(view_to_world);
771 // Calculate view_z for shadow cascade selection
772 let view_pos = view_transformations::position_world_to_view(fragment_world_position);
773 let view_z = view_pos.z;
775 // Approximate surface normal using view direction for shadow sampling
776 let view_direction_normal = normalize(-view_to_world);
777 let fragment_world_position_vec4 = vec4<f32>(fragment_world_position, 1.0);
779 var scattering = vec3<f32>(0.0);
780 if fog_params.directional_light_color.a > 0.0 {
781 let view_to_world_normalized = view_to_world / distance;
782 let n_directional_lights = view_bindings::lights.n_directional_lights;
783 for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
784 let light = view_bindings::lights.directional_lights[i];
785 let scattering_contribution = pow(
786 max(
787 dot(view_to_world_normalized, light.direction_to_light),
788 0.0
789 ),
790 fog_params.directional_light_exponent
791 ) * light.color.rgb * view_bindings::view.exposure;
793 // Sample shadow map to attenuate inscattering in shadowed areas
794 var shadow: f32 = 1.0;
795 if ((light.flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
796 shadow = shadows::fetch_directional_shadow(i, fragment_world_position_vec4, view_direction_normal, view_z);
797 }
798 scattering += scattering_contribution * shadow;
799 }
800 }
802 if fog_params.mode == mesh_view_types::FOG_MODE_LINEAR {
803 return bevy_pbr::fog::linear_fog(fog_params, input_color, distance, scattering);
804 } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL {
805 return bevy_pbr::fog::exponential_fog(fog_params, input_color, distance, scattering);
806 } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL_SQUARED {
807 return bevy_pbr::fog::exponential_squared_fog(fog_params, input_color, distance, scattering);
808 } else if fog_params.mode == mesh_view_types::FOG_MODE_ATMOSPHERIC {
809 return bevy_pbr::fog::atmospheric_fog(fog_params, input_color, distance, scattering);
810 } else {
811 return input_color;
812 }
814#endif // DISTANCE_FOG
816#ifdef PREMULTIPLY_ALPHA
817fn premultiply_alpha(standard_material_flags: u32, color: vec4<f32>) -> vec4<f32> {
818// `Blend`, `Premultiplied` and `Alpha` all share the same `BlendState`. Depending
819// on the alpha mode, we premultiply the color channels by the alpha channel value,
820// (and also optionally replace the alpha value with 0.0) so that the result produces
821// the desired blend mode when sent to the blending operation.
822#ifdef BLEND_PREMULTIPLIED_ALPHA
823 // For `BlendState::PREMULTIPLIED_ALPHA_BLENDING` the blend function is:
824 //
825 // result = 1 * src_color + (1 - src_alpha) * dst_color
826 let alpha_mode = standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
827 if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD {
828 // Here, we premultiply `src_color` by `src_alpha`, and replace `src_alpha` with 0.0:
829 //
830 // src_color *= src_alpha
831 // src_alpha = 0.0
832 //
833 // We end up with:
834 //
835 // result = 1 * (src_alpha * src_color) + (1 - 0) * dst_color
836 // result = src_alpha * src_color + 1 * dst_color
837 //
838 // Which is the blend operation for additive blending
839 return vec4<f32>(color.rgb * color.a, 0.0);
840 } else {
841 // Here, we don't do anything, so that we get premultiplied alpha blending. (As expected)
842 return color.rgba;
843 }
844#endif
845// `Multiply` uses its own `BlendState`, but we still need to premultiply here in the
846// shader so that we get correct results as we tweak the alpha channel
847#ifdef BLEND_MULTIPLY
848 // The blend function is:
849 //
850 // result = dst_color * src_color + (1 - src_alpha) * dst_color
851 //
852 // We premultiply `src_color` by `src_alpha`:
853 //
854 // src_color *= src_alpha
855 //
856 // We end up with:
857 //
858 // result = dst_color * (src_color * src_alpha) + (1 - src_alpha) * dst_color
859 // result = src_alpha * (src_color * dst_color) + (1 - src_alpha) * dst_color
860 //
861 // Which is the blend operation for multiplicative blending with arbitrary mixing
862 // controlled by the source alpha channel
863 return vec4<f32>(color.rgb * color.a, color.a);
864#endif
866#endif
868// fog, alpha premultiply
869// for non-hdr cameras, tonemapping and debanding
870fn main_pass_post_lighting_processing(
871 pbr_input: pbr_types::PbrInput,
872 input_color: vec4<f32>,
873) -> vec4<f32> {
874 var output_color = input_color;
876#ifdef DISTANCE_FOG
877 // fog
878 if ((pbr_input.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
879 output_color = apply_fog(view_bindings::fog, output_color, pbr_input.world_position.xyz, view_bindings::view.world_position.xyz);
880 }
881#endif // DISTANCE_FOG
883#ifdef TONEMAP_IN_SHADER
884 output_color = tone_mapping(output_color, view_bindings::view.color_grading);
885#ifdef DEBAND_DITHER
886 var output_rgb = output_color.rgb;
887 output_rgb = powsafe(output_rgb, 1.0 / 2.2);
888 output_rgb += screen_space_dither(pbr_input.frag_coord.xy);
889 // This conversion back to linear space is required because our output texture format is
890 // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion.
891 output_rgb = powsafe(output_rgb, 2.2);
892 output_color = vec4(output_rgb, output_color.a);
893#endif
894#endif
895#ifdef PREMULTIPLY_ALPHA
896 output_color = premultiply_alpha(pbr_input.material.flags, output_color);
897#endif
898 return output_color;
Contents