Merge branch 'main' into res-acq

This commit is contained in:
MiniaczQ 2024-09-18 22:31:34 +02:00 committed by GitHub
commit a9500897ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 2468 additions and 226 deletions

View file

@ -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"

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

View file

@ -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" },

View file

@ -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)

View file

@ -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<'_>) {

View file

@ -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,25 +1784,41 @@ 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);
Some(D::fetch(
&mut self.fetch,
*entity,
TableRow::from_usize(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);
Some(D::fetch(
&mut self.fetch,
archetype_entity.id(),
archetype_entity.table_row(),
))
// 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

View file

@ -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

View file

@ -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>();
}

View file

@ -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.

View file

@ -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,

View file

@ -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.

View file

@ -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 {

View file

@ -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);

View file

@ -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,
}

View file

@ -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(

View file

@ -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;
}

View file

@ -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,
}

View file

@ -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()
}),
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,
compare: Some(CompareFunction::GreaterEqual),
..Default::default()
..base_sampler_descriptor
}),
point_light_linear_sampler: render_device.create_sampler(&base_sampler_descriptor),
directional_light_comparison_sampler: render_device.create_sampler(
&SamplerDescriptor {
compare: Some(CompareFunction::GreaterEqual),
..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

View file

@ -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);

View file

@ -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),

View file

@ -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;

View file

@ -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,

View file

@ -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 =

View file

@ -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
}

View file

@ -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 {

View file

@ -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>();

View file

@ -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()

View file

@ -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" }

View file

@ -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 {

View file

@ -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 {

View file

@ -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.

View file

@ -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");
}
}

View file

@ -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
View 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()
}
}

View file

@ -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
View 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;
}
}

View file

@ -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