Files
marathon/crates/libmarathon/src/render/pbr/light_probe/light_probe.wgsl

155 lines
6.0 KiB
WebGPU Shading Language
Raw Normal View History

#define_import_path bevy_pbr::light_probe
#import bevy_pbr::clustered_forward
#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges
#import bevy_pbr::mesh_view_bindings::light_probes
#import bevy_pbr::mesh_view_types::LightProbe
// The result of searching for a light probe.
struct LightProbeQueryResult {
// The index of the light probe texture or textures in the binding array or
// arrays.
texture_index: i32,
// A scale factor that's applied to the diffuse and specular light from the
// light probe. This is in units of cd/m² (candela per square meter).
intensity: f32,
// Transform from world space to the light probe model space. In light probe
// model space, the light probe is a 1×1×1 cube centered on the origin.
light_from_world: mat4x4<f32>,
// Whether this light probe contributes diffuse light to lightmapped meshes.
affects_lightmapped_mesh_diffuse: bool,
};
fn transpose_affine_matrix(matrix: mat3x4<f32>) -> mat4x4<f32> {
let matrix4x4 = mat4x4<f32>(
matrix[0],
matrix[1],
matrix[2],
vec4<f32>(0.0, 0.0, 0.0, 1.0));
return transpose(matrix4x4);
}
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
// Searches for a light probe that contains the fragment.
//
// This is the version that's used when storage buffers are available and
// light probes are clustered.
//
// TODO: Interpolate between multiple light probes.
fn query_light_probe(
world_position: vec3<f32>,
is_irradiance_volume: bool,
clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
) -> LightProbeQueryResult {
var result: LightProbeQueryResult;
result.texture_index = -1;
// Reflection probe indices are followed by irradiance volume indices in the
// cluster index list. Use this fact to create our bracketing range of
// indices.
var start_offset: u32;
var end_offset: u32;
if is_irradiance_volume {
start_offset = (*clusterable_object_index_ranges).first_irradiance_volume_index_offset;
end_offset = (*clusterable_object_index_ranges).first_decal_offset;
} else {
start_offset = (*clusterable_object_index_ranges).first_reflection_probe_index_offset;
end_offset = (*clusterable_object_index_ranges).first_irradiance_volume_index_offset;
}
for (var light_probe_index_offset: u32 = start_offset;
light_probe_index_offset < end_offset && result.texture_index < 0;
light_probe_index_offset += 1u) {
let light_probe_index = i32(clustered_forward::get_clusterable_object_id(
light_probe_index_offset));
var light_probe: LightProbe;
if is_irradiance_volume {
light_probe = light_probes.irradiance_volumes[light_probe_index];
} else {
light_probe = light_probes.reflection_probes[light_probe_index];
}
// Unpack the inverse transform.
let light_from_world =
transpose_affine_matrix(light_probe.light_from_world_transposed);
// Check to see if the transformed point is inside the unit cube
// centered at the origin.
let probe_space_pos = (light_from_world * vec4<f32>(world_position, 1.0f)).xyz;
if (all(abs(probe_space_pos) <= vec3(0.5f))) {
result.texture_index = light_probe.cubemap_index;
result.intensity = light_probe.intensity;
result.light_from_world = light_from_world;
result.affects_lightmapped_mesh_diffuse =
light_probe.affects_lightmapped_mesh_diffuse != 0u;
break;
}
}
return result;
}
#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
// Searches for a light probe that contains the fragment.
//
// This is the version that's used when storage buffers aren't available and
// light probes aren't clustered. It simply does a brute force search of all
// light probes. Because platforms without sufficient SSBO bindings typically
// lack bindless shaders, there will usually only be one of each type of light
// probe present anyway.
fn query_light_probe(
world_position: vec3<f32>,
is_irradiance_volume: bool,
clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>,
) -> LightProbeQueryResult {
var result: LightProbeQueryResult;
result.texture_index = -1;
var light_probe_count: i32;
if is_irradiance_volume {
light_probe_count = light_probes.irradiance_volume_count;
} else {
light_probe_count = light_probes.reflection_probe_count;
}
for (var light_probe_index: i32 = 0;
light_probe_index < light_probe_count && result.texture_index < 0;
light_probe_index += 1) {
var light_probe: LightProbe;
if is_irradiance_volume {
light_probe = light_probes.irradiance_volumes[light_probe_index];
} else {
light_probe = light_probes.reflection_probes[light_probe_index];
}
// Unpack the inverse transform.
let light_from_world =
transpose_affine_matrix(light_probe.light_from_world_transposed);
// Check to see if the transformed point is inside the unit cube
// centered at the origin.
let probe_space_pos = (light_from_world * vec4<f32>(world_position, 1.0f)).xyz;
if (all(abs(probe_space_pos) <= vec3(0.5f))) {
result.texture_index = light_probe.cubemap_index;
result.intensity = light_probe.intensity;
result.light_from_world = light_from_world;
result.affects_lightmapped_mesh_diffuse =
light_probe.affects_lightmapped_mesh_diffuse != 0u;
// TODO: Workaround for ICE in DXC https://github.com/microsoft/DirectXShaderCompiler/issues/6183
// We can't use `break` here because of the ICE.
// So instead we rely on the fact that we set `result.texture_index`
// above and check its value in the `for` loop header before
// looping.
// break;
}
}
return result;
}
#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3