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.
1import package::{
2 render::{
3 pbr_types,
4 pbr_bindings,
5 mesh_view_bindings as view_bindings,
6 mesh_view_types,
7 pbr_lighting as lighting,
8 pbr_lighting::{LAYER_BASE, LAYER_CLEARCOAT},
9 clustered_forward as clustering,
10 shadows,
11 pbr_ambient as ambient,
12 view_transformations,
13 utils,
14 mesh_types::{
15 MESH_FLAGS_SHADOW_RECEIVER_BIT,
16 MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT,
17 MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT,
18 },
19 },
20 ssr::raymarch,
21};
22import package::render::mesh_view_bindings::globals;
23import package::render::view_transformations::{position_world_to_ndc};
24import bevy_render::maths::{E, powsafe};
26@if(STANDARD_MATERIAL_SPECULAR_TRANSMISSION) import package::transmission;
28@if(IRRADIANCE_VOLUME) import package::light_probe::irradiance_volume;
30@if(MESHLET_MESH_MATERIAL_PASS) import package::meshlet::visibility_buffer_resolve::VertexOutput;
31@elif(PREPASS_PIPELINE) import package::prepass::io::VertexOutput;
32@else import package::render::forward_io::VertexOutput;
34@if(ENVIRONMENT_MAP) import package::light_probe::environment_map;
36@if(TONEMAP_IN_SHADER) import bevy_core_pipeline::tonemapping::{tone_mapping, screen_space_dither};
39/// Biasing info needed to sample from a texture. How this is done depends on
40/// whether we're rendering meshlets or regular meshes.
41struct SampleBias {
42 @if(MESHLET_MESH_MATERIAL_PASS)
43 ddx_uv: vec2<f32>,
44 @if(MESHLET_MESH_MATERIAL_PASS)
45 ddy_uv: vec2<f32>,
46 @else // MESHLET_MESH_MATERIAL_PASS
47 mip_bias: f32,
50/// This is the standard 4x4 ordered dithering pattern from [1].
51///
52/// We can't use `array<vec4<u32>, 4>` because they can't be indexed dynamically
53/// due to Naga limitations. So instead we pack into a single `vec4` and extract
54/// individual bytes.
55///
56/// [1]: https://en.wikipedia.org/wiki/Ordered_dithering#Threshold_map
57const DITHER_THRESHOLD_MAP: vec4<u32> = vec4(
58 0x0a020800,
59 0x060e040c,
60 0x09010b03,
61 0x050d070f
62);
64/// Processes a visibility range dither value and discards the fragment if
65/// needed.
66///
67/// Visibility ranges, also known as HLODs, are crossfades between different
68/// levels of detail.
69///
70/// The `dither` value ranges from [-16, 16]. When zooming out, positive values
71/// are used for meshes that are in the process of disappearing, while negative
72/// values are used for meshes that are in the process of appearing. In other
73/// words, when the camera is moving backwards, the `dither` value counts up from
74/// -16 to 0 when the object is fading in, stays at 0 while the object is
75/// visible, and then counts up to 16 while the object is fading out.
76/// Distinguishing between negative and positive values allows the dither
77/// patterns for different LOD levels of a single mesh to mesh together properly.
78@if(VISIBILITY_RANGE_DITHER)
79fn visibility_range_dither(frag_coord: vec4<f32>, dither: i32) {
80 // If `dither` is 0, the object is visible.
81 if (dither == 0) {
82 return;
83 }
85 // If `dither` is less than -15 or greater than 15, the object is culled.
86 if (dither <= -16 || dither >= 16) {
87 discard;
88 }
90 // Otherwise, check the dither pattern.
91 let coords = vec2<u32>(floor(frag_coord.xy)) % 4u;
92 let threshold = i32((DITHER_THRESHOLD_MAP[coords.y] >> (coords.x * 8)) & 0xff);
93 if ((dither >= 0 && dither + threshold >= 16) || (dither < 0 && 1 + dither + threshold <= 0)) {
94 discard;
95 }
98fn alpha_discard(material_flags: u32, alpha_cutoff: f32, output_color: vec4<f32>) -> vec4<f32> {
99 var color = output_color;
100 let alpha_mode = material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
101 if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
102 // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
103 color.a = 1.0;
104 }
106 else {
107 // NOTE: `MAY_DISCARD` is only defined in the alpha to coverage case if MSAA
108 // was off. This special situation causes alpha to coverage to fall back to
109 // alpha mask.
110 @if(MAY_DISCARD)
111 if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK ||
112 alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE {
113 if color.a >= alpha_cutoff {
114 // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
115 color.a = 1.0;
116 } else {
117 // NOTE: output_color.a < in.material.alpha_cutoff should not be rendered
118 discard;
119 }
120 }
121 }
123 return color;
126fn prepare_world_normal(
127 world_normal: vec3<f32>,
128 double_sided: bool,
129 is_front: bool,
130) -> vec3<f32> {
131 var output: vec3<f32> = world_normal;
132 @if(!VERTEX_TANGENTS && !STANDARD_MATERIAL_NORMAL_MAP)
133 // NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided
134 // material, the normal needs to be inverted. This is a branchless version of that.
135 output = (f32(!double_sided || is_front) * 2.0 - 1.0) * output;
136 return output;
139fn winding_corrected_front_facing(mesh_flags: u32, is_front: bool) -> bool {
140 let positive_determinant = (mesh_flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT) != 0u;
141 return is_front == positive_determinant;
144/// Calculates the three TBN vectors according to [mikktspace]. Returns a matrix
145/// with T, B, N columns in that order.
146///
147/// [mikktspace]: http://www.mikktspace.com/
148fn calculate_tbn_mikktspace(world_normal: vec3<f32>, world_tangent: vec4<f32>) -> mat3x3<f32> {
149 // NOTE: The mikktspace method of normal mapping explicitly requires that the world normal NOT
150 // be re-normalized in the fragment shader. This is primarily to match the way mikktspace
151 // bakes vertex tangents and normal maps so that this is the exact inverse. Blender, Unity,
152 // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code
153 // unless you really know what you are doing.
154 // http://www.mikktspace.com/
155 var N: vec3<f32> = world_normal;
157 // NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be
158 // normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the
159 // vertex tangent! Do not change this code unless you really know what you are doing.
160 // http://www.mikktspace.com/
161 var T: vec3<f32> = world_tangent.xyz;
162 var B: vec3<f32> = world_tangent.w * cross(N, T);
164@if(MESHLET_MESH_MATERIAL_PASS) {
165 // https://www.jeremyong.com/graphics/2023/12/16/surface-gradient-bump-mapping/#a-note-on-mikktspace-usage
166 let inverse_length_n = 1.0 / length(N);
167 T *= inverse_length_n;
168 B *= inverse_length_n;
169 N *= inverse_length_n;
172 return mat3x3(T, B, N);
175fn apply_normal_mapping(
176 standard_material_flags: u32,
177 TBN: mat3x3<f32>,
178 double_sided: bool,
179 is_front: bool,
180 in_Nt: vec3<f32>,
181) -> vec3<f32> {
182 // Unpack the TBN vectors.
183 var T = TBN[0];
184 var B = TBN[1];
185 var N = TBN[2];
187 // Nt is the tangent-space normal.
188 var Nt = in_Nt;
189 if (standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u {
190 // Only use the xy components and derive z for 2-component normal maps.
191 Nt = vec3<f32>(Nt.rg * 2.0 - 1.0, 0.0);
192 Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y);
193 } else {
194 Nt = Nt * 2.0 - 1.0;
195 }
196 // Normal maps authored for DirectX require flipping the y component
197 if (standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u {
198 Nt.y = -Nt.y;
199 }
201 if double_sided && !is_front {
202 Nt = -Nt;
203 }
205 // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from
206 // the normal map texture in this way to be an EXACT inverse of how the normal map baker
207 // calculates the normal maps so there is no error introduced. Do not change this code
208 // unless you really know what you are doing.
209 // http://www.mikktspace.com/
210 N = Nt.x * T + Nt.y * B + Nt.z * N;
212 return normalize(N);
215/// Modifies the normal to achieve a better approximate direction from the
216/// environment map when using anisotropy.
217///
218/// This follows the suggested implementation in the `KHR_materials_anisotropy` specification:
219/// https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md#image-based-lighting
220@if(STANDARD_MATERIAL_ANISOTROPY)
221fn bend_normal_for_anisotropy(lighting_input: ptr<function, lighting::LightingInput>) {
222 // Unpack.
223 let N = (*lighting_input).layers[LAYER_BASE].N;
224 let roughness = (*lighting_input).layers[LAYER_BASE].roughness;
225 let V = (*lighting_input).V;
226 let anisotropy = (*lighting_input).anisotropy;
227 let Ba = (*lighting_input).Ba;
229 var bent_normal = normalize(cross(cross(Ba, V), Ba));
231 // The `KHR_materials_anisotropy` spec states:
232 //
233 // > This heuristic can probably be improved upon
234 let bendFactor = 1.0 - anisotropy * (1.0 - roughness);
235 let bendFactorPow4 = bendFactor * bendFactor * bendFactor * bendFactor;
236 bent_normal = normalize(mix(bent_normal, N, bendFactorPow4));
238 // The `KHR_materials_anisotropy` spec states:
239 //
240 // > Mixing the reflection with the normal is more accurate both with and
241 // > without anisotropy and keeps rough objects from gathering light from
242 // > behind their tangent plane.
243 let R = normalize(mix(reflect(-V, bent_normal), bent_normal, roughness * roughness));
245 (*lighting_input).layers[LAYER_BASE].N = bent_normal;
246 (*lighting_input).layers[LAYER_BASE].R = R;
249/// NOTE: Correctly calculates the view vector depending on whether
250/// the projection is orthographic or perspective.
251fn calculate_view(
252 world_position: vec4<f32>,
253 is_orthographic: bool,
254) -> vec3<f32> {
255 var V: vec3<f32>;
256 if is_orthographic {
257 // Orthographic view vector
258 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));
259 } else {
260 // Only valid for a perspective projection
261 V = normalize(view_bindings::view.world_position.xyz - world_position.xyz);
262 }
263 return V;
266/// Diffuse strength is inversely related to metallicity, specular and diffuse transmission
267fn calculate_diffuse_color(
268 base_color: vec3<f32>,
269 metallic: f32,
270 specular_transmission: f32,
271 diffuse_transmission: f32
272) -> vec3<f32> {
273 return base_color * (1.0 - metallic) * (1.0 - specular_transmission) *
274 (1.0 - diffuse_transmission);
277/// Remapping [0,1] reflectance to F0 for dielectrics
278fn calculate_F0_dielectric(reflectance: vec3<f32>) -> vec3<f32> {
279 return 0.16 * reflectance * reflectance;
282/// Remapping [0,1] reflectance to F0
283/// See https://google.github.io/filament/Filament.md.html#materialsystem/parameterization/remapping
284fn calculate_F0(base_color: vec3<f32>, metallic: f32, reflectance: vec3<f32>) -> vec3<f32> {
285 return mix(calculate_F0_dielectric(reflectance), base_color, metallic);
288@if(CONTACT_SHADOWS && DEPTH_PREPASS)
289fn calculate_contact_shadow(
290 world_position: vec3<f32>,
291 frag_coord: vec2<f32>,
292 light_dir: vec3<f32>,
293 contact_shadow_steps: u32,
294) -> f32 {
295 @if(BLUE_NOISE_TEXTURE)
296 let noise_size = textureDimensions(view_bindings::blue_noise_texture, 0);
297 @if(BLUE_NOISE_TEXTURE)
298 let noise_layers = textureNumLayers(view_bindings::blue_noise_texture);
299 @if(BLUE_NOISE_TEXTURE)
300 let noise = textureLoad(
301 view_bindings::blue_noise_texture,
302 vec2<i32>(frag_coord) % vec2<i32>(noise_size),
303 i32(view_bindings::globals.frame_count % noise_layers),
304 0
305 ).x;
306 @else
307 let noise = utils::interleaved_gradient_noise(frag_coord, view_bindings::globals.frame_count);
309 let depth_size = vec2<f32>(textureDimensions(view_bindings::depth_prepass_texture));
310 var rm = raymarch::depth_ray_march_new_from_depth(depth_size);
311 raymarch::depth_ray_march_from_cs(&rm, position_world_to_ndc(world_position));
312 raymarch::depth_ray_march_to_ws(&rm, world_position + light_dir * view_bindings::contact_shadows_settings.length);
313 rm.linear_steps = contact_shadow_steps;
314 rm.depth_thickness_linear_z = view_bindings::contact_shadows_settings.thickness;
315 rm.march_behind_surfaces = true;
316 rm.jitter = noise;
318 let rm_result = raymarch::depth_ray_march_march(&rm);
319 if rm_result.hit {
320 return clamp((rm_result.hit_penetration_frac - 0.5) / (1.0 - 0.5), 0.0, 1.0);
321 }
322 return 1.0;
325@if(!PREPASS_FRAGMENT)
326fn apply_pbr_lighting(
327 in: pbr_types::PbrInput,
328) -> vec4<f32> {
329 var output_color: vec4<f32> = in.material.base_color;
331 let emissive = in.material.emissive;
333 // calculate non-linear roughness from linear perceptualRoughness
334 let metallic = in.material.metallic;
335 let perceptual_roughness = in.material.perceptual_roughness;
336 let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness);
337 let ior = in.material.ior;
338 let thickness = in.material.thickness;
339 let reflectance = in.material.reflectance;
340 let diffuse_transmission = in.material.diffuse_transmission;
341 let specular_transmission = in.material.specular_transmission;
343 let specular_transmissive_color = specular_transmission * in.material.base_color.rgb;
345 let diffuse_occlusion = in.diffuse_occlusion;
346 let specular_occlusion = in.specular_occlusion;
348 // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
349 let NdotV = max(dot(in.N, in.V), 0.0001);
350 let R = reflect(-in.V, in.N);
352 // Do the above calculations again for the clearcoat layer. Remember that
353 // the clearcoat can have its own roughness and its own normal.
354 @if(STANDARD_MATERIAL_CLEARCOAT)
355 let clearcoat = in.material.clearcoat;
356 @if(STANDARD_MATERIAL_CLEARCOAT)
357 let clearcoat_perceptual_roughness = in.material.clearcoat_perceptual_roughness;
358 @if(STANDARD_MATERIAL_CLEARCOAT)
359 let clearcoat_roughness = lighting::perceptualRoughnessToRoughness(clearcoat_perceptual_roughness);
360 @if(STANDARD_MATERIAL_CLEARCOAT)
361 let clearcoat_N = in.clearcoat_N;
362 @if(STANDARD_MATERIAL_CLEARCOAT)
363 let clearcoat_NdotV = max(dot(clearcoat_N, in.V), 0.0001);
364 @if(STANDARD_MATERIAL_CLEARCOAT)
365 let clearcoat_R = reflect(-in.V, clearcoat_N);
367 let diffuse_color = calculate_diffuse_color(
368 output_color.rgb,
369 metallic,
370 specular_transmission,
371 diffuse_transmission
372 );
374 // Diffuse transmissive strength is inversely related to metallicity and specular transmission, but directly related to diffuse transmission
375 let diffuse_transmissive_color = output_color.rgb * (1.0 - metallic) * (1.0 - specular_transmission) * diffuse_transmission;
377 // Calculate the world position of the second Lambertian lobe used for diffuse transmission, by subtracting material thickness
378 let diffuse_transmissive_lobe_world_position = in.world_position - vec4<f32>(in.world_normal, 0.0) * thickness;
380 let F0 = calculate_F0(output_color.rgb, metallic, reflectance);
381 let F_ab = lighting::F_AB(perceptual_roughness, NdotV);
383 var direct_light: vec3<f32> = vec3<f32>(0.0);
385 // Transmitted Light (Specular and Diffuse)
386 var transmitted_light: vec3<f32> = vec3<f32>(0.0);
388 // Pack all the values into a structure.
389 var lighting_input: lighting::LightingInput;
390 lighting_input.layers[LAYER_BASE].NdotV = NdotV;
391 lighting_input.layers[LAYER_BASE].N = in.N;
392 lighting_input.layers[LAYER_BASE].R = R;
393 lighting_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness;
394 lighting_input.layers[LAYER_BASE].roughness = roughness;
395 lighting_input.P = in.world_position.xyz;
396 lighting_input.V = in.V;
397 lighting_input.diffuse_color = diffuse_color;
398 lighting_input.metallic = metallic;
399 lighting_input.F0_dielectric = calculate_F0_dielectric(reflectance);
400 lighting_input.F0_metallic = output_color.rgb;
401 lighting_input.F_ab = F_ab;
402@if(STANDARD_MATERIAL_CLEARCOAT) {
403 lighting_input.layers[LAYER_CLEARCOAT].NdotV = clearcoat_NdotV;
404 lighting_input.layers[LAYER_CLEARCOAT].N = clearcoat_N;
405 lighting_input.layers[LAYER_CLEARCOAT].R = clearcoat_R;
406 lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = clearcoat_perceptual_roughness;
407 lighting_input.layers[LAYER_CLEARCOAT].roughness = clearcoat_roughness;
408 lighting_input.clearcoat_strength = clearcoat;
410@if(STANDARD_MATERIAL_ANISOTROPY) {
411 lighting_input.anisotropy = in.anisotropy_strength;
412 lighting_input.Ta = in.anisotropy_T;
413 lighting_input.Ba = in.anisotropy_B;
416 // And do the same for transmissive if we need to.
417 @if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION)
418 var transmissive_lighting_input: lighting::LightingInput;
419@if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION) {
420 transmissive_lighting_input.layers[LAYER_BASE].NdotV = 1.0;
421 transmissive_lighting_input.layers[LAYER_BASE].N = -in.N;
422 transmissive_lighting_input.layers[LAYER_BASE].R = vec3(0.0);
423 transmissive_lighting_input.layers[LAYER_BASE].perceptual_roughness = 1.0;
424 transmissive_lighting_input.layers[LAYER_BASE].roughness = 1.0;
425 transmissive_lighting_input.P = diffuse_transmissive_lobe_world_position.xyz;
426 transmissive_lighting_input.V = -in.V;
427 transmissive_lighting_input.diffuse_color = diffuse_transmissive_color;
428 transmissive_lighting_input.metallic = 0.0;
429 transmissive_lighting_input.F0_dielectric = vec3(0.0);
430 transmissive_lighting_input.F0_metallic = vec3(0.0);
431 transmissive_lighting_input.F_ab = vec2(0.1);
432@if(STANDARD_MATERIAL_CLEARCOAT) {
433 transmissive_lighting_input.layers[LAYER_CLEARCOAT].NdotV = 0.0;
434 transmissive_lighting_input.layers[LAYER_CLEARCOAT].N = vec3(0.0);
435 transmissive_lighting_input.layers[LAYER_CLEARCOAT].R = vec3(0.0);
436 transmissive_lighting_input.layers[LAYER_CLEARCOAT].perceptual_roughness = 0.0;
437 transmissive_lighting_input.layers[LAYER_CLEARCOAT].roughness = 0.0;
438 transmissive_lighting_input.clearcoat_strength = 0.0;
440@if(STANDARD_MATERIAL_ANISOTROPY) {
441 transmissive_lighting_input.anisotropy = in.anisotropy_strength;
442 transmissive_lighting_input.Ta = in.anisotropy_T;
443 transmissive_lighting_input.Ba = in.anisotropy_B;
447 let view_z = dot(vec4<f32>(
448 view_bindings::view.view_from_world[0].z,
449 view_bindings::view.view_from_world[1].z,
450 view_bindings::view.view_from_world[2].z,
451 view_bindings::view.view_from_world[3].z
452 ), in.world_position);
453 let cluster_index = clustering::view_fragment_cluster_index(in.frag_coord.xy, view_z, in.is_orthographic);
454 var clusterable_object_index_ranges =
455 clustering::unpack_clusterable_object_index_ranges(cluster_index);
457 @if(CONTACT_SHADOWS)
458 let contact_shadow_steps = view_bindings::contact_shadows_settings.linear_steps;
459 @if(CONTACT_SHADOWS)
460 let contact_shadow_enabled = contact_shadow_steps > 0u;
462 // Point lights (direct)
463 for (var i: u32 = clusterable_object_index_ranges.first_point_light_index_offset;
464 i < clusterable_object_index_ranges.first_spot_light_index_offset;
465 i = i + 1u) {
466 let light_id = clustering::get_clusterable_object_id(i);
468 // If we're lightmapped, disable diffuse contribution from the light if
469 // requested, to avoid double-counting light.
470 @if(LIGHTMAP)
471 let enable_diffuse =
472 (view_bindings::clustered_lights.data[light_id].flags &
473 mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u;
474 @else // LIGHTMAP
475 let enable_diffuse = true;
477 var shadow: f32 = 1.0;
478 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
479 && (view_bindings::clustered_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
480 shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal, in.frag_coord.xy);
481 }
483 @if(CONTACT_SHADOWS && DEPTH_PREPASS)
484 if contact_shadow_enabled && (in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && shadow > 0.0 &&
485 (view_bindings::clustered_lights.data[light_id].flags &
486 mesh_view_types::POINT_LIGHT_FLAGS_CONTACT_SHADOWS_ENABLED_BIT) != 0u {
487 let L = normalize(view_bindings::clustered_lights.data[light_id].position_radius.xyz - in.world_position.xyz);
488 shadow *= calculate_contact_shadow(in.world_position.xyz, in.frag_coord.xy, L, contact_shadow_steps);
489 }
491 let light_contrib = lighting::point_light(light_id, &lighting_input, enable_diffuse, true);
492 direct_light += light_contrib * shadow;
494@if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION) {
495 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
496 // world position, inverted normal and view vectors, and the following simplified
497 // values for a fully diffuse transmitted light contribution approximation:
498 //
499 // roughness = 1.0;
500 // NdotV = 1.0;
501 // R = vec3<f32>(0.0) // doesn't really matter
502 // F_ab = vec2<f32>(0.1)
503 // F0 = vec3<f32>(0.0)
504 var transmitted_shadow: f32 = 1.0;
505 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)
506 && (view_bindings::clustered_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
507 transmitted_shadow = shadows::fetch_point_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal, in.frag_coord.xy);
508 }
510 let transmitted_light_contrib =
511 lighting::point_light(light_id, &transmissive_lighting_input, enable_diffuse, true);
512 transmitted_light += transmitted_light_contrib * transmitted_shadow;
514 }
516 // Spot lights (direct)
517 for (var i: u32 = clusterable_object_index_ranges.first_spot_light_index_offset;
518 i < clusterable_object_index_ranges.first_rect_light_index_offset;
519 i = i + 1u) {
520 let light_id = clustering::get_clusterable_object_id(i);
522 // If we're lightmapped, disable diffuse contribution from the light if
523 // requested, to avoid double-counting light.
524 @if(LIGHTMAP)
525 let enable_diffuse =
526 (view_bindings::clustered_lights.data[light_id].flags &
527 mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u;
528 @else // LIGHTMAP
529 let enable_diffuse = true;
531 var shadow: f32 = 1.0;
532 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
533 && (view_bindings::clustered_lights.data[light_id].flags &
534 mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
535 shadow = shadows::fetch_spot_shadow(
536 light_id,
537 in.world_position,
538 in.world_normal,
539 view_bindings::clustered_lights.data[light_id].shadow_map_near_z,
540 in.frag_coord.xy,
541 );
542 }
544 @if(CONTACT_SHADOWS && DEPTH_PREPASS)
545 if contact_shadow_enabled && (in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && shadow > 0.0 &&
546 (view_bindings::clustered_lights.data[light_id].flags &
547 mesh_view_types::POINT_LIGHT_FLAGS_CONTACT_SHADOWS_ENABLED_BIT) != 0u {
548 let L = normalize(view_bindings::clustered_lights.data[light_id].position_radius.xyz - in.world_position.xyz);
549 shadow *= calculate_contact_shadow(in.world_position.xyz, in.frag_coord.xy, L, contact_shadow_steps);
550 }
552 let light_contrib = lighting::spot_light(light_id, &lighting_input, enable_diffuse);
553 direct_light += light_contrib * shadow;
555@if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION) {
556 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
557 // world position, inverted normal and view vectors, and the following simplified
558 // values for a fully diffuse transmitted light contribution approximation:
559 //
560 // roughness = 1.0;
561 // NdotV = 1.0;
562 // R = vec3<f32>(0.0) // doesn't really matter
563 // F_ab = vec2<f32>(0.1)
564 // F0 = vec3<f32>(0.0)
565 var transmitted_shadow: f32 = 1.0;
566 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)
567 && (view_bindings::clustered_lights.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
568 transmitted_shadow = shadows::fetch_spot_shadow(
569 light_id,
570 diffuse_transmissive_lobe_world_position,
571 -in.world_normal,
572 view_bindings::clustered_lights.data[light_id].shadow_map_near_z,
573 in.frag_coord.xy,
574 );
575 }
577 let transmitted_light_contrib =
578 lighting::spot_light(light_id, &transmissive_lighting_input, enable_diffuse);
579 transmitted_light += transmitted_light_contrib * transmitted_shadow;
581 }
583 // directional lights (direct)
584 let n_directional_lights = view_bindings::lights.n_directional_lights;
585 for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
586 // check if this light should be skipped, which occurs if this light does not intersect with the view
587 // note point and spot lights aren't skippable, as the relevant lights are filtered in `assign_lights_to_clusters`
588 let light = &view_bindings::lights.directional_lights[i];
590 // If we're lightmapped, disable diffuse contribution from the light if
591 // requested, to avoid double-counting light.
592 @if(LIGHTMAP)
593 let enable_diffuse =
594 ((*light).flags &
595 mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) !=
596 0u;
597 @else // LIGHTMAP
598 let enable_diffuse = true;
600 var shadow: f32 = 1.0;
601 if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
602 && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
603 shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z, in.frag_coord.xy);
604 shadow *= in.directional_shadow_factor;
605 }
607 @if(CONTACT_SHADOWS && DEPTH_PREPASS)
608 if contact_shadow_enabled && (in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && shadow > 0.0 &&
609 (view_bindings::lights.directional_lights[i].flags &
610 mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_CONTACT_SHADOWS_ENABLED_BIT) != 0u {
611 let L = view_bindings::lights.directional_lights[i].direction_to_light;
612 shadow *= calculate_contact_shadow(in.world_position.xyz, in.frag_coord.xy, L, contact_shadow_steps);
613 }
615 var light_contrib = lighting::directional_light(i, &lighting_input, enable_diffuse);
617 @if(DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES)
618 light_contrib = shadows::cascade_debug_visualization(light_contrib, i, view_z);
619 direct_light += light_contrib * shadow;
621@if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION) {
622 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
623 // world position, inverted normal and view vectors, and the following simplified
624 // values for a fully diffuse transmitted light contribution approximation:
625 //
626 // roughness = 1.0;
627 // NdotV = 1.0;
628 // R = vec3<f32>(0.0) // doesn't really matter
629 // F_ab = vec2<f32>(0.1)
630 // F0 = vec3<f32>(0.0)
631 var transmitted_shadow: f32 = 1.0;
632 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)
633 && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
634 transmitted_shadow = shadows::fetch_directional_shadow(i, diffuse_transmissive_lobe_world_position, -in.world_normal, view_z, in.frag_coord.xy);
635 }
637 let transmitted_light_contrib =
638 lighting::directional_light(i, &transmissive_lighting_input, enable_diffuse);
639 transmitted_light += transmitted_light_contrib * transmitted_shadow;
641 }
643@if(AREA_LIGHT_LUTS) {
644 // Rect lights
645 // This combines a clustered path if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
646 // and a non-clustered fallback for WebGL2.
647@if(AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
648 let rect_light_start = clusterable_object_index_ranges.first_rect_light_index_offset;
649@if(AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
650 let rect_light_end = clusterable_object_index_ranges.first_reflection_probe_index_offset;
651@if(!AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
652 let rect_light_start = 0u;
653@if(!AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
654 let rect_light_end = view_bindings::lights.n_rect_lights;
655 for (var i: u32 = rect_light_start; i < rect_light_end; i = i + 1u) {
656@if(AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
657 var light = lighting::unpack_clustered_rect_light(clustering::get_clusterable_object_id(i));
658@if(!AVAILABLE_STORAGE_BUFFER_BINDINGS__GE_3)
659 var light = view_bindings::lights.rect_lights[i];
660 let enable_diffuse = true;
661 let light_contrib = lighting::rect_light(&light, &lighting_input, enable_diffuse);
662 direct_light += light_contrib;
664 @if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION) {
665 let transmitted_light_contrib =
666 lighting::rect_light(&light, &transmissive_lighting_input, enable_diffuse);
667 transmitted_light += transmitted_light_contrib;
668 }
669 }
672 @if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION)
673 // NOTE: We use the diffuse transmissive color, the second Lambertian lobe's calculated
674 // world position, inverted normal and view vectors, and the following simplified
675 // values for a fully diffuse transmitted light contribution approximation:
676 //
677 // perceptual_roughness = 1.0;
678 // NdotV = 1.0;
679 // F0 = vec3<f32>(0.0)
680 // diffuse_occlusion = vec3<f32>(1.0)
681 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));
683 // Diffuse indirect lighting can come from a variety of sources. The
684 // priority goes like this:
685 //
686 // 1. Lightmap (highest)
687 // 2. Irradiance volume
688 // 3. Environment map (lowest)
689 //
690 // When we find a source of diffuse indirect lighting, we stop accumulating
691 // any more diffuse indirect light. This avoids double-counting if, for
692 // example, both lightmaps and irradiance volumes are present.
694 var indirect_light = vec3(0.0f);
695 var found_diffuse_indirect = false;
697@if(LIGHTMAP) {
698 indirect_light += in.lightmap_light * diffuse_color;
699 found_diffuse_indirect = true;
702 @if(IRRADIANCE_VOLUME)
703 // Irradiance volume light (indirect)
704 if (!found_diffuse_indirect) {
705 let irradiance_volume_light = irradiance_volume::irradiance_volume_light(
706 in.world_position.xyz,
707 in.N,
708 &clusterable_object_index_ranges,
709 );
710 indirect_light += irradiance_volume_light * diffuse_color * diffuse_occlusion;
711 found_diffuse_indirect = true;
712 }
714 // Environment map light (indirect)
715@if(ENVIRONMENT_MAP) {
716 // If screen space reflections are going to be used for this material, only
717 // accumulate the diffuse part of the environment map light. The SSR shader
718 // will accumulate the specular part (including the environment map fallback
719 // if SSR misses).
720 @if(SCREEN_SPACE_REFLECTIONS)
721 let use_ssr = perceptual_roughness <= view_bindings::ssr_settings.max_perceptual_roughness
722 && perceptual_roughness >= view_bindings::ssr_settings.min_perceptual_roughness;
723 @else // SCREEN_SPACE_REFLECTIONS
724 let use_ssr = false;
726 @if(STANDARD_MATERIAL_ANISOTROPY)
727 var bent_normal_lighting_input = lighting_input;
728 @if(STANDARD_MATERIAL_ANISOTROPY)
729 bend_normal_for_anisotropy(&bent_normal_lighting_input);
730 @if(STANDARD_MATERIAL_ANISOTROPY)
731 let environment_map_lighting_input = &bent_normal_lighting_input;
732 @else // STANDARD_MATERIAL_ANISOTROPY
733 let environment_map_lighting_input = &lighting_input;
735 let environment_light = environment_map::environment_map_light(
736 environment_map_lighting_input,
737 &clusterable_object_index_ranges,
738 found_diffuse_indirect,
739 );
741 indirect_light += environment_light.diffuse * diffuse_occlusion;
742 if (!use_ssr) {
743 indirect_light += environment_light.specular * specular_occlusion;
744 }
747 // Ambient light (indirect)
748 // If we are lightmapped, disable the ambient contribution if requested.
749 // This is to avoid double-counting ambient light. (It might be part of the lightmap)
750 @if(LIGHTMAP)
751 let enable_ambient = (view_bindings::lights.ambient_light_flags & mesh_view_types::AMBIENT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESHES_BIT) != 0u;
752 @else // LIGHTMAP
753 let enable_ambient = true;
754 if (enable_ambient) {
755 indirect_light += ambient::ambient_light(in.world_position, in.N, in.V, NdotV, diffuse_color, F0, perceptual_roughness, diffuse_occlusion);
756 }
758 // we'll use the specular component of the transmitted environment
759 // light in the call to `specular_transmissive_light()` below
760 var specular_transmitted_environment_light = vec3<f32>(0.0);
762@if(ENVIRONMENT_MAP) {
764@if(STANDARD_MATERIAL_DIFFUSE_OR_SPECULAR_TRANSMISSION) {
765 // NOTE: We use the diffuse transmissive color, inverted normal and view vectors,
766 // and the following simplified values for the transmitted environment light contribution
767 // approximation:
768 //
769 // diffuse_color = vec3<f32>(1.0) // later we use `diffuse_transmissive_color` and `specular_transmissive_color`
770 // NdotV = 1.0;
771 // R = T // see definition below
772 // F0 = vec3<f32>(1.0) (using F0_dielectric = 1, F0_metallic = 0 and metallic = 0)
773 // diffuse_occlusion = 1.0
774 //
775 // (This one is slightly different from the other light types above, because the environment
776 // map light returns both diffuse and specular components separately, and we want to use both)
778 let T = -normalize(
779 in.V + // start with view vector at entry point
780 refract(in.V, -in.N, 1.0 / ior) * thickness // add refracted vector scaled by thickness, towards exit point
781 ); // normalize to find exit point view vector
783 var transmissive_environment_light_input: lighting::LightingInput;
784 transmissive_environment_light_input.diffuse_color = vec3(1.0);
785 transmissive_environment_light_input.layers[LAYER_BASE].NdotV = 1.0;
786 transmissive_environment_light_input.P = in.world_position.xyz;
787 transmissive_environment_light_input.layers[LAYER_BASE].N = -in.N;
788 transmissive_environment_light_input.V = in.V;
789 transmissive_environment_light_input.layers[LAYER_BASE].R = T;
790 transmissive_environment_light_input.layers[LAYER_BASE].perceptual_roughness = perceptual_roughness;
791 transmissive_environment_light_input.layers[LAYER_BASE].roughness = roughness;
792 transmissive_environment_light_input.metallic = 0.0;
793 transmissive_environment_light_input.F0_dielectric = vec3<f32>(1.0);
794 transmissive_environment_light_input.F0_metallic = vec3<f32>(0.0);
795 transmissive_environment_light_input.F_ab = vec2(0.1);
796@if(STANDARD_MATERIAL_CLEARCOAT) {
797 // No clearcoat.
798 transmissive_environment_light_input.clearcoat_strength = 0.0;
799 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].NdotV = 0.0;
800 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].N = in.N;
801 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].R = vec3(0.0);
802 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].perceptual_roughness = 0.0;
803 transmissive_environment_light_input.layers[LAYER_CLEARCOAT].roughness = 0.0;
806 let transmitted_environment_light = environment_map::environment_map_light(
807 &transmissive_environment_light_input,
808 &clusterable_object_index_ranges,
809 false,
810 );
812 @if(STANDARD_MATERIAL_DIFFUSE_TRANSMISSION)
813 transmitted_light += transmitted_environment_light.diffuse * diffuse_transmissive_color;
814 @if(STANDARD_MATERIAL_SPECULAR_TRANSMISSION)
815 specular_transmitted_environment_light = transmitted_environment_light.specular * specular_transmissive_color;
821 var emissive_light = emissive.rgb * output_color.a;
823 // "The clearcoat layer is on top of emission in the layering stack.
824 // Consequently, the emission is darkened by the Fresnel term."
825 //
826 // <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md#emission>
827 @if(STANDARD_MATERIAL_CLEARCOAT) {
828 let clearcoat_fresnel = lighting::F_Schlick(0.04, 1.0, clearcoat_NdotV);
829 emissive_light = emissive_light * (1.0 - clearcoat * clearcoat_fresnel);
830 }
832 emissive_light = emissive_light * mix(1.0, view_bindings::view.exposure, emissive.a);
834@if(STANDARD_MATERIAL_SPECULAR_TRANSMISSION) {
835 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;
837 if (in.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ATTENUATION_ENABLED_BIT) != 0u
838 && in.material.attenuation_distance != 0.0 {
839 // Compute light attenuation using Beer's law.
840 let transmittance = pow(
841 in.material.attenuation_color.rgb,
842 vec3<f32>(thickness / in.material.attenuation_distance),
843 );
844 transmitted_light *= transmittance;
845 }
848 // Total light
849 output_color = vec4<f32>(
850 (view_bindings::view.exposure * (transmitted_light + direct_light + indirect_light)) + emissive_light,
851 output_color.a
852 );
854 output_color = clustering::cluster_debug_visualization(
855 output_color,
856 view_z,
857 in.is_orthographic,
858 clusterable_object_index_ranges,
859 cluster_index,
860 );
862 return output_color;
865@if(DISTANCE_FOG)
866fn apply_fog(
867 fog_params: mesh_view_types::Fog,
868 input_color: vec4<f32>,
869 fragment_world_position: vec3<f32>,
870 view_world_position: vec3<f32>,
871 frag_coord_xy: vec2<f32>,
872) -> vec4<f32> {
873 let view_to_world = fragment_world_position.xyz - view_world_position.xyz;
875 // `length()` is used here instead of just `view_to_world.z` since that produces more
876 // high quality results, especially for denser/smaller fogs. we get a "curved"
877 // fog shape that remains consistent with camera rotation, instead of a "linear"
878 // fog shape that looks a bit fake
879 let distance = length(view_to_world);
881 // Calculate view_z for shadow cascade selection
882 let view_pos = view_transformations::position_world_to_view(fragment_world_position);
883 let view_z = view_pos.z;
885 // Approximate surface normal using view direction for shadow sampling
886 let view_direction_normal = normalize(-view_to_world);
887 let fragment_world_position_vec4 = vec4<f32>(fragment_world_position, 1.0);
889 var scattering = vec3<f32>(0.0);
890 if fog_params.directional_light_color.a > 0.0 {
891 let view_to_world_normalized = view_to_world / distance;
892 let n_directional_lights = view_bindings::lights.n_directional_lights;
893 for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
894 let light = view_bindings::lights.directional_lights[i];
895 let scattering_contribution = pow(
896 max(
897 dot(view_to_world_normalized, light.direction_to_light),
898 0.0
899 ),
900 fog_params.directional_light_exponent
901 ) * light.color.rgb * view_bindings::view.exposure;
903 // Sample shadow map to attenuate inscattering in shadowed areas
904 var shadow: f32 = 1.0;
905 if ((light.flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
906 shadow = shadows::fetch_directional_shadow(i, fragment_world_position_vec4, view_direction_normal, view_z, frag_coord_xy);
907 }
908 scattering += scattering_contribution * shadow;
909 }
910 }
912 if fog_params.mode == mesh_view_types::FOG_MODE_LINEAR {
913 return package::render::fog::linear_fog(fog_params, input_color, distance, scattering);
914 } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL {
915 return package::render::fog::exponential_fog(fog_params, input_color, distance, scattering);
916 } else if fog_params.mode == mesh_view_types::FOG_MODE_EXPONENTIAL_SQUARED {
917 return package::render::fog::exponential_squared_fog(fog_params, input_color, distance, scattering);
918 } else if fog_params.mode == mesh_view_types::FOG_MODE_ATMOSPHERIC {
919 return package::render::fog::atmospheric_fog(fog_params, input_color, distance, scattering);
920 } else {
921 return input_color;
922 }
925@if(PREMULTIPLY_ALPHA)
926fn premultiply_alpha(standard_material_flags: u32, color: vec4<f32>) -> vec4<f32> {
927// `Blend`, `Premultiplied` and `Alpha` all share the same `BlendState`. Depending
928// on the alpha mode, we premultiply the color channels by the alpha channel value,
929// (and also optionally replace the alpha value with 0.0) so that the result produces
930// the desired blend mode when sent to the blending operation.
931@if(BLEND_PREMULTIPLIED_ALPHA) {
932 // For `BlendState::PREMULTIPLIED_ALPHA_BLENDING` the blend function is:
933 //
934 // result = 1 * src_color + (1 - src_alpha) * dst_color
935 let alpha_mode = standard_material_flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
936 if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD {
937 // Here, we premultiply `src_color` by `src_alpha`, and replace `src_alpha` with 0.0:
938 //
939 // src_color *= src_alpha
940 // src_alpha = 0.0
941 //
942 // We end up with:
943 //
944 // result = 1 * (src_alpha * src_color) + (1 - 0) * dst_color
945 // result = src_alpha * src_color + 1 * dst_color
946 //
947 // Which is the blend operation for additive blending
948 return vec4<f32>(color.rgb * color.a, 0.0);
949 } else {
950 // Here, we don't do anything, so that we get premultiplied alpha blending. (As expected)
951 return color.rgba;
952 }
954// `Multiply` uses its own `BlendState`, but we still need to premultiply here in the
955// shader so that we get correct results as we tweak the alpha channel
956@if(BLEND_MULTIPLY) {
957 // The blend function is:
958 //
959 // result = dst_color * src_color + (1 - src_alpha) * dst_color
960 //
961 // We premultiply `src_color` by `src_alpha`:
962 //
963 // src_color *= src_alpha
964 //
965 // We end up with:
966 //
967 // result = dst_color * (src_color * src_alpha) + (1 - src_alpha) * dst_color
968 // result = src_alpha * (src_color * dst_color) + (1 - src_alpha) * dst_color
969 //
970 // Which is the blend operation for multiplicative blending with arbitrary mixing
971 // controlled by the source alpha channel
972 return vec4<f32>(color.rgb * color.a, color.a);
976/// fog, alpha premultiply
977/// for non-hdr cameras, tonemapping and debanding
978fn main_pass_post_lighting_processing(
979 pbr_input: pbr_types::PbrInput,
980 input_color: vec4<f32>,
981) -> vec4<f32> {
982 var output_color = input_color;
984 @if(DISTANCE_FOG)
985 // fog
986 if ((pbr_input.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
987 output_color = apply_fog(
988 view_bindings::fog,
989 output_color,
990 pbr_input.world_position.xyz,
991 view_bindings::view.world_position.xyz,
992 pbr_input.frag_coord.xy,
993 );
994 }
996@if(TONEMAP_IN_SHADER) {
997 output_color = tone_mapping(output_color, view_bindings::view.color_grading);
998@if(DEBAND_DITHER) {
999 var output_rgb = output_color.rgb;
1000 output_rgb = powsafe(output_rgb, 1.0 / 2.2);
1001 output_rgb += screen_space_dither(pbr_input.frag_coord.xy);
1002 // This conversion back to linear space is required because our output texture format is
1003 // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion.
1004 output_rgb = powsafe(output_rgb, 2.2);
1005 output_color = vec4(output_rgb, output_color.a);
1008 @if(PREMULTIPLY_ALPHA)
1009 output_color = premultiply_alpha(pbr_input.material.flags, output_color);
1010 return output_color;
Contents