Make DirectionalLight
Cascades
computation generic over CameraProjection
(#9226)
# Objective Fixes https://github.com/bevyengine/bevy/issues/9077 (see this issue for motivations) ## Solution Implement 1 and 2 of the "How to fix it" section of https://github.com/bevyengine/bevy/issues/9077 `update_directional_light_cascades` is split into `clear_directional_light_cascades` and a generic `build_directional_light_cascades`, to clear once and potentially insert many times. --- ## Changelog `DirectionalLight`'s computation is now generic over `CameraProjection` and can work with custom camera projections. ## Migration Guide If you have a component `MyCustomProjection` that implements `CameraProjection`: - You need to implement a new required associated method, `get_frustum_corners`, returning an array of the corners of a subset of the frustum with given `z_near` and `z_far`, in local camera space. - You can now add the `build_directional_light_cascades::<MyCustomProjection>` system in `SimulationLightSystems::UpdateDirectionalLightCascades` after `clear_directional_light_cascades` for your projection to work with directional lights. --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
3ec52c2bdb
commit
c376954b87
@ -58,10 +58,17 @@ use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, AssetApp, Assets, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::CameraUpdateSystem, extract_component::ExtractComponentPlugin,
|
||||
extract_resource::ExtractResourcePlugin, prelude::Color, render_asset::prepare_assets,
|
||||
render_graph::RenderGraph, render_phase::sort_phase_system, render_resource::Shader,
|
||||
texture::Image, view::VisibilitySystems, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
camera::{CameraUpdateSystem, Projection},
|
||||
extract_component::ExtractComponentPlugin,
|
||||
extract_resource::ExtractResourcePlugin,
|
||||
prelude::Color,
|
||||
render_asset::prepare_assets,
|
||||
render_graph::RenderGraph,
|
||||
render_phase::sort_phase_system,
|
||||
render_resource::Shader,
|
||||
texture::Image,
|
||||
view::VisibilitySystems,
|
||||
ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_transform::TransformSystem;
|
||||
use environment_map::EnvironmentMapPlugin;
|
||||
@ -273,7 +280,11 @@ impl Plugin for PbrPlugin {
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.after(VisibilitySystems::CheckVisibility)
|
||||
.after(CameraUpdateSystem),
|
||||
update_directional_light_cascades
|
||||
(
|
||||
clear_directional_light_cascades,
|
||||
build_directional_light_cascades::<Projection>,
|
||||
)
|
||||
.chain()
|
||||
.in_set(SimulationLightSystems::UpdateDirectionalLightCascades)
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.after(CameraUpdateSystem),
|
||||
|
@ -1,14 +1,13 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::{Mat4, Rect, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::Camera,
|
||||
camera::{Camera, CameraProjection},
|
||||
color::Color,
|
||||
extract_component::ExtractComponent,
|
||||
extract_resource::ExtractResource,
|
||||
prelude::Projection,
|
||||
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
|
||||
render_resource::BufferBindingType,
|
||||
renderer::RenderDevice,
|
||||
@ -397,9 +396,18 @@ pub struct Cascade {
|
||||
pub(crate) texel_size: f32,
|
||||
}
|
||||
|
||||
pub fn update_directional_light_cascades(
|
||||
pub fn clear_directional_light_cascades(mut lights: Query<(&DirectionalLight, &mut Cascades)>) {
|
||||
for (directional_light, mut cascades) in lights.iter_mut() {
|
||||
if !directional_light.shadows_enabled {
|
||||
continue;
|
||||
}
|
||||
cascades.cascades.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_directional_light_cascades<P: CameraProjection + Component>(
|
||||
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
|
||||
views: Query<(Entity, &GlobalTransform, &Projection, &Camera)>,
|
||||
views: Query<(Entity, &GlobalTransform, &P, &Camera)>,
|
||||
mut lights: Query<(
|
||||
&GlobalTransform,
|
||||
&DirectionalLight,
|
||||
@ -432,7 +440,6 @@ pub fn update_directional_light_cascades(
|
||||
let light_to_world = Mat4::from_quat(transform.compute_transform().rotation);
|
||||
let light_to_world_inverse = light_to_world.inverse();
|
||||
|
||||
cascades.cascades.clear();
|
||||
for (view_entity, projection, view_to_world) in views.iter().copied() {
|
||||
let camera_to_light_view = light_to_world_inverse * view_to_world;
|
||||
let view_cascades = cascades_config
|
||||
@ -449,17 +456,8 @@ pub fn update_directional_light_cascades(
|
||||
};
|
||||
let z_far = -far_bound;
|
||||
|
||||
let corners = match projection {
|
||||
Projection::Perspective(projection) => frustum_corners(
|
||||
projection.aspect_ratio,
|
||||
(projection.fov / 2.).tan(),
|
||||
z_near,
|
||||
z_far,
|
||||
),
|
||||
Projection::Orthographic(projection) => {
|
||||
frustum_corners_ortho(projection.area, z_near, z_far)
|
||||
}
|
||||
};
|
||||
let corners = projection.get_frustum_corners(z_near, z_far);
|
||||
|
||||
calculate_cascade(
|
||||
corners,
|
||||
directional_light_shadow_map.size as f32,
|
||||
@ -473,36 +471,6 @@ pub fn update_directional_light_cascades(
|
||||
}
|
||||
}
|
||||
|
||||
fn frustum_corners_ortho(area: Rect, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
||||
[
|
||||
Vec3A::new(area.max.x, area.min.y, z_near), // bottom right
|
||||
Vec3A::new(area.max.x, area.max.y, z_near), // top right
|
||||
Vec3A::new(area.min.x, area.max.y, z_near), // top left
|
||||
Vec3A::new(area.min.x, area.min.y, z_near), // bottom left
|
||||
Vec3A::new(area.max.x, area.min.y, z_far), // bottom right
|
||||
Vec3A::new(area.max.x, area.max.y, z_far), // top right
|
||||
Vec3A::new(area.min.x, area.max.y, z_far), // top left
|
||||
Vec3A::new(area.min.x, area.min.y, z_far), // bottom left
|
||||
]
|
||||
}
|
||||
|
||||
fn frustum_corners(aspect_ratio: f32, tan_half_fov: f32, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
let a = z_near.abs() * tan_half_fov;
|
||||
let b = z_far.abs() * tan_half_fov;
|
||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
||||
[
|
||||
Vec3A::new(a * aspect_ratio, -a, z_near), // bottom right
|
||||
Vec3A::new(a * aspect_ratio, a, z_near), // top right
|
||||
Vec3A::new(-a * aspect_ratio, a, z_near), // top left
|
||||
Vec3A::new(-a * aspect_ratio, -a, z_near), // bottom left
|
||||
Vec3A::new(b * aspect_ratio, -b, z_far), // bottom right
|
||||
Vec3A::new(b * aspect_ratio, b, z_far), // top right
|
||||
Vec3A::new(-b * aspect_ratio, b, z_far), // top left
|
||||
Vec3A::new(-b * aspect_ratio, -b, z_far), // bottom left
|
||||
]
|
||||
}
|
||||
|
||||
/// Returns a [`Cascade`] for the frustum defined by `frustum_corners`.
|
||||
/// The corner vertices should be specified in the following order:
|
||||
/// first the bottom right, top right, top left, bottom left for the near plane, then similar for the far plane.
|
||||
|
@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
||||
|
||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
||||
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
|
||||
use bevy_math::{Mat4, Rect, Vec2};
|
||||
use bevy_math::{Mat4, Rect, Vec2, Vec3A};
|
||||
use bevy_reflect::{
|
||||
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
|
||||
};
|
||||
@ -58,6 +58,7 @@ pub trait CameraProjection {
|
||||
fn get_projection_matrix(&self) -> Mat4;
|
||||
fn update(&mut self, width: f32, height: f32);
|
||||
fn far(&self) -> f32;
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];
|
||||
}
|
||||
|
||||
/// A configurable [`CameraProjection`] that can select its projection type at runtime.
|
||||
@ -101,6 +102,13 @@ impl CameraProjection for Projection {
|
||||
Projection::Orthographic(projection) => projection.far(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
match self {
|
||||
Projection::Perspective(projection) => projection.get_frustum_corners(z_near, z_far),
|
||||
Projection::Orthographic(projection) => projection.get_frustum_corners(z_near, z_far),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Projection {
|
||||
@ -153,6 +161,24 @@ impl CameraProjection for PerspectiveProjection {
|
||||
fn far(&self) -> f32 {
|
||||
self.far
|
||||
}
|
||||
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
let tan_half_fov = (self.fov / 2.).tan();
|
||||
let a = z_near.abs() * tan_half_fov;
|
||||
let b = z_far.abs() * tan_half_fov;
|
||||
let aspect_ratio = self.aspect_ratio;
|
||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
||||
[
|
||||
Vec3A::new(a * aspect_ratio, -a, z_near), // bottom right
|
||||
Vec3A::new(a * aspect_ratio, a, z_near), // top right
|
||||
Vec3A::new(-a * aspect_ratio, a, z_near), // top left
|
||||
Vec3A::new(-a * aspect_ratio, -a, z_near), // bottom left
|
||||
Vec3A::new(b * aspect_ratio, -b, z_far), // bottom right
|
||||
Vec3A::new(b * aspect_ratio, b, z_far), // top right
|
||||
Vec3A::new(-b * aspect_ratio, b, z_far), // top left
|
||||
Vec3A::new(-b * aspect_ratio, -b, z_far), // bottom left
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PerspectiveProjection {
|
||||
@ -309,6 +335,21 @@ impl CameraProjection for OrthographicProjection {
|
||||
fn far(&self) -> f32 {
|
||||
self.far
|
||||
}
|
||||
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8] {
|
||||
let area = self.area;
|
||||
// NOTE: These vertices are in the specific order required by [`calculate_cascade`].
|
||||
[
|
||||
Vec3A::new(area.max.x, area.min.y, z_near), // bottom right
|
||||
Vec3A::new(area.max.x, area.max.y, z_near), // top right
|
||||
Vec3A::new(area.min.x, area.max.y, z_near), // top left
|
||||
Vec3A::new(area.min.x, area.min.y, z_near), // bottom left
|
||||
Vec3A::new(area.max.x, area.min.y, z_far), // bottom right
|
||||
Vec3A::new(area.max.x, area.max.y, z_far), // top right
|
||||
Vec3A::new(area.min.x, area.max.y, z_far), // top left
|
||||
Vec3A::new(area.min.x, area.min.y, z_far), // bottom left
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OrthographicProjection {
|
||||
|
Loading…
Reference in New Issue
Block a user