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