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