Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_pbr/src/cluster/cluster.wgsl
30636 views
#define_import_path bevy_pbr::cluster
#import bevy_pbr::clustered_forward::view_z_to_z_slice

// Valid values for the `object_type` field.
const CLUSTERABLE_OBJECT_TYPE_POINT_LIGHT: u32 = 0u;
const CLUSTERABLE_OBJECT_TYPE_SPOT_LIGHT: u32 = 1u;
const CLUSTERABLE_OBJECT_TYPE_REFLECTION_PROBE: u32 = 2u;
const CLUSTERABLE_OBJECT_TYPE_IRRADIANCE_VOLUME: u32 = 3u;
const CLUSTERABLE_OBJECT_TYPE_DECAL: u32 = 4u;

const NDC_MIN: vec2<f32> = vec2<f32>(-1.0);
const NDC_MAX: vec2<f32> = vec2<f32>(1.0);

// Metadata stored on GPU that's global to all clusters for a view.
//
// See the comments in `bevy_pbr/src/cluster/gpu.rs` for information on the
// fields.
struct ClusterMetadata {
    indirect_draw_params: ClusterRasterIndirectDrawParams,

    clustered_light_count: u32,
    reflection_probe_count: u32,
    irradiance_volume_count: u32,
    decal_count: u32,

    z_slice_list_capacity: u32,
    index_list_size: u32,

    farthest_z: atomic<u32>,
};

// Indirect draw parameters in the format required by the WebGPU specification.
struct ClusterRasterIndirectDrawParams {
    index_count: u32,
    instance_count: atomic<u32>,
    first_index: u32,
    base_vertex: u32,
    first_instance: u32,
}

// The GPU representation of a single Z-slice of a clusterable object.
//
// See the comments in `bevy_pbr/src/cluster/gpu.rs` for information on the
// fields.
struct ClusterableObjectZSlice {
    object_index: u32,
    object_type: u32,
    z_slice: u32,
};

// An axis-aligned bounding box.
struct Aabb {
    // The minimum extents of the box.
    min: vec3<f32>,
    // The maximum extents of the box.
    max: vec3<f32>,
};

// An axis-aligned bounding box using unsigned integer coordinates.
//
// This is used for cluster bounds.
struct AabbU {
    // The minimum extents of the box.
    min: vec3<u32>,
    // The maximum extents of the box, plus one.
    //
    // We add 1 here so that 0-size AABBs can be expressed.
    max: vec3<u32>,
}

// Returns the AABB of an object suitable for conversion into an AABB of
// clusters.
//
// See `bevy_light::cluster::assign::cluster_space_clusterable_object_aabb`.
fn cluster_space_object_aabb(
    position: vec3<f32>,
    radius: f32,
    view_from_world: mat4x4<f32>,
    clip_from_view: mat4x4<f32>,
    view_from_world_scale: vec3<f32>
) -> Aabb {
    let position_view = (view_from_world * vec4(position, 1.0)).xyz;
    let half_extents = radius * abs(view_from_world_scale);

    var view_min = position_view - half_extents;
    var view_max = position_view + half_extents;

    // Constrain view z to be negative - i.e. in front of the camera
    // When view z is >= 0.0 and we're using a perspective projection, bad
    // things happen.  At view z == 0.0, ndc x,y are mathematically undefined.
    // At view z > 0.0, i.e. behind the camera, the perspective projection flips
    // the directions of the axes. This breaks assumptions about use of min/max
    // operations as something that was to the left in view space is now
    // returning a coordinate that for view z in front of the camera would be on
    // the right, but at view z behind the camera is on the left. So, we just
    // constrain view z to be < 0.0 and necessarily in front of the camera.
    view_min.z = min(view_min.z, -0.00001);
    view_max.z = min(view_max.z, -0.00001);

    // Is there a cheaper way to do this? The problem is that because of
    // perspective the point at max z but min xy may be less xy in screenspace,
    // and similar. As such, projecting the min and max xy at both the closer
    // and further z and taking the min and max of those projected points
    // addresses this.
    let view_xymin_near = view_min;
    let view_xymin_far = vec3(view_min.xy, view_max.z);
    let view_xymax_near = vec3(view_max.xy, view_min.z);
    let view_xymax_far = view_max;

    let clip_xymin_near = clip_from_view * vec4(view_xymin_near, 1.0);
    let clip_xymin_far = clip_from_view * vec4(view_xymin_far, 1.0);
    let clip_xymax_near = clip_from_view * vec4(view_xymax_near, 1.0);
    let clip_xymax_far = clip_from_view * vec4(view_xymax_far, 1.0);

    let ndc_xymin_near = clip_xymin_near.xyz / clip_xymin_near.w;
    let ndc_xymin_far = clip_xymin_far.xyz / clip_xymin_far.w;
    let ndc_xymax_near = clip_xymax_near.xyz / clip_xymax_near.w;
    let ndc_xymax_far = clip_xymax_far.xyz / clip_xymax_far.w;

    var ndc_min = min(min(ndc_xymin_near, ndc_xymin_far), min(ndc_xymax_near, ndc_xymax_far));
    var ndc_max = max(max(ndc_xymin_near, ndc_xymin_far), max(ndc_xymax_near, ndc_xymax_far));

    // clamp to ndc coords without depth
    ndc_min = vec3(clamp(ndc_min.xy, NDC_MIN, NDC_MAX), ndc_min.z);
    ndc_max = vec3(clamp(ndc_max.xy, NDC_MIN, NDC_MAX), ndc_max.z);

    // pack unadjusted z depth into the vecs
    return Aabb(vec3(ndc_min.xy, view_min.z), vec3(ndc_max.xy, view_max.z));
}

// Computes the scale of the camera from the view matrix.
fn compute_view_from_world_scale(world_from_view: mat4x4<f32>) -> vec3<f32> {
    let world_from_view_3x3 = mat3x3<f32>(
        world_from_view[0].xyz,
        world_from_view[1].xyz,
        world_from_view[2].xyz
    );
    let det = determinant(world_from_view_3x3);
    let scale = vec3<f32>(
        length(world_from_view_3x3[0]) * sign(det),
        length(world_from_view_3x3[1]),
        length(world_from_view_3x3[2])
    );
    return vec3<f32>(1.0) / scale;
}

// Returns the cluster coordinates corresponding to a position in normalized
// device coordinates.
// See `bevy_light::cluster::assign::ndc_position_to_cluster`.
fn ndc_position_to_cluster(
    cluster_dimensions: vec3<u32>,
    cluster_factors: vec2<f32>,
    is_orthographic: bool,
    ndc_p: vec3<f32>,
    view_z: f32
) -> vec3<u32> {
    let frag_coord = clamp(ndc_p.xy * vec2(0.5, -0.5) + vec2(0.5), vec2(0.0), vec2(1.0));
    let xy = vec2<u32>(floor(frag_coord * vec2<f32>(cluster_dimensions.xy)));
    let z_slice = view_z_to_z_slice(cluster_factors, cluster_dimensions.z, view_z, is_orthographic);
    return clamp(vec3<u32>(xy, z_slice), vec3(0u), cluster_dimensions - vec3(1u));
}

// Returns the AABB encompassing all clusters that intersect a sphere.
fn calculate_sphere_cluster_bounds(
    position: vec3<f32>,
    radius: f32,
    view_from_world: mat4x4<f32>,
    clip_from_view: mat4x4<f32>,
    view_from_world_scale: vec3<f32>,
    cluster_dimensions: vec3<u32>,
    cluster_factors: vec2<f32>,
    is_orthographic: bool,
) -> AabbU {
    let aabb_ndc = cluster_space_object_aabb(
        position,
        radius,
        view_from_world,
        clip_from_view,
        view_from_world_scale
    );

    let temp_min_cluster = ndc_position_to_cluster(
        cluster_dimensions,
        cluster_factors,
        is_orthographic,
        aabb_ndc.min,
        aabb_ndc.min.z
    );
    let temp_max_cluster = ndc_position_to_cluster(
        cluster_dimensions,
        cluster_factors,
        is_orthographic,
        aabb_ndc.max,
        aabb_ndc.max.z
    );

    let min_cluster = min(temp_min_cluster, temp_max_cluster);
    let max_cluster = max(temp_min_cluster, temp_max_cluster);

    return AabbU(min_cluster, max_cluster);
}