Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Frustum culling #2861

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
563e396
Add primitives to support frustum culling
superdump Sep 14, 2021
0371410
Copy over RenderLayers from the old renderer
superdump Sep 14, 2021
bc9faf3
Add functionality to Mesh to compute an AABB
superdump Sep 14, 2021
b44698b
Load AABBs from glTF models
superdump Sep 14, 2021
dd47c76
Add far() method to CameraProjection trait
superdump Sep 14, 2021
087fd7b
Add visibility types and systems
superdump Sep 14, 2021
ea546a9
Add VisibleEntities and Frustum to camera and light bundles as approp…
superdump Sep 14, 2021
b4fd252
Add Visibility and ComputedVisibility to entities to be drawn
superdump Sep 14, 2021
34f785c
Extract mesh/sprite entities based on ComputedVisibility
superdump Sep 14, 2021
c1954bc
Extract VisibleEntities for cameras
superdump Sep 14, 2021
2cb2490
Enable VisibilityPlugin within the ViewPlugin
superdump Sep 14, 2021
2a4cf48
Remove unnecessary Camera.far member
superdump Sep 14, 2021
27287cd
Pass the plane normal to Aabb::relative_radius()
superdump Sep 14, 2021
273d8fa
Add sphere - obb intsersection test
superdump Sep 14, 2021
69d2440
Add a CubeFrusta type containing 6 frusta for cube maps
superdump Sep 14, 2021
e25b1da
Add CubeFrustaVisibleEntities for use with cube maps
superdump Sep 14, 2021
69d824d
Add culling for lights
superdump Sep 14, 2021
7c03a3d
Minor renaming
superdump Sep 15, 2021
69845d2
Unify bounded entity query into visible entity query for performance
superdump Sep 15, 2021
e0c88f4
Register types to support loading from glTF models
superdump Sep 16, 2021
d5c2c2c
Fix the logic of the point light sphere vs mesh obb test
superdump Sep 16, 2021
1fe1df6
Remove unnecessary With<PointLight> in check_light_visibility query
superdump Sep 23, 2021
5727a9d
Address review comments
superdump Nov 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions pipelined/bevy_gltf2/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ use bevy_asset::{
use bevy_core::Name;
use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_math::Mat4;
use bevy_math::{Mat4, Vec3};
use bevy_pbr2::{PbrBundle, StandardMaterial};
use bevy_render2::{
camera::{
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
},
color::Color,
mesh::{Indices, Mesh, VertexAttributeValues},
primitives::Aabb,
texture::{Image, ImageType, TextureError},
};
use bevy_scene::Scene;
Expand Down Expand Up @@ -528,11 +529,17 @@ fn load_node(
let material_asset_path =
AssetPath::new_ref(load_context.path(), Some(&material_label));

parent.spawn_bundle(PbrBundle {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
});
let bounds = primitive.bounding_box();
parent
.spawn_bundle(PbrBundle {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
})
.insert(Aabb::from_min_max(
Vec3::from_slice(&bounds.min),
Vec3::from_slice(&bounds.max),
));
}
}

Expand Down
39 changes: 38 additions & 1 deletion pipelined/bevy_pbr2/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use crate::{DirectionalLight, PointLight, StandardMaterial};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render2::mesh::Mesh;
use bevy_render2::{
mesh::Mesh,
primitives::{CubemapFrusta, Frustum},
view::{ComputedVisibility, Visibility, VisibleEntities},
};
use bevy_transform::components::{GlobalTransform, Transform};

#[derive(Bundle, Clone)]
Expand All @@ -10,6 +14,10 @@ pub struct PbrBundle {
pub material: Handle<StandardMaterial>,
pub transform: Transform,
pub global_transform: GlobalTransform,
/// User indication of whether an entity is visible
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
}

impl Default for PbrBundle {
Expand All @@ -19,14 +27,41 @@ impl Default for PbrBundle {
material: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
}
}
}

#[derive(Clone, Debug, Default)]
pub struct CubemapVisibleEntities {
data: [VisibleEntities; 6],
}

impl CubemapVisibleEntities {
pub fn get(&self, i: usize) -> &VisibleEntities {
&self.data[i]
}

pub fn get_mut(&mut self, i: usize) -> &mut VisibleEntities {
&mut self.data[i]
}

pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleEntities> {
self.data.iter()
}

pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut VisibleEntities> {
self.data.iter_mut()
}
}

/// A component bundle for "point light" entities
#[derive(Debug, Bundle, Default)]
pub struct PointLightBundle {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all lights need to cast shadows, and given how expensive point light shadows are, its probably worth adding a way to disable shadows for lights. This should also skip entity visibility calculations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have another branch for that that comes when I knew it would be needed - before clustered forward rendering. Can we add it in after #3072 or do I have to move it in here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitely down to wait!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do it in whatever order works best for you :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I'll wait on this one as it's coming just after depth prepass and alpha modes. Thanks. :)

pub point_light: PointLight,
pub cubemap_visible_entities: CubemapVisibleEntities,
pub cubemap_frusta: CubemapFrusta,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
Expand All @@ -35,6 +70,8 @@ pub struct PointLightBundle {
#[derive(Debug, Bundle, Default)]
pub struct DirectionalLightBundle {
pub directional_light: DirectionalLight,
pub frustum: Frustum,
pub visible_entities: VisibleEntities,
pub transform: Transform,
pub global_transform: GlobalTransform,
}
43 changes: 39 additions & 4 deletions pipelined/bevy_pbr2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use bevy_render2::{
render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
render_resource::{Shader, SpecializedPipelines},
view::VisibilitySystems,
RenderApp, RenderStage,
};
use bevy_transform::TransformSystem;

pub mod draw_3d_graph {
pub mod node {
Expand Down Expand Up @@ -49,20 +51,53 @@ impl Plugin for PbrPlugin {
.init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.init_resource::<AmbientLight>();
.init_resource::<AmbientLight>()
.add_system_to_stage(
CoreStage::PostUpdate,
update_directional_light_frusta
.label(SimulationLightSystems::UpdateDirectionalLightFrusta)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_point_light_frusta
.label(SimulationLightSystems::UpdatePointLightFrusta)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
check_light_visibility
.label(SimulationLightSystems::CheckLightVisibility)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CalculateBounds)
.after(SimulationLightSystems::UpdateDirectionalLightFrusta)
.after(SimulationLightSystems::UpdatePointLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// which would override any results from this otherwise
.after(VisibilitySystems::CheckVisibility),
);

let render_app = app.sub_app(RenderApp);
render_app
.add_system_to_stage(RenderStage::Extract, render::extract_meshes)
.add_system_to_stage(RenderStage::Extract, render::extract_lights)
.add_system_to_stage(
RenderStage::Extract,
render::extract_lights.label(RenderLightSystems::ExtractLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_lights.exclusive_system(),
render::prepare_lights
.exclusive_system()
.label(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(RenderStage::Queue, render::queue_meshes)
.add_system_to_stage(RenderStage::Queue, render::queue_shadows)
.add_system_to_stage(
RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows),
)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::Queue, render::queue_transform_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
Expand Down
186 changes: 185 additions & 1 deletion pipelined/bevy_pbr2/src/light.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
use bevy_render2::{camera::OrthographicProjection, color::Color};
use bevy_ecs::prelude::*;
use bevy_math::Mat4;
use bevy_render2::{
camera::{CameraProjection, OrthographicProjection},
color::Color,
primitives::{Aabb, CubemapFrusta, Frustum, Sphere},
view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities, VisibleEntity},
};
use bevy_transform::components::GlobalTransform;

use crate::{CubeMapFace, CubemapVisibleEntities, CUBE_MAP_FACES};

/// A light that emits light in all directions from a central point.
///
Expand Down Expand Up @@ -156,3 +166,177 @@ impl Default for AmbientLight {
pub struct NotShadowCaster;
/// Add this component to make a `Mesh` not receive shadows
pub struct NotShadowReceiver;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum SimulationLightSystems {
UpdateDirectionalLightFrusta,
UpdatePointLightFrusta,
CheckLightVisibility,
}

pub fn update_directional_light_frusta(
mut views: Query<(&GlobalTransform, &DirectionalLight, &mut Frustum)>,
) {
for (transform, directional_light, mut frustum) in views.iter_mut() {
let view_projection = directional_light.shadow_projection.get_projection_matrix()
* transform.compute_matrix().inverse();
*frustum = Frustum::from_view_projection(
&view_projection,
&transform.translation,
&transform.back(),
directional_light.shadow_projection.far(),
);
}
}

pub fn update_point_light_frusta(
mut views: Query<(&GlobalTransform, &PointLight, &mut CubemapFrusta)>,
) {
let projection = Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1);
let view_rotations = CUBE_MAP_FACES
.iter()
.map(|CubeMapFace { target, up }| GlobalTransform::identity().looking_at(*target, *up))
.collect::<Vec<_>>();

for (transform, point_light, mut cubemap_frusta) in views.iter_mut() {
// ignore scale because we don't want to effectively scale light radius and range
// by applying those as a view transform to shadow map rendering of objects
// and ignore rotation because we want the shadow map projections to align with the axes
let view_translation = GlobalTransform::from_translation(transform.translation);
let view_backward = transform.back();

for (view_rotation, frustum) in view_rotations.iter().zip(cubemap_frusta.iter_mut()) {
let view = view_translation * *view_rotation;
let view_projection = projection * view.compute_matrix().inverse();

*frustum = Frustum::from_view_projection(
&view_projection,
&transform.translation,
&view_backward,
point_light.range,
);
}
}
}

pub fn check_light_visibility(
mut point_lights: Query<(
&PointLight,
&GlobalTransform,
&CubemapFrusta,
&mut CubemapVisibleEntities,
Option<&RenderLayers>,
)>,
mut directional_lights: Query<
(&Frustum, &mut VisibleEntities, Option<&RenderLayers>),
With<DirectionalLight>,
>,
mut visible_entity_query: Query<
(
Entity,
&Visibility,
&mut ComputedVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&GlobalTransform>,
),
Without<NotShadowCaster>,
>,
) {
// Directonal lights
for (frustum, mut visible_entities, maybe_view_mask) in directional_lights.iter_mut() {
visible_entities.entities.clear();
let view_mask = maybe_view_mask.copied().unwrap_or_default();

for (
entity,
visibility,
mut computed_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
) in visible_entity_query.iter_mut()
{
if !visibility.is_visible {
continue;
}

let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
continue;
}

// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
if !frustum.intersects_obb(aabb, &transform.compute_matrix()) {
continue;
}
}

computed_visibility.is_visible = true;
visible_entities.entities.push(VisibleEntity { entity });
}

// TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize
// to prevent holding unneeded memory
}

// Point lights
for (point_light, transform, cubemap_frusta, mut cubemap_visible_entities, maybe_view_mask) in
point_lights.iter_mut()
{
for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.entities.clear();
}
let view_mask = maybe_view_mask.copied().unwrap_or_default();
let light_sphere = Sphere {
center: transform.translation,
radius: point_light.range,
};

for (
entity,
visibility,
mut computed_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
) in visible_entity_query.iter_mut()
{
if !visibility.is_visible {
continue;
}

let entity_mask = maybe_entity_mask.copied().unwrap_or_default();
if !view_mask.intersects(&entity_mask) {
continue;
}

// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.compute_matrix();
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
if !light_sphere.intersects_obb(aabb, &model_to_world) {
continue;
}
for (frustum, visible_entities) in cubemap_frusta
.iter()
.zip(cubemap_visible_entities.iter_mut())
{
if frustum.intersects_obb(aabb, &model_to_world) {
computed_visibility.is_visible = true;
visible_entities.entities.push(VisibleEntity { entity });
}
}
} else {
computed_visibility.is_visible = true;
for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.entities.push(VisibleEntity { entity })
}
}
}

// TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize
// to prevent holding unneeded memory
}
}
Loading