mirror of
https://github.com/bevyengine/bevy
synced 2024-11-14 00:47:32 +00:00
Merge branch 'main' into res-acq
This commit is contained in:
commit
a9500897ef
40 changed files with 2468 additions and 226 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -3452,6 +3452,17 @@ description = "Demonstrates animation masks"
|
|||
category = "Animation"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "pcss"
|
||||
path = "examples/3d/pcss.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.pcss]
|
||||
name = "Percentage-closer soft shadows"
|
||||
description = "Demonstrates percentage-closer soft shadows (PCSS)"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
|
BIN
assets/environment_maps/sky_skybox.ktx2
Normal file
BIN
assets/environment_maps/sky_skybox.ktx2
Normal file
Binary file not shown.
BIN
assets/models/PalmTree/PalmTree.bin
Normal file
BIN
assets/models/PalmTree/PalmTree.bin
Normal file
Binary file not shown.
1066
assets/models/PalmTree/PalmTree.gltf
Normal file
1066
assets/models/PalmTree/PalmTree.gltf
Normal file
File diff suppressed because it is too large
Load diff
BIN
assets/models/PalmTree/StylizedWater.png
Normal file
BIN
assets/models/PalmTree/StylizedWater.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 540 KiB |
|
@ -11,6 +11,8 @@ doc-valid-idents = [
|
|||
"..",
|
||||
]
|
||||
|
||||
check-private-items = true
|
||||
|
||||
disallowed-methods = [
|
||||
{ path = "f32::powi", reason = "use bevy_math::ops::FloatPow::squared, bevy_math::ops::FloatPow::cubed, or bevy_math::ops::powf instead for libm determinism" },
|
||||
{ path = "f32::log", reason = "use bevy_math::ops::ln, bevy_math::ops::log2, or bevy_math::ops::log10 instead for libm determinism" },
|
||||
|
|
|
@ -1279,7 +1279,7 @@ impl<'w> BundleSpawner<'w> {
|
|||
unsafe { &mut self.world.world_mut().entities }
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// # Safety
|
||||
/// - `Self` must be dropped after running this function as it may invalidate internal pointers.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn flush_commands(&mut self) {
|
||||
|
@ -1344,11 +1344,13 @@ impl Bundles {
|
|||
}
|
||||
|
||||
/// # Safety
|
||||
/// A `BundleInfo` with the given `BundleId` must have been initialized for this instance of `Bundles`.
|
||||
/// A [`BundleInfo`] with the given [`BundleId`] must have been initialized for this instance of `Bundles`.
|
||||
pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo {
|
||||
self.bundle_infos.get_unchecked(id.0)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This [`BundleId`] must have been initialized with a single [`Component`] (via [`init_component_info`](Self::init_dynamic_info))
|
||||
pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType {
|
||||
*self
|
||||
.dynamic_component_storages
|
||||
|
@ -1356,6 +1358,8 @@ impl Bundles {
|
|||
.debug_checked_unwrap()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This [`BundleId`] must have been initialized with multiple [`Component`]s (via [`init_dynamic_info`](Self::init_dynamic_info))
|
||||
pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec<StorageType> {
|
||||
self.dynamic_bundle_storages
|
||||
.get_mut(&id)
|
||||
|
|
|
@ -738,7 +738,7 @@ impl Debug for ComponentDescriptor {
|
|||
}
|
||||
|
||||
impl ComponentDescriptor {
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
///
|
||||
/// `x` must point to a valid value of type `T`.
|
||||
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
|
||||
|
|
|
@ -1290,7 +1290,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: Borrow<Entity>>>
|
|||
}
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// # Safety
|
||||
/// All arguments must stem from the same valid `QueryManyIter`.
|
||||
///
|
||||
/// The lifetime here is not restrictive enough for Fetch with &mut access,
|
||||
|
@ -1578,7 +1578,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
|
|||
}
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// # Safety
|
||||
/// The lifetime here is not restrictive enough for Fetch with &mut access,
|
||||
/// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple
|
||||
/// references to the same component, leading to unique reference aliasing.
|
||||
|
@ -1733,6 +1733,9 @@ impl<D: QueryData, F: QueryFilter> Clone for QueryIterationCursor<'_, '_, D, F>
|
|||
}
|
||||
|
||||
impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
|
||||
/// # Safety
|
||||
/// - `world` must have permission to access any of the components registered in `query_state`.
|
||||
/// - `world` must be the same one used to initialize `query_state`.
|
||||
unsafe fn init_empty(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
query_state: &'s QueryState<D, F>,
|
||||
|
@ -1781,26 +1784,42 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
|
|||
}
|
||||
}
|
||||
|
||||
/// retrieve item returned from most recent `next` call again.
|
||||
/// Retrieve item returned from most recent `next` call again.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result of `next` and any previous calls to `peek_last` with this row must have been
|
||||
/// dropped to prevent aliasing mutable references.
|
||||
#[inline]
|
||||
unsafe fn peek_last(&mut self) -> Option<D::Item<'w>> {
|
||||
if self.current_row > 0 {
|
||||
let index = self.current_row - 1;
|
||||
if self.is_dense {
|
||||
let entity = self.table_entities.get_unchecked(index);
|
||||
// SAFETY: This must have been called previously in `next` as `current_row > 0`
|
||||
let entity = unsafe { self.table_entities.get_unchecked(index) };
|
||||
// SAFETY:
|
||||
// - `set_table` must have been called previously either in `next` or before it.
|
||||
// - `*entity` and `index` are in the current table.
|
||||
unsafe {
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
*entity,
|
||||
TableRow::from_usize(index),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let archetype_entity = self.archetype_entities.get_unchecked(index);
|
||||
// SAFETY: This must have been called previously in `next` as `current_row > 0`
|
||||
let archetype_entity = unsafe { self.archetype_entities.get_unchecked(index) };
|
||||
// SAFETY:
|
||||
// - `set_archetype` must have been called previously either in `next` or before it.
|
||||
// - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype.
|
||||
unsafe {
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
archetype_entity.id(),
|
||||
archetype_entity.table_row(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
///
|
||||
/// Consider using `as_readonly` or `as_nop` instead which are safe functions.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
///
|
||||
/// `NewD` must have a subset of the access that `D` does and match the exact same archetypes/tables
|
||||
/// `NewF` must have a subset of the access that `F` does and match the exact same archetypes/tables
|
||||
|
|
|
@ -507,8 +507,12 @@ mod tests {
|
|||
use super::BlobVec;
|
||||
use std::{alloc::Layout, cell::RefCell, mem::align_of, rc::Rc};
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The pointer `x` must point to a valid value of type `T` and it must be safe to drop this value.
|
||||
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
|
||||
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
|
||||
// SAFETY: It is guaranteed by the caller that `x` points to a
|
||||
// valid value of type `T` and it is safe to drop this value.
|
||||
unsafe {
|
||||
x.drop_as::<T>();
|
||||
}
|
||||
|
|
|
@ -2173,7 +2173,7 @@ pub struct DynSystemParam<'w, 's> {
|
|||
}
|
||||
|
||||
impl<'w, 's> DynSystemParam<'w, 's> {
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
/// - `state` must be a `ParamState<T>` for some inner `T: SystemParam`.
|
||||
/// - The passed [`UnsafeWorldCell`] must have access to any world data registered
|
||||
/// in [`init_state`](SystemParam::init_state) for the inner system param.
|
||||
|
@ -2248,7 +2248,7 @@ impl<'w, 's> DynSystemParam<'w, 's> {
|
|||
}
|
||||
}
|
||||
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
/// - `state` must be a `ParamState<T>` for some inner `T: SystemParam`.
|
||||
/// - The passed [`UnsafeWorldCell`] must have access to any world data registered
|
||||
/// in [`init_state`](SystemParam::init_state) for the inner system param.
|
||||
|
|
|
@ -1130,7 +1130,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||
|
||||
/// Remove the components of `bundle` from `entity`.
|
||||
///
|
||||
/// SAFETY:
|
||||
/// # Safety
|
||||
/// - A `BundleInfo` with the corresponding `BundleId` must have been initialized.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation {
|
||||
|
@ -1519,7 +1519,8 @@ impl<'w> EntityWorldMut<'w> {
|
|||
}
|
||||
}
|
||||
|
||||
/// SAFETY: all components in the archetype must exist in world
|
||||
/// # Safety
|
||||
/// All components in the archetype must exist in world
|
||||
unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||
deferred_world: &mut DeferredWorld,
|
||||
archetype: &Archetype,
|
||||
|
@ -2387,6 +2388,8 @@ impl<'w, B> EntityRefExcept<'w, B>
|
|||
where
|
||||
B: Bundle,
|
||||
{
|
||||
/// # Safety
|
||||
/// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`.
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
|
@ -2466,6 +2469,8 @@ impl<'w, B> EntityMutExcept<'w, B>
|
|||
where
|
||||
B: Bundle,
|
||||
{
|
||||
/// # Safety
|
||||
/// Other users of `UnsafeEntityCell` must not have access to any components not in `B`.
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
|
|
|
@ -248,7 +248,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
|||
}
|
||||
|
||||
/// Retrieves this world's [`Observers`] collection.
|
||||
pub(crate) unsafe fn observers(self) -> &'w Observers {
|
||||
pub(crate) fn observers(self) -> &'w Observers {
|
||||
// SAFETY:
|
||||
// - we only access world metadata
|
||||
&unsafe { self.world_metadata() }.observers
|
||||
|
@ -1002,7 +1002,7 @@ impl<'w> UnsafeEntityCell<'w> {
|
|||
|
||||
impl<'w> UnsafeWorldCell<'w> {
|
||||
#[inline]
|
||||
/// # Safety:
|
||||
/// # Safety
|
||||
/// - the returned `Table` is only used in ways that this [`UnsafeWorldCell`] has permission for.
|
||||
/// - the returned `Table` is only used in ways that would not conflict with any existing borrows of world data.
|
||||
unsafe fn fetch_table(self, location: EntityLocation) -> Option<&'w Table> {
|
||||
|
@ -1013,7 +1013,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
/// # Safety:
|
||||
/// # Safety
|
||||
/// - the returned `ComponentSparseSet` is only used in ways that this [`UnsafeWorldCell`] has permission for.
|
||||
/// - the returned `ComponentSparseSet` is only used in ways that would not conflict with any existing
|
||||
/// borrows of world data.
|
||||
|
|
|
@ -152,6 +152,10 @@ pub struct GpuClusterableObject {
|
|||
pub(crate) shadow_depth_bias: f32,
|
||||
pub(crate) shadow_normal_bias: f32,
|
||||
pub(crate) spot_light_tan_angle: f32,
|
||||
pub(crate) soft_shadow_size: f32,
|
||||
pub(crate) shadow_map_near_z: f32,
|
||||
pub(crate) pad_a: f32,
|
||||
pub(crate) pad_b: f32,
|
||||
}
|
||||
|
||||
pub enum GpuClusterableObjects {
|
||||
|
|
|
@ -259,11 +259,11 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
|
|||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
|
||||
21,
|
||||
22,
|
||||
));
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
|
||||
22,
|
||||
23,
|
||||
));
|
||||
|
||||
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||
|
|
|
@ -50,7 +50,11 @@ use super::*;
|
|||
#[derive(Component, Debug, Clone, Reflect)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct DirectionalLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
/// By default, this is white.
|
||||
pub color: Color,
|
||||
|
||||
/// Illuminance in lux (lumens per square meter), representing the amount of
|
||||
/// light projected onto surfaces by this light source. Lux is used here
|
||||
/// instead of lumens because a directional light illuminates all surfaces
|
||||
|
@ -58,10 +62,45 @@ pub struct DirectionalLight {
|
|||
/// can only be specified for light sources which emit light from a specific
|
||||
/// area.
|
||||
pub illuminance: f32,
|
||||
|
||||
/// Whether this light casts shadows.
|
||||
///
|
||||
/// Note that shadows are rather expensive and become more so with every
|
||||
/// light that casts them. In general, it's best to aggressively limit the
|
||||
/// number of lights with shadows enabled to one or two at most.
|
||||
pub shadows_enabled: bool,
|
||||
|
||||
/// Whether soft shadows are enabled, and if so, the size of the light.
|
||||
///
|
||||
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
|
||||
/// cause shadows to become blurrier (i.e. their penumbra increases in
|
||||
/// radius) as they extend away from objects. The blurriness of the shadow
|
||||
/// depends on the size of the light; larger lights result in larger
|
||||
/// penumbras and therefore blurrier shadows.
|
||||
///
|
||||
/// Currently, soft shadows are rather noisy if not using the temporal mode.
|
||||
/// If you enable soft shadows, consider choosing
|
||||
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
|
||||
/// (TAA) to smooth the noise out over time.
|
||||
///
|
||||
/// Note that soft shadows are significantly more expensive to render than
|
||||
/// hard shadows.
|
||||
pub soft_shadow_size: Option<f32>,
|
||||
|
||||
/// A value that adjusts the tradeoff between self-shadowing artifacts and
|
||||
/// proximity of shadows to their casters.
|
||||
///
|
||||
/// This value frequently must be tuned to the specific scene; this is
|
||||
/// normal and a well-known part of the shadow mapping workflow. If set too
|
||||
/// low, unsightly shadow patterns appear on objects not in shadow as
|
||||
/// objects incorrectly cast shadows on themselves, known as *shadow acne*.
|
||||
/// If set too high, shadows detach from the objects casting them and seem
|
||||
/// to "fly" off the objects, known as *Peter Panning*.
|
||||
pub shadow_depth_bias: f32,
|
||||
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
|
||||
/// shadow map's texel size so that it is automatically adjusted to the orthographic projection.
|
||||
|
||||
/// A bias applied along the direction of the fragment's surface normal. It
|
||||
/// is scaled to the shadow map's texel size so that it is automatically
|
||||
/// adjusted to the orthographic projection.
|
||||
pub shadow_normal_bias: f32,
|
||||
}
|
||||
|
||||
|
@ -71,6 +110,7 @@ impl Default for DirectionalLight {
|
|||
color: Color::WHITE,
|
||||
illuminance: light_consts::lux::AMBIENT_DAYLIGHT,
|
||||
shadows_enabled: false,
|
||||
soft_shadow_size: None,
|
||||
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
|
||||
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
|
||||
}
|
||||
|
|
|
@ -580,8 +580,6 @@ pub fn update_point_light_frusta(
|
|||
Or<(Changed<GlobalTransform>, Changed<PointLight>)>,
|
||||
>,
|
||||
) {
|
||||
let clip_from_view =
|
||||
Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z);
|
||||
let view_rotations = CUBE_MAP_FACES
|
||||
.iter()
|
||||
.map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
|
||||
|
@ -597,6 +595,12 @@ pub fn update_point_light_frusta(
|
|||
continue;
|
||||
}
|
||||
|
||||
let clip_from_view = Mat4::perspective_infinite_reverse_rh(
|
||||
std::f32::consts::FRAC_PI_2,
|
||||
1.0,
|
||||
point_light.shadow_map_near_z,
|
||||
);
|
||||
|
||||
// ignore scale because we don't want to effectively scale light radius and range
|
||||
// by applying those as a view transform to shadow map rendering of objects
|
||||
// and ignore rotation because we want the shadow map projections to align with the axes
|
||||
|
@ -639,7 +643,8 @@ pub fn update_spot_light_frusta(
|
|||
let view_backward = transform.back();
|
||||
|
||||
let spot_world_from_view = spot_light_world_from_view(transform);
|
||||
let spot_clip_from_view = spot_light_clip_from_view(spot_light.outer_angle);
|
||||
let spot_clip_from_view =
|
||||
spot_light_clip_from_view(spot_light.outer_angle, spot_light.shadow_map_near_z);
|
||||
let clip_from_world = spot_clip_from_view * spot_world_from_view.inverse();
|
||||
|
||||
*frustum = Frustum::from_clip_from_world_custom_far(
|
||||
|
|
|
@ -22,29 +22,63 @@ use super::*;
|
|||
pub struct PointLight {
|
||||
/// The color of this light source.
|
||||
pub color: Color,
|
||||
|
||||
/// Luminous power in lumens, representing the amount of light emitted by this source in all directions.
|
||||
pub intensity: f32,
|
||||
|
||||
/// Cut-off for the light's area-of-effect. Fragments outside this range will not be affected by
|
||||
/// this light at all, so it's important to tune this together with `intensity` to prevent hard
|
||||
/// lighting cut-offs.
|
||||
pub range: f32,
|
||||
/// Simulates a light source coming from a spherical volume with the given radius. Only affects
|
||||
/// the size of specular highlights created by this light. Because of this, large values may not
|
||||
/// produce the intended result -- for example, light radius does not affect shadow softness or
|
||||
/// diffuse lighting.
|
||||
|
||||
/// Simulates a light source coming from a spherical volume with the given
|
||||
/// radius.
|
||||
///
|
||||
/// This affects the size of specular highlights created by this light, as
|
||||
/// well as the soft shadow penumbra size. Because of this, large values may
|
||||
/// not produce the intended result -- for example, light radius does not
|
||||
/// affect shadow softness or diffuse lighting.
|
||||
pub radius: f32,
|
||||
|
||||
/// Whether this light casts shadows.
|
||||
pub shadows_enabled: bool,
|
||||
|
||||
/// Whether soft shadows are enabled.
|
||||
///
|
||||
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
|
||||
/// cause shadows to become blurrier (i.e. their penumbra increases in
|
||||
/// radius) as they extend away from objects. The blurriness of the shadow
|
||||
/// depends on the [`PointLight::radius`] of the light; larger lights result
|
||||
/// in larger penumbras and therefore blurrier shadows.
|
||||
///
|
||||
/// Currently, soft shadows are rather noisy if not using the temporal mode.
|
||||
/// If you enable soft shadows, consider choosing
|
||||
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
|
||||
/// (TAA) to smooth the noise out over time.
|
||||
///
|
||||
/// Note that soft shadows are significantly more expensive to render than
|
||||
/// hard shadows.
|
||||
pub soft_shadows_enabled: bool,
|
||||
|
||||
/// A bias used when sampling shadow maps to avoid "shadow-acne", or false shadow occlusions
|
||||
/// that happen as a result of shadow-map fragments not mapping 1:1 to screen-space fragments.
|
||||
/// Too high of a depth bias can lead to shadows detaching from their casters, or
|
||||
/// "peter-panning". This bias can be tuned together with `shadow_normal_bias` to correct shadow
|
||||
/// artifacts for a given scene.
|
||||
pub shadow_depth_bias: f32,
|
||||
|
||||
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
|
||||
/// shadow map's texel size so that it can be small close to the camera and gets larger further
|
||||
/// away.
|
||||
pub shadow_normal_bias: f32,
|
||||
|
||||
/// The distance from the light to near Z plane in the shadow map.
|
||||
///
|
||||
/// Objects closer than this distance to the light won't cast shadows.
|
||||
/// Setting this higher increases the shadow map's precision.
|
||||
///
|
||||
/// This only has an effect if shadows are enabled.
|
||||
pub shadow_map_near_z: f32,
|
||||
}
|
||||
|
||||
impl Default for PointLight {
|
||||
|
@ -58,8 +92,10 @@ impl Default for PointLight {
|
|||
range: 20.0,
|
||||
radius: 0.0,
|
||||
shadows_enabled: false,
|
||||
soft_shadows_enabled: false,
|
||||
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
|
||||
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
|
||||
shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,4 +103,5 @@ impl Default for PointLight {
|
|||
impl PointLight {
|
||||
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.08;
|
||||
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
|
||||
pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1;
|
||||
}
|
||||
|
|
|
@ -7,23 +7,85 @@ use super::*;
|
|||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct SpotLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
/// By default, this is white.
|
||||
pub color: Color,
|
||||
|
||||
/// Luminous power in lumens, representing the amount of light emitted by this source in all directions.
|
||||
pub intensity: f32,
|
||||
|
||||
/// Range in meters that this light illuminates.
|
||||
///
|
||||
/// Note that this value affects resolution of the shadow maps; generally, the
|
||||
/// higher you set it, the lower-resolution your shadow maps will be.
|
||||
/// Consequently, you should set this value to be only the size that you need.
|
||||
pub range: f32,
|
||||
|
||||
/// Simulates a light source coming from a spherical volume with the given
|
||||
/// radius.
|
||||
///
|
||||
/// This affects the size of specular highlights created by this light, as
|
||||
/// well as the soft shadow penumbra size. Because of this, large values may
|
||||
/// not produce the intended result -- for example, light radius does not
|
||||
/// affect shadow softness or diffuse lighting.
|
||||
pub radius: f32,
|
||||
|
||||
/// Whether this light casts shadows.
|
||||
///
|
||||
/// Note that shadows are rather expensive and become more so with every
|
||||
/// light that casts them. In general, it's best to aggressively limit the
|
||||
/// number of lights with shadows enabled to one or two at most.
|
||||
pub shadows_enabled: bool,
|
||||
|
||||
/// Whether soft shadows are enabled.
|
||||
///
|
||||
/// Soft shadows, also known as *percentage-closer soft shadows* or PCSS,
|
||||
/// cause shadows to become blurrier (i.e. their penumbra increases in
|
||||
/// radius) as they extend away from objects. The blurriness of the shadow
|
||||
/// depends on the [`SpotLight::radius`] of the light; larger lights result in larger
|
||||
/// penumbras and therefore blurrier shadows.
|
||||
///
|
||||
/// Currently, soft shadows are rather noisy if not using the temporal mode.
|
||||
/// If you enable soft shadows, consider choosing
|
||||
/// [`ShadowFilteringMethod::Temporal`] and enabling temporal antialiasing
|
||||
/// (TAA) to smooth the noise out over time.
|
||||
///
|
||||
/// Note that soft shadows are significantly more expensive to render than
|
||||
/// hard shadows.
|
||||
pub soft_shadows_enabled: bool,
|
||||
|
||||
/// A value that adjusts the tradeoff between self-shadowing artifacts and
|
||||
/// proximity of shadows to their casters.
|
||||
///
|
||||
/// This value frequently must be tuned to the specific scene; this is
|
||||
/// normal and a well-known part of the shadow mapping workflow. If set too
|
||||
/// low, unsightly shadow patterns appear on objects not in shadow as
|
||||
/// objects incorrectly cast shadows on themselves, known as *shadow acne*.
|
||||
/// If set too high, shadows detach from the objects casting them and seem
|
||||
/// to "fly" off the objects, known as *Peter Panning*.
|
||||
pub shadow_depth_bias: f32,
|
||||
|
||||
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
|
||||
/// shadow map's texel size so that it can be small close to the camera and gets larger further
|
||||
/// away.
|
||||
pub shadow_normal_bias: f32,
|
||||
|
||||
/// The distance from the light to the near Z plane in the shadow map.
|
||||
///
|
||||
/// Objects closer than this distance to the light won't cast shadows.
|
||||
/// Setting this higher increases the shadow map's precision.
|
||||
///
|
||||
/// This only has an effect if shadows are enabled.
|
||||
pub shadow_map_near_z: f32,
|
||||
|
||||
/// Angle defining the distance from the spot light direction to the outer limit
|
||||
/// of the light's cone of effect.
|
||||
/// `outer_angle` should be < `PI / 2.0`.
|
||||
/// `PI / 2.0` defines a hemispherical spot light, but shadows become very blocky as the angle
|
||||
/// approaches this limit.
|
||||
pub outer_angle: f32,
|
||||
|
||||
/// Angle defining the distance from the spot light direction to the inner limit
|
||||
/// of the light's cone of effect.
|
||||
/// Light is attenuated from `inner_angle` to `outer_angle` to give a smooth falloff.
|
||||
|
@ -34,6 +96,7 @@ pub struct SpotLight {
|
|||
impl SpotLight {
|
||||
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
|
||||
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 1.8;
|
||||
pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1;
|
||||
}
|
||||
|
||||
impl Default for SpotLight {
|
||||
|
@ -48,8 +111,10 @@ impl Default for SpotLight {
|
|||
range: 20.0,
|
||||
radius: 0.0,
|
||||
shadows_enabled: false,
|
||||
soft_shadows_enabled: false,
|
||||
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
|
||||
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
|
||||
shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z,
|
||||
inner_angle: 0.0,
|
||||
outer_angle: std::f32::consts::FRAC_PI_4,
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ use bevy_render::{
|
|||
Extract,
|
||||
};
|
||||
use bevy_transform::{components::GlobalTransform, prelude::Transform};
|
||||
use bevy_utils::prelude::default;
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
use bevy_utils::tracing::{error, warn};
|
||||
|
@ -35,8 +36,10 @@ pub struct ExtractedPointLight {
|
|||
pub radius: f32,
|
||||
pub transform: GlobalTransform,
|
||||
pub shadows_enabled: bool,
|
||||
pub soft_shadows_enabled: bool,
|
||||
pub shadow_depth_bias: f32,
|
||||
pub shadow_normal_bias: f32,
|
||||
pub shadow_map_near_z: f32,
|
||||
pub spot_light_angles: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
|
@ -47,6 +50,7 @@ pub struct ExtractedDirectionalLight {
|
|||
pub transform: GlobalTransform,
|
||||
pub shadows_enabled: bool,
|
||||
pub volumetric: bool,
|
||||
pub soft_shadow_size: Option<f32>,
|
||||
pub shadow_depth_bias: f32,
|
||||
pub shadow_normal_bias: f32,
|
||||
pub cascade_shadow_config: CascadeShadowConfig,
|
||||
|
@ -79,6 +83,7 @@ pub struct GpuDirectionalLight {
|
|||
color: Vec4,
|
||||
dir_to_light: Vec3,
|
||||
flags: u32,
|
||||
soft_shadow_size: f32,
|
||||
shadow_depth_bias: f32,
|
||||
shadow_normal_bias: f32,
|
||||
num_cascades: u32,
|
||||
|
@ -134,8 +139,10 @@ pub const MAX_CASCADES_PER_LIGHT: usize = 1;
|
|||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct ShadowSamplers {
|
||||
pub point_light_sampler: Sampler,
|
||||
pub directional_light_sampler: Sampler,
|
||||
pub point_light_comparison_sampler: Sampler,
|
||||
pub point_light_linear_sampler: Sampler,
|
||||
pub directional_light_comparison_sampler: Sampler,
|
||||
pub directional_light_linear_sampler: Sampler,
|
||||
}
|
||||
|
||||
// TODO: this pattern for initializing the shaders / pipeline isn't ideal. this should be handled by the asset system
|
||||
|
@ -143,27 +150,30 @@ impl FromWorld for ShadowSamplers {
|
|||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
|
||||
let base_sampler_descriptor = SamplerDescriptor {
|
||||
address_mode_u: AddressMode::ClampToEdge,
|
||||
address_mode_v: AddressMode::ClampToEdge,
|
||||
address_mode_w: AddressMode::ClampToEdge,
|
||||
mag_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mipmap_filter: FilterMode::Nearest,
|
||||
..default()
|
||||
};
|
||||
|
||||
ShadowSamplers {
|
||||
point_light_sampler: render_device.create_sampler(&SamplerDescriptor {
|
||||
address_mode_u: AddressMode::ClampToEdge,
|
||||
address_mode_v: AddressMode::ClampToEdge,
|
||||
address_mode_w: AddressMode::ClampToEdge,
|
||||
mag_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mipmap_filter: FilterMode::Nearest,
|
||||
point_light_comparison_sampler: render_device.create_sampler(&SamplerDescriptor {
|
||||
compare: Some(CompareFunction::GreaterEqual),
|
||||
..Default::default()
|
||||
..base_sampler_descriptor
|
||||
}),
|
||||
directional_light_sampler: render_device.create_sampler(&SamplerDescriptor {
|
||||
address_mode_u: AddressMode::ClampToEdge,
|
||||
address_mode_v: AddressMode::ClampToEdge,
|
||||
address_mode_w: AddressMode::ClampToEdge,
|
||||
mag_filter: FilterMode::Linear,
|
||||
min_filter: FilterMode::Linear,
|
||||
mipmap_filter: FilterMode::Nearest,
|
||||
point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
|
||||
directional_light_comparison_sampler: render_device.create_sampler(
|
||||
&SamplerDescriptor {
|
||||
compare: Some(CompareFunction::GreaterEqual),
|
||||
..Default::default()
|
||||
}),
|
||||
..base_sampler_descriptor
|
||||
},
|
||||
),
|
||||
directional_light_linear_sampler: render_device
|
||||
.create_sampler(&base_sampler_descriptor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,11 +262,13 @@ pub fn extract_lights(
|
|||
radius: point_light.radius,
|
||||
transform: *transform,
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
soft_shadows_enabled: point_light.soft_shadows_enabled,
|
||||
shadow_depth_bias: point_light.shadow_depth_bias,
|
||||
// The factor of SQRT_2 is for the worst-case diagonal offset
|
||||
shadow_normal_bias: point_light.shadow_normal_bias
|
||||
* point_light_texel_size
|
||||
* std::f32::consts::SQRT_2,
|
||||
shadow_map_near_z: point_light.shadow_map_near_z,
|
||||
spot_light_angles: None,
|
||||
};
|
||||
point_lights_values.push((
|
||||
|
@ -301,11 +313,13 @@ pub fn extract_lights(
|
|||
radius: spot_light.radius,
|
||||
transform: *transform,
|
||||
shadows_enabled: spot_light.shadows_enabled,
|
||||
soft_shadows_enabled: spot_light.soft_shadows_enabled,
|
||||
shadow_depth_bias: spot_light.shadow_depth_bias,
|
||||
// The factor of SQRT_2 is for the worst-case diagonal offset
|
||||
shadow_normal_bias: spot_light.shadow_normal_bias
|
||||
* texel_size
|
||||
* std::f32::consts::SQRT_2,
|
||||
shadow_map_near_z: spot_light.shadow_map_near_z,
|
||||
spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)),
|
||||
},
|
||||
render_visible_entities,
|
||||
|
@ -342,6 +356,7 @@ pub fn extract_lights(
|
|||
illuminance: directional_light.illuminance,
|
||||
transform: *transform,
|
||||
volumetric: volumetric_light.is_some(),
|
||||
soft_shadow_size: directional_light.soft_shadow_size,
|
||||
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
|
||||
|
@ -356,8 +371,6 @@ pub fn extract_lights(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) const POINT_LIGHT_NEAR_Z: f32 = 0.1f32;
|
||||
|
||||
pub(crate) struct CubeMapFace {
|
||||
pub(crate) target: Vec3,
|
||||
pub(crate) up: Vec3,
|
||||
|
@ -502,9 +515,9 @@ pub(crate) fn spot_light_world_from_view(transform: &GlobalTransform) -> Mat4 {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn spot_light_clip_from_view(angle: f32) -> Mat4 {
|
||||
pub(crate) fn spot_light_clip_from_view(angle: f32, near_z: f32) -> Mat4 {
|
||||
// spot light projection FOV is 2x the angle from spot light center to outer edge
|
||||
Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, POINT_LIGHT_NEAR_Z)
|
||||
Mat4::perspective_infinite_reverse_rh(angle * 2.0, 1.0, near_z)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -549,8 +562,6 @@ pub fn prepare_lights(
|
|||
};
|
||||
|
||||
// Pre-calculate for PointLights
|
||||
let cube_face_projection =
|
||||
Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z);
|
||||
let cube_face_rotations = CUBE_MAP_FACES
|
||||
.iter()
|
||||
.map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
|
||||
|
@ -685,6 +696,12 @@ pub fn prepare_lights(
|
|||
flags |= PointLightFlags::SHADOWS_ENABLED;
|
||||
}
|
||||
|
||||
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
|
||||
std::f32::consts::FRAC_PI_2,
|
||||
1.0,
|
||||
light.shadow_map_near_z,
|
||||
);
|
||||
|
||||
let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
|
||||
Some((inner, outer)) => {
|
||||
let light_direction = light.transform.forward();
|
||||
|
@ -727,9 +744,17 @@ pub fn prepare_lights(
|
|||
.extend(1.0 / (light.range * light.range)),
|
||||
position_radius: light.transform.translation().extend(light.radius),
|
||||
flags: flags.bits(),
|
||||
soft_shadow_size: if light.soft_shadows_enabled {
|
||||
light.radius
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
shadow_depth_bias: light.shadow_depth_bias,
|
||||
shadow_normal_bias: light.shadow_normal_bias,
|
||||
shadow_map_near_z: light.shadow_map_near_z,
|
||||
spot_light_tan_angle,
|
||||
pad_a: 0.0,
|
||||
pad_b: 0.0,
|
||||
});
|
||||
global_light_meta.entity_to_index.insert(entity, index);
|
||||
}
|
||||
|
@ -771,6 +796,7 @@ pub fn prepare_lights(
|
|||
// direction is negated to be ready for N.L
|
||||
dir_to_light: light.transform.back().into(),
|
||||
flags: flags.bits(),
|
||||
soft_shadow_size: light.soft_shadow_size.unwrap_or_default(),
|
||||
shadow_depth_bias: light.shadow_depth_bias,
|
||||
shadow_normal_bias: light.shadow_normal_bias,
|
||||
num_cascades: num_cascades as u32,
|
||||
|
@ -878,6 +904,12 @@ pub fn prepare_lights(
|
|||
// and ignore rotation because we want the shadow map projections to align with the axes
|
||||
let view_translation = GlobalTransform::from_translation(light.transform.translation());
|
||||
|
||||
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
|
||||
std::f32::consts::FRAC_PI_2,
|
||||
1.0,
|
||||
light.shadow_map_near_z,
|
||||
);
|
||||
|
||||
for (face_index, (view_rotation, frustum)) in cube_face_rotations
|
||||
.iter()
|
||||
.zip(&point_light_frusta.unwrap().frusta)
|
||||
|
@ -946,7 +978,7 @@ pub fn prepare_lights(
|
|||
|
||||
let angle = light.spot_light_angles.expect("lights should be sorted so that \
|
||||
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
|
||||
let spot_projection = spot_light_clip_from_view(angle);
|
||||
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
|
||||
|
||||
let depth_texture_view =
|
||||
directional_light_depth_texture
|
||||
|
|
|
@ -1837,11 +1837,11 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||
shader_defs.push("TONEMAP_IN_SHADER".into());
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
|
||||
21,
|
||||
23,
|
||||
));
|
||||
shader_defs.push(ShaderDefVal::UInt(
|
||||
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
|
||||
22,
|
||||
24,
|
||||
));
|
||||
|
||||
let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS);
|
||||
|
|
|
@ -213,11 +213,13 @@ fn layout_entries(
|
|||
))]
|
||||
texture_cube(TextureSampleType::Depth),
|
||||
),
|
||||
// Point Shadow Texture Array Sampler
|
||||
// Point Shadow Texture Array Comparison Sampler
|
||||
(3, sampler(SamplerBindingType::Comparison)),
|
||||
// Point Shadow Texture Array Linear Sampler
|
||||
(4, sampler(SamplerBindingType::Filtering)),
|
||||
// Directional Shadow Texture Array
|
||||
(
|
||||
4,
|
||||
5,
|
||||
#[cfg(any(
|
||||
not(feature = "webgl"),
|
||||
not(target_arch = "wasm32"),
|
||||
|
@ -227,11 +229,13 @@ fn layout_entries(
|
|||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
texture_2d(TextureSampleType::Depth),
|
||||
),
|
||||
// Directional Shadow Texture Array Sampler
|
||||
(5, sampler(SamplerBindingType::Comparison)),
|
||||
// ClusterableObjects
|
||||
// Directional Shadow Texture Array Comparison Sampler
|
||||
(6, sampler(SamplerBindingType::Comparison)),
|
||||
// Directional Shadow Texture Array Linear Sampler
|
||||
(7, sampler(SamplerBindingType::Filtering)),
|
||||
// PointLights
|
||||
(
|
||||
6,
|
||||
8,
|
||||
buffer_layout(
|
||||
clustered_forward_buffer_binding_type,
|
||||
false,
|
||||
|
@ -242,7 +246,7 @@ fn layout_entries(
|
|||
),
|
||||
// ClusteredLightIndexLists
|
||||
(
|
||||
7,
|
||||
9,
|
||||
buffer_layout(
|
||||
clustered_forward_buffer_binding_type,
|
||||
false,
|
||||
|
@ -255,7 +259,7 @@ fn layout_entries(
|
|||
),
|
||||
// ClusterOffsetsAndCounts
|
||||
(
|
||||
8,
|
||||
10,
|
||||
buffer_layout(
|
||||
clustered_forward_buffer_binding_type,
|
||||
false,
|
||||
|
@ -266,16 +270,16 @@ fn layout_entries(
|
|||
),
|
||||
// Globals
|
||||
(
|
||||
9,
|
||||
11,
|
||||
uniform_buffer::<GlobalsUniform>(false).visibility(ShaderStages::VERTEX_FRAGMENT),
|
||||
),
|
||||
// Fog
|
||||
(10, uniform_buffer::<GpuFog>(true)),
|
||||
(12, uniform_buffer::<GpuFog>(true)),
|
||||
// Light probes
|
||||
(11, uniform_buffer::<LightProbesUniform>(true)),
|
||||
(13, uniform_buffer::<LightProbesUniform>(true)),
|
||||
// Visibility ranges
|
||||
(
|
||||
12,
|
||||
14,
|
||||
buffer_layout(
|
||||
visibility_ranges_buffer_binding_type,
|
||||
false,
|
||||
|
@ -284,10 +288,10 @@ fn layout_entries(
|
|||
.visibility(ShaderStages::VERTEX),
|
||||
),
|
||||
// Screen space reflection settings
|
||||
(13, uniform_buffer::<ScreenSpaceReflectionsUniform>(true)),
|
||||
(15, uniform_buffer::<ScreenSpaceReflectionsUniform>(true)),
|
||||
// Screen space ambient occlusion texture
|
||||
(
|
||||
14,
|
||||
16,
|
||||
texture_2d(TextureSampleType::Float { filterable: false }),
|
||||
),
|
||||
),
|
||||
|
@ -296,10 +300,10 @@ fn layout_entries(
|
|||
// EnvironmentMapLight
|
||||
let environment_map_entries = environment_map::get_bind_group_layout_entries(render_device);
|
||||
entries = entries.extend_with_indices((
|
||||
(15, environment_map_entries[0]),
|
||||
(16, environment_map_entries[1]),
|
||||
(17, environment_map_entries[2]),
|
||||
(18, environment_map_entries[3]),
|
||||
(17, environment_map_entries[0]),
|
||||
(18, environment_map_entries[1]),
|
||||
(19, environment_map_entries[2]),
|
||||
(20, environment_map_entries[3]),
|
||||
));
|
||||
|
||||
// Irradiance volumes
|
||||
|
@ -307,16 +311,16 @@ fn layout_entries(
|
|||
let irradiance_volume_entries =
|
||||
irradiance_volume::get_bind_group_layout_entries(render_device);
|
||||
entries = entries.extend_with_indices((
|
||||
(19, irradiance_volume_entries[0]),
|
||||
(20, irradiance_volume_entries[1]),
|
||||
(21, irradiance_volume_entries[0]),
|
||||
(22, irradiance_volume_entries[1]),
|
||||
));
|
||||
}
|
||||
|
||||
// Tonemapping
|
||||
let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
|
||||
entries = entries.extend_with_indices((
|
||||
(21, tonemapping_lut_entries[0]),
|
||||
(22, tonemapping_lut_entries[1]),
|
||||
(23, tonemapping_lut_entries[0]),
|
||||
(24, tonemapping_lut_entries[1]),
|
||||
));
|
||||
|
||||
// Prepass
|
||||
|
@ -326,7 +330,7 @@ fn layout_entries(
|
|||
{
|
||||
for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key)
|
||||
.iter()
|
||||
.zip([23, 24, 25, 26])
|
||||
.zip([25, 26, 27, 28])
|
||||
{
|
||||
if let Some(entry) = entry {
|
||||
entries = entries.extend_with_indices(((binding as u32, *entry),));
|
||||
|
@ -337,10 +341,10 @@ fn layout_entries(
|
|||
// View Transmission Texture
|
||||
entries = entries.extend_with_indices((
|
||||
(
|
||||
27,
|
||||
29,
|
||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||
),
|
||||
(28, sampler(SamplerBindingType::Filtering)),
|
||||
(30, sampler(SamplerBindingType::Filtering)),
|
||||
));
|
||||
|
||||
entries.to_vec()
|
||||
|
@ -527,23 +531,25 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
(0, view_binding.clone()),
|
||||
(1, light_binding.clone()),
|
||||
(2, &shadow_bindings.point_light_depth_texture_view),
|
||||
(3, &shadow_samplers.point_light_sampler),
|
||||
(4, &shadow_bindings.directional_light_depth_texture_view),
|
||||
(5, &shadow_samplers.directional_light_sampler),
|
||||
(6, clusterable_objects_binding.clone()),
|
||||
(3, &shadow_samplers.point_light_comparison_sampler),
|
||||
(4, &shadow_samplers.point_light_linear_sampler),
|
||||
(5, &shadow_bindings.directional_light_depth_texture_view),
|
||||
(6, &shadow_samplers.directional_light_comparison_sampler),
|
||||
(7, &shadow_samplers.directional_light_linear_sampler),
|
||||
(8, clusterable_objects_binding.clone()),
|
||||
(
|
||||
7,
|
||||
9,
|
||||
cluster_bindings
|
||||
.clusterable_object_index_lists_binding()
|
||||
.unwrap(),
|
||||
),
|
||||
(8, cluster_bindings.offsets_and_counts_binding().unwrap()),
|
||||
(9, globals.clone()),
|
||||
(10, fog_binding.clone()),
|
||||
(11, light_probes_binding.clone()),
|
||||
(12, visibility_ranges_buffer.as_entire_binding()),
|
||||
(13, ssr_binding.clone()),
|
||||
(14, ssao_view),
|
||||
(10, cluster_bindings.offsets_and_counts_binding().unwrap()),
|
||||
(11, globals.clone()),
|
||||
(12, fog_binding.clone()),
|
||||
(13, light_probes_binding.clone()),
|
||||
(14, visibility_ranges_buffer.as_entire_binding()),
|
||||
(15, ssr_binding.clone()),
|
||||
(16, ssao_view),
|
||||
));
|
||||
|
||||
let environment_map_bind_group_entries = RenderViewEnvironmentMapBindGroupEntries::get(
|
||||
|
@ -560,10 +566,10 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
sampler,
|
||||
} => {
|
||||
entries = entries.extend_with_indices((
|
||||
(15, diffuse_texture_view),
|
||||
(16, specular_texture_view),
|
||||
(17, sampler),
|
||||
(18, environment_map_binding.clone()),
|
||||
(17, diffuse_texture_view),
|
||||
(18, specular_texture_view),
|
||||
(19, sampler),
|
||||
(20, environment_map_binding.clone()),
|
||||
));
|
||||
}
|
||||
RenderViewEnvironmentMapBindGroupEntries::Multiple {
|
||||
|
@ -572,10 +578,10 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
sampler,
|
||||
} => {
|
||||
entries = entries.extend_with_indices((
|
||||
(15, diffuse_texture_views.as_slice()),
|
||||
(16, specular_texture_views.as_slice()),
|
||||
(17, sampler),
|
||||
(18, environment_map_binding.clone()),
|
||||
(17, diffuse_texture_views.as_slice()),
|
||||
(18, specular_texture_views.as_slice()),
|
||||
(19, sampler),
|
||||
(20, environment_map_binding.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -596,21 +602,21 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
texture_view,
|
||||
sampler,
|
||||
}) => {
|
||||
entries = entries.extend_with_indices(((19, texture_view), (20, sampler)));
|
||||
entries = entries.extend_with_indices(((21, texture_view), (22, sampler)));
|
||||
}
|
||||
Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple {
|
||||
ref texture_views,
|
||||
sampler,
|
||||
}) => {
|
||||
entries = entries
|
||||
.extend_with_indices(((19, texture_views.as_slice()), (20, sampler)));
|
||||
.extend_with_indices(((21, texture_views.as_slice()), (22, sampler)));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let lut_bindings =
|
||||
get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);
|
||||
entries = entries.extend_with_indices(((21, lut_bindings.0), (22, lut_bindings.1)));
|
||||
entries = entries.extend_with_indices(((23, lut_bindings.0), (24, lut_bindings.1)));
|
||||
|
||||
// When using WebGL, we can't have a depth texture with multisampling
|
||||
let prepass_bindings;
|
||||
|
@ -620,7 +626,7 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
for (binding, index) in prepass_bindings
|
||||
.iter()
|
||||
.map(Option::as_ref)
|
||||
.zip([23, 24, 25, 26])
|
||||
.zip([25, 26, 27, 28])
|
||||
.flat_map(|(b, i)| b.map(|b| (b, i)))
|
||||
{
|
||||
entries = entries.extend_with_indices(((index, binding),));
|
||||
|
@ -636,7 +642,7 @@ pub fn prepare_mesh_view_bind_groups(
|
|||
.unwrap_or(&fallback_image_zero.sampler);
|
||||
|
||||
entries =
|
||||
entries.extend_with_indices(((27, transmission_view), (28, transmission_sampler)));
|
||||
entries.extend_with_indices(((29, transmission_view), (30, transmission_sampler)));
|
||||
|
||||
commands.entity(entity).insert(MeshViewBindGroup {
|
||||
value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries),
|
||||
|
|
|
@ -13,88 +13,91 @@
|
|||
#else
|
||||
@group(0) @binding(2) var point_shadow_textures: texture_depth_cube_array;
|
||||
#endif
|
||||
@group(0) @binding(3) var point_shadow_textures_sampler: sampler_comparison;
|
||||
@group(0) @binding(3) var point_shadow_textures_comparison_sampler: sampler_comparison;
|
||||
@group(0) @binding(4) var point_shadow_textures_linear_sampler: sampler;
|
||||
#ifdef NO_ARRAY_TEXTURES_SUPPORT
|
||||
@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d;
|
||||
@group(0) @binding(5) var directional_shadow_textures: texture_depth_2d;
|
||||
#else
|
||||
@group(0) @binding(4) var directional_shadow_textures: texture_depth_2d_array;
|
||||
@group(0) @binding(5) var directional_shadow_textures: texture_depth_2d_array;
|
||||
#endif
|
||||
@group(0) @binding(5) var directional_shadow_textures_sampler: sampler_comparison;
|
||||
@group(0) @binding(6) var directional_shadow_textures_comparison_sampler: sampler_comparison;
|
||||
@group(0) @binding(7) var directional_shadow_textures_linear_sampler: sampler;
|
||||
|
||||
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
|
||||
@group(0) @binding(6) var<storage> clusterable_objects: types::ClusterableObjects;
|
||||
@group(0) @binding(7) var<storage> clusterable_object_index_lists: types::ClusterLightIndexLists;
|
||||
@group(0) @binding(8) var<storage> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
|
||||
@group(0) @binding(8) var<storage> clusterable_objects: types::ClusterableObjects;
|
||||
@group(0) @binding(9) var<storage> clusterable_object_index_lists: types::ClusterLightIndexLists;
|
||||
@group(0) @binding(10) var<storage> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
|
||||
#else
|
||||
@group(0) @binding(6) var<uniform> clusterable_objects: types::ClusterableObjects;
|
||||
@group(0) @binding(7) var<uniform> clusterable_object_index_lists: types::ClusterLightIndexLists;
|
||||
@group(0) @binding(8) var<uniform> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
|
||||
@group(0) @binding(8) var<uniform> clusterable_objects: types::ClusterableObjects;
|
||||
@group(0) @binding(9) var<uniform> clusterable_object_index_lists: types::ClusterLightIndexLists;
|
||||
@group(0) @binding(10) var<uniform> cluster_offsets_and_counts: types::ClusterOffsetsAndCounts;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(9) var<uniform> globals: Globals;
|
||||
@group(0) @binding(10) var<uniform> fog: types::Fog;
|
||||
@group(0) @binding(11) var<uniform> light_probes: types::LightProbes;
|
||||
@group(0) @binding(11) var<uniform> globals: Globals;
|
||||
@group(0) @binding(12) var<uniform> fog: types::Fog;
|
||||
@group(0) @binding(13) var<uniform> light_probes: types::LightProbes;
|
||||
|
||||
const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
|
||||
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
|
||||
@group(0) @binding(12) var<storage> visibility_ranges: array<vec4<f32>>;
|
||||
@group(0) @binding(14) var<storage> visibility_ranges: array<vec4<f32>>;
|
||||
#else
|
||||
@group(0) @binding(12) var<uniform> visibility_ranges: array<vec4<f32>, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>;
|
||||
@group(0) @binding(14) var<uniform> visibility_ranges: array<vec4<f32>, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(13) var<uniform> ssr_settings: types::ScreenSpaceReflectionsSettings;
|
||||
@group(0) @binding(14) var screen_space_ambient_occlusion_texture: texture_2d<f32>;
|
||||
@group(0) @binding(15) var<uniform> ssr_settings: types::ScreenSpaceReflectionsSettings;
|
||||
@group(0) @binding(16) var screen_space_ambient_occlusion_texture: texture_2d<f32>;
|
||||
|
||||
#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
|
||||
@group(0) @binding(15) var diffuse_environment_maps: binding_array<texture_cube<f32>, 8u>;
|
||||
@group(0) @binding(16) var specular_environment_maps: binding_array<texture_cube<f32>, 8u>;
|
||||
@group(0) @binding(17) var diffuse_environment_maps: binding_array<texture_cube<f32>, 8u>;
|
||||
@group(0) @binding(18) var specular_environment_maps: binding_array<texture_cube<f32>, 8u>;
|
||||
#else
|
||||
@group(0) @binding(15) var diffuse_environment_map: texture_cube<f32>;
|
||||
@group(0) @binding(16) var specular_environment_map: texture_cube<f32>;
|
||||
@group(0) @binding(17) var diffuse_environment_map: texture_cube<f32>;
|
||||
@group(0) @binding(18) var specular_environment_map: texture_cube<f32>;
|
||||
#endif
|
||||
@group(0) @binding(17) var environment_map_sampler: sampler;
|
||||
@group(0) @binding(18) var<uniform> environment_map_uniform: types::EnvironmentMapUniform;
|
||||
@group(0) @binding(19) var environment_map_sampler: sampler;
|
||||
@group(0) @binding(20) var<uniform> environment_map_uniform: types::EnvironmentMapUniform;
|
||||
|
||||
#ifdef IRRADIANCE_VOLUMES_ARE_USABLE
|
||||
#ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY
|
||||
@group(0) @binding(19) var irradiance_volumes: binding_array<texture_3d<f32>, 8u>;
|
||||
@group(0) @binding(21) var irradiance_volumes: binding_array<texture_3d<f32>, 8u>;
|
||||
#else
|
||||
@group(0) @binding(19) var irradiance_volume: texture_3d<f32>;
|
||||
@group(0) @binding(21) var irradiance_volume: texture_3d<f32>;
|
||||
#endif
|
||||
@group(0) @binding(20) var irradiance_volume_sampler: sampler;
|
||||
@group(0) @binding(22) var irradiance_volume_sampler: sampler;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(21) var dt_lut_texture: texture_3d<f32>;
|
||||
@group(0) @binding(22) var dt_lut_sampler: sampler;
|
||||
// NB: If you change these, make sure to update `tonemapping_shared.wgsl` too.
|
||||
@group(0) @binding(23) var dt_lut_texture: texture_3d<f32>;
|
||||
@group(0) @binding(24) var dt_lut_sampler: sampler;
|
||||
|
||||
#ifdef MULTISAMPLED
|
||||
#ifdef DEPTH_PREPASS
|
||||
@group(0) @binding(23) var depth_prepass_texture: texture_depth_multisampled_2d;
|
||||
@group(0) @binding(25) var depth_prepass_texture: texture_depth_multisampled_2d;
|
||||
#endif // DEPTH_PREPASS
|
||||
#ifdef NORMAL_PREPASS
|
||||
@group(0) @binding(24) var normal_prepass_texture: texture_multisampled_2d<f32>;
|
||||
@group(0) @binding(26) var normal_prepass_texture: texture_multisampled_2d<f32>;
|
||||
#endif // NORMAL_PREPASS
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
@group(0) @binding(25) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
|
||||
@group(0) @binding(27) var motion_vector_prepass_texture: texture_multisampled_2d<f32>;
|
||||
#endif // MOTION_VECTOR_PREPASS
|
||||
|
||||
#else // MULTISAMPLED
|
||||
|
||||
#ifdef DEPTH_PREPASS
|
||||
@group(0) @binding(23) var depth_prepass_texture: texture_depth_2d;
|
||||
@group(0) @binding(25) var depth_prepass_texture: texture_depth_2d;
|
||||
#endif // DEPTH_PREPASS
|
||||
#ifdef NORMAL_PREPASS
|
||||
@group(0) @binding(24) var normal_prepass_texture: texture_2d<f32>;
|
||||
@group(0) @binding(26) var normal_prepass_texture: texture_2d<f32>;
|
||||
#endif // NORMAL_PREPASS
|
||||
#ifdef MOTION_VECTOR_PREPASS
|
||||
@group(0) @binding(25) var motion_vector_prepass_texture: texture_2d<f32>;
|
||||
@group(0) @binding(27) var motion_vector_prepass_texture: texture_2d<f32>;
|
||||
#endif // MOTION_VECTOR_PREPASS
|
||||
|
||||
#endif // MULTISAMPLED
|
||||
|
||||
#ifdef DEFERRED_PREPASS
|
||||
@group(0) @binding(26) var deferred_prepass_texture: texture_2d<u32>;
|
||||
@group(0) @binding(28) var deferred_prepass_texture: texture_2d<u32>;
|
||||
#endif // DEFERRED_PREPASS
|
||||
|
||||
@group(0) @binding(27) var view_transmission_texture: texture_2d<f32>;
|
||||
@group(0) @binding(28) var view_transmission_sampler: sampler;
|
||||
@group(0) @binding(29) var view_transmission_texture: texture_2d<f32>;
|
||||
@group(0) @binding(30) var view_transmission_sampler: sampler;
|
||||
|
|
|
@ -11,6 +11,10 @@ struct ClusterableObject {
|
|||
shadow_depth_bias: f32,
|
||||
shadow_normal_bias: f32,
|
||||
spot_light_tan_angle: f32,
|
||||
soft_shadow_size: f32,
|
||||
shadow_map_near_z: f32,
|
||||
pad_a: f32,
|
||||
pad_b: f32,
|
||||
};
|
||||
|
||||
const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
|
||||
|
@ -28,6 +32,7 @@ struct DirectionalLight {
|
|||
direction_to_light: vec3<f32>,
|
||||
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
|
||||
flags: u32,
|
||||
soft_shadow_size: f32,
|
||||
shadow_depth_bias: f32,
|
||||
shadow_normal_bias: f32,
|
||||
num_cascades: u32,
|
||||
|
|
|
@ -447,8 +447,14 @@ fn apply_pbr_lighting(
|
|||
|
||||
var shadow: f32 = 1.0;
|
||||
if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal);
|
||||
&& (view_bindings::clusterable_objects.data[light_id].flags &
|
||||
mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = shadows::fetch_spot_shadow(
|
||||
light_id,
|
||||
in.world_position,
|
||||
in.world_normal,
|
||||
view_bindings::clusterable_objects.data[light_id].shadow_map_near_z,
|
||||
);
|
||||
}
|
||||
|
||||
let light_contrib = lighting::spot_light(light_id, &lighting_input);
|
||||
|
@ -467,7 +473,12 @@ fn apply_pbr_lighting(
|
|||
var transmitted_shadow: f32 = 1.0;
|
||||
if ((in.flags & (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)) == (MESH_FLAGS_SHADOW_RECEIVER_BIT | MESH_FLAGS_TRANSMITTED_SHADOW_RECEIVER_BIT)
|
||||
&& (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
transmitted_shadow = shadows::fetch_spot_shadow(light_id, diffuse_transmissive_lobe_world_position, -in.world_normal);
|
||||
transmitted_shadow = shadows::fetch_spot_shadow(
|
||||
light_id,
|
||||
diffuse_transmissive_lobe_world_position,
|
||||
-in.world_normal,
|
||||
view_bindings::clusterable_objects.data[light_id].shadow_map_near_z,
|
||||
);
|
||||
}
|
||||
|
||||
let transmitted_light_contrib =
|
||||
|
|
|
@ -12,14 +12,14 @@ fn sample_shadow_map_hardware(light_local: vec2<f32>, depth: f32, array_index: i
|
|||
#ifdef NO_ARRAY_TEXTURES_SUPPORT
|
||||
return textureSampleCompare(
|
||||
view_bindings::directional_shadow_textures,
|
||||
view_bindings::directional_shadow_textures_sampler,
|
||||
view_bindings::directional_shadow_textures_comparison_sampler,
|
||||
light_local,
|
||||
depth,
|
||||
);
|
||||
#else
|
||||
return textureSampleCompareLevel(
|
||||
view_bindings::directional_shadow_textures,
|
||||
view_bindings::directional_shadow_textures_sampler,
|
||||
view_bindings::directional_shadow_textures_comparison_sampler,
|
||||
light_local,
|
||||
array_index,
|
||||
depth,
|
||||
|
@ -27,6 +27,40 @@ fn sample_shadow_map_hardware(light_local: vec2<f32>, depth: f32, array_index: i
|
|||
#endif
|
||||
}
|
||||
|
||||
// Does a single sample of the blocker search, a part of the PCSS algorithm.
|
||||
// This is the variant used for directional lights.
|
||||
fn search_for_blockers_in_shadow_map_hardware(
|
||||
light_local: vec2<f32>,
|
||||
depth: f32,
|
||||
array_index: i32,
|
||||
) -> vec2<f32> {
|
||||
#ifdef WEBGL2
|
||||
// Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled
|
||||
// with different samplers, or it'll blow up.
|
||||
return vec2(0.0);
|
||||
#else // WEBGL2
|
||||
|
||||
#ifdef NO_ARRAY_TEXTURES_SUPPORT
|
||||
let sampled_depth = textureSampleLevel(
|
||||
view_bindings::directional_shadow_textures,
|
||||
view_bindings::directional_shadow_textures_linear_sampler,
|
||||
light_local,
|
||||
0.0,
|
||||
);
|
||||
#else // NO_ARRAY_TEXTURES_SUPPORT
|
||||
let sampled_depth = textureSampleLevel(
|
||||
view_bindings::directional_shadow_textures,
|
||||
view_bindings::directional_shadow_textures_linear_sampler,
|
||||
light_local,
|
||||
array_index,
|
||||
0.0,
|
||||
);
|
||||
#endif // NO_ARRAY_TEXTURES_SUPPORT
|
||||
return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth);
|
||||
|
||||
#endif // WEBGL2
|
||||
}
|
||||
|
||||
// Numbers determined by trial and error that gave nice results.
|
||||
const SPOT_SHADOW_TEXEL_SIZE: f32 = 0.0134277345;
|
||||
const POINT_SHADOW_SCALE: f32 = 0.003;
|
||||
|
@ -113,9 +147,9 @@ fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 {
|
|||
// Creates a random rotation matrix using interleaved gradient noise.
|
||||
//
|
||||
// See: https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
|
||||
fn random_rotation_matrix(scale: vec2<f32>) -> mat2x2<f32> {
|
||||
fn random_rotation_matrix(scale: vec2<f32>, temporal: bool) -> mat2x2<f32> {
|
||||
let random_angle = 2.0 * PI * interleaved_gradient_noise(
|
||||
scale, view_bindings::globals.frame_count);
|
||||
scale, select(1u, view_bindings::globals.frame_count, temporal));
|
||||
let m = vec2(sin(random_angle), cos(random_angle));
|
||||
return mat2x2(
|
||||
m.y, -m.x,
|
||||
|
@ -123,13 +157,28 @@ fn random_rotation_matrix(scale: vec2<f32>) -> mat2x2<f32> {
|
|||
);
|
||||
}
|
||||
|
||||
fn sample_shadow_map_jimenez_fourteen(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
|
||||
// Calculates the distance between spiral samples for the given texel size and
|
||||
// penumbra size. This is used for the Jimenez '14 (i.e. temporal) variant of
|
||||
// shadow sampling.
|
||||
fn calculate_uv_offset_scale_jimenez_fourteen(texel_size: f32, blur_size: f32) -> vec2<f32> {
|
||||
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
||||
let rotation_matrix = random_rotation_matrix(light_local * shadow_map_size);
|
||||
|
||||
// Empirically chosen fudge factor to make PCF look better across different CSM cascades
|
||||
let f = map(0.00390625, 0.022949219, 0.015, 0.035, texel_size);
|
||||
let uv_offset_scale = f / (texel_size * shadow_map_size);
|
||||
return f * blur_size / (texel_size * shadow_map_size);
|
||||
}
|
||||
|
||||
fn sample_shadow_map_jimenez_fourteen(
|
||||
light_local: vec2<f32>,
|
||||
depth: f32,
|
||||
array_index: i32,
|
||||
texel_size: f32,
|
||||
blur_size: f32,
|
||||
temporal: bool,
|
||||
) -> f32 {
|
||||
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
||||
let rotation_matrix = random_rotation_matrix(light_local * shadow_map_size, temporal);
|
||||
let uv_offset_scale = calculate_uv_offset_scale_jimenez_fourteen(texel_size, blur_size);
|
||||
|
||||
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
|
||||
let sample_offset0 = (rotation_matrix * utils::SPIRAL_OFFSET_0_) * uv_offset_scale;
|
||||
|
@ -153,11 +202,57 @@ fn sample_shadow_map_jimenez_fourteen(light_local: vec2<f32>, depth: f32, array_
|
|||
return sum / 8.0;
|
||||
}
|
||||
|
||||
// Performs the blocker search portion of percentage-closer soft shadows (PCSS).
|
||||
// This is the variation used for directional lights.
|
||||
//
|
||||
// We can't use Castano '13 here because that has a hard-wired fixed size, while
|
||||
// the PCSS algorithm requires a search size that varies based on the size of
|
||||
// the light. So we instead use the D3D sample point positions, spaced according
|
||||
// to the search size, to provide a sample pattern in a similar manner to the
|
||||
// cubemap sampling approach we use for PCF.
|
||||
//
|
||||
// `search_size` is the size of the search region in texels.
|
||||
fn search_for_blockers_in_shadow_map(
|
||||
light_local: vec2<f32>,
|
||||
depth: f32,
|
||||
array_index: i32,
|
||||
texel_size: f32,
|
||||
search_size: f32,
|
||||
) -> f32 {
|
||||
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
||||
let uv_offset_scale = search_size / (texel_size * shadow_map_size);
|
||||
|
||||
let offset0 = D3D_SAMPLE_POINT_POSITIONS[0] * uv_offset_scale;
|
||||
let offset1 = D3D_SAMPLE_POINT_POSITIONS[1] * uv_offset_scale;
|
||||
let offset2 = D3D_SAMPLE_POINT_POSITIONS[2] * uv_offset_scale;
|
||||
let offset3 = D3D_SAMPLE_POINT_POSITIONS[3] * uv_offset_scale;
|
||||
let offset4 = D3D_SAMPLE_POINT_POSITIONS[4] * uv_offset_scale;
|
||||
let offset5 = D3D_SAMPLE_POINT_POSITIONS[5] * uv_offset_scale;
|
||||
let offset6 = D3D_SAMPLE_POINT_POSITIONS[6] * uv_offset_scale;
|
||||
let offset7 = D3D_SAMPLE_POINT_POSITIONS[7] * uv_offset_scale;
|
||||
|
||||
var sum = vec2(0.0);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset0, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset1, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset2, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset3, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset4, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset5, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset6, depth, array_index);
|
||||
sum += search_for_blockers_in_shadow_map_hardware(light_local + offset7, depth, array_index);
|
||||
|
||||
if (sum.y == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
return sum.x / sum.y;
|
||||
}
|
||||
|
||||
fn sample_shadow_map(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
|
||||
#ifdef SHADOW_FILTER_METHOD_GAUSSIAN
|
||||
return sample_shadow_map_castano_thirteen(light_local, depth, array_index);
|
||||
#else ifdef SHADOW_FILTER_METHOD_TEMPORAL
|
||||
return sample_shadow_map_jimenez_fourteen(light_local, depth, array_index, texel_size);
|
||||
return sample_shadow_map_jimenez_fourteen(
|
||||
light_local, depth, array_index, texel_size, 1.0, true);
|
||||
#else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2
|
||||
return sample_shadow_map_hardware(light_local, depth, array_index);
|
||||
#else
|
||||
|
@ -169,6 +264,45 @@ fn sample_shadow_map(light_local: vec2<f32>, depth: f32, array_index: i32, texel
|
|||
#endif
|
||||
}
|
||||
|
||||
// Samples the shadow map for a directional light when percentage-closer soft
|
||||
// shadows are being used.
|
||||
//
|
||||
// We first search for a *blocker*, which is the average depth value of any
|
||||
// shadow map samples that are adjacent to the sample we're considering. That
|
||||
// allows us to determine the penumbra size; a larger gap between the blocker
|
||||
// and the depth of this sample results in a wider penumbra. Finally, we sample
|
||||
// the shadow map the same way we do in PCF, using that penumbra width.
|
||||
//
|
||||
// A good overview of the technique:
|
||||
// <https://medium.com/@varunm100/soft-shadows-for-mobile-ar-9e8da2e6f4ba>
|
||||
fn sample_shadow_map_pcss(
|
||||
light_local: vec2<f32>,
|
||||
depth: f32,
|
||||
array_index: i32,
|
||||
texel_size: f32,
|
||||
light_size: f32,
|
||||
) -> f32 {
|
||||
// Determine the average Z value of the closest blocker.
|
||||
let z_blocker = search_for_blockers_in_shadow_map(
|
||||
light_local, depth, array_index, texel_size, light_size);
|
||||
|
||||
// Don't let the blur size go below 0.5, or shadows will look unacceptably aliased.
|
||||
let blur_size = max((z_blocker - depth) * light_size / depth, 0.5);
|
||||
|
||||
// FIXME: We can't use Castano '13 here because that has a hard-wired fixed
|
||||
// size. So we instead use Jimenez '14 unconditionally. In the non-temporal
|
||||
// variant this is unfortunately rather noisy. This may be improvable in the
|
||||
// future by generating a mip chain of the shadow map and using that to
|
||||
// provide better blurs.
|
||||
#ifdef SHADOW_FILTER_METHOD_TEMPORAL
|
||||
return sample_shadow_map_jimenez_fourteen(
|
||||
light_local, depth, array_index, texel_size, blur_size, true);
|
||||
#else // SHADOW_FILTER_METHOD_TEMPORAL
|
||||
return sample_shadow_map_jimenez_fourteen(
|
||||
light_local, depth, array_index, texel_size, blur_size, false);
|
||||
#endif // SHADOW_FILTER_METHOD_TEMPORAL
|
||||
}
|
||||
|
||||
// NOTE: Due to the non-uniform control flow in `shadows::fetch_point_shadow`,
|
||||
// we must use the Level variant of textureSampleCompare to avoid undefined
|
||||
// behavior due to some of the fragments in a quad (2x2 fragments) being
|
||||
|
@ -176,12 +310,56 @@ fn sample_shadow_map(light_local: vec2<f32>, depth: f32, array_index: i32, texel
|
|||
// The shadow maps have no mipmaps so Level just samples from LOD 0.
|
||||
fn sample_shadow_cubemap_hardware(light_local: vec3<f32>, depth: f32, light_id: u32) -> f32 {
|
||||
#ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT
|
||||
return textureSampleCompare(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, light_local, depth);
|
||||
return textureSampleCompare(
|
||||
view_bindings::point_shadow_textures,
|
||||
view_bindings::point_shadow_textures_comparison_sampler,
|
||||
light_local,
|
||||
depth
|
||||
);
|
||||
#else
|
||||
return textureSampleCompareLevel(view_bindings::point_shadow_textures, view_bindings::point_shadow_textures_sampler, light_local, i32(light_id), depth);
|
||||
return textureSampleCompareLevel(
|
||||
view_bindings::point_shadow_textures,
|
||||
view_bindings::point_shadow_textures_comparison_sampler,
|
||||
light_local,
|
||||
i32(light_id),
|
||||
depth
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Performs one sample of the blocker search. This variation of the blocker
|
||||
// search function is for point and spot lights.
|
||||
fn search_for_blockers_in_shadow_cubemap_hardware(
|
||||
light_local: vec3<f32>,
|
||||
depth: f32,
|
||||
light_id: u32,
|
||||
) -> vec2<f32> {
|
||||
#ifdef WEBGL2
|
||||
// Make sure that the WebGL 2 compiler doesn't see `sampled_depth` sampled
|
||||
// with different samplers, or it'll blow up.
|
||||
return vec2(0.0);
|
||||
#else // WEBGL2
|
||||
|
||||
#ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT
|
||||
let sampled_depth = textureSample(
|
||||
view_bindings::point_shadow_textures,
|
||||
view_bindings::point_shadow_textures_linear_sampler,
|
||||
light_local,
|
||||
);
|
||||
#else
|
||||
let sampled_depth = textureSample(
|
||||
view_bindings::point_shadow_textures,
|
||||
view_bindings::point_shadow_textures_linear_sampler,
|
||||
light_local,
|
||||
i32(light_id),
|
||||
);
|
||||
#endif
|
||||
|
||||
return select(vec2(0.0), vec2(sampled_depth, 1.0), sampled_depth >= depth);
|
||||
|
||||
#endif // WEBGL2
|
||||
}
|
||||
|
||||
fn sample_shadow_cubemap_at_offset(
|
||||
position: vec2<f32>,
|
||||
coeff: f32,
|
||||
|
@ -198,6 +376,26 @@ fn sample_shadow_cubemap_at_offset(
|
|||
) * coeff;
|
||||
}
|
||||
|
||||
// Computes the search position and performs one sample of the blocker search.
|
||||
// This variation of the blocker search function is for point and spot lights.
|
||||
//
|
||||
// `x_basis`, `y_basis`, and `light_local` form an orthonormal basis over which
|
||||
// the blocker search happens.
|
||||
fn search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
position: vec2<f32>,
|
||||
x_basis: vec3<f32>,
|
||||
y_basis: vec3<f32>,
|
||||
light_local: vec3<f32>,
|
||||
depth: f32,
|
||||
light_id: u32,
|
||||
) -> vec2<f32> {
|
||||
return search_for_blockers_in_shadow_cubemap_hardware(
|
||||
light_local + position.x * x_basis + position.y * y_basis,
|
||||
depth,
|
||||
light_id
|
||||
);
|
||||
}
|
||||
|
||||
// This more or less does what Castano13 does, but in 3D space. Castano13 is
|
||||
// essentially an optimized 2D Gaussian filter that takes advantage of the
|
||||
// bilinear filtering hardware to reduce the number of samples needed. This
|
||||
|
@ -249,12 +447,13 @@ fn sample_shadow_cubemap_gaussian(
|
|||
// This is a port of the Jimenez14 filter above to the 3D space. It jitters the
|
||||
// points in the spiral pattern after first creating a 2D orthonormal basis
|
||||
// along the principal light direction.
|
||||
fn sample_shadow_cubemap_temporal(
|
||||
fn sample_shadow_cubemap_jittered(
|
||||
light_local: vec3<f32>,
|
||||
depth: f32,
|
||||
scale: f32,
|
||||
distance_to_light: f32,
|
||||
light_id: u32,
|
||||
temporal: bool,
|
||||
) -> f32 {
|
||||
// Create an orthonormal basis so we can apply a 2D sampling pattern to a
|
||||
// cubemap.
|
||||
|
@ -264,7 +463,7 @@ fn sample_shadow_cubemap_temporal(
|
|||
}
|
||||
let basis = orthonormalize(light_local, up) * scale * distance_to_light;
|
||||
|
||||
let rotation_matrix = random_rotation_matrix(vec2(1.0));
|
||||
let rotation_matrix = random_rotation_matrix(vec2(1.0), temporal);
|
||||
|
||||
let sample_offset0 = rotation_matrix * utils::SPIRAL_OFFSET_0_ *
|
||||
POINT_SHADOW_TEMPORAL_OFFSET_SCALE;
|
||||
|
@ -313,8 +512,8 @@ fn sample_shadow_cubemap(
|
|||
return sample_shadow_cubemap_gaussian(
|
||||
light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id);
|
||||
#else ifdef SHADOW_FILTER_METHOD_TEMPORAL
|
||||
return sample_shadow_cubemap_temporal(
|
||||
light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id);
|
||||
return sample_shadow_cubemap_jittered(
|
||||
light_local, depth, POINT_SHADOW_SCALE, distance_to_light, light_id, true);
|
||||
#else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2
|
||||
return sample_shadow_cubemap_hardware(light_local, depth, light_id);
|
||||
#else
|
||||
|
@ -325,3 +524,76 @@ fn sample_shadow_cubemap(
|
|||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Searches for PCSS blockers in a cubemap. This is the variant of the blocker
|
||||
// search used for point and spot lights.
|
||||
//
|
||||
// This follows the logic in `sample_shadow_cubemap_gaussian`, but uses linear
|
||||
// sampling instead of percentage-closer filtering.
|
||||
//
|
||||
// The `scale` parameter represents the size of the light.
|
||||
fn search_for_blockers_in_shadow_cubemap(
|
||||
light_local: vec3<f32>,
|
||||
depth: f32,
|
||||
scale: f32,
|
||||
distance_to_light: f32,
|
||||
light_id: u32,
|
||||
) -> f32 {
|
||||
// Create an orthonormal basis so we can apply a 2D sampling pattern to a
|
||||
// cubemap.
|
||||
var up = vec3(0.0, 1.0, 0.0);
|
||||
if (dot(up, normalize(light_local)) > 0.99) {
|
||||
up = vec3(1.0, 0.0, 0.0); // Avoid creating a degenerate basis.
|
||||
}
|
||||
let basis = orthonormalize(light_local, up) * scale * distance_to_light;
|
||||
|
||||
var sum: vec2<f32> = vec2(0.0);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[0], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[1], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[2], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[3], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[4], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[5], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[6], basis[0], basis[1], light_local, depth, light_id);
|
||||
sum += search_for_blockers_in_shadow_cubemap_at_offset(
|
||||
D3D_SAMPLE_POINT_POSITIONS[7], basis[0], basis[1], light_local, depth, light_id);
|
||||
|
||||
if (sum.y == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
return sum.x / sum.y;
|
||||
}
|
||||
|
||||
// Samples the shadow map for a point or spot light when percentage-closer soft
|
||||
// shadows are being used.
|
||||
//
|
||||
// A good overview of the technique:
|
||||
// <https://medium.com/@varunm100/soft-shadows-for-mobile-ar-9e8da2e6f4ba>
|
||||
fn sample_shadow_cubemap_pcss(
|
||||
light_local: vec3<f32>,
|
||||
distance_to_light: f32,
|
||||
depth: f32,
|
||||
light_id: u32,
|
||||
light_size: f32,
|
||||
) -> f32 {
|
||||
let z_blocker = search_for_blockers_in_shadow_cubemap(
|
||||
light_local, depth, light_size, distance_to_light, light_id);
|
||||
|
||||
// Don't let the blur size go below 0.5, or shadows will look unacceptably aliased.
|
||||
let blur_size = max((z_blocker - depth) * light_size / depth, 0.5);
|
||||
|
||||
#ifdef SHADOW_FILTER_METHOD_TEMPORAL
|
||||
return sample_shadow_cubemap_jittered(
|
||||
light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, true);
|
||||
#else
|
||||
return sample_shadow_cubemap_jittered(
|
||||
light_local, depth, POINT_SHADOW_SCALE * blur_size, distance_to_light, light_id, false);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
#import bevy_pbr::{
|
||||
mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE,
|
||||
mesh_view_bindings as view_bindings,
|
||||
shadow_sampling::{SPOT_SHADOW_TEXEL_SIZE, sample_shadow_cubemap, sample_shadow_map}
|
||||
shadow_sampling::{
|
||||
SPOT_SHADOW_TEXEL_SIZE, sample_shadow_cubemap, sample_shadow_cubemap_pcss,
|
||||
sample_shadow_map, sample_shadow_map_pcss,
|
||||
}
|
||||
}
|
||||
|
||||
#import bevy_render::{
|
||||
|
@ -41,12 +44,30 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: v
|
|||
let zw = -major_axis_magnitude * (*light).light_custom_data.xy + (*light).light_custom_data.zw;
|
||||
let depth = zw.x / zw.y;
|
||||
|
||||
// Do the lookup, using HW PCF and comparison. Cubemaps assume a left-handed coordinate space,
|
||||
// so we have to flip the z-axis when sampling.
|
||||
// If soft shadows are enabled, use the PCSS path. Cubemaps assume a
|
||||
// left-handed coordinate space, so we have to flip the z-axis when
|
||||
// sampling.
|
||||
if ((*light).soft_shadow_size > 0.0) {
|
||||
return sample_shadow_cubemap_pcss(
|
||||
frag_ls * flip_z,
|
||||
distance_to_light,
|
||||
depth,
|
||||
light_id,
|
||||
(*light).soft_shadow_size,
|
||||
);
|
||||
}
|
||||
|
||||
// Do the lookup, using HW PCF and comparison. Cubemaps assume a left-handed
|
||||
// coordinate space, so we have to flip the z-axis when sampling.
|
||||
return sample_shadow_cubemap(frag_ls * flip_z, distance_to_light, depth, light_id);
|
||||
}
|
||||
|
||||
fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
||||
fn fetch_spot_shadow(
|
||||
light_id: u32,
|
||||
frag_position: vec4<f32>,
|
||||
surface_normal: vec3<f32>,
|
||||
near_z: f32,
|
||||
) -> f32 {
|
||||
let light = &view_bindings::clusterable_objects.data[light_id];
|
||||
|
||||
let surface_to_light = (*light).position_radius.xyz - frag_position.xyz;
|
||||
|
@ -91,15 +112,16 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: ve
|
|||
// convert to uv coordinates
|
||||
let shadow_uv = shadow_xy_ndc * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5);
|
||||
|
||||
// 0.1 must match POINT_LIGHT_NEAR_Z
|
||||
let depth = 0.1 / -projected_position.z;
|
||||
let depth = near_z / -projected_position.z;
|
||||
|
||||
return sample_shadow_map(
|
||||
shadow_uv,
|
||||
depth,
|
||||
i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset,
|
||||
SPOT_SHADOW_TEXEL_SIZE
|
||||
);
|
||||
// If soft shadows are enabled, use the PCSS path.
|
||||
let array_index = i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset;
|
||||
if ((*light).soft_shadow_size > 0.0) {
|
||||
return sample_shadow_map_pcss(
|
||||
shadow_uv, depth, array_index, SPOT_SHADOW_TEXEL_SIZE, (*light).soft_shadow_size);
|
||||
}
|
||||
|
||||
return sample_shadow_map(shadow_uv, depth, array_index, SPOT_SHADOW_TEXEL_SIZE);
|
||||
}
|
||||
|
||||
fn get_cascade_index(light_id: u32, view_z: f32) -> u32 {
|
||||
|
@ -146,7 +168,12 @@ fn world_to_directional_light_local(
|
|||
return vec4(light_local, depth, 1.0);
|
||||
}
|
||||
|
||||
fn sample_directional_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
||||
fn sample_directional_cascade(
|
||||
light_id: u32,
|
||||
cascade_index: u32,
|
||||
frag_position: vec4<f32>,
|
||||
surface_normal: vec3<f32>,
|
||||
) -> f32 {
|
||||
let light = &view_bindings::lights.directional_lights[light_id];
|
||||
let cascade = &(*light).cascades[cascade_index];
|
||||
|
||||
|
@ -161,7 +188,15 @@ fn sample_directional_cascade(light_id: u32, cascade_index: u32, frag_position:
|
|||
}
|
||||
|
||||
let array_index = i32((*light).depth_texture_base_index + cascade_index);
|
||||
return sample_shadow_map(light_local.xy, light_local.z, array_index, (*cascade).texel_size);
|
||||
let texel_size = (*cascade).texel_size;
|
||||
|
||||
// If soft shadows are enabled, use the PCSS path.
|
||||
if ((*light).soft_shadow_size > 0.0) {
|
||||
return sample_shadow_map_pcss(
|
||||
light_local.xy, light_local.z, array_index, texel_size, (*light).soft_shadow_size);
|
||||
}
|
||||
|
||||
return sample_shadow_map(light_local.xy, light_local.z, array_index, texel_size);
|
||||
}
|
||||
|
||||
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, view_z: f32) -> f32 {
|
||||
|
|
|
@ -447,7 +447,8 @@ fn extract(main_world: &mut World, render_world: &mut World) {
|
|||
main_world.insert_resource(ScratchMainWorld(scratch_world));
|
||||
}
|
||||
|
||||
/// SAFETY: this function must be called from the main thread.
|
||||
/// # Safety
|
||||
/// This function must be called from the main thread.
|
||||
unsafe fn initialize_render_app(app: &mut App) {
|
||||
app.init_resource::<ScratchMainWorld>();
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ impl AppExtStates for SubApp {
|
|||
.init_resource::<NextState<S>>()
|
||||
.add_event::<StateTransitionEvent<S>>();
|
||||
let schedule = self.get_schedule_mut(StateTransition).expect(
|
||||
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling init_state?"
|
||||
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?"
|
||||
);
|
||||
S::register_state(schedule);
|
||||
self.world_mut().send_event(StateTransitionEvent {
|
||||
|
@ -146,7 +146,9 @@ impl AppExtStates for SubApp {
|
|||
.contains_resource::<Events<StateTransitionEvent<S>>>()
|
||||
{
|
||||
self.add_event::<StateTransitionEvent<S>>();
|
||||
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
||||
let schedule = self.get_schedule_mut(StateTransition).expect(
|
||||
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_computed_state?"
|
||||
);
|
||||
S::register_computed_state_systems(schedule);
|
||||
let state = self
|
||||
.world()
|
||||
|
@ -172,7 +174,9 @@ impl AppExtStates for SubApp {
|
|||
{
|
||||
self.init_resource::<NextState<S>>();
|
||||
self.add_event::<StateTransitionEvent<S>>();
|
||||
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
||||
let schedule = self.get_schedule_mut(StateTransition).expect(
|
||||
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_sub_state?"
|
||||
);
|
||||
S::register_sub_state_systems(schedule);
|
||||
let state = self
|
||||
.world()
|
||||
|
|
|
@ -9,11 +9,16 @@ license = "MIT OR Apache-2.0"
|
|||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["alloc", "tracing/std", "ahash/std"]
|
||||
alloc = []
|
||||
detailed_trace = []
|
||||
|
||||
[dependencies]
|
||||
ahash = "0.8.7"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
ahash = { version = "0.8.7", default-features = false, features = [
|
||||
"runtime-rng",
|
||||
] }
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
web-time = { version = "1.1" }
|
||||
hashbrown = { version = "0.14.2", features = ["serde"] }
|
||||
bevy_utils_proc_macros = { version = "0.15.0-dev", path = "macros" }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! Utilities for working with [`Future`]s.
|
||||
use std::{
|
||||
use core::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
|
||||
|
@ -36,15 +36,15 @@ pub fn check_ready<F: Future + Unpin>(future: &mut F) -> Option<F::Output> {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn noop_clone(_data: *const ()) -> RawWaker {
|
||||
fn noop_clone(_data: *const ()) -> RawWaker {
|
||||
noop_raw_waker()
|
||||
}
|
||||
unsafe fn noop(_data: *const ()) {}
|
||||
fn noop(_data: *const ()) {}
|
||||
|
||||
const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop);
|
||||
|
||||
fn noop_raw_waker() -> RawWaker {
|
||||
RawWaker::new(std::ptr::null(), &NOOP_WAKER_VTABLE)
|
||||
RawWaker::new(core::ptr::null(), &NOOP_WAKER_VTABLE)
|
||||
}
|
||||
|
||||
fn noop_waker() -> Waker {
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
//! General utilities for first-party [Bevy] engine crates.
|
||||
//!
|
||||
//! [Bevy]: https://bevyengine.org/
|
||||
//!
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
/// The utilities prelude.
|
||||
///
|
||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||
|
@ -18,7 +22,9 @@ pub mod prelude {
|
|||
}
|
||||
|
||||
pub mod futures;
|
||||
#[cfg(feature = "alloc")]
|
||||
mod short_names;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use short_names::get_short_name;
|
||||
pub mod synccell;
|
||||
pub mod syncunsafecell;
|
||||
|
@ -37,8 +43,10 @@ pub use parallel_queue::*;
|
|||
pub use tracing;
|
||||
pub use web_time::{Duration, Instant, SystemTime, SystemTimeError, TryFromFloatSecsError};
|
||||
|
||||
use hashbrown::hash_map::RawEntryMut;
|
||||
use std::{
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
|
||||
use core::{
|
||||
any::TypeId,
|
||||
fmt::Debug,
|
||||
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
|
||||
|
@ -46,6 +54,7 @@ use std::{
|
|||
mem::ManuallyDrop,
|
||||
ops::Deref,
|
||||
};
|
||||
use hashbrown::hash_map::RawEntryMut;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod conditional_send {
|
||||
|
@ -66,11 +75,12 @@ pub use conditional_send::*;
|
|||
|
||||
/// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. Wasm),
|
||||
/// futures aren't Send.
|
||||
pub trait ConditionalSendFuture: std::future::Future + ConditionalSend {}
|
||||
impl<T: std::future::Future + ConditionalSend> ConditionalSendFuture for T {}
|
||||
pub trait ConditionalSendFuture: core::future::Future + ConditionalSend {}
|
||||
impl<T: core::future::Future + ConditionalSend> ConditionalSendFuture for T {}
|
||||
|
||||
/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection.
|
||||
pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>;
|
||||
#[cfg(feature = "alloc")]
|
||||
pub type BoxedFuture<'a, T> = core::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>;
|
||||
|
||||
/// A shortcut alias for [`hashbrown::hash_map::Entry`].
|
||||
pub type Entry<'a, K, V, S = BuildHasherDefault<AHasher>> = hashbrown::hash_map::Entry<'a, K, V, S>;
|
||||
|
@ -192,7 +202,7 @@ impl<V: PartialEq, H> PartialEq for Hashed<V, H> {
|
|||
}
|
||||
|
||||
impl<V: Debug, H> Debug for Hashed<V, H> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Hashed")
|
||||
.field("hash", &self.hash)
|
||||
.field("value", &self.value)
|
||||
|
@ -417,7 +427,7 @@ mod tests {
|
|||
fn fast_typeid_hash() {
|
||||
struct Hasher;
|
||||
|
||||
impl std::hash::Hasher for Hasher {
|
||||
impl core::hash::Hasher for Hasher {
|
||||
fn finish(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
|
@ -430,8 +440,11 @@ mod tests {
|
|||
Hash::hash(&TypeId::of::<()>(), &mut Hasher);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
#[test]
|
||||
fn stable_hash_within_same_program_execution() {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
let mut map_1 = HashMap::new();
|
||||
let mut map_2 = HashMap::new();
|
||||
for i in 1..10 {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use std::{cell::RefCell, ops::DerefMut};
|
||||
#[cfg(all(feature = "alloc", not(feature = "std")))]
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::{cell::RefCell, ops::DerefMut};
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
/// A cohesive set of thread-local values of a given type.
|
||||
|
@ -56,6 +59,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T: Send> Parallel<Vec<T>> {
|
||||
/// Collect all enqueued items from all threads and appends them to the end of a
|
||||
/// single Vec.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use alloc::string::String;
|
||||
|
||||
/// Shortens a type name to remove all module paths.
|
||||
///
|
||||
/// The short name of a type is its full name as returned by
|
||||
|
@ -88,43 +90,37 @@ mod name_formatting_tests {
|
|||
fn path_separated() {
|
||||
assert_eq!(
|
||||
get_short_name("bevy_prelude::make_fun_game"),
|
||||
"make_fun_game".to_string()
|
||||
"make_fun_game"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_type() {
|
||||
assert_eq!(
|
||||
get_short_name("(String, String)"),
|
||||
"(String, String)".to_string()
|
||||
);
|
||||
assert_eq!(get_short_name("(String, String)"), "(String, String)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_type() {
|
||||
assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string());
|
||||
assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trivial_generics() {
|
||||
assert_eq!(get_short_name("a<B>"), "a<B>".to_string());
|
||||
assert_eq!(get_short_name("a<B>"), "a<B>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_type_parameters() {
|
||||
assert_eq!(get_short_name("a<B, C>"), "a<B, C>".to_string());
|
||||
assert_eq!(get_short_name("a<B, C>"), "a<B, C>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enums() {
|
||||
assert_eq!(get_short_name("Option::None"), "Option::None".to_string());
|
||||
assert_eq!(
|
||||
get_short_name("Option::Some(2)"),
|
||||
"Option::Some(2)".to_string()
|
||||
);
|
||||
assert_eq!(get_short_name("Option::None"), "Option::None");
|
||||
assert_eq!(get_short_name("Option::Some(2)"), "Option::Some(2)");
|
||||
assert_eq!(
|
||||
get_short_name("bevy_render::RenderSet::Prepare"),
|
||||
"RenderSet::Prepare".to_string()
|
||||
"RenderSet::Prepare"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -132,7 +128,7 @@ mod name_formatting_tests {
|
|||
fn generics() {
|
||||
assert_eq!(
|
||||
get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"),
|
||||
"extract_cameras<Camera3d>".to_string()
|
||||
"extract_cameras<Camera3d>"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -140,7 +136,7 @@ mod name_formatting_tests {
|
|||
fn nested_generics() {
|
||||
assert_eq!(
|
||||
get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"),
|
||||
"do_mad_science<Test<Tube>, TypeSystemAbuse>".to_string()
|
||||
"do_mad_science<Test<Tube>, TypeSystemAbuse>"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -148,15 +144,12 @@ mod name_formatting_tests {
|
|||
fn sub_path_after_closing_bracket() {
|
||||
assert_eq!(
|
||||
get_short_name("bevy_asset::assets::Assets<bevy_scene::dynamic_scene::DynamicScene>::asset_event_system"),
|
||||
"Assets<DynamicScene>::asset_event_system".to_string()
|
||||
"Assets<DynamicScene>::asset_event_system"
|
||||
);
|
||||
assert_eq!(
|
||||
get_short_name("(String, String)::default"),
|
||||
"(String, String)::default".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
get_short_name("[i32; 16]::default"),
|
||||
"[i32; 16]::default".to_string()
|
||||
"(String, String)::default"
|
||||
);
|
||||
assert_eq!(get_short_name("[i32; 16]::default"), "[i32; 16]::default");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
|
||||
|
||||
use std::ptr;
|
||||
use core::ptr;
|
||||
|
||||
/// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation,
|
||||
/// which should replace this one entirely.
|
||||
|
|
417
examples/3d/pcss.rs
Normal file
417
examples/3d/pcss.rs
Normal file
|
@ -0,0 +1,417 @@
|
|||
//! Demonstrates percentage-closer soft shadows (PCSS).
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::{
|
||||
experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasing},
|
||||
prepass::{DepthPrepass, MotionVectorPrepass},
|
||||
Skybox,
|
||||
},
|
||||
math::vec3,
|
||||
pbr::{CubemapVisibleEntities, ShadowFilteringMethod, VisibleMeshEntities},
|
||||
prelude::*,
|
||||
render::{
|
||||
camera::TemporalJitter,
|
||||
primitives::{CubemapFrusta, Frustum},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender};
|
||||
|
||||
#[path = "../helpers/widgets.rs"]
|
||||
mod widgets;
|
||||
|
||||
/// The size of the light, which affects the size of the penumbras.
|
||||
const LIGHT_RADIUS: f32 = 10.0;
|
||||
|
||||
/// The intensity of the point and spot lights.
|
||||
const POINT_LIGHT_INTENSITY: f32 = 1_000_000_000.0;
|
||||
|
||||
/// The range in meters of the point and spot lights.
|
||||
const POINT_LIGHT_RANGE: f32 = 110.0;
|
||||
|
||||
/// The depth bias for directional and spot lights. This value is set higher
|
||||
/// than the default to avoid shadow acne.
|
||||
const DIRECTIONAL_SHADOW_DEPTH_BIAS: f32 = 0.20;
|
||||
|
||||
/// The depth bias for point lights. This value is set higher than the default to
|
||||
/// avoid shadow acne.
|
||||
///
|
||||
/// Unfortunately, there is a bit of Peter Panning with this value, because of
|
||||
/// the distance and angle of the light. This can't be helped in this scene
|
||||
/// without increasing the shadow map size beyond reasonable limits.
|
||||
const POINT_SHADOW_DEPTH_BIAS: f32 = 0.35;
|
||||
|
||||
/// The near Z value for the shadow map, in meters. This is set higher than the
|
||||
/// default in order to achieve greater resolution in the shadow map for point
|
||||
/// and spot lights.
|
||||
const SHADOW_MAP_NEAR_Z: f32 = 50.0;
|
||||
|
||||
/// The current application settings (light type, shadow filter, and the status
|
||||
/// of PCSS).
|
||||
#[derive(Resource)]
|
||||
struct AppStatus {
|
||||
/// The type of light presently in the scene: either directional or point.
|
||||
light_type: LightType,
|
||||
/// The type of shadow filter: Gaussian or temporal.
|
||||
shadow_filter: ShadowFilter,
|
||||
/// Whether soft shadows are enabled.
|
||||
soft_shadows: bool,
|
||||
}
|
||||
|
||||
impl Default for AppStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
light_type: default(),
|
||||
shadow_filter: default(),
|
||||
soft_shadows: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of light presently in the scene: directional, point, or spot.
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
enum LightType {
|
||||
/// A directional light, with a cascaded shadow map.
|
||||
#[default]
|
||||
Directional,
|
||||
/// A point light, with a cube shadow map.
|
||||
Point,
|
||||
/// A spot light, with a cube shadow map.
|
||||
Spot,
|
||||
}
|
||||
|
||||
/// The type of shadow filter.
|
||||
///
|
||||
/// Generally, `Gaussian` is preferred when temporal antialiasing isn't in use,
|
||||
/// while `Temporal` is preferred when TAA is in use. In this example, this
|
||||
/// setting also turns TAA on and off.
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
enum ShadowFilter {
|
||||
/// The non-temporal Gaussian filter (Castano '13 for directional lights, an
|
||||
/// analogous alternative for point and spot lights).
|
||||
#[default]
|
||||
NonTemporal,
|
||||
/// The temporal Gaussian filter (Jimenez '14 for directional lights, an
|
||||
/// analogous alternative for point and spot lights).
|
||||
Temporal,
|
||||
}
|
||||
|
||||
/// Each example setting that can be toggled in the UI.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum AppSetting {
|
||||
/// The type of light presently in the scene: directional, point, or spot.
|
||||
LightType(LightType),
|
||||
/// The type of shadow filter.
|
||||
ShadowFilter(ShadowFilter),
|
||||
/// Whether PCSS is enabled or disabled.
|
||||
SoftShadows(bool),
|
||||
}
|
||||
|
||||
/// The example application entry point.
|
||||
fn main() {
|
||||
App::new()
|
||||
.init_resource::<AppStatus>()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Bevy Percentage Closer Soft Shadows Example".into(),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugins(TemporalAntiAliasPlugin)
|
||||
.add_event::<WidgetClickEvent<AppSetting>>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, widgets::handle_ui_interactions::<AppSetting>)
|
||||
.add_systems(
|
||||
Update,
|
||||
update_radio_buttons.after(widgets::handle_ui_interactions::<AppSetting>),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_light_type_change,
|
||||
handle_shadow_filter_change,
|
||||
handle_pcss_toggle,
|
||||
)
|
||||
.after(widgets::handle_ui_interactions::<AppSetting>),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
/// Creates all the objects in the scene.
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
|
||||
spawn_camera(&mut commands, &asset_server);
|
||||
spawn_light(&mut commands, &app_status);
|
||||
spawn_gltf_scene(&mut commands, &asset_server);
|
||||
spawn_buttons(&mut commands);
|
||||
}
|
||||
|
||||
/// Spawns the camera, with the initial shadow filtering method.
|
||||
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
|
||||
commands
|
||||
.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(-12.912 * 0.7, 4.466 * 0.7, -10.624 * 0.7)
|
||||
.with_rotation(Quat::from_euler(
|
||||
EulerRot::YXZ,
|
||||
-134.76 / 180.0 * PI,
|
||||
-0.175,
|
||||
0.0,
|
||||
)),
|
||||
..default()
|
||||
})
|
||||
.insert(ShadowFilteringMethod::Gaussian)
|
||||
// `TemporalJitter` is needed for TAA. Note that it does nothing without
|
||||
// `TemporalAntiAliasSettings`.
|
||||
.insert(TemporalJitter::default())
|
||||
// We want MSAA off for TAA to work properly.
|
||||
.insert(Msaa::Off)
|
||||
// The depth prepass is needed for TAA.
|
||||
.insert(DepthPrepass)
|
||||
// The motion vector prepass is needed for TAA.
|
||||
.insert(MotionVectorPrepass)
|
||||
// Add a nice skybox.
|
||||
.insert(Skybox {
|
||||
image: asset_server.load("environment_maps/sky_skybox.ktx2"),
|
||||
brightness: 500.0,
|
||||
rotation: Quat::IDENTITY,
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns the initial light.
|
||||
fn spawn_light(commands: &mut Commands, app_status: &AppStatus) {
|
||||
// Because this light can become a directional light, point light, or spot
|
||||
// light depending on the settings, we add the union of the components
|
||||
// necessary for this light to behave as all three of those.
|
||||
commands
|
||||
.spawn(DirectionalLightBundle {
|
||||
directional_light: create_directional_light(app_status),
|
||||
transform: Transform::from_rotation(Quat::from_array([
|
||||
0.6539259,
|
||||
-0.34646285,
|
||||
0.36505926,
|
||||
-0.5648683,
|
||||
]))
|
||||
.with_translation(vec3(57.693, 34.334, -6.422)),
|
||||
..default()
|
||||
})
|
||||
// These two are needed for point lights.
|
||||
.insert(CubemapVisibleEntities::default())
|
||||
.insert(CubemapFrusta::default())
|
||||
// These two are needed for spot lights.
|
||||
.insert(VisibleMeshEntities::default())
|
||||
.insert(Frustum::default());
|
||||
}
|
||||
|
||||
/// Loads and spawns the glTF palm tree scene.
|
||||
fn spawn_gltf_scene(commands: &mut Commands, asset_server: &AssetServer) {
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/PalmTree/PalmTree.gltf#Scene0"),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns all the buttons at the bottom of the screen.
|
||||
fn spawn_buttons(commands: &mut Commands) {
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: widgets::main_ui_style(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
widgets::spawn_option_buttons(
|
||||
parent,
|
||||
"Light Type",
|
||||
&[
|
||||
(AppSetting::LightType(LightType::Directional), "Directional"),
|
||||
(AppSetting::LightType(LightType::Point), "Point"),
|
||||
(AppSetting::LightType(LightType::Spot), "Spot"),
|
||||
],
|
||||
);
|
||||
widgets::spawn_option_buttons(
|
||||
parent,
|
||||
"Shadow Filter",
|
||||
&[
|
||||
(AppSetting::ShadowFilter(ShadowFilter::Temporal), "Temporal"),
|
||||
(
|
||||
AppSetting::ShadowFilter(ShadowFilter::NonTemporal),
|
||||
"Non-Temporal",
|
||||
),
|
||||
],
|
||||
);
|
||||
widgets::spawn_option_buttons(
|
||||
parent,
|
||||
"Soft Shadows",
|
||||
&[
|
||||
(AppSetting::SoftShadows(true), "On"),
|
||||
(AppSetting::SoftShadows(false), "Off"),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates the style of the radio buttons that enable and disable soft shadows
|
||||
/// to reflect whether PCSS is enabled.
|
||||
fn update_radio_buttons(
|
||||
mut widgets: Query<
|
||||
(
|
||||
Option<&mut BackgroundColor>,
|
||||
Option<&mut Text>,
|
||||
&WidgetClickSender<AppSetting>,
|
||||
),
|
||||
Or<(With<RadioButton>, With<RadioButtonText>)>,
|
||||
>,
|
||||
app_status: Res<AppStatus>,
|
||||
) {
|
||||
for (image, text, sender) in widgets.iter_mut() {
|
||||
let selected = match **sender {
|
||||
AppSetting::LightType(light_type) => light_type == app_status.light_type,
|
||||
AppSetting::ShadowFilter(shadow_filter) => shadow_filter == app_status.shadow_filter,
|
||||
AppSetting::SoftShadows(soft_shadows) => soft_shadows == app_status.soft_shadows,
|
||||
};
|
||||
|
||||
if let Some(mut bg_color) = image {
|
||||
widgets::update_ui_radio_button(&mut bg_color, selected);
|
||||
}
|
||||
if let Some(mut text) = text {
|
||||
widgets::update_ui_radio_button_text(&mut text, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles requests from the user to change the type of light.
|
||||
fn handle_light_type_change(
|
||||
mut commands: Commands,
|
||||
mut lights: Query<Entity, Or<(With<DirectionalLight>, With<PointLight>, With<SpotLight>)>>,
|
||||
mut events: EventReader<WidgetClickEvent<AppSetting>>,
|
||||
mut app_status: ResMut<AppStatus>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let AppSetting::LightType(light_type) = **event else {
|
||||
continue;
|
||||
};
|
||||
app_status.light_type = light_type;
|
||||
|
||||
for light in lights.iter_mut() {
|
||||
let light_commands = commands
|
||||
.entity(light)
|
||||
.remove::<DirectionalLight>()
|
||||
.remove::<PointLight>()
|
||||
.remove::<SpotLight>();
|
||||
match light_type {
|
||||
LightType::Point => {
|
||||
light_commands.insert(create_point_light(&app_status));
|
||||
}
|
||||
LightType::Spot => {
|
||||
light_commands.insert(create_spot_light(&app_status));
|
||||
}
|
||||
LightType::Directional => {
|
||||
light_commands.insert(create_directional_light(&app_status));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles requests from the user to change the shadow filter method.
|
||||
///
|
||||
/// This system is also responsible for enabling and disabling TAA as
|
||||
/// appropriate.
|
||||
fn handle_shadow_filter_change(
|
||||
mut commands: Commands,
|
||||
mut cameras: Query<(Entity, &mut ShadowFilteringMethod)>,
|
||||
mut events: EventReader<WidgetClickEvent<AppSetting>>,
|
||||
mut app_status: ResMut<AppStatus>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let AppSetting::ShadowFilter(shadow_filter) = **event else {
|
||||
continue;
|
||||
};
|
||||
app_status.shadow_filter = shadow_filter;
|
||||
|
||||
for (camera, mut shadow_filtering_method) in cameras.iter_mut() {
|
||||
match shadow_filter {
|
||||
ShadowFilter::NonTemporal => {
|
||||
*shadow_filtering_method = ShadowFilteringMethod::Gaussian;
|
||||
commands.entity(camera).remove::<TemporalAntiAliasing>();
|
||||
}
|
||||
ShadowFilter::Temporal => {
|
||||
*shadow_filtering_method = ShadowFilteringMethod::Temporal;
|
||||
commands
|
||||
.entity(camera)
|
||||
.insert(TemporalAntiAliasing::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles requests from the user to toggle soft shadows on and off.
|
||||
fn handle_pcss_toggle(
|
||||
mut lights: Query<AnyOf<(&mut DirectionalLight, &mut PointLight, &mut SpotLight)>>,
|
||||
mut events: EventReader<WidgetClickEvent<AppSetting>>,
|
||||
mut app_status: ResMut<AppStatus>,
|
||||
) {
|
||||
for event in events.read() {
|
||||
let AppSetting::SoftShadows(value) = **event else {
|
||||
continue;
|
||||
};
|
||||
app_status.soft_shadows = value;
|
||||
|
||||
// Recreating the lights is the simplest way to toggle soft shadows.
|
||||
for (directional_light, point_light, spot_light) in lights.iter_mut() {
|
||||
if let Some(mut directional_light) = directional_light {
|
||||
*directional_light = create_directional_light(&app_status);
|
||||
}
|
||||
if let Some(mut point_light) = point_light {
|
||||
*point_light = create_point_light(&app_status);
|
||||
}
|
||||
if let Some(mut spot_light) = spot_light {
|
||||
*spot_light = create_spot_light(&app_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`DirectionalLight`] component with the appropriate settings.
|
||||
fn create_directional_light(app_status: &AppStatus) -> DirectionalLight {
|
||||
DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
soft_shadow_size: if app_status.soft_shadows {
|
||||
Some(LIGHT_RADIUS)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
shadow_depth_bias: DIRECTIONAL_SHADOW_DEPTH_BIAS,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`PointLight`] component with the appropriate settings.
|
||||
fn create_point_light(app_status: &AppStatus) -> PointLight {
|
||||
PointLight {
|
||||
intensity: POINT_LIGHT_INTENSITY,
|
||||
range: POINT_LIGHT_RANGE,
|
||||
shadows_enabled: true,
|
||||
radius: LIGHT_RADIUS,
|
||||
soft_shadows_enabled: app_status.soft_shadows,
|
||||
shadow_depth_bias: POINT_SHADOW_DEPTH_BIAS,
|
||||
shadow_map_near_z: SHADOW_MAP_NEAR_Z,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`SpotLight`] component with the appropriate settings.
|
||||
fn create_spot_light(app_status: &AppStatus) -> SpotLight {
|
||||
SpotLight {
|
||||
intensity: POINT_LIGHT_INTENSITY,
|
||||
range: POINT_LIGHT_RANGE,
|
||||
radius: LIGHT_RADIUS,
|
||||
shadows_enabled: true,
|
||||
soft_shadows_enabled: app_status.soft_shadows,
|
||||
shadow_depth_bias: DIRECTIONAL_SHADOW_DEPTH_BIAS,
|
||||
shadow_map_near_z: SHADOW_MAP_NEAR_Z,
|
||||
..default()
|
||||
}
|
||||
}
|
|
@ -156,6 +156,7 @@ Example | Description
|
|||
[Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)
|
||||
[Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and depth map for parallax mapping
|
||||
[Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
|
||||
[Percentage-closer soft shadows](../examples/3d/pcss.rs) | Demonstrates percentage-closer soft shadows (PCSS)
|
||||
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
|
||||
[Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes
|
||||
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
|
||||
|
|
177
examples/helpers/widgets.rs
Normal file
177
examples/helpers/widgets.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
//! Simple widgets for example UI.
|
||||
|
||||
use bevy::{ecs::system::EntityCommands, prelude::*};
|
||||
|
||||
/// An event that's sent whenever the user changes one of the settings by
|
||||
/// clicking a radio button.
|
||||
#[derive(Clone, Event, Deref, DerefMut)]
|
||||
pub struct WidgetClickEvent<T>(T);
|
||||
|
||||
/// A marker component that we place on all widgets that send
|
||||
/// [`WidgetClickEvent`]s of the given type.
|
||||
#[derive(Clone, Component, Deref, DerefMut)]
|
||||
pub struct WidgetClickSender<T>(T)
|
||||
where
|
||||
T: Clone + Send + Sync + 'static;
|
||||
|
||||
/// A marker component that we place on all radio `Button`s.
|
||||
#[derive(Clone, Copy, Component)]
|
||||
pub struct RadioButton;
|
||||
|
||||
/// A marker component that we place on all `Text` inside radio buttons.
|
||||
#[derive(Clone, Copy, Component)]
|
||||
pub struct RadioButtonText;
|
||||
|
||||
/// Returns a [`Style`] appropriate for the outer main UI node.
|
||||
///
|
||||
/// This UI is in the bottom left corner and has flex column support
|
||||
pub fn main_ui_style() -> Style {
|
||||
Style {
|
||||
flex_direction: FlexDirection::Column,
|
||||
position_type: PositionType::Absolute,
|
||||
row_gap: Val::Px(6.0),
|
||||
left: Val::Px(10.0),
|
||||
bottom: Val::Px(10.0),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a single radio button that allows configuration of a setting.
|
||||
///
|
||||
/// The type parameter specifies the value that will be packaged up and sent in
|
||||
/// a [`WidgetClickEvent`] when the radio button is clicked.
|
||||
pub fn spawn_option_button<T>(
|
||||
parent: &mut ChildBuilder,
|
||||
option_value: T,
|
||||
option_name: &str,
|
||||
is_selected: bool,
|
||||
is_first: bool,
|
||||
is_last: bool,
|
||||
) where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let (bg_color, fg_color) = if is_selected {
|
||||
(Color::WHITE, Color::BLACK)
|
||||
} else {
|
||||
(Color::BLACK, Color::WHITE)
|
||||
};
|
||||
|
||||
// Add the button node.
|
||||
parent
|
||||
.spawn(ButtonBundle {
|
||||
style: Style {
|
||||
border: UiRect::all(Val::Px(1.0)).with_left(if is_first {
|
||||
Val::Px(1.0)
|
||||
} else {
|
||||
Val::Px(0.0)
|
||||
}),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::axes(Val::Px(12.0), Val::Px(6.0)),
|
||||
..default()
|
||||
},
|
||||
border_color: BorderColor(Color::WHITE),
|
||||
border_radius: BorderRadius::ZERO
|
||||
.with_left(if is_first { Val::Px(6.0) } else { Val::Px(0.0) })
|
||||
.with_right(if is_last { Val::Px(6.0) } else { Val::Px(0.0) }),
|
||||
background_color: BackgroundColor(bg_color),
|
||||
..default()
|
||||
})
|
||||
.insert(RadioButton)
|
||||
.insert(WidgetClickSender(option_value.clone()))
|
||||
.with_children(|parent| {
|
||||
spawn_ui_text(parent, option_name, fg_color)
|
||||
.insert(RadioButtonText)
|
||||
.insert(WidgetClickSender(option_value));
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns the buttons that allow configuration of a setting.
|
||||
///
|
||||
/// The user may change the setting to any one of the labeled `options`. The
|
||||
/// value of the given type parameter will be packaged up and sent as a
|
||||
/// [`WidgetClickEvent`] when one of the radio buttons is clicked.
|
||||
pub fn spawn_option_buttons<T>(parent: &mut ChildBuilder, title: &str, options: &[(T, &str)])
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
// Add the parent node for the row.
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
spawn_ui_text(parent, title, Color::BLACK).insert(Style {
|
||||
width: Val::Px(125.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
for (option_index, (option_value, option_name)) in options.iter().cloned().enumerate() {
|
||||
spawn_option_button(
|
||||
parent,
|
||||
option_value,
|
||||
option_name,
|
||||
option_index == 0,
|
||||
option_index == 0,
|
||||
option_index == options.len() - 1,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Spawns text for the UI.
|
||||
///
|
||||
/// Returns the `EntityCommands`, which allow further customization of the text
|
||||
/// style.
|
||||
pub fn spawn_ui_text<'a>(
|
||||
parent: &'a mut ChildBuilder,
|
||||
label: &str,
|
||||
color: Color,
|
||||
) -> EntityCommands<'a> {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
label,
|
||||
TextStyle {
|
||||
font_size: 18.0,
|
||||
color,
|
||||
..default()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks for clicks on the radio buttons and sends `RadioButtonChangeEvent`s
|
||||
/// as necessary.
|
||||
pub fn handle_ui_interactions<T>(
|
||||
mut interactions: Query<
|
||||
(&Interaction, &WidgetClickSender<T>),
|
||||
(With<Button>, With<RadioButton>),
|
||||
>,
|
||||
mut widget_click_events: EventWriter<WidgetClickEvent<T>>,
|
||||
) where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
for (interaction, click_event) in interactions.iter_mut() {
|
||||
if *interaction == Interaction::Pressed {
|
||||
widget_click_events.send(WidgetClickEvent((**click_event).clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the style of the button part of a radio button to reflect its
|
||||
/// selected status.
|
||||
pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected: bool) {
|
||||
background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
|
||||
}
|
||||
|
||||
/// Updates the style of the label of a radio button to reflect its selected
|
||||
/// status.
|
||||
pub fn update_ui_radio_button_text(text: &mut Text, selected: bool) {
|
||||
let text_color = if selected { Color::BLACK } else { Color::WHITE };
|
||||
|
||||
for section in &mut text.sections {
|
||||
section.style.color = text_color;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
extend-exclude = [
|
||||
"*.pbxproj", # metadata file
|
||||
"*.patch", # Automatically generated files that should not be manually modified.
|
||||
"*.bin", # Binary files
|
||||
]
|
||||
|
||||
# Corrections take the form of a key/value pair. The key is the incorrect word
|
||||
|
|
Loading…
Reference in a new issue