Skip to content

Add lens dirt support to Bloom#24493

Open
Breakdown-Dog wants to merge 7 commits into
bevyengine:mainfrom
Breakdown-Dog:lens-dirt
Open

Add lens dirt support to Bloom#24493
Breakdown-Dog wants to merge 7 commits into
bevyengine:mainfrom
Breakdown-Dog:lens-dirt

Conversation

@Breakdown-Dog
Copy link
Copy Markdown
Contributor

@Breakdown-Dog Breakdown-Dog commented May 30, 2026

Objective

  • This PR adds optional lens dirt support to the bloom post-processing pipeline.

Solution

  • The effect is applied during the final upsampling stage and remains fully optional.

  • There's a TODO comment in upsampling_pipeline.rs, but I didn't follow it exactly for this lens dirt implementation.
    It felt like overkill for what I needed, and I just wanted a working lens dirt effect without too much extra complexity.
    The TODO approach might make things cleaner in the long run, but this simpler version works well enough for now.

  • The upsampling process is now calculated on the CPU side and blended on the GPU side, which enables future features such as bloom mask without needing to move compute_blend_factor() to the GPU side.

Testing

  • The bloom_3d example adds a lens dirt effect and runs as expected.
  • The lens_dirt_texture.png just for review. This would probably be better placed in bevy_asset_files.

lens_dirt.mov

@kfc35 kfc35 added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 30, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering May 30, 2026
@GageHowe
Copy link
Copy Markdown
Contributor

Very nice

@kfc35 kfc35 added the D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes label May 31, 2026
@alice-i-cecile
Copy link
Copy Markdown
Member

Make the case for me: why should we add this as a feature, rather than repurposing it as a usage example?

This seems like the sort of thing you would want to tune heavily on a per-game basis, and it's a nice moderate level of difficulty.

@Breakdown-Dog
Copy link
Copy Markdown
Contributor Author

Make the case for me: why should we add this as a feature, rather than repurposing it as a usage example?

This seems like the sort of thing you would want to tune heavily on a per-game basis, and it's a nice moderate level of difficulty.

Integrating lens dirt directly into the bloom pipeline is already standard practice across major engines:

  • Unity includes it as part of its Bloom.
  • Godot provides a glow map slot specifically for importing lens dirt textures.
  • Unreal Engine treats the dirt mask as a separate component, yet its official documentation discusses it alongside Bloom, highlighting how tightly coupled these features are.

For these reasons, Bevy’s Bloom should support this as well.

Furthermore, lens dirt should not simply appear wherever the screen is bright; it should appear wherever Bloom is happening. It is important to recognize that lens dirt is a "cherry on top" effect—it should enhance the image, not pollute it. If developers are left to implement this via a custom fullscreen shader, they will likely base it on screen luminance. This often leads to excessive dirt artifacts that overwhelm the scene. By having Bevy provide this feature natively, we ensure that lens dirt stays correctly bounded by the Bloom intensity, preventing visual pollution by design.

Regarding the existing TODO in the Bloom code:

If we want to support a bloom mask to control per-region intensity in the future, we will indeed need to move blend factor computation entirely to the GPU during the upsample passes. One possible direction would be to keep CPU-side blend constants when no mask is present (maintaining the current simplicity), and switch to GPU-side calculation only when masks are used. This PR demonstrates that this minimal approach is sufficient for lens dirt.

However, if Bloom is expected to gain more advanced features beyond my current knowledge, then fully removing CPU-side blend constants could be the cleaner long-term option.

@alice-i-cecile
Copy link
Copy Markdown
Member

Lovely analysis; thanks for convincing me!

@alice-i-cecile alice-i-cecile added the X-Uncontroversial This work is generally agreed upon label May 31, 2026
@coreh
Copy link
Copy Markdown
Contributor

coreh commented Jun 1, 2026

Maybe instead of an optional texture inside a LensDirt field within Bloom, we could make LensDirt a component, with a non-optional texture? So its addition/removal would enable/disable the effect? This seems to be the direction we're going for for these sorts of things

@Breakdown-Dog
Copy link
Copy Markdown
Contributor Author

Maybe instead of an optional texture inside a LensDirt field within Bloom, we could make LensDirt a component, with a non-optional texture? So its addition/removal would enable/disable the effect? This seems to be the direction we're going for for these sorts of things

Personally, I don’t think there’s a huge difference right now between having LensDirt as a standalone component versus keeping it as a field inside Bloom, since lens dirt currently only affects bloom.

If Bevy gains screen-space lens flare in the future—which is also a post-processing effect that accepts a lens dirt texture (and used heavily in games like Battlefield)—then it would make sense to promote lens dirt to its own component so that both effects can share the same texture.

For now, though, I’d prefer to keep it as a field inside Bloom. It’s simpler, and I’d rather iterate incrementally.

@group(0) @binding(4) var dirt_sampler: sampler;

// Shader version of `compute_blend_factor`.
fn compute_blend_factor_gpu(mip: f32, max_mip: f32) -> f32 {
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.

Why is this needed? This seems to conflict with #23824

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.

Oops, your explanation made me realize something—I may have been too quick to assume the TODO was strictly necessary.

It turns out we probably don't need to compute the blend factor on the GPU at all; we can simply calculate it on the CPU and upload it.

I think I was taking the TODO a bit too literally. My guess is that the original author wanted to support features like lens dirt, realized the GPU needed access to blend factors, but then ran into the issue that the mip count isn't fixed, making it awkward to fit into a uniform. That’s likely why they suggested moving compute_blend_factor to the GPU.

Instead, I’m now passing the blend factors via var<storage, read>, which works perfectly fine.

That said, I do feel like this approach conflicts even more with #23824... 😅

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 C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Uncontroversial This work is generally agreed upon

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

6 participants