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::light_probe
3#import bevy_pbr::clustered_forward
4#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges
5#import bevy_pbr::mesh_view_bindings::light_probes
6#import bevy_pbr::mesh_view_types::{
7 LightProbe, LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE,
8 LIGHT_PROBE_FLAG_PARALLAX_CORRECT
9}
11// The result of searching for a light probe.
12//
13// Light probe iterators yield values of this type. Note that multiple light
14// probes can affect a single fragment.
15struct LightProbeQueryResult {
16 // The index of the light probe texture or textures in the binding array or
17 // arrays.
18 texture_index: i32,
19 // A scale factor that's applied to the diffuse and specular light from the
20 // light probe. This is in units of cd/m² (candela per square meter).
21 intensity: f32,
22 // Transform from world space to the light probe model space. In light probe
23 // model space, the light probe is a 1×1×1 cube centered on the origin.
24 light_from_world: mat4x4<f32>,
25 // The boundaries of the simulated space used for parallax correction,
26 // specified as *half* extents in light probe space.
27 parallax_correction_bounds: vec3<f32>,
28 // The weight of this light probe, determined by the position of the
29 // fragment within the falloff range. The sum of the weights of all light
30 // probes affecting a fragment need not be 1.
31 weight: f32,
32 // The flags that the light probe has: a combination of
33 // `LIGHT_PROBE_FLAG_*`.
34 flags: u32,
35};
37fn transpose_affine_matrix(matrix: mat3x4<f32>) -> mat4x4<f32> {
38 let matrix4x4 = mat4x4<f32>(
39 matrix[0],
40 matrix[1],
41 matrix[2],
42 vec4<f32>(0.0, 0.0, 0.0, 1.0));
43 return transpose(matrix4x4);
46#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
48// A type that allows iterating through the list of light probes that overlap
49// the current fragment.
50//
51// This is the version used when light probes are clustered.
52struct LightProbeIterator {
53 // The current offset in the light probes list.
54 current_offset: u32,
55 // The last offset in the light probes list.
56 end_offset: u32,
57 // The world-space position of the current fragment.
58 world_position: vec3<f32>,
59 // True if we're searching for an irradiance volume; false if we're
60 // searching for a reflection probe.
61 is_irradiance_volume: bool,
64// Creates a new light probe iterator ready to iterate through light probes in
65// the froxel containing the `world_position`.
66fn light_probe_iterator_new(
67 world_position: vec3<f32>,
68 is_irradiance_volume: bool,
69 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
70) -> LightProbeIterator {
71 // Reflection probe indices are followed by irradiance volume indices in the
72 // cluster index list. Use this fact to create our bracketing range of
73 // indices.
75 if is_irradiance_volume {
76 return LightProbeIterator(
77 (*clusterable_object_index_ranges).first_irradiance_volume_index_offset,
78 (*clusterable_object_index_ranges).first_decal_offset,
79 world_position,
80 true
81 );
82 }
84 return LightProbeIterator(
85 (*clusterable_object_index_ranges).first_reflection_probe_index_offset,
86 (*clusterable_object_index_ranges).first_irradiance_volume_index_offset,
87 world_position,
88 false
89 );
92// Searches for a light probe that contains the fragment and returns the next
93// such probe.
94//
95// Note that multiple light probes can affect a fragment. The caller is
96// generally expected to blend their influences together in a weighted sum.
97fn light_probe_iterator_next(iterator: ptr<function, LightProbeIterator>) -> LightProbeQueryResult {
98 let world_position = (*iterator).world_position;
100 var result: LightProbeQueryResult;
101 result.texture_index = -1;
102 result.weight = 0.0;
104 while ((*iterator).current_offset < (*iterator).end_offset) {
105 let light_probe_index = i32(clustered_forward::get_clusterable_object_id(
106 (*iterator).current_offset));
107 (*iterator).current_offset += 1u;
109 // FIXME: This happens when one or more images for the light probe
110 // aren't loaded yet. Really, though, we shouldn't be clustering such
111 // objects at all.
112 if (light_probe_index < 0) {
113 continue;
114 }
116 var light_probe: LightProbe;
117 if (*iterator).is_irradiance_volume {
118 light_probe = light_probes.irradiance_volumes[light_probe_index];
119 } else {
120 light_probe = light_probes.reflection_probes[light_probe_index];
121 }
123 // Unpack the inverse transform.
124 let light_from_world =
125 transpose_affine_matrix(light_probe.light_from_world_transposed);
127 // Transform the point into local space, with the cube edges at ±0.5 on
128 // each axis.
129 let probe_space_pos = (light_from_world * vec4<f32>(world_position, 1.0f)).xyz;
130 // Avoid division by zero.
131 let falloff = max(light_probe.falloff, vec3(0.0001));
132 // Calculate the per-axis weight by doing a linear ramp from 0.0 at the
133 // inside of the falloff region to 1.0 at the outside of the falloff
134 // region.
135 let axis_weights = saturate((1.0 - 2.0 * abs(probe_space_pos)) / (2.0 * falloff));
136 // The actual weight is the minimum of all the per-axis weights.
137 let weight = min(min(axis_weights.x, axis_weights.y), axis_weights.z);
138 // If the resulting weight is zero, we're outside the light probe
139 // entirely. Bail.
140 if (weight == 0.0) {
141 continue;
142 }
144 result.texture_index = light_probe.cubemap_index;
145 result.intensity = light_probe.intensity;
146 result.light_from_world = light_from_world;
147 result.parallax_correction_bounds = light_probe.parallax_correction_bounds;
148 result.flags = light_probe.flags;
149 result.weight = weight;
150 return result;
151 }
153 return result;
156#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
158// A type that allows iterating through the list of light probes that overlap
159// the current fragment.
160//
161// This is the version that's used when sufficient storage buffers aren't
162// available and consequently when light probes aren't clustered. It simply does
163// a brute force search of all light probes. Because platforms without
164// sufficient SSBO bindings typically lack bindless shaders, there will usually
165// only be one of each type of light probe present anyway.
166struct LightProbeIterator {
167 // The current index in the list of light probes for this cluster.
168 current_index: u32,
169 // The last index in the list.
170 end_index: u32,
171 // The position of the current fragment.
172 world_position: vec3<f32>,
173 // True if we're searching for irradiance volumes; false if we're searching
174 // for reflection probes.
175 is_irradiance_volume: bool,
178// Creates a new light probe iterator ready to search through light probes.
179fn light_probe_iterator_new(
180 world_position: vec3<f32>,
181 is_irradiance_volume: bool,
182 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
183) -> LightProbeIterator {
184 return LightProbeIterator(
185 0,
186 select(
187 light_probes.reflection_probe_count,
188 light_probes.irradiance_volume_count,
189 is_irradiance_volume
190 ),
191 world_position,
192 is_irradiance_volume
193 );
196// Searches for a light probe that contains the fragment and returns the next
197// such probe.
198//
199// Note that, theoretically, multiple light probes can affect a fragment, and
200// the caller is generally expected to blend their influences together in a
201// weighted sum. In practice, this version of `light_probe_iterator_next` is
202// used on platforms that lack bindless shaders, so there will only be at most
203// one light probe that affects the current fragment in the first place.
204fn light_probe_iterator_next(iterator: ptr<function, LightProbeIterator>) -> LightProbeQueryResult {
205 var result: LightProbeQueryResult;
206 result.texture_index = -1;
207 result.weight = 0.0;
209 while (true) {
210 let light_probe_index = (*iterator).current_index;
212 var light_probe: LightProbe;
213 if is_irradiance_volume {
214 light_probe = light_probes.irradiance_volumes[light_probe_index];
215 } else {
216 light_probe = light_probes.reflection_probes[light_probe_index];
217 }
219 // Unpack the inverse transform.
220 let light_from_world =
221 transpose_affine_matrix(light_probe.light_from_world_transposed);
223 // Transform the point into local space, with the cube edges at ±0.5 on
224 // each axis.
225 let probe_space_pos = (light_from_world * vec4<f32>(world_position, 1.0f)).xyz;
226 // Avoid division by zero.
227 let falloff = max(light_probe.falloff, vec3(0.0001));
228 // Calculate the per-axis weight by doing a linear ramp from 0.0 at the
229 // inside of the falloff region to 1.0 at the outside of the falloff
230 // region.
231 let axis_weights = saturate((1.0 - 2.0 * abs(probe_space_pos)) / (2.0 * falloff));
232 // The actual weight is the minimum of all the per-axis weights.
233 let weight = min(min(axis_weights.x, axis_weights.y), axis_weights.z);
234 // If the resulting weight is zero, we're outside the light probe
235 // entirely. Bail.
236 if (weight == 0.0) {
237 continue;
238 }
240 result.texture_index = light_probe.cubemap_index;
241 result.intensity = light_probe.intensity;
242 result.light_from_world = light_from_world;
243 result.parallax_correction_bounds = light_probe.parallax_correction_bounds;
244 result.flags = light_probe.flags;
245 result.weight = weight;
246 return result;
247 }
249 return result;
252#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
Contents