Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions crates/bevy_light/src/directional_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,38 @@ pub fn update_directional_light_frusta(
.collect();
}
}

/// This component marks a [`DirectionalLight`] entity for changing the size and intensity of the sun disk.
#[derive(Component, Clone)]
#[require(DirectionalLight)]
pub struct SunLight {
/// The angular size (diameter) of the sun disk in radians as observed from Earth.
pub angular_size: f32,
/// Multiplier applied to the brightness of the sun disk in the sky.
///
/// `0.0` disables the sun disk entirely while still
/// allowing the sun's radiance to scatter into the atmosphere,
/// and `1.0` renders the sun disk at its normal intensity.
pub intensity: f32,
}

impl SunLight {
pub const SUN: SunLight = SunLight {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typing out SunLight:SUN or SunDisk:SUN feels a bit awkward, but I don't have a better idea. maybe SunDisk:EARTH ? since this is exactly 1 AU away? I am adding martian atmosphere eventually so there can be a SunDisk:MARS too that has different values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe SunDisk::VIEWED_FROM_EARTH

Copy link
Contributor Author

@defuz defuz Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to ::EARTH.

VIEWED_FROM_EARTH sounds too verbose to me. "Viewed from" is the meaning of SunDisk component, so SunDisk::EARTH is unambiguous.

// 32 arc minutes is the mean size of the sun disk when the Earth is
// exactly 1 astronomical unit from the sun.
angular_size: 0.00930842,
intensity: 1.0,
};
}

impl Default for SunLight {
fn default() -> Self {
Self::SUN
}
}

impl Default for &SunLight {
fn default() -> Self {
&SunLight::SUN
}
}
2 changes: 1 addition & 1 deletion crates/bevy_light/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub use spot_light::{
mod directional_light;
pub use directional_light::{
update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap,
DirectionalLightTexture,
DirectionalLightTexture, SunLight,
};

/// The light prelude.
Expand Down
17 changes: 9 additions & 8 deletions crates/bevy_pbr/src/atmosphere/functions.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@


// CONSTANTS

const FRAC_PI: f32 = 0.3183098862; // 1 / π
const FRAC_2_PI: f32 = 0.15915494309; // 1 / (2π)
const FRAC_3_16_PI: f32 = 0.0596831036594607509; // 3 / (16π)
Expand Down Expand Up @@ -275,8 +274,6 @@ fn sample_local_inscattering(local_atmosphere: AtmosphereSample, ray_dir: vec3<f
return inscattering;
}

const SUN_ANGULAR_SIZE: f32 = 0.0174533; // angular diameter of sun in radians

fn sample_sun_radiance(ray_dir_ws: vec3<f32>) -> vec3<f32> {
let r = view_radius();
let mu_view = ray_dir_ws.y;
Expand All @@ -285,11 +282,15 @@ fn sample_sun_radiance(ray_dir_ws: vec3<f32>) -> vec3<f32> {
for (var light_i: u32 = 0u; light_i < lights.n_directional_lights; light_i++) {
let light = &lights.directional_lights[light_i];
let neg_LdotV = dot((*light).direction_to_light, ray_dir_ws);
let angle_to_sun = fast_acos(neg_LdotV);
let pixel_size = fwidth(angle_to_sun);
let factor = smoothstep(0.0, -pixel_size * ROOT_2, angle_to_sun - SUN_ANGULAR_SIZE * 0.5);
let sun_solid_angle = (SUN_ANGULAR_SIZE * SUN_ANGULAR_SIZE) * 4.0 * FRAC_PI;
sun_radiance += ((*light).color.rgb / sun_solid_angle) * factor * shadow_factor;
let angle_to_sun = fast_acos(clamp(neg_LdotV, -1.0, 1.0));
let w = max(0.5 * fwidth(angle_to_sun), 1e-6);
let sun_angular_size = (*light).angular_size;
let sun_intensity = (*light).intensity;
if sun_angular_size > 0.0 && sun_intensity > 0.0 {
let factor = 1 - smoothstep(sun_angular_size * 0.5 - w, sun_angular_size * 0.5 + w, angle_to_sun);
let sun_solid_angle = (sun_angular_size * sun_angular_size) * 0.25 * PI;
Copy link
Contributor

@mate-h mate-h Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same expression as before, even though the PR description/comment above suggests otherwise.
FRAC_PI = 1 / PI

0.25 = 1 / 4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's precisely reciprocal, no? 4/pi vs pi/4. :)

sun_radiance += ((*light).color.rgb / sun_solid_angle) * sun_intensity * factor * shadow_factor;
}
}
return sun_radiance;
}
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use bevy_ecs::{
use bevy_light::cascade::Cascade;
use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
use bevy_light::cluster::GlobalVisibleClusterableObjects;
use bevy_light::SunLight;
use bevy_light::{
spot_light_clip_from_view, spot_light_world_from_view, AmbientLight, CascadeShadowConfig,
Cascades, DirectionalLight, DirectionalLightShadowMap, NotShadowCaster, PointLight,
Expand Down Expand Up @@ -103,6 +104,8 @@ pub struct ExtractedDirectionalLight {
pub soft_shadow_size: Option<f32>,
/// True if this light is using two-phase occlusion culling.
pub occlusion_culling: bool,
pub angular_size: f32,
pub intensity: f32,
}

// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
Expand Down Expand Up @@ -138,6 +141,8 @@ pub struct GpuDirectionalLight {
cascades_overlap_proportion: f32,
depth_texture_base_index: u32,
decal_index: u32,
angular_size: f32,
intensity: f32,
}

// NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_view_types.wgsl!
Expand Down Expand Up @@ -279,6 +284,7 @@ pub fn extract_lights(
Option<&RenderLayers>,
Option<&VolumetricLight>,
Has<OcclusionCulling>,
Option<&SunLight>,
),
Without<SpotLight>,
>,
Expand Down Expand Up @@ -460,6 +466,7 @@ pub fn extract_lights(
maybe_layers,
volumetric_light,
occlusion_culling,
sun_light,
) in &directional_lights
{
if !view_visibility.get() {
Expand Down Expand Up @@ -526,6 +533,8 @@ pub fn extract_lights(
frusta: extracted_frusta,
render_layers: maybe_layers.unwrap_or_default().clone(),
occlusion_culling,
angular_size: sun_light.unwrap_or_default().angular_size,
intensity: sun_light.unwrap_or_default().intensity,
},
RenderCascadesVisibleEntities {
entities: cascade_visible_entities,
Expand Down Expand Up @@ -1152,6 +1161,8 @@ pub fn prepare_lights(
num_cascades: num_cascades as u32,
cascades_overlap_proportion: light.cascade_shadow_config.overlap_proportion,
depth_texture_base_index: num_directional_cascades_enabled_for_this_view as u32,
angular_size: light.angular_size,
intensity: light.intensity,
decal_index: decals
.as_ref()
.and_then(|decals| decals.get(*light_entity))
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/render/mesh_view_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ struct DirectionalLight {
cascades_overlap_proportion: f32,
depth_texture_base_index: u32,
decal_index: u32,
angular_size: f32,
intensity: f32,
};

const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u << 0u;
Expand Down
6 changes: 1 addition & 5 deletions crates/bevy_solari/src/scene/binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ use core::{f32::consts::TAU, hash::Hash, num::NonZeroU32, ops::Deref};
const MAX_MESH_SLAB_COUNT: NonZeroU32 = NonZeroU32::new(500).unwrap();
const MAX_TEXTURE_COUNT: NonZeroU32 = NonZeroU32::new(5_000).unwrap();

/// Average angular diameter of the sun as seen from earth.
/// <https://en.wikipedia.org/wiki/Angular_diameter#Use_in_astronomy>
const SUN_ANGULAR_DIAMETER_RADIANS: f32 = 0.00930842;

const TEXTURE_MAP_NONE: u32 = u32::MAX;
const LIGHT_NOT_PRESENT_THIS_FRAME: u32 = u32::MAX;

Expand Down Expand Up @@ -407,7 +403,7 @@ struct GpuDirectionalLight {

impl GpuDirectionalLight {
fn new(directional_light: &ExtractedDirectionalLight) -> Self {
let cos_theta_max = cos(SUN_ANGULAR_DIAMETER_RADIANS / 2.0);
let cos_theta_max = cos(directional_light.angular_size / 2.0);
let solid_angle = TAU * (1.0 - cos_theta_max);
let luminance =
(directional_light.color.to_vec3() * directional_light.illuminance) / solid_angle;
Expand Down
Loading