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 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_render::{
 | 
			
		||||
    camera::Camera,
 | 
			
		||||
@ -413,19 +413,12 @@ pub fn update_directional_light_cascades(
 | 
			
		||||
) {
 | 
			
		||||
    let views = views
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter_map(|view| match view {
 | 
			
		||||
            // TODO: orthographic camera projection support.
 | 
			
		||||
            (entity, transform, Projection::Perspective(projection), camera)
 | 
			
		||||
                if camera.is_active =>
 | 
			
		||||
            {
 | 
			
		||||
                Some((
 | 
			
		||||
                    entity,
 | 
			
		||||
                    projection.aspect_ratio,
 | 
			
		||||
                    (0.5 * projection.fov).tan(),
 | 
			
		||||
                    transform.compute_matrix(),
 | 
			
		||||
                ))
 | 
			
		||||
        .filter_map(|(entity, transform, projection, camera)| {
 | 
			
		||||
            if camera.is_active {
 | 
			
		||||
                Some((entity, projection, transform.compute_matrix()))
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
            _ => None,
 | 
			
		||||
        })
 | 
			
		||||
        .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
@ -444,27 +437,38 @@ pub fn update_directional_light_cascades(
 | 
			
		||||
        let light_to_world_inverse = light_to_world.inverse();
 | 
			
		||||
 | 
			
		||||
        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 view_cascades = cascades_config
 | 
			
		||||
                .bounds
 | 
			
		||||
                .iter()
 | 
			
		||||
                .enumerate()
 | 
			
		||||
                .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(
 | 
			
		||||
                        aspect_ratio,
 | 
			
		||||
                        tan_half_fov,
 | 
			
		||||
                        corners,
 | 
			
		||||
                        directional_light_shadow_map.size as f32,
 | 
			
		||||
                        light_to_world,
 | 
			
		||||
                        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();
 | 
			
		||||
@ -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(
 | 
			
		||||
    aspect_ratio: f32,
 | 
			
		||||
    tan_half_fov: f32,
 | 
			
		||||
    frustum_corners: [Vec3A; 8],
 | 
			
		||||
    cascade_texture_size: f32,
 | 
			
		||||
    light_to_world: Mat4,
 | 
			
		||||
    camera_to_light: Mat4,
 | 
			
		||||
    z_near: f32,
 | 
			
		||||
    z_far: f32,
 | 
			
		||||
) -> 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 max = Vec3A::splat(f32::MIN);
 | 
			
		||||
    for corner_camera_view in frustum_corners {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user