1#import bevy_pbr::meshlet_bindings::{ 2 meshlet_cluster_meshlet_ids, 3 meshlet_bounding_spheres, 4 meshlet_simplification_errors, 5 meshlet_cluster_instance_ids, 6 meshlet_instance_uniforms, 7 meshlet_second_pass_candidates, 12 cluster_is_second_pass_candidate, 13 meshlet_software_raster_indirect_args, 14 meshlet_hardware_raster_indirect_args, 15 meshlet_raster_clusters, 17 MeshletBoundingSphere, 19#import bevy_render::maths::affine3_to_square 27@workgroup_size(128, 1, 1) 29 @builtin(workgroup_id) workgroup_id: vec3<u32>, 30 @builtin(num_workgroups) num_workgroups: vec3<u32>, 31 @builtin(local_invocation_index) local_invocation_index: u32, 34 let cluster_id = local_invocation_index + 128u * dot(workgroup_id, vec3(num_workgroups.x * num_workgroups.x, num_workgroups.x, 1u)); 35 if cluster_id >= constants.scene_cluster_count { return; } 37#ifdef MESHLET_SECOND_CULLING_PASS 38 if !cluster_is_second_pass_candidate(cluster_id) { return; } 42 let instance_id = meshlet_cluster_instance_ids[cluster_id]; 43#ifdef MESHLET_FIRST_CULLING_PASS 44 if should_cull_instance(instance_id) { return; } 48 let instance_uniform = meshlet_instance_uniforms[instance_id]; 49 let meshlet_id = meshlet_cluster_meshlet_ids[cluster_id]; 50 let world_from_local = affine3_to_square(instance_uniform.world_from_local); 51 let world_scale = max(length(world_from_local[0]), max(length(world_from_local[1]), length(world_from_local[2]))); 52 let bounding_spheres = meshlet_bounding_spheres[meshlet_id]; 53 let culling_bounding_sphere_center = world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0); 54 let culling_bounding_sphere_radius = world_scale * bounding_spheres.culling_sphere.radius; 56#ifdef MESHLET_FIRST_CULLING_PASS 59 for (var i = 0u; i < 6u; i++) { 60 if dot(view.frustum[i], culling_bounding_sphere_center) + culling_bounding_sphere_radius <= 0.0 { 66 let simplification_errors = unpack2x16float(meshlet_simplification_errors[meshlet_id]); 67 let lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_group_sphere, simplification_errors.x, world_from_local, world_scale); 68 let parent_lod_is_ok = lod_error_is_imperceptible(bounding_spheres.lod_parent_group_sphere, simplification_errors.y, world_from_local, world_scale); 69 if !lod_is_ok || parent_lod_is_ok { return; } 73#ifdef MESHLET_FIRST_CULLING_PASS 74 let previous_world_from_local = affine3_to_square(instance_uniform.previous_world_from_local); 75 let previous_world_from_local_scale = max(length(previous_world_from_local[0]), max(length(previous_world_from_local[1]), length(previous_world_from_local[2]))); 76 let occlusion_culling_bounding_sphere_center = previous_world_from_local * vec4(bounding_spheres.culling_sphere.center, 1.0); 77 let occlusion_culling_bounding_sphere_radius = previous_world_from_local_scale * bounding_spheres.culling_sphere.radius; 78 let occlusion_culling_bounding_sphere_center_view_space = (previous_view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz; 80 let occlusion_culling_bounding_sphere_center = culling_bounding_sphere_center; 81 let occlusion_culling_bounding_sphere_radius = culling_bounding_sphere_radius; 82 let occlusion_culling_bounding_sphere_center_view_space = (view.view_from_world * vec4(occlusion_culling_bounding_sphere_center.xyz, 1.0)).xyz; 85 var aabb = project_view_space_sphere_to_screen_space_aabb(occlusion_culling_bounding_sphere_center_view_space, occlusion_culling_bounding_sphere_radius); 86 let depth_pyramid_size_mip_0 = vec2<f32>(textureDimensions(depth_pyramid, 0)); 87 var aabb_width_pixels = (aabb.z - aabb.x) * depth_pyramid_size_mip_0.x; 88 var aabb_height_pixels = (aabb.w - aabb.y) * depth_pyramid_size_mip_0.y; 89 let depth_level = max(0, i32(ceil(log2(max(aabb_width_pixels, aabb_height_pixels))))); 90 let depth_pyramid_size = vec2<f32>(textureDimensions(depth_pyramid, depth_level)); 91 let aabb_top_left = vec2<u32>(aabb.xy * depth_pyramid_size); 93 let depth_quad_a = textureLoad(depth_pyramid, aabb_top_left, depth_level).x; 94 let depth_quad_b = textureLoad(depth_pyramid, aabb_top_left + vec2(1u, 0u), depth_level).x; 95 let depth_quad_c = textureLoad(depth_pyramid, aabb_top_left + vec2(0u, 1u), depth_level).x; 96 let depth_quad_d = textureLoad(depth_pyramid, aabb_top_left + vec2(1u, 1u), depth_level).x; 97 let occluder_depth = min(min(depth_quad_a, depth_quad_b), min(depth_quad_c, depth_quad_d)); 100 var cluster_visible: bool; 101 if view.clip_from_view[3][3] == 1.0 { 103 let sphere_depth = view.clip_from_view[3][2] + (occlusion_culling_bounding_sphere_center_view_space.z + occlusion_culling_bounding_sphere_radius) * view.clip_from_view[2][2]; 104 cluster_visible = sphere_depth >= occluder_depth; 107 let sphere_depth = -view.clip_from_view[3][2] / (occlusion_culling_bounding_sphere_center_view_space.z + occlusion_culling_bounding_sphere_radius); 108 cluster_visible = sphere_depth >= occluder_depth; 112#ifdef MESHLET_FIRST_CULLING_PASS 113 if !cluster_visible { 114 let bit = 1u << cluster_id % 32u; 115 atomicOr(&meshlet_second_pass_candidates[cluster_id / 32u], bit); 120 if !cluster_visible { return; } 123#ifdef MESHLET_FIRST_CULLING_PASS 124 let culling_bounding_sphere_center_view_space = (view.view_from_world * vec4(culling_bounding_sphere_center.xyz, 1.0)).xyz; 125 aabb = project_view_space_sphere_to_screen_space_aabb(culling_bounding_sphere_center_view_space, culling_bounding_sphere_radius); 126 aabb_width_pixels = (aabb.z - aabb.x) * view.viewport.z; 127 aabb_height_pixels = (aabb.w - aabb.y) * view.viewport.w; 129 let cluster_is_small = all(vec2(aabb_width_pixels, aabb_height_pixels) < vec2(64.0)); 132 let not_intersects_near_plane = dot(view.frustum[4u], culling_bounding_sphere_center) > culling_bounding_sphere_radius; 134 var buffer_slot: u32; 135 if cluster_is_small && not_intersects_near_plane { 137 buffer_slot = atomicAdd(&meshlet_software_raster_indirect_args.x, 1u); 140 buffer_slot = atomicAdd(&meshlet_hardware_raster_indirect_args.instance_count, 1u); 141 buffer_slot = constants.meshlet_raster_cluster_rightmost_slot - buffer_slot; 143 meshlet_raster_clusters[buffer_slot] = cluster_id; 147fn lod_error_is_imperceptible(lod_sphere: MeshletBoundingSphere, simplification_error: f32, world_from_local: mat4x4<f32>, world_scale: f32) -> bool { 148 let sphere_world_space = (world_from_local * vec4(lod_sphere.center, 1.0)).xyz; 149 let radius_world_space = world_scale * lod_sphere.radius; 150 let error_world_space = world_scale * simplification_error; 152 var projected_error = error_world_space; 153 if view.clip_from_view[3][3] != 1.0 { 155 let distance_to_closest_point_on_sphere = distance(sphere_world_space, view.world_position) - radius_world_space; 156 let distance_to_closest_point_on_sphere_clamped_to_znear = max(distance_to_closest_point_on_sphere, view.clip_from_view[3][2]); 157 projected_error /= distance_to_closest_point_on_sphere_clamped_to_znear; 159 projected_error *= view.clip_from_view[1][1] * 0.5; 160 projected_error *= view.viewport.w; 162 return projected_error < 1.0; 166fn project_view_space_sphere_to_screen_space_aabb(cp: vec3<f32>, r: f32) -> vec4<f32> { 167 let inv_width = view.clip_from_view[0][0] * 0.5; 168 let inv_height = view.clip_from_view[1][1] * 0.5; 169 if view.clip_from_view[3][3] == 1.0 { 171 let min_x = cp.x - r; 172 let max_x = cp.x + r; 174 let min_y = cp.y - r; 175 let max_y = cp.y + r; 177 return vec4(min_x * inv_width, 1.0 - max_y * inv_height, max_x * inv_width, 1.0 - min_y * inv_height); 180 let c = vec3(cp.xy, -cp.z); 182 let czr2 = c.z * c.z - r * r; 184 let vx = sqrt(c.x * c.x + czr2); 185 let min_x = (vx * c.x - cr.z) / (vx * c.z + cr.x); 186 let max_x = (vx * c.x + cr.z) / (vx * c.z - cr.x); 188 let vy = sqrt(c.y * c.y + czr2); 189 let min_y = (vy * c.y - cr.z) / (vy * c.z + cr.y); 190 let max_y = (vy * c.y + cr.z) / (vy * c.z - cr.y); 192 return vec4(min_x * inv_width, -max_y * inv_height, max_x * inv_width, -min_y * inv_height) + vec4(0.5);