1#define_import_path bevy_pbr::light_probe3#import bevy_pbr::clustered_forward4#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges5#import bevy_pbr::mesh_view_bindings::light_probes6#import bevy_pbr::mesh_view_types::{7 LightProbe, LIGHT_PROBE_FLAG_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE,8 LIGHT_PROBE_FLAG_PARALLAX_CORRECT9}11// The result of searching for a light probe.12//13// Light probe iterators yield values of this type. Note that multiple light14// probes can affect a single fragment.15struct LightProbeQueryResult {16 // The index of the light probe texture or textures in the binding array or17 // arrays.18 texture_index: i32,19 // A scale factor that's applied to the diffuse and specular light from the20 // 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 probe23 // 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 the29 // fragment within the falloff range. The sum of the weights of all light30 // probes affecting a fragment need not be 1.31 weight: f32,32 // The flags that the light probe has: a combination of33 // `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);44}46#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 348// A type that allows iterating through the list of light probes that overlap49// 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're60 // searching for a reflection probe.61 is_irradiance_volume: bool,62}64// Creates a new light probe iterator ready to iterate through light probes in65// 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 the72 // cluster index list. Use this fact to create our bracketing range of73 // 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 true81 );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 false89 );90}92// Searches for a light probe that contains the fragment and returns the next93// such probe.94//95// Note that multiple light probes can affect a fragment. The caller is96// 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 probe110 // aren't loaded yet. Really, though, we shouldn't be clustering such111 // 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 on128 // 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 the133 // inside of the falloff region to 1.0 at the outside of the falloff134 // 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 probe139 // 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;154}156#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3158// A type that allows iterating through the list of light probes that overlap159// the current fragment.160//161// This is the version that's used when sufficient storage buffers aren't162// available and consequently when light probes aren't clustered. It simply does163// a brute force search of all light probes. Because platforms without164// sufficient SSBO bindings typically lack bindless shaders, there will usually165// 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 searching174 // for reflection probes.175 is_irradiance_volume: bool,176}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_volume190 ),191 world_position,192 is_irradiance_volume193 );194}196// Searches for a light probe that contains the fragment and returns the next197// such probe.198//199// Note that, theoretically, multiple light probes can affect a fragment, and200// the caller is generally expected to blend their influences together in a201// weighted sum. In practice, this version of `light_probe_iterator_next` is202// used on platforms that lack bindless shaders, so there will only be at most203// 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 on224 // 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 the229 // inside of the falloff region to 1.0 at the outside of the falloff230 // 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 probe235 // 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;250}252#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3