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::shadow_sampling
3#import bevy_pbr::{
4 mesh_view_bindings as view_bindings,
5 utils::interleaved_gradient_noise,
6 utils,
7}
8#import bevy_render::maths::{orthonormalize, PI}
10// Do the lookup, using HW 2x2 PCF and comparison
11fn sample_shadow_map_hardware(light_local: vec2<f32>, depth: f32, array_index: i32) -> f32 {
12#ifdef NO_ARRAY_TEXTURES_SUPPORT
13 return textureSampleCompare(
14 view_bindings::directional_shadow_textures,
15 view_bindings::directional_shadow_textures_comparison_sampler,
16 light_local,
17 depth,
18 );
19#else
20 return textureSampleCompareLevel(
21 view_bindings::directional_shadow_textures,
22 view_bindings::directional_shadow_textures_comparison_sampler,
23 light_local,
24 array_index,
25 depth,
26 );
27#endif
30// Does a single sample of the blocker search, a part of the PCSS algorithm.
31// This is the variant used for directional lights.
32fn search_for_blockers_in_shadow_map_hardware(
33 light_local: vec2<f32>,
34 depth: f32,
35 array_index: i32,
36) -> vec2<f32> {
37#ifdef WEBGL2
38 // Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled
39 // with different samplers, or it'll blow up.
40 return vec2(0.0);
41#else // WEBGL2
43#ifdef PCSS_SAMPLERS_AVAILABLE
45#ifdef NO_ARRAY_TEXTURES_SUPPORT
46 let sampled_depth = textureSampleLevel(
47 view_bindings::directional_shadow_textures,
48 view_bindings::directional_shadow_textures_linear_sampler,
49 light_local,
50 0u,
51 );
52#else // NO_ARRAY_TEXTURES_SUPPORT
53 let sampled_depth = textureSampleLevel(
54 view_bindings::directional_shadow_textures,
55 view_bindings::directional_shadow_textures_linear_sampler,
56 light_local,
57 array_index,
58 0u,
59 );
60#endif // NO_ARRAY_TEXTURES_SUPPORT
61 return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth);
63#else // PCSS_SAMPLERS_AVAILABLE
64 return vec2(0.0);
65#endif // PCSS_SAMPLERS_AVAILABLE
67#endif // WEBGL2
70// Numbers determined by trial and error that gave nice results.
71const SPOT_SHADOW_TEXEL_SIZE: f32 = 0.0134277345;
72const POINT_SHADOW_SCALE: f32 = 0.003;
73const POINT_SHADOW_TEMPORAL_OFFSET_SCALE: f32 = 0.5;
75// These are the standard MSAA sample point positions from D3D. They were chosen
76// to get a reasonable distribution that's not too regular.
77//
78// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels?redirectedfrom=MSDN
79const D3D_SAMPLE_POINT_POSITIONS: array<vec2<f32>, 8> = array(
80 vec2( 0.125, -0.375),
81 vec2(-0.125, 0.375),
82 vec2( 0.625, 0.125),
83 vec2(-0.375, -0.625),
84 vec2(-0.625, 0.625),
85 vec2(-0.875, -0.125),
86 vec2( 0.375, 0.875),
87 vec2( 0.875, -0.875),
88);
90// And these are the coefficients corresponding to the probability distribution
91// function of a 2D Gaussian lobe with zero mean and the identity covariance
92// matrix at those points.
93const D3D_SAMPLE_POINT_COEFFS: array<f32, 8> = array(
94 0.157112,
95 0.157112,
96 0.138651,
97 0.130251,
98 0.114946,
99 0.114946,
100 0.107982,
101 0.079001,
102);
104// https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1
105fn sample_shadow_map_castano_thirteen(light_local: vec2<f32>, depth: f32, array_index: i32) -> f32 {
106 let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
107 let inv_shadow_map_size = 1.0 / shadow_map_size;
109 let uv = light_local * shadow_map_size;
110 var base_uv = floor(uv + 0.5);
111 let s = (uv.x + 0.5 - base_uv.x);
112 let t = (uv.y + 0.5 - base_uv.y);
113 base_uv -= 0.5;
114 base_uv *= inv_shadow_map_size;
116 let uw0 = (4.0 - 3.0 * s);
117 let uw1 = 7.0;
118 let uw2 = (1.0 + 3.0 * s);
120 let u0 = (3.0 - 2.0 * s) / uw0 - 2.0;
121 let u1 = (3.0 + s) / uw1;
122 let u2 = s / uw2 + 2.0;
124 let vw0 = (4.0 - 3.0 * t);
125 let vw1 = 7.0;
126 let vw2 = (1.0 + 3.0 * t);
128 let v0 = (3.0 - 2.0 * t) / vw0 - 2.0;
129 let v1 = (3.0 + t) / vw1;
130 let v2 = t / vw2 + 2.0;
132 var sum = 0.0;
134 sum += uw0 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u0, v0) * inv_shadow_map_size), depth, array_index);
135 sum += uw1 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u1, v0) * inv_shadow_map_size), depth, array_index);
136 sum += uw2 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u2, v0) * inv_shadow_map_size), depth, array_index);
138 sum += uw0 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u0, v1) * inv_shadow_map_size), depth, array_index);
139 sum += uw1 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u1, v1) * inv_shadow_map_size), depth, array_index);
140 sum += uw2 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u2, v1) * inv_shadow_map_size), depth, array_index);
142 sum += uw0 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u0, v2) * inv_shadow_map_size), depth, array_index);
143 sum += uw1 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u1, v2) * inv_shadow_map_size), depth, array_index);
144 sum += uw2 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u2, v2) * inv_shadow_map_size), depth, array_index);
146 return sum * (1.0 / 144.0);
149fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 {
150 return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
153// Creates a random rotation matrix using interleaved gradient noise.
154//
155// See: https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
156fn random_rotation_matrix(scale: vec2<f32>, temporal: bool) -> mat2x2<f32> {
157 let random_angle = 2.0 * PI * interleaved_gradient_noise(
158 scale, select(1u, view_bindings::globals.frame_count, temporal));
159 let m = vec2(sin(random_angle), cos(random_angle));
160 return mat2x2(
161 m.y, -m.x,
162 m.x, m.y
163 );
166// Calculates the distance between spiral samples for the given texel size and
167// penumbra size. This is used for the Jimenez '14 (i.e. temporal) variant of
168// shadow sampling.
169fn calculate_uv_offset_scale_jimenez_fourteen(texel_size: f32, blur_size: f32) -> vec2<f32> {
170 let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
172 // Empirically chosen fudge factor to make PCF look better across different CSM cascades
173 let f = map(0.00390625, 0.022949219, 0.015, 0.035, texel_size);
174 return f * blur_size / (texel_size * shadow_map_size);
177fn sample_shadow_map_jimenez_fourteen(
178 light_local: vec2<f32>,
179 depth: f32,
180 array_index: i32,
181 texel_size: f32,
182 blur_size: f32,
183 temporal: bool,
184) -> f32 {
185 let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
186 let rotation_matrix = random_rotation_matrix(light_local * shadow_map_size, temporal);
187 let uv_offset_scale = calculate_uv_offset_scale_jimenez_fourteen(texel_size, blur_size);
189 // https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
190 let sample_offset0 = (rotation_matrix * utils::SPIRAL_OFFSET_0_) * uv_offset_scale;
191 let sample_offset1 = (rotation_matrix * utils::SPIRAL_OFFSET_1_) * uv_offset_scale;
192 let sample_offset2 = (rotation_matrix * utils::SPIRAL_OFFSET_2_) * uv_offset_scale;
193 let sample_offset3 = (rotation_matrix * utils::SPIRAL_OFFSET_3_) * uv_offset_scale;
194 let sample_offset4 = (rotation_matrix * utils::SPIRAL_OFFSET_4_) * uv_offset_scale;
195 let sample_offset5 = (rotation_matrix * utils::SPIRAL_OFFSET_5_) * uv_offset_scale;
196 let sample_offset6 = (rotation_matrix * utils::SPIRAL_OFFSET_6_) * uv_offset_scale;
197 let sample_offset7 = (rotation_matrix * utils::SPIRAL_OFFSET_7_) * uv_offset_scale;
199 var sum = 0.0;
200 sum += sample_shadow_map_hardware(light_local + sample_offset0, depth, array_index);
201 sum += sample_shadow_map_hardware(light_local + sample_offset1, depth, array_index);
202 sum += sample_shadow_map_hardware(light_local + sample_offset2, depth, array_index);
203 sum += sample_shadow_map_hardware(light_local + sample_offset3, depth, array_index);
204 sum += sample_shadow_map_hardware(light_local + sample_offset4, depth, array_index);
205 sum += sample_shadow_map_hardware(light_local + sample_offset5, depth, array_index);
206 sum += sample_shadow_map_hardware(light_local + sample_offset6, depth, array_index);
207 sum += sample_shadow_map_hardware(light_local + sample_offset7, depth, array_index);
208 return sum / 8.0;
211// Performs the blocker search portion of percentage-closer soft shadows (PCSS).
212// This is the variation used for directional lights.
213//
214// We can't use Castano '13 here because that has a hard-wired fixed size, while
215// the PCSS algorithm requires a search size that varies based on the size of
216// the light. So we instead use the D3D sample point positions, spaced according
217// to the search size, to provide a sample pattern in a similar manner to the
218// cubemap sampling approach we use for PCF.
219//
220// `search_size` is the size of the search region in texels.
221fn search_for_blockers_in_shadow_map(
222 light_local: vec2<f32>,
223 depth: f32,
224 array_index: i32,
225 texel_size: f32,
226 search_size: f32,
227) -> f32 {
228 let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
229 let uv_offset_scale = search_size / (texel_size * shadow_map_size);
231 let offset0 = D3D_SAMPLE_POINT_POSITIONS[0] * uv_offset_scale;
232 let offset1 = D3D_SAMPLE_POINT_POSITIONS[1] * uv_offset_scale;
233 let offset2 = D3D_SAMPLE_POINT_POSITIONS[2] * uv_offset_scale;
234 let offset3 = D3D_SAMPLE_POINT_POSITIONS[3] * uv_offset_scale;
235 let offset4 = D3D_SAMPLE_POINT_POSITIONS[4] * uv_offset_scale;
236 let offset5 = D3D_SAMPLE_POINT_POSITIONS[5] * uv_offset_scale;
237 let offset6 = D3D_SAMPLE_POINT_POSITIONS[6] * uv_offset_scale;
238 let offset7 = D3D_SAMPLE_POINT_POSITIONS[7] * uv_offset_scale;
240 var sum = vec2(0.0);
241 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset0, depth, array_index);
242 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset1, depth, array_index);
243 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset2, depth, array_index);
244 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset3, depth, array_index);
245 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset4, depth, array_index);
246 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset5, depth, array_index);
247 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset6, depth, array_index);
248 sum += search_for_blockers_in_shadow_map_hardware(light_local + offset7, depth, array_index);
250 if (sum.y == 0.0) {
251 return 0.0;
252 }
253 return sum.x / sum.y;
256fn sample_shadow_map(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
257#ifdef SHADOW_FILTER_METHOD_GAUSSIAN
258 return sample_shadow_map_castano_thirteen(light_local, depth, array_index);
259#else ifdef SHADOW_FILTER_METHOD_TEMPORAL
260 return sample_shadow_map_jimenez_fourteen(
261 light_local, depth, array_index, texel_size, 1.0, true);
262#else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2
263 return sample_shadow_map_hardware(light_local, depth, array_index);
264#else
265 // This needs a default return value to avoid shader compilation errors if it's compiled with no SHADOW_FILTER_METHOD_* defined.
266 // (eg. if the normal prepass is enabled it ends up compiling this due to the normal prepass depending on pbr_functions, which depends on shadows)
267 // This should never actually get used, as anyone using bevy's lighting/shadows should always have a SHADOW_FILTER_METHOD defined.
268 // Set to 0 to make it obvious that something is wrong.
269 return 0.0;
270#endif
273// Samples the shadow map for a directional light when percentage-closer soft
274// shadows are being used.
275//
276// We first search for a *blocker*, which is the average depth value of any
277// shadow map samples that are adjacent to the sample we're considering. That
278// allows us to determine the penumbra size; a larger gap between the blocker
279// and the depth of this sample results in a wider penumbra. Finally, we sample
280// the shadow map the same way we do in PCF, using that penumbra width.
281//
282// A good overview of the technique:
283// <https://medium.com/@varunm100/soft-shadows-for-mobile-ar-9e8da2e6f4ba>
284fn sample_shadow_map_pcss(
285 light_local: vec2<f32>,
286 depth: f32,
287 array_index: i32,
288 texel_size: f32,
289 light_size: f32,
290) -> f32 {
291 // Determine the average Z value of the closest blocker.
292 let z_blocker = search_for_blockers_in_shadow_map(
293 light_local, depth, array_index, texel_size, light_size);
295 // Don't let the blur size go below 0.5, or shadows will look unacceptably aliased.
296 let blur_size = max((z_blocker - depth) * light_size / depth, 0.5);
298 // FIXME: We can't use Castano '13 here because that has a hard-wired fixed
299 // size. So we instead use Jimenez '14 unconditionally. In the non-temporal
300 // variant this is unfortunately rather noisy. This may be improvable in the
301 // future by generating a mip chain of the shadow map and using that to
302 // provide better blurs.
303#ifdef SHADOW_FILTER_METHOD_TEMPORAL
304 return sample_shadow_map_jimenez_fourteen(
305 light_local, depth, array_index, texel_size, blur_size, true);
306#else // SHADOW_FILTER_METHOD_TEMPORAL
307 return sample_shadow_map_jimenez_fourteen(
308 light_local, depth, array_index, texel_size, blur_size, false);
309#endif // SHADOW_FILTER_METHOD_TEMPORAL
312// NOTE: Due to the non-uniform control flow in `shadows::fetch_point_shadow`,
313// we must use the Level variant of textureSampleCompare to avoid undefined
314// behavior due to some of the fragments in a quad (2x2 fragments) being
315// processed not being sampled, and this messing with mip-mapping functionality.
316// The shadow maps have no mipmaps so Level just samples from LOD 0.
317fn sample_shadow_cubemap_hardware(light_local: vec3<f32>, depth: f32, light_id: u32) -> f32 {
318#ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT
319 return textureSampleCompare(
320 view_bindings::point_shadow_textures,
321 view_bindings::point_shadow_textures_comparison_sampler,
322 light_local,
323 depth
324 );
325#else
326 return textureSampleCompareLevel(
327 view_bindings::point_shadow_textures,
328 view_bindings::point_shadow_textures_comparison_sampler,
329 light_local,
330 i32(light_id),
331 depth
332 );
333#endif
336// Performs one sample of the blocker search. This variation of the blocker
337// search function is for point and spot lights.
338fn search_for_blockers_in_shadow_cubemap_hardware(
339 light_local: vec3<f32>,
340 depth: f32,
341 light_id: u32,
342) -> vec2<f32> {
343#ifdef WEBGL2
344 // Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled
345 // with different samplers, or it'll blow up.
346 return vec2(0.0);
347#else // WEBGL2
349#ifdef PCSS_SAMPLERS_AVAILABLE
351#ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT
352 let sampled_depth = textureSample(
353 view_bindings::point_shadow_textures,
354 view_bindings::point_shadow_textures_linear_sampler,
355 light_local,
356 );
357#else
358 let sampled_depth = textureSample(
359 view_bindings::point_shadow_textures,
360 view_bindings::point_shadow_textures_linear_sampler,
361 light_local,
362 i32(light_id),
363 );
364#endif
366 return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth);
368#else // PCSS_SAMPLERS_AVAILABLE
369 return vec2(0.0);
370#endif // PCSS_SAMPLERS_AVAILABLE
372#endif // WEBGL2
375fn sample_shadow_cubemap_at_offset(
376 position: vec2<f32>,
377 coeff: f32,
378 x_basis: vec3<f32>,
379 y_basis: vec3<f32>,
380 light_local: vec3<f32>,
381 depth: f32,
382 light_id: u32,
383) -> f32 {
384 return sample_shadow_cubemap_hardware(
385 light_local + position.x * x_basis + position.y * y_basis,
386 depth,
387 light_id
388 ) * coeff;
391// Computes the search position and performs one sample of the blocker search.
392// This variation of the blocker search function is for point and spot lights.
393//
394// `x_basis`, `y_basis`, and `light_local` form an orthonormal basis over which
395// the blocker search happens.
396fn search_for_blockers_in_shadow_cubemap_at_offset(
397 position: vec2<f32>,
398 x_basis: vec3<f32>,
399 y_basis: vec3<f32>,
400 light_local: vec3<f32>,
401 depth: f32,
402 light_id: u32,
403) -> vec2<f32> {
404 return search_for_blockers_in_shadow_cubemap_hardware(
405 light_local + position.x * x_basis + position.y * y_basis,
406 depth,
407 light_id
408 );
411// This more or less does what Castano13 does, but in 3D space. Castano13 is
412// essentially an optimized 2D Gaussian filter that takes advantage of the
413// bilinear filtering hardware to reduce the number of samples needed. This
414// trick doesn't apply to cubemaps, so we manually apply a Gaussian filter over
415// the standard 8xMSAA pattern instead.
416fn sample_shadow_cubemap_gaussian(
417 light_local: vec3<f32>,
418 depth: f32,
419 scale: f32,
420 distance_to_light: f32,
421 light_id: u32,
422) -> f32 {
423 // Create an orthonormal basis so we can apply a 2D sampling pattern to a
424 // cubemap.
425 var up = vec3(0.0, 1.0, 0.0);
426 if (dot(up, normalize(light_local)) > 0.99) {
427 up = vec3(1.0, 0.0, 0.0); // Avoid creating a degenerate basis.
428 }
429 let basis = orthonormalize(light_local, up) * scale * distance_to_light;
431 var sum: f32 = 0.0;
432 sum += sample_shadow_cubemap_at_offset(
433 D3D_SAMPLE_POINT_POSITIONS[0], D3D_SAMPLE_POINT_COEFFS[0],
434 basis[0], basis[1], light_local, depth, light_id);
435 sum += sample_shadow_cubemap_at_offset(
436 D3D_SAMPLE_POINT_POSITIONS[1], D3D_SAMPLE_POINT_COEFFS[1],
437 basis[0], basis[1], light_local, depth, light_id);
438 sum += sample_shadow_cubemap_at_offset(
439 D3D_SAMPLE_POINT_POSITIONS[2], D3D_SAMPLE_POINT_COEFFS[2],
440 basis[0], basis[1], light_local, depth, light_id);
441 sum += sample_shadow_cubemap_at_offset(
442 D3D_SAMPLE_POINT_POSITIONS[3], D3D_SAMPLE_POINT_COEFFS[3],
443 basis[0], basis[1], light_local, depth, light_id);
444 sum += sample_shadow_cubemap_at_offset(
445 D3D_SAMPLE_POINT_POSITIONS[4], D3D_SAMPLE_POINT_COEFFS[4],
446 basis[0], basis[1], light_local, depth, light_id);
447 sum += sample_shadow_cubemap_at_offset(
448 D3D_SAMPLE_POINT_POSITIONS[5], D3D_SAMPLE_POINT_COEFFS[5],
449 basis[0], basis[1], light_local, depth, light_id);
450 sum += sample_shadow_cubemap_at_offset(
451 D3D_SAMPLE_POINT_POSITIONS[6], D3D_SAMPLE_POINT_COEFFS[6],
452 basis[0], basis[1], light_local, depth, light_id);
453 sum += sample_shadow_cubemap_at_offset(
454 D3D_SAMPLE_POINT_POSITIONS[7], D3D_SAMPLE_POINT_COEFFS[7],
455 basis[0], basis[1], light_local, depth, light_id);
456 return sum;
459// This is a port of the Jimenez14 filter above to the 3D space. It jitters the
460// points in the spiral pattern after first creating a 2D orthonormal basis
461// along the principal light direction.
462fn sample_shadow_cubemap_jittered(
463 light_local: vec3<f32>,
464 depth: f32,
465 scale: f32,
466 distance_to_light: f32,
467 light_id: u32,
468 temporal: bool,
469) -> f32 {
470 // Create an orthonormal basis so we can apply a 2D sampling pattern to a
471 // cubemap.
472 var up = vec3(0.0, 1.0, 0.0);
473 if (dot(up, normalize(light_local)) > 0.99) {
474 up = vec3(1.0, 0.0, 0.0); // Avoid creating a degenerate basis.
475 }
476 let basis = orthonormalize(light_local, up) * scale * distance_to_light;
478 let rotation_matrix = random_rotation_matrix(vec2(1.0), temporal);
480 let sample_offset0 = rotation_matrix * utils::SPIRAL_OFFSET_0_ *
481 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
482 let sample_offset1 = rotation_matrix * utils::SPIRAL_OFFSET_1_ *
483 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
484 let sample_offset2 = rotation_matrix * utils::SPIRAL_OFFSET_2_ *
485 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
486 let sample_offset3 = rotation_matrix * utils::SPIRAL_OFFSET_3_ *
487 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
488 let sample_offset4 = rotation_matrix * utils::SPIRAL_OFFSET_4_ *
489 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
490 let sample_offset5 = rotation_matrix * utils::SPIRAL_OFFSET_5_ *
491 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
492 let sample_offset6 = rotation_matrix * utils::SPIRAL_OFFSET_6_ *
493 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
494 let sample_offset7 = rotation_matrix * utils::SPIRAL_OFFSET_7_ *
495 POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
497 var sum: f32 = 0.0;
498 sum += sample_shadow_cubemap_at_offset(
499 sample_offset0, 0.125, basis[0], basis[1], light_local, depth, light_id);
500 sum += sample_shadow_cubemap_at_offset(
501 sample_offset1, 0.125, basis[0], basis[1], light_local, depth, light_id);
502 sum += sample_shadow_cubemap_at_offset(
503 sample_offset2, 0.125, basis[0], basis[1], light_local, depth, light_id);
504 sum += sample_shadow_cubemap_at_offset(
505 sample_offset3, 0.125, basis[0], basis[1], light_local, depth, light_id);
506 sum += sample_shadow_cubemap_at_offset(
507 sample_offset4, 0.125, basis[0], basis[1], light_local, depth, light_id);
508 sum += sample_shadow_cubemap_at_offset(
509 sample_offset5, 0.125, basis[0], basis[1], light_local, depth, light_id);
510 sum += sample_shadow_cubemap_at_offset(
511 sample_offset6, 0.125, basis[0], basis[1], light_local, depth, light_id);
512 sum += sample_shadow_cubemap_at_offset(
513 sample_offset7, 0.125, basis[0], basis[1], light_local, depth, light_id);
514 return sum;
517fn sample_shadow_cubemap(
518 light_local: vec3<f32>,
519 distance_to_light: f32,
520 depth: f32,
521 light_id: u32,
522) -> f32 {
523#ifdef SHADOW_FILTER_METHOD_GAUSSIAN
524 return sample_shadow_cubemap_gaussian(
525 light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id);
526#else ifdef SHADOW_FILTER_METHOD_TEMPORAL
527 return sample_shadow_cubemap_jittered(
528 light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id, true);
529#else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2
530 return sample_shadow_cubemap_hardware(light_local, depth, light_id);
531#else
532 // This needs a default return value to avoid shader compilation errors if it's compiled with no SHADOW_FILTER_METHOD_* defined.
533 // (eg. if the normal prepass is enabled it ends up compiling this due to the normal prepass depending on pbr_functions, which depends on shadows)
534 // This should never actually get used, as anyone using bevy's lighting/shadows should always have a SHADOW_FILTER_METHOD defined.
535 // Set to 0 to make it obvious that something is wrong.
536 return 0.0;
537#endif
540// Searches for PCSS blockers in a cubemap. This is the variant of the blocker
541// search used for point and spot lights.
542//
543// This follows the logic in `sample_shadow_cubemap_gaussian`, but uses linear
544// sampling instead of percentage-closer filtering.
545//
546// The `scale` parameter represents the size of the light.
547fn search_for_blockers_in_shadow_cubemap(
548 light_local: vec3<f32>,
549 depth: f32,
550 scale: f32,
551 distance_to_light: f32,
552 light_id: u32,
553) -> f32 {
554 // Create an orthonormal basis so we can apply a 2D sampling pattern to a
555 // cubemap.
556 var up = vec3(0.0, 1.0, 0.0);
557 if (dot(up, normalize(light_local)) > 0.99) {
558 up = vec3(1.0, 0.0, 0.0); // Avoid creating a degenerate basis.
559 }
560 let basis = orthonormalize(light_local, up) * scale * distance_to_light;
562 var sum: vec2<f32> = vec2(0.0);
563 sum += search_for_blockers_in_shadow_cubemap_at_offset(
564 D3D_SAMPLE_POINT_POSITIONS[0], basis[0], basis[1], light_local, depth, light_id);
565 sum += search_for_blockers_in_shadow_cubemap_at_offset(
566 D3D_SAMPLE_POINT_POSITIONS[1], basis[0], basis[1], light_local, depth, light_id);
567 sum += search_for_blockers_in_shadow_cubemap_at_offset(
568 D3D_SAMPLE_POINT_POSITIONS[2], basis[0], basis[1], light_local, depth, light_id);
569 sum += search_for_blockers_in_shadow_cubemap_at_offset(
570 D3D_SAMPLE_POINT_POSITIONS[3], basis[0], basis[1], light_local, depth, light_id);
571 sum += search_for_blockers_in_shadow_cubemap_at_offset(
572 D3D_SAMPLE_POINT_POSITIONS[4], basis[0], basis[1], light_local, depth, light_id);
573 sum += search_for_blockers_in_shadow_cubemap_at_offset(
574 D3D_SAMPLE_POINT_POSITIONS[5], basis[0], basis[1], light_local, depth, light_id);
575 sum += search_for_blockers_in_shadow_cubemap_at_offset(
576 D3D_SAMPLE_POINT_POSITIONS[6], basis[0], basis[1], light_local, depth, light_id);
577 sum += search_for_blockers_in_shadow_cubemap_at_offset(
578 D3D_SAMPLE_POINT_POSITIONS[7], basis[0], basis[1], light_local, depth, light_id);
580 if (sum.y == 0.0) {
581 return 0.0;
582 }
583 return sum.x / sum.y;
586// Samples the shadow map for a point or spot light when percentage-closer soft
587// shadows are being used.
588//
589// A good overview of the technique:
590// <https://medium.com/@varunm100/soft-shadows-for-mobile-ar-9e8da2e6f4ba>
591fn sample_shadow_cubemap_pcss(
592 light_local: vec3<f32>,
593 distance_to_light: f32,
594 depth: f32,
595 light_id: u32,
596 light_size: f32,
597) -> f32 {
598 let z_blocker = search_for_blockers_in_shadow_cubemap(
599 light_local, depth, light_size, distance_to_light, light_id);
601 // Don't let the blur size go below 0.5, or shadows will look unacceptably aliased.
602 let blur_size = max((z_blocker - depth) * light_size / depth, 0.5);
604#ifdef SHADOW_FILTER_METHOD_TEMPORAL
605 return sample_shadow_cubemap_jittered(
606 light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, true);
607#else
608 return sample_shadow_cubemap_jittered(
609 light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, false);
610#endif
Contents