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] - Take DirectionalLight's GlobalTransform into account when calculating shadow map volume (not just direction) #6384

Closed
wants to merge 10 commits into from
39 changes: 39 additions & 0 deletions crates/bevy_pbr/src/light.rs
Expand Up @@ -167,13 +167,51 @@ impl Default for SpotLight {
/// | 32,000–100,000 | Direct sunlight |
///
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
///
/// ## Shadows
///
/// To enable shadows, set the `shadows_enabled` property to `true`.
///
/// While directional lights contribute to the illumination of meshes regardless
/// of their (or the meshes') positions, currently only a limited region of the scene
/// (the _shadow volume_) can cast and receive shadows for any given directional light.
///
/// The shadow volume is a _rectangular cuboid_, with left/right/bottom/top/near/far
/// planes controllable via the `shadow_projection` field. It is affected by the
/// directional light entity's [`GlobalTransform`], and as such can be freely repositioned in the
/// scene, (or even scaled!) without affecting illumination in any other way, by simply
/// moving (or scaling) the entity around. The shadow volume is always oriented towards the
/// light entity's forward direction.
///
/// For smaller scenes, a static directional light with a preset volume is typically
/// sufficient. For larger scenes with movable cameras, you might want to introduce
/// a system that dynamically repositions and scales the light entity (and therefore
/// its shadow volume) based on the scene subject's position (e.g. a player character)
/// and its relative distance to the camera.
///
/// Shadows are produced via [shadow mapping](https://en.wikipedia.org/wiki/Shadow_mapping).
/// To control the resolution of the shadow maps, use the [`DirectionalLightShadowMap`] resource:
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_pbr::DirectionalLightShadowMap;
/// App::new()
/// .insert_resource(DirectionalLightShadowMap { size: 2048 });
/// ```
///
/// **Note:** Very large shadow map resolutions (> 4K) can have non-negligible performance and
/// memory impact, and not work properly under mobile or lower-end hardware. To improve the visual
/// fidelity of shadow maps, it's typically advisable to first reduce the `shadow_projection`
/// left/right/top/bottom to a scene-appropriate size, before ramping up the shadow map
/// resolution.
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Default)]
pub struct DirectionalLight {
pub color: Color,
/// Illuminance in lux
pub illuminance: f32,
pub shadows_enabled: bool,
/// A projection that controls the volume in which shadow maps are rendered
pub shadow_projection: OrthographicProjection,
pub shadow_depth_bias: f32,
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
Expand Down Expand Up @@ -208,6 +246,7 @@ impl DirectionalLight {
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
}

/// Controls the resolution of [`DirectionalLight`] shadow maps.
#[derive(Resource, Clone, Debug, Reflect)]
#[reflect(Resource)]
pub struct DirectionalLightShadowMap {
Expand Down
38 changes: 14 additions & 24 deletions crates/bevy_pbr/src/render/light.rs
Expand Up @@ -10,7 +10,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::{
camera::{Camera, CameraProjection},
color::Color,
Expand Down Expand Up @@ -66,7 +66,7 @@ pub struct ExtractedPointLight {
pub struct ExtractedDirectionalLight {
color: Color,
illuminance: f32,
direction: Vec3,
transform: GlobalTransform,
projection: Mat4,
shadows_enabled: bool,
shadow_depth_bias: f32,
Expand Down Expand Up @@ -550,32 +550,27 @@ pub fn extract_lights(
continue;
}

// Calulate the directional light shadow map texel size using the largest x,y dimension of
// Calculate the directional light shadow map texel size using the scaled x,y length of
// the orthographic projection divided by the shadow map resolution
// NOTE: When using various PCF kernel sizes, this will need to be adjusted, according to:
// https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/
let largest_dimension = (directional_light.shadow_projection.right
- directional_light.shadow_projection.left)
.max(
directional_light.shadow_projection.top
- directional_light.shadow_projection.bottom,
);
let directional_light_texel_size =
largest_dimension / directional_light_shadow_map.size as f32;
let directional_light_texel_size = transform.radius_vec3a(Vec3A::new(
coreh marked this conversation as resolved.
Show resolved Hide resolved
directional_light.shadow_projection.right - directional_light.shadow_projection.left,
directional_light.shadow_projection.top - directional_light.shadow_projection.bottom,
0.,
)) / directional_light_shadow_map.size as f32;
// TODO: As above
let render_visible_entities = visible_entities.clone();
commands.get_or_spawn(entity).insert((
ExtractedDirectionalLight {
color: directional_light.color,
illuminance: directional_light.illuminance,
direction: transform.forward(),
transform: *transform,
projection: directional_light.shadow_projection.get_projection_matrix(),
shadows_enabled: directional_light.shadows_enabled,
shadow_depth_bias: directional_light.shadow_depth_bias,
// The factor of SQRT_2 is for the worst-case diagonal offset
shadow_normal_bias: directional_light.shadow_normal_bias
* directional_light_texel_size
* std::f32::consts::SQRT_2,
* directional_light_texel_size,
},
render_visible_entities,
));
Expand Down Expand Up @@ -947,7 +942,7 @@ pub fn prepare_lights(
}

// direction is negated to be ready for N.L
let dir_to_light = -light.direction;
let dir_to_light = light.transform.back();

// convert from illuminance (lux) to candelas
//
Expand All @@ -961,9 +956,8 @@ pub fn prepare_lights(
let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
let intensity = light.illuminance * exposure;

// NOTE: A directional light seems to have to have an eye position on the line along the direction of the light
// through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this.
let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y);
// NOTE: For the purpose of rendering shadow maps, we apply the directional light's transform to an orthographic camera
let view = light.transform.compute_matrix().inverse();
// NOTE: This orthographic projection defines the volume within which shadows from a directional light can be cast
let projection = light.projection;

Expand Down Expand Up @@ -1175,10 +1169,6 @@ pub fn prepare_lights(
.enumerate()
.take(directional_shadow_maps_count)
{
// NOTE: A directional light seems to have to have an eye position on the line along the direction of the light
// through the world origin. I (Rob Swain) do not yet understand why it cannot be translated away from this.
let view = Mat4::look_at_rh(Vec3::ZERO, light.direction, Vec3::Y);

let depth_texture_view =
directional_light_depth_texture
.texture
Expand Down Expand Up @@ -1206,7 +1196,7 @@ pub fn prepare_lights(
directional_light_shadow_map.size as u32,
directional_light_shadow_map.size as u32,
),
transform: GlobalTransform::from(view.inverse()),
transform: light.transform,
projection: light.projection,
hdr: false,
},
Expand Down