Add orthographic camera support back to directional shadows (#7796)
# Objective Fixes #7797 ## Solution This **seems** like a simple fix, but I'm not 100% confident and I may have messed up the math in some way. In particular, I'm not sure what I should be using for an FOV value. However, this seems to be producing similar results to 0.9. Here's the `orthographic` example with a default directional light. edit: better screen grab below.
This commit is contained in:
parent
91ff782439
commit
a39c22386b
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
|
use bevy_math::{Mat4, Rect, UVec2, UVec3, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::Camera,
|
camera::Camera,
|
||||||
@ -413,19 +413,12 @@ pub fn update_directional_light_cascades(
|
|||||||
) {
|
) {
|
||||||
let views = views
|
let views = views
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|view| match view {
|
.filter_map(|(entity, transform, projection, camera)| {
|
||||||
// TODO: orthographic camera projection support.
|
if camera.is_active {
|
||||||
(entity, transform, Projection::Perspective(projection), camera)
|
Some((entity, projection, transform.compute_matrix()))
|
||||||
if camera.is_active =>
|
} else {
|
||||||
{
|
None
|
||||||
Some((
|
|
||||||
entity,
|
|
||||||
projection.aspect_ratio,
|
|
||||||
(0.5 * projection.fov).tan(),
|
|
||||||
transform.compute_matrix(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@ -444,27 +437,38 @@ pub fn update_directional_light_cascades(
|
|||||||
let light_to_world_inverse = light_to_world.inverse();
|
let light_to_world_inverse = light_to_world.inverse();
|
||||||
|
|
||||||
cascades.cascades.clear();
|
cascades.cascades.clear();
|
||||||
for (view_entity, aspect_ratio, tan_half_fov, view_to_world) in views.iter().copied() {
|
for (view_entity, projection, view_to_world) in views.iter().copied() {
|
||||||
let camera_to_light_view = light_to_world_inverse * view_to_world;
|
let camera_to_light_view = light_to_world_inverse * view_to_world;
|
||||||
let view_cascades = cascades_config
|
let view_cascades = cascades_config
|
||||||
.bounds
|
.bounds
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, far_bound)| {
|
.map(|(idx, far_bound)| {
|
||||||
|
// Negate bounds as -z is camera forward direction.
|
||||||
|
let z_near = if idx > 0 {
|
||||||
|
(1.0 - cascades_config.overlap_proportion)
|
||||||
|
* -cascades_config.bounds[idx - 1]
|
||||||
|
} else {
|
||||||
|
-cascades_config.minimum_distance
|
||||||
|
};
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
};
|
||||||
calculate_cascade(
|
calculate_cascade(
|
||||||
aspect_ratio,
|
corners,
|
||||||
tan_half_fov,
|
|
||||||
directional_light_shadow_map.size as f32,
|
directional_light_shadow_map.size as f32,
|
||||||
light_to_world,
|
light_to_world,
|
||||||
camera_to_light_view,
|
camera_to_light_view,
|
||||||
// Negate bounds as -z is camera forward direction.
|
|
||||||
if idx > 0 {
|
|
||||||
(1.0 - cascades_config.overlap_proportion)
|
|
||||||
* -cascades_config.bounds[idx - 1]
|
|
||||||
} else {
|
|
||||||
-cascades_config.minimum_distance
|
|
||||||
},
|
|
||||||
-far_bound,
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -473,37 +477,45 @@ 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.
|
||||||
fn calculate_cascade(
|
fn calculate_cascade(
|
||||||
aspect_ratio: f32,
|
frustum_corners: [Vec3A; 8],
|
||||||
tan_half_fov: f32,
|
|
||||||
cascade_texture_size: f32,
|
cascade_texture_size: f32,
|
||||||
light_to_world: Mat4,
|
light_to_world: Mat4,
|
||||||
camera_to_light: Mat4,
|
camera_to_light: Mat4,
|
||||||
z_near: f32,
|
|
||||||
z_far: f32,
|
|
||||||
) -> Cascade {
|
) -> Cascade {
|
||||||
debug_assert!(z_near <= 0.0, "z_near {z_near} must be <= 0.0");
|
|
||||||
debug_assert!(z_far <= 0.0, "z_far {z_far} must be <= 0.0");
|
|
||||||
// NOTE: This whole function is very sensitive to floating point precision and instability and
|
|
||||||
// has followed instructions to avoid view dependence from the section on cascade shadow maps in
|
|
||||||
// Eric Lengyel's Foundations of Game Engine Development 2: Rendering. Be very careful when
|
|
||||||
// modifying this code!
|
|
||||||
|
|
||||||
let a = z_near.abs() * tan_half_fov;
|
|
||||||
let b = z_far.abs() * tan_half_fov;
|
|
||||||
// NOTE: These vertices are in a specific order: bottom right, top right, top left, bottom left
|
|
||||||
// for near then for far
|
|
||||||
let frustum_corners = [
|
|
||||||
Vec3A::new(a * aspect_ratio, -a, z_near),
|
|
||||||
Vec3A::new(a * aspect_ratio, a, z_near),
|
|
||||||
Vec3A::new(-a * aspect_ratio, a, z_near),
|
|
||||||
Vec3A::new(-a * aspect_ratio, -a, z_near),
|
|
||||||
Vec3A::new(b * aspect_ratio, -b, z_far),
|
|
||||||
Vec3A::new(b * aspect_ratio, b, z_far),
|
|
||||||
Vec3A::new(-b * aspect_ratio, b, z_far),
|
|
||||||
Vec3A::new(-b * aspect_ratio, -b, z_far),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut min = Vec3A::splat(f32::MAX);
|
let mut min = Vec3A::splat(f32::MAX);
|
||||||
let mut max = Vec3A::splat(f32::MIN);
|
let mut max = Vec3A::splat(f32::MIN);
|
||||||
for corner_camera_view in frustum_corners {
|
for corner_camera_view in frustum_corners {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user