diff --git a/crates/bevy_camera/src/visibility/range.rs b/crates/bevy_camera/src/visibility/range.rs index bd3b460744cad..3864627f8d258 100644 --- a/crates/bevy_camera/src/visibility/range.rs +++ b/crates/bevy_camera/src/visibility/range.rs @@ -10,7 +10,7 @@ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ component::Component, entity::{Entity, EntityHashMap}, - query::{Or, With}, + query::{Or, With, Without}, reflect::ReflectComponent, resource::Resource, schedule::IntoScheduleConfigs as _, @@ -22,7 +22,7 @@ use bevy_transform::components::GlobalTransform; use bevy_utils::Parallel; use super::{check_visibility_cpu_culling, VisibilitySystems}; -use crate::{camera::Camera, primitives::Aabb, ShadowLodOrigin}; +use crate::{camera::Camera, primitives::Aabb, visibility::NoCpuCulling, ShadowLodOrigin}; /// A plugin that enables [`VisibilityRange`]s, which allow entities to be /// hidden or shown based on distance to the camera. @@ -231,7 +231,10 @@ pub fn check_visibility_ranges( mut visible_entity_ranges: ResMut, view_query: Query<(Entity, &GlobalTransform), Or<(With, With)>>, mut par_local: Local>>, - entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>, + entity_query: Query< + (Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange), + Without, + >, ) { visible_entity_ranges.clear(); diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 7e64bebb567c3..33617abac86dc 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -32,6 +32,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_log::warn_once; +use bevy_math::Vec4; use bevy_platform::collections::HashMap; use bevy_render::{ batching::gpu_preprocessing::{ @@ -50,17 +51,18 @@ use bevy_render::{ render_resource::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, - BindingResource, Buffer, BufferBinding, CachedComputePipelineId, ComputePassDescriptor, - ComputePipelineDescriptor, DynamicBindGroupLayoutEntries, PartialBufferVec, PipelineCache, - RawBufferVec, ShaderStages, ShaderType, SparseBufferUpdateBindGroups, - SparseBufferUpdateJobs, SparseBufferUpdatePipelines, SpecializedComputePipeline, - SpecializedComputePipelines, TextureSampleType, UninitBufferVec, + BindingResource, Buffer, BufferBinding, BufferVec, CachedComputePipelineId, + ComputePassDescriptor, ComputePipelineDescriptor, DynamicBindGroupLayoutEntries, + PartialBufferVec, PipelineCache, RawBufferVec, ShaderStages, ShaderType, + SparseBufferUpdateBindGroups, SparseBufferUpdateJobs, SparseBufferUpdatePipelines, + SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType, + UninitBufferVec, }, renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, settings::WgpuFeatures, view::{ - ExtractedView, NoIndirectDrawing, RetainedViewEntity, ViewUniform, ViewUniformOffset, - ViewUniforms, + ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewUniform, + ViewUniformOffset, ViewUniforms, }, GpuResourceAppExt, Render, RenderApp, RenderSystems, }; @@ -1265,11 +1267,11 @@ impl FromWorld for PreprocessPipelines { let gpu_early_occlusion_culling_bind_group_layout_entries = gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices(( ( - 11, + 12, storage_buffer::(/*has_dynamic_offset=*/ false), ), ( - 12, + 13, storage_buffer::( /*has_dynamic_offset=*/ false, ), @@ -1277,7 +1279,7 @@ impl FromWorld for PreprocessPipelines { )); let gpu_late_occlusion_culling_bind_group_layout_entries = gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices((( - 12, + 13, storage_buffer_read_only::( /*has_dynamic_offset=*/ false, ), @@ -1468,6 +1470,11 @@ fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries { 9, storage_buffer_read_only::(/* has_dynamic_offset= */ false), ), + // `visibility_ranges` + ( + 10, + storage_buffer_read_only::(/* has_dynamic_offset= */ false), + ), )) } @@ -1478,7 +1485,7 @@ fn gpu_occlusion_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEn uniform_buffer::(/*has_dynamic_offset=*/ false), ), ( - 10, + 11, texture_2d(TextureSampleType::Float { filterable: true }), ), )) @@ -1798,6 +1805,7 @@ pub fn prepare_preprocess_bind_groups( indirect_parameters_buffers: Res, bin_unpacking_buffers: Res, mesh_culling_data_buffer: Res, + visibility_ranges: Res, view_uniforms: Res, previous_view_uniforms: Res, pipelines: Res, @@ -1858,6 +1866,7 @@ pub fn prepare_preprocess_bind_groups( pipeline_cache: &pipeline_cache, phase_indirect_parameters_buffers, mesh_culling_data_buffer: &mesh_culling_data_buffer, + visibility_range_data_buffer: visibility_ranges.buffer(), view_uniforms: &view_uniforms, previous_view_uniforms: &previous_view_uniforms, pipelines: &pipelines, @@ -1967,6 +1976,9 @@ struct PreprocessBindGroupBuilder<'a> { phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers, /// The GPU buffer that stores the information needed to cull each mesh. mesh_culling_data_buffer: &'a MeshCullingDataBuffer, + /// The device buffer that stores the information needed to process + /// visibility ranges on the GPU. + visibility_range_data_buffer: &'a BufferVec, /// The GPU buffer that stores information about the view. view_uniforms: &'a ViewUniforms, /// The GPU buffer that stores information about the view from last frame. @@ -2083,6 +2095,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { late_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; @@ -2137,8 +2150,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, indexed_cpu_metadata_buffer.as_entire_binding()), (8, indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), - (10, &view_depth_pyramid.all_mips), + (11, &view_depth_pyramid.all_mips), ( 2, BufferBinding { @@ -2148,7 +2162,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 11, + 12, BufferBinding { buffer: late_indexed_work_item_gpu_buffer, offset: 0, @@ -2156,7 +2170,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 12, + 13, BufferBinding { buffer: late_indexed_indirect_parameters_buffer, offset: 0, @@ -2183,6 +2197,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { late_non_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; @@ -2237,8 +2252,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), - (10, &view_depth_pyramid.all_mips), + (11, &view_depth_pyramid.all_mips), ( 2, BufferBinding { @@ -2248,7 +2264,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 11, + 12, BufferBinding { buffer: late_non_indexed_work_item_buffer, offset: 0, @@ -2256,7 +2272,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 12, + 13, BufferBinding { buffer: late_non_indexed_indirect_parameters_buffer, offset: 0, @@ -2282,6 +2298,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { late_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; @@ -2334,8 +2351,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, indexed_cpu_metadata_buffer.as_entire_binding()), (8, indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), - (10, &view_depth_pyramid.all_mips), + (11, &view_depth_pyramid.all_mips), ( 2, BufferBinding { @@ -2345,7 +2363,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 12, + 13, BufferBinding { buffer: late_indexed_indirect_parameters_buffer, offset: 0, @@ -2371,6 +2389,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { late_non_indexed_work_item_buffer: &UninitBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?; @@ -2423,8 +2442,9 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), - (10, &view_depth_pyramid.all_mips), + (11, &view_depth_pyramid.all_mips), ( 2, BufferBinding { @@ -2434,7 +2454,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { }, ), ( - 12, + 13, BufferBinding { buffer: late_non_indexed_indirect_parameters_buffer, offset: 0, @@ -2474,6 +2494,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { indexed_work_item_buffer: &PartialBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; match ( @@ -2523,6 +2544,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, indexed_cpu_metadata_buffer.as_entire_binding()), (8, indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), )), ), @@ -2539,6 +2561,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { non_indexed_work_item_buffer: &PartialBufferVec, ) -> Option { let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?; + let visibility_range_binding = self.visibility_range_data_buffer.binding()?; let view_uniforms_binding = self.view_uniforms.uniforms.binding()?; match ( @@ -2588,6 +2611,7 @@ impl<'a> PreprocessBindGroupBuilder<'a> { (7, non_indexed_cpu_metadata_buffer.as_entire_binding()), (8, non_indexed_gpu_metadata_buffer.as_entire_binding()), (9, mesh_culling_data_buffer.as_entire_binding()), + (10, visibility_range_binding.clone()), (0, view_uniforms_binding.clone()), )), ), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9658abc3392b8..253540c6dbcb9 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -707,6 +707,12 @@ bitflags::bitflags! { /// /// This will be `u16::MAX` if this mesh has no LOD. const LOD_INDEX_MASK = (1 << 16) - 1; + /// Whether visibility ranges use the center of the AABB to compute + /// distance from the camera. + /// + /// If false, this uses distance from the world-space translation of the + /// mesh instead. + const AABB_BASED_VISIBILITY_RANGE = 1 << 27; /// Disables frustum culling for this mesh. /// /// This corresponds to the @@ -726,6 +732,7 @@ impl MeshFlags { fn from_components( transform: &GlobalTransform, lod_index: Option, + visibility_range: Option<&VisibilityRange>, no_frustum_culling: bool, not_shadow_receiver: bool, transmitted_receiver: bool, @@ -735,6 +742,9 @@ impl MeshFlags { } else { MeshFlags::SHADOW_RECEIVER }; + if visibility_range.is_some_and(|visibility_range| visibility_range.use_aabb) { + mesh_flags |= MeshFlags::AABB_BASED_VISIBILITY_RANGE; + } if no_frustum_culling { mesh_flags |= MeshFlags::NO_FRUSTUM_CULLING; } @@ -1764,7 +1774,7 @@ pub fn extract_meshes_for_cpu_building( Has, Has, Has, - Has, + Option<&VisibilityRange>, Option<&RenderLayers>, )>, >, @@ -1792,13 +1802,14 @@ pub fn extract_meshes_for_cpu_building( } let mut lod_index = None; - if visibility_range { + if visibility_range.is_some() { lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); } let mesh_flags = MeshFlags::from_components( transform, lod_index, + visibility_range, no_frustum_culling, not_shadow_receiver, transmitted_receiver, @@ -1875,7 +1886,7 @@ type GpuMeshExtractionQuery = ( Has, Has, ), - Has, + Option>, Option>, ); @@ -2104,7 +2115,7 @@ fn extract_mesh_for_gpu_building( // If the entity has a visibility range, determine its LOD index. let mut lod_index = None; - if visibility_range { + if visibility_range.is_some() { lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); } @@ -2112,6 +2123,7 @@ fn extract_mesh_for_gpu_building( let mesh_flags = MeshFlags::from_components( transform, lod_index, + visibility_range, no_frustum_culling, not_shadow_receiver, transmitted_receiver, diff --git a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl index 4923803ed6339..437c2a449c120 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl @@ -17,7 +17,10 @@ #import bevy_pbr::mesh_preprocess_types::{ IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, MeshInput, PreprocessWorkItem } -#import bevy_pbr::mesh_types::{Mesh, MESH_FLAGS_NO_FRUSTUM_CULLING_BIT} +#import bevy_pbr::mesh_types::{ + Mesh, MESH_FLAGS_AABB_BASED_VISIBILITY_RANGE_BIT, MESH_FLAGS_NO_FRUSTUM_CULLING_BIT, + MESH_FLAGS_VISIBILITY_RANGE_INDEX_BITS +} #import bevy_pbr::mesh_view_bindings::view #import bevy_pbr::occlusion_culling #import bevy_pbr::prepass_bindings::previous_view_uniforms @@ -93,21 +96,23 @@ struct Immediates { // // At the moment, this consists only of AABBs. @group(0) @binding(9) var mesh_culling_data: array; + +@group(0) @binding(10) var visibility_ranges: array>; #endif // FRUSTUM_CULLING #ifdef OCCLUSION_CULLING -@group(0) @binding(10) var depth_pyramid: texture_2d; +@group(0) @binding(11) var depth_pyramid: texture_2d; #ifdef EARLY_PHASE -@group(0) @binding(11) var late_preprocess_work_items: +@group(0) @binding(12) var late_preprocess_work_items: array; -@group(0) @binding(12) var late_preprocess_work_item_indirect_parameters: +@group(0) @binding(13) var late_preprocess_work_item_indirect_parameters: array; #endif // EARLY_PHASE #ifdef LATE_PHASE -@group(0) @binding(12) var late_preprocess_work_item_indirect_parameters: +@group(0) @binding(13) var late_preprocess_work_item_indirect_parameters: array; #endif // LATE_PHASE @@ -201,7 +206,31 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { return; } } -#endif + + // Visibility range cull if necessary. + let visibility_buffer_array_len = arrayLength(&visibility_ranges); + let visibility_buffer_index = + current_input[input_index].flags & MESH_FLAGS_VISIBILITY_RANGE_INDEX_BITS; + if (visibility_buffer_index < visibility_buffer_array_len) { + let lod_range = visibility_ranges[visibility_buffer_index]; + + // If we're using the AABB as the mesh center, determine its world space position. + // Otherwise, just use the center of the transform. + var world_pos: vec3; + if ((current_input[input_index].flags & MESH_FLAGS_AABB_BASED_VISIBILITY_RANGE_BIT) != 0u) { + let aabb_center = mesh_culling_data[input_index].aabb_center.xyz; + world_pos = (world_from_local * vec4(aabb_center, 1.0)).xyz; + } else { + world_pos = world_from_local[3].xyz; + } + + let camera_distance = length(world_pos - view.lod_view_world_position); + // `x` is the minimum range; `w` is the largest range. + if (camera_distance < lod_range.x || camera_distance >= lod_range.w) { + return; + } + } +#endif // FRUSTUM_CULLING // See whether the `MeshInputUniform` was updated on this frame. If it // wasn't, then we know the transforms of this mesh must be identical to diff --git a/crates/bevy_pbr/src/render/mesh_types.wgsl b/crates/bevy_pbr/src/render/mesh_types.wgsl index 993185d5b35f9..80b59b6ee11d6 100644 --- a/crates/bevy_pbr/src/render/mesh_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_types.wgsl @@ -74,6 +74,7 @@ struct MorphAttributes { // [2^0, 2^16) const MESH_FLAGS_VISIBILITY_RANGE_INDEX_BITS: u32 = (1u << 16u) - 1u; +const MESH_FLAGS_AABB_BASED_VISIBILITY_RANGE_BIT: u32 = 1u << 27u; const MESH_FLAGS_NO_FRUSTUM_CULLING_BIT: u32 = 1u << 28u; const MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u << 29u; const MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT: u32 = 1u << 30u; diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index 5ea3d32e5ac0b..2ce0d221eb9c8 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -3,7 +3,7 @@ use std::f32::consts::PI; use bevy::{ - camera::visibility::VisibilityRange, + camera::visibility::{NoCpuCulling, VisibilityRange}, core_pipeline::prepass::{DepthPrepass, NormalPrepass}, input::mouse::MouseWheel, light::{light_consts::lux::FULL_DAYLIGHT, CascadeShadowConfigBuilder}, @@ -11,6 +11,8 @@ use bevy::{ prelude::*, }; +use argh::FromArgs; + // Where the camera is focused. const CAMERA_FOCAL_POINT: Vec3 = vec3(0.0, 0.3, 0.0); // Speed in units per frame. @@ -68,8 +70,20 @@ struct AppStatus { prepass: bool, } -// Sets up the app. +/// Demonstrates visibility ranges, also known as HLODs +#[derive(FromArgs, Resource)] +struct Args { + /// whether to use GPU culling only + #[argh(switch)] + no_cpu_culling: bool, +} + fn main() { + #[cfg(not(target_arch = "wasm32"))] + let args: Args = argh::from_env(); + #[cfg(target_arch = "wasm32")] + let args = Args::from_args(&[], &[]).unwrap(); + App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { @@ -79,6 +93,7 @@ fn main() { ..default() })) .init_resource::() + .insert_resource(args) .add_systems(Startup, setup) .add_systems( Update, @@ -176,6 +191,7 @@ fn set_visibility_ranges( mut commands: Commands, mut new_meshes: Query>, children: Query<(Option<&ChildOf>, Option<&MainModel>)>, + args: Res, ) { // Loop over each newly-added mesh. for new_mesh in new_meshes.iter_mut() { @@ -195,16 +211,22 @@ fn set_visibility_ranges( // Add the `VisibilityRange` component. match main_model { Some(MainModel::HighPoly) => { - commands - .entity(new_mesh) + let mut entity_commands = commands.entity(new_mesh); + entity_commands .insert(NORMAL_VISIBILITY_RANGE_HIGH_POLY.clone()) .insert(MainModel::HighPoly); + if args.no_cpu_culling { + entity_commands.insert(NoCpuCulling); + } } Some(MainModel::LowPoly) => { - commands - .entity(new_mesh) + let mut entity_commands = commands.entity(new_mesh); + entity_commands .insert(NORMAL_VISIBILITY_RANGE_LOW_POLY.clone()) .insert(MainModel::LowPoly); + if args.no_cpu_culling { + entity_commands.insert(NoCpuCulling); + } } None => {} }