Skip to content

Commit 549b5f6

Browse files
committed
frustum culling for sprites
1 parent b17f8a4 commit 549b5f6

File tree

11 files changed

+174
-24
lines changed

11 files changed

+174
-24
lines changed

crates/bevy_render/src/camera/visible_entities.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{Camera, DepthCalculation};
2-
use crate::prelude::Visible;
2+
use crate::{draw::WithinFrustum, prelude::Visible};
33
use bevy_core::FloatOrd;
44
use bevy_ecs::{entity::Entity, query::With, reflect::ReflectComponent, system::Query};
55
use bevy_reflect::Reflect;
@@ -204,8 +204,8 @@ pub fn visible_entities_system(
204204
&mut VisibleEntities,
205205
Option<&RenderLayers>,
206206
)>,
207-
visible_query: Query<(Entity, &Visible, Option<&RenderLayers>)>,
208-
visible_transform_query: Query<&GlobalTransform, With<Visible>>,
207+
visible_query: Query<(Entity, &Visible, Option<&RenderLayers>), With<WithinFrustum>>,
208+
visible_transform_query: Query<&GlobalTransform, With<WithinFrustum>>,
209209
) {
210210
for (camera, camera_global_transform, mut visible_entities, maybe_camera_mask) in
211211
camera_query.iter_mut()

crates/bevy_render/src/draw.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ impl Default for Visible {
6666
}
6767
}
6868

69+
/// Viewable is used for frustum culling.
70+
/// Any Sprite or AtlasTextureSprite will have this removed if they are outside the camera frustum and thus not be rendered.
71+
#[derive(Debug, Default, Clone, Reflect)]
72+
#[reflect(Component)]
73+
pub struct WithinFrustum;
74+
6975
/// A component that indicates how to draw an entity.
7076
#[derive(Debug, Clone, Reflect)]
7177
#[reflect(Component)]

crates/bevy_render/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ use bevy_ecs::{
1717
system::{IntoExclusiveSystem, IntoSystem},
1818
};
1919
use bevy_transform::TransformSystem;
20-
use draw::Visible;
20+
use draw::{Visible, WithinFrustum};
21+
2122
pub use once_cell;
2223

2324
pub mod prelude {
@@ -137,6 +138,7 @@ impl Plugin for RenderPlugin {
137138
.register_type::<DepthCalculation>()
138139
.register_type::<Draw>()
139140
.register_type::<Visible>()
141+
.register_type::<WithinFrustum>()
140142
.register_type::<RenderPipelines>()
141143
.register_type::<OrthographicProjection>()
142144
.register_type::<PerspectiveProjection>()

crates/bevy_render/src/pipeline/render_pipelines.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use super::{PipelineDescriptor, PipelineSpecialization};
22
use crate::{
3-
draw::{Draw, DrawContext},
3+
draw::{Draw, DrawContext, WithinFrustum},
44
mesh::{Indices, Mesh},
55
prelude::{Msaa, Visible},
66
renderer::RenderResourceBindings,
77
};
88
use bevy_asset::{Assets, Handle};
99
use bevy_ecs::{
10+
query::With,
1011
reflect::ReflectComponent,
1112
system::{Query, Res, ResMut},
1213
};
@@ -86,7 +87,10 @@ pub fn draw_render_pipelines_system(
8687
mut render_resource_bindings: ResMut<RenderResourceBindings>,
8788
msaa: Res<Msaa>,
8889
meshes: Res<Assets<Mesh>>,
89-
mut query: Query<(&mut Draw, &mut RenderPipelines, &Handle<Mesh>, &Visible)>,
90+
mut query: Query<
91+
(&mut Draw, &mut RenderPipelines, &Handle<Mesh>, &Visible),
92+
With<WithinFrustum>,
93+
>,
9094
) {
9195
for (mut draw, mut render_pipelines, mesh_handle, visible) in query.iter_mut() {
9296
if !visible.is_visible {

crates/bevy_render/src/shader/shader_defs.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use bevy_asset::{Asset, Assets, Handle};
22

3-
use crate::{pipeline::RenderPipelines, Texture};
3+
use crate::{draw::WithinFrustum, pipeline::RenderPipelines, Texture};
44
pub use bevy_derive::ShaderDefs;
5-
use bevy_ecs::system::{Query, Res};
5+
use bevy_ecs::{
6+
query::With,
7+
system::{Query, Res},
8+
};
69

710
/// Something that can either be "defined" or "not defined". This is used to determine if a "shader
811
/// def" should be considered "defined"
@@ -61,7 +64,7 @@ impl ShaderDef for Option<Handle<Texture>> {
6164
}
6265

6366
/// Updates [RenderPipelines] with the latest [ShaderDefs]
64-
pub fn shader_defs_system<T>(mut query: Query<(&T, &mut RenderPipelines)>)
67+
pub fn shader_defs_system<T>(mut query: Query<(&T, &mut RenderPipelines), With<WithinFrustum>>)
6568
where
6669
T: ShaderDefs + Send + Sync + 'static,
6770
{
@@ -94,7 +97,7 @@ pub fn clear_shader_defs_system(mut query: Query<&mut RenderPipelines>) {
9497
/// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type
9598
pub fn asset_shader_defs_system<T: Asset>(
9699
assets: Res<Assets<T>>,
97-
mut query: Query<(&Handle<T>, &mut RenderPipelines)>,
100+
mut query: Query<(&Handle<T>, &mut RenderPipelines), With<WithinFrustum>>,
98101
) where
99102
T: ShaderDefs + Send + Sync + 'static,
100103
{

crates/bevy_sprite/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"
2424
bevy_render = { path = "../bevy_render", version = "0.4.0" }
2525
bevy_transform = { path = "../bevy_transform", version = "0.4.0" }
2626
bevy_utils = { path = "../bevy_utils", version = "0.4.0" }
27+
bevy_window = { path = "../bevy_window", version = "0.4.0" }
2728

2829
# other
2930
rectangle-pack = "0.2"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use bevy_asset::{Assets, Handle};
2+
use bevy_ecs::prelude::{Commands, Entity, Query, Res, With};
3+
use bevy_math::Vec2;
4+
use bevy_render::{camera::Camera, draw::WithinFrustum};
5+
use bevy_transform::components::Transform;
6+
use bevy_window::Windows;
7+
8+
use crate::{Sprite, TextureAtlas, TextureAtlasSprite};
9+
10+
struct Rect {
11+
position: Vec2,
12+
size: Vec2,
13+
}
14+
15+
impl Rect {
16+
#[inline]
17+
pub fn is_intersecting(&self, other: Rect) -> bool {
18+
self.position.distance(other.position) < (self.get_radius() + other.get_radius())
19+
}
20+
21+
#[inline]
22+
pub fn get_radius(&self) -> f32 {
23+
let half_size = self.size / Vec2::splat(2.0);
24+
(half_size.x.powf(2.0) + half_size.y.powf(2.0)).sqrt()
25+
}
26+
}
27+
28+
pub fn sprites(
29+
mut commands: Commands,
30+
windows: Res<Windows>,
31+
cameras: Query<&Transform, With<Camera>>,
32+
visible: Query<&WithinFrustum, With<Sprite>>,
33+
sprites: Query<(Entity, &Transform, &Sprite)>,
34+
) {
35+
let window_size = if let Some(window) = windows.get_primary() {
36+
Vec2::new(window.width(), window.height())
37+
} else {
38+
return;
39+
};
40+
41+
for camera_transform in cameras.iter() {
42+
let camera_size = window_size * camera_transform.scale.truncate();
43+
44+
let rect = Rect {
45+
position: camera_transform.translation.truncate(),
46+
size: camera_size,
47+
};
48+
49+
for (entity, drawable_transform, sprite) in sprites.iter() {
50+
let sprite_rect = Rect {
51+
position: drawable_transform.translation.truncate(),
52+
size: sprite.size,
53+
};
54+
55+
if rect.is_intersecting(sprite_rect) {
56+
if visible.get(entity).is_err() {
57+
commands.insert(entity, WithinFrustum);
58+
}
59+
} else if visible.get(entity).is_ok() {
60+
commands.remove::<WithinFrustum>(entity);
61+
}
62+
}
63+
}
64+
}
65+
66+
pub fn atlases(
67+
mut commands: Commands,
68+
windows: Res<Windows>,
69+
textures: Res<Assets<TextureAtlas>>,
70+
cameras: Query<&Transform, With<Camera>>,
71+
visible: Query<&WithinFrustum, With<TextureAtlasSprite>>,
72+
sprites: Query<(
73+
Entity,
74+
&Transform,
75+
&TextureAtlasSprite,
76+
&Handle<TextureAtlas>,
77+
)>,
78+
) {
79+
let window = windows.get_primary().unwrap();
80+
let window_size = Vec2::new(window.width(), window.height());
81+
82+
for camera_transform in cameras.iter() {
83+
let camera_size = window_size * camera_transform.scale.truncate();
84+
85+
let rect = Rect {
86+
position: camera_transform.translation.truncate(),
87+
size: camera_size,
88+
};
89+
90+
for (entity, drawable_transform, sprite, atlas_handle) in sprites.iter() {
91+
if let Some(atlas) = textures.get(atlas_handle) {
92+
if let Some(sprite) = atlas.textures.get(sprite.index as usize) {
93+
let size = Vec2::new(sprite.width(), sprite.height());
94+
95+
let sprite_rect = Rect {
96+
position: drawable_transform.translation.truncate(),
97+
size,
98+
};
99+
100+
if rect.is_intersecting(sprite_rect) {
101+
if visible.get(entity).is_err() {
102+
commands.insert(entity, WithinFrustum::default());
103+
}
104+
} else if visible.get(entity).is_ok() {
105+
commands.remove::<WithinFrustum>(entity);
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}

crates/bevy_sprite/src/lib.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod entity;
33

44
mod color_material;
55
mod dynamic_texture_atlas_builder;
6+
mod frustum_culling;
67
mod rect;
78
mod render;
89
mod sprite;
@@ -26,10 +27,14 @@ pub use texture_atlas_builder::*;
2627

2728
use bevy_app::prelude::*;
2829
use bevy_asset::{AddAsset, Assets, Handle, HandleUntyped};
29-
use bevy_ecs::system::IntoSystem;
30+
use bevy_ecs::{
31+
component::{ComponentDescriptor, StorageType},
32+
system::IntoSystem,
33+
};
3034
use bevy_math::Vec2;
3135
use bevy_reflect::TypeUuid;
3236
use bevy_render::{
37+
draw::WithinFrustum,
3338
mesh::{shape, Mesh},
3439
pipeline::PipelineDescriptor,
3540
render_graph::RenderGraph,
@@ -50,21 +55,32 @@ impl Plugin for SpritePlugin {
5055
.register_type::<Sprite>()
5156
.register_type::<SpriteResizeMode>()
5257
.add_system_to_stage(CoreStage::PostUpdate, sprite_system.system())
58+
.add_system_to_stage(CoreStage::PostUpdate, frustum_culling::sprites.system())
59+
.add_system_to_stage(CoreStage::PostUpdate, frustum_culling::atlases.system())
5360
.add_system_to_stage(
5461
CoreStage::PostUpdate,
5562
asset_shader_defs_system::<ColorMaterial>.system(),
5663
);
5764

58-
let world = app.world_mut().cell();
59-
let mut render_graph = world.get_resource_mut::<RenderGraph>().unwrap();
60-
let mut pipelines = world
65+
let world = app.world_mut();
66+
world
67+
.register_component(ComponentDescriptor::new::<WithinFrustum>(
68+
StorageType::SparseSet,
69+
))
70+
.unwrap();
71+
72+
let world_cell = world.cell();
73+
let mut render_graph = world_cell.get_resource_mut::<RenderGraph>().unwrap();
74+
let mut pipelines = world_cell
6175
.get_resource_mut::<Assets<PipelineDescriptor>>()
6276
.unwrap();
63-
let mut shaders = world.get_resource_mut::<Assets<Shader>>().unwrap();
77+
let mut shaders = world_cell.get_resource_mut::<Assets<Shader>>().unwrap();
6478
crate::render::add_sprite_graph(&mut render_graph, &mut pipelines, &mut shaders);
6579

66-
let mut meshes = world.get_resource_mut::<Assets<Mesh>>().unwrap();
67-
let mut color_materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
80+
let mut meshes = world_cell.get_resource_mut::<Assets<Mesh>>().unwrap();
81+
let mut color_materials = world_cell
82+
.get_resource_mut::<Assets<ColorMaterial>>()
83+
.unwrap();
6884
color_materials.set_untracked(Handle::<ColorMaterial>::default(), ColorMaterial::default());
6985
meshes.set_untracked(
7086
QUAD_HANDLE,

crates/bevy_sprite/src/sprite.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
use crate::ColorMaterial;
22
use bevy_asset::{Assets, Handle};
33
use bevy_core::Bytes;
4-
use bevy_ecs::system::{Query, Res};
4+
use bevy_ecs::{
5+
query::With,
6+
system::{Query, Res},
7+
};
58
use bevy_math::Vec2;
69
use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid};
710
use bevy_render::{
11+
draw::WithinFrustum,
812
renderer::{RenderResource, RenderResourceType, RenderResources},
913
texture::Texture,
1014
};
@@ -76,7 +80,7 @@ impl Sprite {
7680
pub fn sprite_system(
7781
materials: Res<Assets<ColorMaterial>>,
7882
textures: Res<Assets<Texture>>,
79-
mut query: Query<(&mut Sprite, &Handle<ColorMaterial>)>,
83+
mut query: Query<(&mut Sprite, &Handle<ColorMaterial>), With<WithinFrustum>>,
8084
) {
8185
for (mut sprite, handle) in query.iter_mut() {
8286
match sprite.resize_mode {

crates/bevy_text/src/text2d.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use bevy_ecs::{
77
};
88
use bevy_math::{Size, Vec3};
99
use bevy_render::{
10-
draw::{DrawContext, Drawable},
10+
draw::{DrawContext, Drawable, WithinFrustum},
1111
mesh::Mesh,
1212
prelude::{Draw, Msaa, Texture, Visible},
1313
render_graph::base::MainPass,
@@ -72,7 +72,7 @@ pub fn draw_text2d_system(
7272
&GlobalTransform,
7373
&Text2dSize,
7474
),
75-
With<MainPass>,
75+
(With<MainPass>, With<WithinFrustum>),
7676
>,
7777
) {
7878
let font_quad = meshes.get(&QUAD_HANDLE).unwrap();

crates/bevy_ui/src/widget/text.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ use crate::{CalculatedSize, Node, Style, Val};
22
use bevy_asset::Assets;
33
use bevy_ecs::{
44
entity::Entity,
5-
query::{Changed, Or},
5+
query::{Changed, Or, With},
66
system::{Local, Query, QuerySet, Res, ResMut},
77
};
88
use bevy_math::Size;
99
use bevy_render::{
10-
draw::{Draw, DrawContext, Drawable},
10+
draw::{Draw, DrawContext, Drawable, WithinFrustum},
1111
mesh::Mesh,
1212
prelude::{Msaa, Visible},
1313
renderer::RenderResourceBindings,
@@ -136,7 +136,10 @@ pub fn draw_text_system(
136136
meshes: Res<Assets<Mesh>>,
137137
mut render_resource_bindings: ResMut<RenderResourceBindings>,
138138
text_pipeline: Res<DefaultTextPipeline>,
139-
mut query: Query<(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform)>,
139+
mut query: Query<
140+
(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform),
141+
With<WithinFrustum>,
142+
>,
140143
) {
141144
let scale_factor = if let Some(window) = windows.get_primary() {
142145
window.scale_factor()

0 commit comments

Comments
 (0)