Skip to content

Commit

Permalink
Make DirectionalLight Cascades computation generic over `CameraPr…
Browse files Browse the repository at this point in the history
…ojection` (#9226)

# Objective

Fixes #9077 (see this issue for
motivations)

## Solution

Implement 1 and 2 of the "How to fix it" section of
#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>
  • Loading branch information
Selene-Amanita and cart authored Nov 3, 2023
1 parent 3ec52c2 commit c376954
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 53 deletions.
21 changes: 16 additions & 5 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
62 changes: 15 additions & 47 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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.
Expand Down
43 changes: 42 additions & 1 deletion crates/bevy_render/src/camera/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit c376954

Please sign in to comment.