Skip to content

Atmosphere as entity with transforms#23651

Merged
alice-i-cecile merged 7 commits intobevyengine:mainfrom
mate-h:m/atmosphere-transform
Apr 6, 2026
Merged

Atmosphere as entity with transforms#23651
alice-i-cecile merged 7 commits intobevyengine:mainfrom
mate-h:m/atmosphere-transform

Conversation

@mate-h
Copy link
Copy Markdown
Contributor

@mate-h mate-h commented Apr 3, 2026

Objective

Anecdotal feedback:

Solution

  • Atmosphere component can be spawned stand-alone
  • AtmosphereSettings remains on camera
  • A closest-to-camera heuristic is used to pick the primary atmosphere to render. Deliberately no multi-atmosphere support to keep the scope of this PR small and self contained. See WIP: Multiple atmospheres in one scene mate-h/bevy#19 at an attempt.
  • scene_units_to_m removed in favor of using Transform
  • Z up now possible by offsetting the viewer position to the equator
  • Floating origin systems now possible
  • Simplify the AtmosphereBuffer / AtmosphereData structs to just use the plain extracted GpuAtmosphere struct. this reduces the complexity of the struct in the mesh view bindings. Since atmosphere settings is coupled with the rendering pipeline of the atmosphere this makes sense architecturally.
  • We no longer hard code the offset to the north pole from the planet center in places.

Why not multi atmosphere:
The atmosphere uses multiple LUTs (lookup textures) to accelerate the rendering performance. Some of them are not view dependent:

  • Transmittance LUT
  • Multiple scattering LUT
  • Scattering / density LUTs

These can be coupled and rendered for each atmosphere individually. However the remainder of the pipeline is view dependent:

  • Aerial View LUT
  • Sky View LUT
  • Render Sky pass

In raymarched rendering mode, these LUTs can be skipped and only the render sky pass runs sampling on all of the atmospheres with a raymarch in screen space.

Further, the Sky View LUT uses a local reference frame to concentrate texel density along the horizon's local up axis. This in turn means it's coupled with both a specific atmosphere's local coordinates as well as the view's transform matrix. We cannot consider rendering both atmospheres into a single LUT for this reason. So it has to be unique for each pair of (view, atmosphere). Given two views and two atmospheres we would need 4 of these Sky View LUTs and at some point, raymarched rendering will become the less expensive option.

Lastly the Render Sky pass needs to happen once per view, we cannot realistically composite them in sequence with simple dual-source blending as we do with the scene, this would result in incorrect scattering integration. This in turn means we need to bind ALL of the luts calculated previously so a single render sky pass and render aerial view lut - perhaps making use of array textures. Rely on unified volumetric ingegration in the raymarching loop: for each light,for each atmosphere, attenuate inscattering and transmittance along the path integral. It is suffice to say this change is overall too complex for the time being and is likely the reason Unreal Engine also do not support multiple atmospheres. For context: our research is based heavily on Sebastian Hillarie's work, one of the Unreal graphics engineers.

That being said about multiple atmospheres - I am thinking of this PR as a segway into unified volumetrics in Bevy. that is: Render the FogVolume and Atmosphere in a single pass! Making use of the frustum aligned voxel grid "froxel" approach to accelerate the rendering. This would drastically increase the performance for scenes wanting to make use of both the atmosphere and local fog volumes.

Testing

  • Ran the examples/3d/atmosphere.rs example.

Showcase

(example screenshot unchanged compared to main.)

// Spawn earth atmosphere
commands.spawn(Atmosphere::earth(earth_medium));

commands.spawn((
    Camera3d::default(),
    // Can be adjusted to change the rendering quality
    AtmosphereSettings::default(),
));

@mate-h mate-h added A-Rendering Drawing game state to the screen M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 3, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in Rendering Apr 3, 2026
@mate-h mate-h requested a review from ecoskey April 3, 2026 20:20
@carrapaz
Copy link
Copy Markdown

carrapaz commented Apr 3, 2026

Thanks! Eagerly waiting for this 😄

Copy link
Copy Markdown
Contributor

@ecoskey ecoskey left a comment

Choose a reason for hiding this comment

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

Ooh nice! Mostly just have some nitpicks and some comments for future work.

Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_pbr/src/atmosphere/types.wgsl Outdated
Comment thread examples/large_scenes/bevy_city/src/main.rs
Comment thread examples/large_scenes/bevy_city/src/main.rs
Comment thread crates/bevy_pbr/src/atmosphere/functions.wgsl
Comment thread crates/bevy_pbr/src/atmosphere/functions.wgsl Outdated
Comment thread crates/bevy_pbr/src/atmosphere/mod.rs
Comment thread crates/bevy_pbr/src/atmosphere/mod.rs Outdated
Comment thread crates/bevy_pbr/src/atmosphere/mod.rs
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_pbr/src/atmosphere/mod.rs Outdated
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Comment thread crates/bevy_light/src/atmosphere.rs Outdated
Co-authored-by: Emerson Coskey <emerson@coskey.dev>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

Copy link
Copy Markdown
Contributor

@ecoskey ecoskey left a comment

Choose a reason for hiding this comment

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

Looks good! Agree that we shouldn't hold this up too long on renaming or other refactors. Approving 🙂

  • I do still think we can save three matmuls in ndc_to_camera_dist if the planet's scale is uniform (which it should always be imo) and we keep track of the scale separate from the matrix.
  • I'm fine with keeping AtmosphereSettings as the "driver" component for now, but will note that we moved away from this style in several other effects. i.e. BloomSettings was renamed to Bloom iirc

#[derive(Clone, Component)]
#[require(Hdr)]
#[require(GlobalTransform::default())]
#[component(on_add = set_default_transform)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice if there was some way to opt out of setting the Transform for users of non-default transform systems. I don't know if there is a nice solution that doesn't add more complexity.

The only thing I can think of is making the Transform and GlobalTransform optional (not required components), and placing the atmosphere at -6000km in Y if they are not present. That way you have to opt in to manual atmosphere positioning and can use your own components?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I need to better understand the use case, maybe I should test this change with big_space. is that a good example of a non-default transform system? also, would just changing the lines in set_default_transform to GlobalTransform work for decoupling it?

Copy link
Copy Markdown
Member

@aevyrie aevyrie Apr 5, 2026

Choose a reason for hiding this comment

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

I need to better understand the use case, maybe I should test this change with big_space. is that a good example of a non-default transform system?

Not really, it integrates with Transform. It's more about only depending on GlobalTransform instead of Transform everywhere.

would just changing the lines in set_default_transform to GlobalTransform work for decoupling it?

No, because it would just get overwritten by whatever transform system is present, including bevy's. That's why I was suggesting making the atmosphere positioning entirely opt-in: you get good default behavior for bevy out of the box, and don't have any dependency on Transform for arbitrary downstream use cases - the code only reads GlobalTransform.

@mate-h mate-h added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 5, 2026
Copy link
Copy Markdown
Contributor

@EmbersArc EmbersArc left a comment

Choose a reason for hiding this comment

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

Neat! Just some nits from me.

Comment on lines +29 to +30
/// The scale on [`GlobalTransform`] rescales the planet in world space. Tune it with the radius offset
/// when your scene uses other units, like kilometer-sized scenes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The Atmosphere fields say units: m. But according to this comment this is only accurate for scale = Vec3::ONE.

Maybe there should also be a note about transforms that are not x==y==z.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point I never tested non-uniform scale, hopefully it doesn't break too badly 😬 However, regarding the units: m that should stay the same since these units are still expressed in atmosphere-space and not world-space. So scaling the atmosphere doesn't change the meaning of those units, those are still meters. For actually scaling the atmosphere in a physically based way you'd set a smaller inner_radius and outer_radius. Now I realize this might be confusing so maybe it deserves more docs.

+ Vec3::new(0.0, atmosphere.bottom_radius, 0.0),
gpu_atmosphere
.world_to_atmosphere
.transform_point3(cam_world),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Recent versions of glam have to_vec3a()

ground_albedo: Vec3::ZERO,
inner_radius: 0.0,
outer_radius: 0.0,
world_to_atmosphere: Mat4::IDENTITY,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I prefer e.g. atmosphere_from_world for those transforms, but bevy seems to use both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason I kept the inverse here is so that I can pass it to the GPU uniform and it becomes a simple multiplication by the world space position. The public facing component API i.e. the struct Atmosphere doesn't expose this world_to_atmosphere matrix to the userspace, they are tuning the regular, non-inverted Transform component. This field is only used on the GpuAtmosphere so that the shader doesn't have to compute the inverse (expensive).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I fully agree with the inverse if it makes more sense in this case. My comment was just about the naming convention.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Apr 6, 2026
Merged via the queue into bevyengine:main with commit 18596dc Apr 6, 2026
40 checks passed
@github-project-automation github-project-automation bot moved this from Needs SME Triage to Done in Rendering Apr 6, 2026
github-merge-queue bot pushed a commit that referenced this pull request Apr 7, 2026
# Objective

- Once PR #23651 is merged we need a migration guide for breaking
changes to Bevy's atmosphere.
- Post the release note changes as a new PR to review grammar/writing
separately

## Solution

- Write a migration guide detailing the breaking changes
- Adhere to a friendly non-technical tone 
- Explain the new way of scaling the atmosphere with `Transform`

## Out of scope
- Explain how to customize the apparent up axis (i.e. horizon's
orientation) -> this should follow intuitively but maybe I should add
this too? or maybe this belongs in a release note instead?
mate-h added a commit to mate-h/bevy that referenced this pull request Apr 14, 2026
# Objective

**Anecdotal feedback:** 
- no floating origin support for implementing large-scale worlds, forked
Bevy's atmosphere at
https://github.com/philpax/veldera/blob/main/crates/bevy_pbr_atmosphere_planet/NOTICE.md
- no custom up axis support, resorted to using a custom sky shader for
flight simulator with Z up coordinate system. Bevy's atmosphere appears
tilted at a 90 degree angle with no way of changing it.

## Solution

- Atmosphere component can be spawned stand-alone
- AtmosphereSettings remains on camera
- A closest-to-camera heuristic is used to pick the primary atmosphere
to render. Deliberately no multi-atmosphere support to keep the scope of
this PR small and self contained. See
#19 at an attempt.
- `scene_units_to_m` removed in favor of using `Transform`
- Z up now possible by offsetting the viewer position to the equator
- Floating origin systems now possible
- Simplify the `AtmosphereBuffer` / `AtmosphereData` structs to just use
the plain extracted `GpuAtmosphere` struct. this reduces the complexity
of the struct in the mesh view bindings. Since atmosphere settings is
coupled with the rendering pipeline of the atmosphere this makes sense
architecturally.
- We no longer hard code the offset to the north pole from the planet
center in places.

**Why not multi atmosphere:**
The atmosphere uses multiple LUTs (lookup textures) to accelerate the
rendering performance. Some of them are not view dependent:
- Transmittance LUT
- Multiple scattering LUT
- Scattering / density LUTs

These can be coupled and rendered for each atmosphere individually.
However the remainder of the pipeline is view dependent:
- Aerial View LUT
- Sky View LUT
- Render Sky pass

In raymarched rendering mode, these LUTs can be skipped and only the
render sky pass runs sampling on all of the atmospheres with a raymarch
in screen space.

Further, the Sky View LUT uses a local reference frame to concentrate
texel density along the horizon's local up axis. This in turn means it's
coupled with both a _specific_ atmosphere's local coordinates as well as
the view's transform matrix. We cannot consider rendering both
atmospheres into a single LUT for this reason. So it has to be unique
for each pair of (view, atmosphere). Given two views and two atmospheres
we would need 4 of these Sky View LUTs and at some point, raymarched
rendering will become the less expensive option.

Lastly the Render Sky pass needs to happen once per view, we cannot
realistically composite them in sequence with simple dual-source
blending as we do with the scene, this would result in incorrect
scattering integration. This in turn means we need to bind ALL of the
luts calculated previously so a single render sky pass and render aerial
view lut - perhaps making use of array textures. Rely on unified
volumetric ingegration in the raymarching loop: for each light,for each
atmosphere, attenuate inscattering and transmittance along the path
integral. It is suffice to say this change is overall _too complex_ for
the time being and is likely the reason Unreal Engine also do not
support multiple atmospheres. For context: our research is based heavily
on Sebastian Hillarie's work, one of the Unreal graphics engineers.

That being said about multiple atmospheres - I am thinking of this PR as
a segway into unified volumetrics in Bevy. that is: Render the FogVolume
and Atmosphere in a single pass! Making use of the frustum aligned voxel
grid "froxel" approach to accelerate the rendering. This would
drastically increase the performance for scenes wanting to make use of
both the atmosphere and local fog volumes.

## Testing

- Ran the `examples/3d/atmosphere.rs` example.

---

## Showcase

(example screenshot unchanged compared to main.)

```rs
// Spawn earth atmosphere
commands.spawn(Atmosphere::earth(earth_medium));

commands.spawn((
    Camera3d::default(),
    // Can be adjusted to change the rendering quality
    AtmosphereSettings::default(),
));
```

---------

Co-authored-by: Emerson Coskey <emerson@coskey.dev>
mate-h added a commit to mate-h/bevy that referenced this pull request Apr 14, 2026
# Objective

- Once PR bevyengine#23651 is merged we need a migration guide for breaking
changes to Bevy's atmosphere.
- Post the release note changes as a new PR to review grammar/writing
separately

## Solution

- Write a migration guide detailing the breaking changes
- Adhere to a friendly non-technical tone 
- Explain the new way of scaling the atmosphere with `Transform`

## Out of scope
- Explain how to customize the apparent up axis (i.e. horizon's
orientation) -> this should follow intuitively but maybe I should add
this too? or maybe this belongs in a release note instead?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants