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

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

Copy link
Copy Markdown
Contributor

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

coreh commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

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

@mholiv

mholiv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

I had a chance to review the PR and compare it to the lens dirt implementation I did independently and think that the implementation here is sound. (my implementation was a more naive light based approach)

I do have an ECS opinion though. I think that LensDirt should be separate component. It would be more clear to add LensDirt as a camera component with a mandatory texture that it would be to add it as an optional part of bloom.

Let's say someone wants to add LensDirt without bloom. We would need to document:

> Hey you need to enable bloom but with bloom set to 0 and enable LensDirt inside of it with your dirt texture with a given strength and if you don't add the texture we have a white full screen fallback texture.-

Even if LensDirt would depend on bloom code behind the scenes having the LensDirt component be fully responsible for lens dirt and the Bloom component be fully responsible for bloom is more ECS idiomatic and, in my book, is more intuitive, while being easier to document.

Edit: See @Breakdown-Dog below.

My two cents.

@Breakdown-Dog

Copy link
Copy Markdown
Contributor Author

I had a chance to review the PR and compare it to the lens dirt implementation I did independently and think that the implementation here is sound. (my implementation was a more naive light based approach)

I do have an ECS opinion though. I think that LensDirt should be separate component. It would be more clear to add LensDirt as a camera component with a mandatory texture that it would be to add it as an optional part of bloom.

Let's say someone wants to add LensDirt without bloom. We would need to document:

Hey you need to enable bloom but with bloom set to 0 and enable LensDirt inside of it with your dirt texture with a given strength and if you don't add the texture we have a white full screen fallback texture.

Even if LensDirt would depend on bloom code behind the scenes having the LensDirt component be fully responsible for lens dirt and the Bloom component be fully responsible for bloom is more ECS idiomatic and, in my book, is more intuitive, while being easier to document.

My two cents.

Sorry, I’m not entirely sure I follow what you meant by “someone wants to add LensDirt without bloom”.

In the current implementation, the dirt texture does not show up at all unless there is bloom. In other words, the visibility of lens dirt is jointly determined by both the bloom intensity and the lens dirt intensity.

If users want a kind of persistent, subtly visible lens dirt effect, I think a custom post‑processing shader would be a perfectly reasonable solution for them.

That said, I do agree that making Lens Dirt a separate component is worth considering.

Right now, adding LensDirt inside Bloom is causing a compilation error in the bsn! macro here:

https://github.com/bevyengine/bevy/blob/main/examples%2Flarge_scenes%2Fbevy_city%2Fsrc%2Fmain.rs

(which enables Bloom via bsn!).

I’m not very familiar with this part of the codebase, and I feel that properly solving it goes a bit beyond the scope of this PR.

So, making LensDirt its own component might be a good way to temporarily sidestep the issue.

It could also make things cleaner overall—for example, Bloom might no longer need to impl Template and FromTemplate, which feels like a nicer design.

That said, I still need to spend some time experimenting to be sure what the best approach really is.

@mholiv

mholiv commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

@Breakdown-Dog Ahh, yah. You are right. Updated. I stand by my ECS components perspective though.

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.

7 participants