Skip to content

Commit

Permalink
Implement volumetric fog support for both point lights and spotlights (
Browse files Browse the repository at this point in the history
…bevyengine#15361)

# Objective
- Fixes: bevyengine#14451

## Solution
- Adding volumetric fog sampling code for both point lights and
spotlights.

## Testing
- I have modified the example of volumetric_fog.rs by adding a
volumetric point light and a volumetric spotlight.


https://github.com/user-attachments/assets/3eeb77a0-f22d-40a6-a48a-2dd75d55a877
  • Loading branch information
Soulghost authored Sep 29, 2024
1 parent 9cc7e7c commit 39d96ef
Show file tree
Hide file tree
Showing 6 changed files with 444 additions and 42 deletions.
35 changes: 22 additions & 13 deletions crates/bevy_pbr/src/cluster/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ use bevy_utils::tracing::warn;

use crate::{
ClusterConfig, ClusterFarZMode, Clusters, GlobalVisibleClusterableObjects, PointLight,
SpotLight, ViewClusterBindings, VisibleClusterableObjects,
SpotLight, ViewClusterBindings, VisibleClusterableObjects, VolumetricLight,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS,
};

use super::ClusterableObjectOrderData;

const NDC_MIN: Vec2 = Vec2::NEG_ONE;
const NDC_MAX: Vec2 = Vec2::ONE;

Expand All @@ -34,6 +36,7 @@ pub(crate) struct ClusterableObjectAssignmentData {
transform: GlobalTransform,
range: f32,
shadows_enabled: bool,
volumetric: bool,
spot_light_angle: Option<f32>,
render_layers: RenderLayers,
}
Expand Down Expand Up @@ -67,13 +70,15 @@ pub(crate) fn assign_objects_to_clusters(
&GlobalTransform,
&PointLight,
Option<&RenderLayers>,
Option<&VolumetricLight>,
&ViewVisibility,
)>,
spot_lights_query: Query<(
Entity,
&GlobalTransform,
&SpotLight,
Option<&RenderLayers>,
Option<&VolumetricLight>,
&ViewVisibility,
)>,
mut clusterable_objects: Local<Vec<ClusterableObjectAssignmentData>>,
Expand All @@ -93,11 +98,12 @@ pub(crate) fn assign_objects_to_clusters(
.iter()
.filter(|(.., visibility)| visibility.get())
.map(
|(entity, transform, point_light, maybe_layers, _visibility)| {
|(entity, transform, point_light, maybe_layers, volumetric, _visibility)| {
ClusterableObjectAssignmentData {
entity,
transform: GlobalTransform::from_translation(transform.translation()),
shadows_enabled: point_light.shadows_enabled,
volumetric: volumetric.is_some(),
range: point_light.range,
spot_light_angle: None,
render_layers: maybe_layers.unwrap_or_default().clone(),
Expand All @@ -110,11 +116,12 @@ pub(crate) fn assign_objects_to_clusters(
.iter()
.filter(|(.., visibility)| visibility.get())
.map(
|(entity, transform, spot_light, maybe_layers, _visibility)| {
|(entity, transform, spot_light, maybe_layers, volumetric, _visibility)| {
ClusterableObjectAssignmentData {
entity,
transform: *transform,
shadows_enabled: spot_light.shadows_enabled,
volumetric: volumetric.is_some(),
range: spot_light.range,
spot_light_angle: Some(spot_light.outer_angle),
render_layers: maybe_layers.unwrap_or_default().clone(),
Expand All @@ -134,16 +141,18 @@ pub(crate) fn assign_objects_to_clusters(
{
clusterable_objects.sort_by(|clusterable_object_1, clusterable_object_2| {
crate::clusterable_object_order(
(
&clusterable_object_1.entity,
&clusterable_object_1.shadows_enabled,
&clusterable_object_1.spot_light_angle.is_some(),
),
(
&clusterable_object_2.entity,
&clusterable_object_2.shadows_enabled,
&clusterable_object_2.spot_light_angle.is_some(),
),
ClusterableObjectOrderData {
entity: &clusterable_object_1.entity,
shadows_enabled: &clusterable_object_1.shadows_enabled,
is_volumetric_light: &clusterable_object_1.volumetric,
is_spot_light: &clusterable_object_1.spot_light_angle.is_some(),
},
ClusterableObjectOrderData {
entity: &clusterable_object_2.entity,
shadows_enabled: &clusterable_object_2.shadows_enabled,
is_volumetric_light: &clusterable_object_2.volumetric,
is_spot_light: &clusterable_object_2.spot_light_angle.is_some(),
},
)
});

Expand Down
20 changes: 14 additions & 6 deletions crates/bevy_pbr/src/cluster/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,13 @@ impl Default for GpuClusterableObjectsUniform {
}
}

pub(crate) struct ClusterableObjectOrderData<'a> {
pub(crate) entity: &'a Entity,
pub(crate) shadows_enabled: &'a bool,
pub(crate) is_volumetric_light: &'a bool,
pub(crate) is_spot_light: &'a bool,
}

#[allow(clippy::too_many_arguments)]
// Sort clusterable objects by:
//
Expand All @@ -505,13 +512,14 @@ impl Default for GpuClusterableObjectsUniform {
// clusterable objects are chosen if the clusterable object count limit is
// exceeded.
pub(crate) fn clusterable_object_order(
(entity_1, shadows_enabled_1, is_spot_light_1): (&Entity, &bool, &bool),
(entity_2, shadows_enabled_2, is_spot_light_2): (&Entity, &bool, &bool),
a: ClusterableObjectOrderData,
b: ClusterableObjectOrderData,
) -> core::cmp::Ordering {
is_spot_light_1
.cmp(is_spot_light_2) // pointlights before spot lights
.then_with(|| shadows_enabled_2.cmp(shadows_enabled_1)) // shadow casters before non-casters
.then_with(|| entity_1.cmp(entity_2)) // stable
a.is_spot_light
.cmp(b.is_spot_light) // pointlights before spot lights
.then_with(|| b.shadows_enabled.cmp(a.shadows_enabled)) // shadow casters before non-casters
.then_with(|| b.is_volumetric_light.cmp(a.is_volumetric_light)) // volumetric lights before non-volumetric lights
.then_with(|| a.entity.cmp(b.entity)) // stable
}

/// Extracts clusters from the main world from the render world.
Expand Down
68 changes: 54 additions & 14 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct ExtractedPointLight {
pub shadow_normal_bias: f32,
pub shadow_map_near_z: f32,
pub spot_light_angles: Option<(f32, f32)>,
pub volumetric: bool,
}

#[derive(Component, Debug)]
Expand All @@ -69,6 +70,7 @@ bitflags::bitflags! {
struct PointLightFlags: u32 {
const SHADOWS_ENABLED = 1 << 0;
const SPOT_LIGHT_Y_NEGATIVE = 1 << 1;
const VOLUMETRIC = 1 << 2;
const NONE = 0;
const UNINITIALIZED = 0xFFFF;
}
Expand Down Expand Up @@ -195,6 +197,7 @@ pub fn extract_lights(
&GlobalTransform,
&ViewVisibility,
&CubemapFrusta,
Option<&VolumetricLight>,
)>,
>,
spot_lights: Extract<
Expand All @@ -204,6 +207,7 @@ pub fn extract_lights(
&GlobalTransform,
&ViewVisibility,
&Frustum,
Option<&VolumetricLight>,
)>,
>,
directional_lights: Extract<
Expand Down Expand Up @@ -245,8 +249,14 @@ pub fn extract_lights(

let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
for entity in global_point_lights.iter().copied() {
let Ok((point_light, cubemap_visible_entities, transform, view_visibility, frusta)) =
point_lights.get(entity)
let Ok((
point_light,
cubemap_visible_entities,
transform,
view_visibility,
frusta,
volumetric_light,
)) = point_lights.get(entity)
else {
continue;
};
Expand Down Expand Up @@ -274,6 +284,7 @@ pub fn extract_lights(
* core::f32::consts::SQRT_2,
shadow_map_near_z: point_light.shadow_map_near_z,
spot_light_angles: None,
volumetric: volumetric_light.is_some(),
};
point_lights_values.push((
entity,
Expand All @@ -289,8 +300,14 @@ pub fn extract_lights(

let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
for entity in global_point_lights.iter().copied() {
if let Ok((spot_light, visible_entities, transform, view_visibility, frustum)) =
spot_lights.get(entity)
if let Ok((
spot_light,
visible_entities,
transform,
view_visibility,
frustum,
volumetric_light,
)) = spot_lights.get(entity)
{
if !view_visibility.get() {
continue;
Expand Down Expand Up @@ -325,6 +342,7 @@ pub fn extract_lights(
* core::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)),
volumetric: volumetric_light.is_some(),
},
render_visible_entities,
*frustum,
Expand Down Expand Up @@ -621,6 +639,12 @@ pub fn prepare_lights(
.filter(|light| light.1.spot_light_angles.is_none())
.count();

let point_light_volumetric_enabled_count = point_lights
.iter()
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_none())
.count()
.min(max_texture_cubes);

let point_light_shadow_maps_count = point_lights
.iter()
.filter(|light| light.1.shadows_enabled && light.1.spot_light_angles.is_none())
Expand All @@ -641,6 +665,12 @@ pub fn prepare_lights(
.count()
.min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);

let spot_light_volumetric_enabled_count = point_lights
.iter()
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
.count()
.min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);

let spot_light_shadow_maps_count = point_lights
.iter()
.filter(|(_, light, _)| light.shadows_enabled && light.spot_light_angles.is_some())
Expand All @@ -654,16 +684,18 @@ pub fn prepare_lights(
// - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
point_lights.sort_by(|(entity_1, light_1, _), (entity_2, light_2, _)| {
clusterable_object_order(
(
entity_1,
&light_1.shadows_enabled,
&light_1.spot_light_angles.is_some(),
),
(
entity_2,
&light_2.shadows_enabled,
&light_2.spot_light_angles.is_some(),
),
ClusterableObjectOrderData {
entity: entity_1,
shadows_enabled: &light_1.shadows_enabled,
is_volumetric_light: &light_1.volumetric,
is_spot_light: &light_1.spot_light_angles.is_some(),
},
ClusterableObjectOrderData {
entity: entity_2,
shadows_enabled: &light_2.shadows_enabled,
is_volumetric_light: &light_2.volumetric,
is_spot_light: &light_2.spot_light_angles.is_some(),
},
)
});

Expand Down Expand Up @@ -706,6 +738,14 @@ pub fn prepare_lights(
1.0,
light.shadow_map_near_z,
);
if light.shadows_enabled
&& light.volumetric
&& (index < point_light_volumetric_enabled_count
|| (light.spot_light_angles.is_some()
&& index - point_light_count < spot_light_volumetric_enabled_count))
{
flags |= PointLightFlags::VOLUMETRIC;
}

let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles {
Some((inner, outer)) => {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ struct ClusterableObject {

const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u;
const POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u;
const POINT_LIGHT_FLAGS_VOLUMETRIC_BIT: u32 = 4u;

struct DirectionalCascade {
clip_from_world: mat4x4<f32>,
Expand Down
Loading

0 comments on commit 39d96ef

Please sign in to comment.