Skip to content

Implement basic clustered decal projectors.#17315

Merged
alice-i-cecile merged 18 commits intobevyengine:mainfrom
pcwalton:decal-projectors
Jan 26, 2025
Merged

Implement basic clustered decal projectors.#17315
alice-i-cecile merged 18 commits intobevyengine:mainfrom
pcwalton:decal-projectors

Conversation

@pcwalton
Copy link
Contributor

@pcwalton pcwalton commented Jan 11, 2025

This commit adds support for decal projectors to Bevy, allowing for textures to be projected on top of geometry. Decal projectors are clusterable objects, just as punctual lights and light probes are. This means that decals are only evaluated for objects within the conservative bounds of the projector, and they don't require a second pass.

These clustered decals require support for bindless textures and as such currently don't work on WebGL 2, WebGPU, macOS, or iOS. For an alternative that doesn't require bindless, see PR #16600. I believe that both contact projective decals in #16600 and clustered decals are desirable to have in Bevy. Contact projective decals offer broader hardware and driver support, while clustered decals don't require the creation of bounding geometry.

A new example, decal_projectors, has been added, which demonstrates multiple decals on a rotating object. The decal projectors can be scaled and rotated with the mouse.

There are several limitations of this initial patch that can be addressed in follow-ups:

  1. There's no way to specify the Z-index of decals. That is, the order in which multiple decals are blended on top of one another is arbitrary. A follow-up could introduce some sort of Z-index field so that artists can specify that some decals should be blended on top of others.

  2. Decals don't take the normal of the surface they're projected onto into account. Most decal implementations in other engines have a feature whereby the angle between the decal projector and the normal of the surface must be within some threshold for the decal to appear. Often, artists can specify a fade-off range for a smooth transition between oblique surfaces and aligned surfaces.

  3. There's no distance-based fadeoff toward the end of the projector range. Many decal implementations have this.

This addresses #2401.

Showcase

Screenshot 2025-01-11 052913

This commit adds support for *decal projectors* to Bevy, allowing for
textures to be projected on top of geometry. Decal projectors are
clusterable objects, just as punctual lights and light probes are. This
means that decals are only evaluated for objects within the conservative
bounds of the projector, and they don't require a second pass.

These clustered decals require support for bindless textures and as such
currently don't work on WebGL 2, WebGPU, macOS, or iOS. For an
alternative that doesn't require bindless, see PR bevyengine#16600. I believe that
both contact projective decals in bevyengine#16600 and clustered decals are
desirable to have in Bevy. Contact projective decals offer broader
hardware and driver support, while clustered decals don't require the
creation of bounding geometry.

A new example, `decal_projectors`, has been added, which demonstrates
multiple decals on a rotating object. The decal projectors can be scaled
and rotated with the mouse.

There are several limitations of this initial patch that can be
addressed in follow-ups:

1. There's no way to specify the Z-index of decals. That is, the order
   in which multiple decals are blended on top of one another is
   arbitrary. A follow-up could introduce some sort of Z-index field so
   that artists can specify that some decals should be blended on top of
   others.

2. Decals don't take the normal of the surface they're projected onto
   into account. Most decal implementations in other engines have a
   feature whereby the angle between the decal projector and the normal
   of the surface must be within some threshold for the decal to appear.
   Often, artists can specify a fade-off range for a smooth transition
   between oblique surfaces and aligned surfaces.

3. There's no distance-based fadeoff toward the end of the projector
   range. Many decal implementations have this.
@pcwalton pcwalton added A-Rendering Drawing game state to the screen C-Feature A new feature, making something new possible S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 11, 2025
@github-actions
Copy link
Contributor

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.

@BenjaminBrienen BenjaminBrienen added D-Complex Quite challenging from either a design or technical perspective. Ask for help! D-Shaders This code uses GPU shader languages labels Jan 11, 2025
@alice-i-cecile alice-i-cecile added the M-Release-Note Work that should be called out in the blog due to impact label Jan 12, 2025
Copy link
Contributor

@JMS55 JMS55 left a comment

Choose a reason for hiding this comment

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

Generally looks fine. Two requests:

  1. Can you unify the naming a little more? In some places they're called clustered decals, othertimes projected. I'd rather stick with clustered everywhere.
  2. Won't block on it, but I would like to have one of the decals in the example be a custom material, so that users can see how to do that. I'm not sure myself, I think you'd need a switch statement on the decal type for any material on a surface that could receive a decal? Not sure.

}

// TODO: this should be "if supports_bindless" or something
if binding_arrays_are_usable(&render_device, &render_adapter) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know how expensive this check is, maybe pull out of the loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the out-of-date comment. It should already be pulled out of the loop.

) -> vec4<f32> {
var base_color = initial_base_color;

#ifdef CLUSTERED_DECALS_ARE_USABLE
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#ifdef CLUSTERED_DECALS_ARE_USABLE
#ifdef CLUSTERED_DECALS_ARE_USABLE

Is this supposed to be called that? Do you mean to call it "CLUSTERED_DECALS_USED_IN_SCENE" or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's supposed to be called CLUSTERED_DECALS_ARE_USABLE; it's #define'd based on whether bindless is available.

Copy link
Contributor

Choose a reason for hiding this comment

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

HAS_CLUSTERED_DECALS or WITH_CLUSTERED_DECALS are more common

@pcwalton
Copy link
Contributor Author

@JMS55 Do you still think a projector should be called a DecalProjector? Or should I change that to ClusteredDecal? I took the name from Unity's Decal Projectors.

@alice-i-cecile
Copy link
Member

Merging #16600 first since it's ready; we can merge the examples together into one here :)

@pcwalton
Copy link
Contributor Author

@JMS55 I updated this with a couple of important changes:

  1. The term "projector" has been replaced with "clustered" everywhere.
  2. There's now a tag, which is intended to be used for application-specific purposes like indexing an array of normal maps. I added a simple demonstration of its use to the example.

@pcwalton pcwalton requested a review from JMS55 January 22, 2025 06:08
Copy link
Contributor

@rparrett rparrett left a comment

Choose a reason for hiding this comment

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

Limited review, most of the rendering code is out of my league. "Approved" once platform support questions are addressed.

/// The maximum number of decals that can be present in a view.
///
/// This number is currently relatively low in order to work around the lack of
/// first-class binding arrays in `wgpu`. When that feature is implemented, this
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a tracking issue for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's gfx-rs/wgpu#3637, is that what you mean?

//!
//! Clustered decals are the highest-quality types of decals that Bevy supports,
//! but they require bindless textures. This means that they presently can't be
//! used on WebGL 2, WebGPU, macOS, or iOS. Bevy's clustered decals can be used
Copy link
Contributor

Choose a reason for hiding this comment

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

The example seems to work on my m1 mac?

Fails as expected on WebGL and WebGPU, but with sort of mysterious errors. Not sure if this is expected or if the bindless gating isn't working properly.

(This text is repeated for ClusteredDecal docs, so any rework should be repeated there)

// webgl2
ERROR crates/bevy_render/src/render_resource/pipeline_cache.rs:895 failed to process shader:error: no definition in scope for identifier: 'bevy_pbr::decal::clustered::clustered_decal_iterator_new'

/// webgpu displays the same error, but also has various complains.
[Invalid ShaderModule (unlabeled)] is invalid.
 - While validating compute stage ([Invalid ShaderModule (unlabeled)], entryPoint: "main").
 - While calling [Device].CreateComputePipeline([ComputePipelineDescriptor ""build indexed indirect parameters""]).
Understand this warningAI
127.0.0.1/:1 Compilation log for [Invalid ShaderModule (unlabeled)]:
1 error(s) generated while compiling the shader:
:36:1 error: atomic variables in 'storage' address space must have 'read_write' access mode
var<storage> indirect_parameters_metadata: array<IndirectParametersMetadataX_naga_oil_mod_XMJSXM6K7OBRHEOR2NVSXG2C7OBZGK4DSN5RWK43TL52HS4DFOMX>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

:25:5 note: atomic sub-type of 'array<IndirectParametersMetadataX_naga_oil_mod_XMJSXM6K7OBRHEOR2NVSXG2C7OBZGK4DSN5RWK43TL52HS4DFOMX>' is declared here
    instance_count: atomic<u32>,
    ^^^^^^^^^^^^^^

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For WebGL, that's expected. I don't think there's much point in gating off decals with an ifdef in the example's custom shader if the example requires a platform that decals don't work on to begin with.
For WebGPU, the indirect parameters metadata error is unrelated and I'd rather not fix that here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point on the macOS/iOS thing. They don't have true bindless, but I forget that wgpu has a partial workaround that's good enough to make clustered decals work. I removed the mention of those platforms.

Copy link
Contributor

Choose a reason for hiding this comment

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

The error means some bound buffer is marked read-only but contains some atomic. That's not allowed by WGSL. In Hanabi I use some #ifdef to change the definition of the atomic fields to non-aromic and read them as non-atomic where it's safe to do so, to avoid that error. That's a bit tedious but it's clean.

Copy link
Member

@tychedelia tychedelia left a comment

Choose a reason for hiding this comment

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

Tested on m1 mbp.

//
// Returns true if another decal was found or false if no more decals were found
// for this position.
fn clustered_decal_iterator_next(iterator: ptr<function, ClusteredDecalIterator>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

cool pattern, hadn't seen this before

@pcwalton pcwalton 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 Jan 25, 2025
@alice-i-cecile
Copy link
Member

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jan 26, 2025
Merged via the queue into bevyengine:main with commit fc831c3 Jan 26, 2025
33 checks passed
mrchantey pushed a commit to mrchantey/bevy that referenced this pull request Feb 4, 2025
This commit adds support for *decal projectors* to Bevy, allowing for
textures to be projected on top of geometry. Decal projectors are
clusterable objects, just as punctual lights and light probes are. This
means that decals are only evaluated for objects within the conservative
bounds of the projector, and they don't require a second pass.

These clustered decals require support for bindless textures and as such
currently don't work on WebGL 2, WebGPU, macOS, or iOS. For an
alternative that doesn't require bindless, see PR bevyengine#16600. I believe that
both contact projective decals in bevyengine#16600 and clustered decals are
desirable to have in Bevy. Contact projective decals offer broader
hardware and driver support, while clustered decals don't require the
creation of bounding geometry.

A new example, `decal_projectors`, has been added, which demonstrates
multiple decals on a rotating object. The decal projectors can be scaled
and rotated with the mouse.

There are several limitations of this initial patch that can be
addressed in follow-ups:

1. There's no way to specify the Z-index of decals. That is, the order
in which multiple decals are blended on top of one another is arbitrary.
A follow-up could introduce some sort of Z-index field so that artists
can specify that some decals should be blended on top of others.

2. Decals don't take the normal of the surface they're projected onto
into account. Most decal implementations in other engines have a feature
whereby the angle between the decal projector and the normal of the
surface must be within some threshold for the decal to appear. Often,
artists can specify a fade-off range for a smooth transition between
oblique surfaces and aligned surfaces.

3. There's no distance-based fadeoff toward the end of the projector
range. Many decal implementations have this.

This addresses bevyengine#2401.
 
## Showcase

![Screenshot 2025-01-11
052913](https://github.com/user-attachments/assets/8fabbafc-60fb-461d-b715-d7977e10fe1f)
@alice-i-cecile
Copy link
Member

Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1986 if you'd like to help out.

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-Complex Quite challenging from either a design or technical perspective. Ask for help! D-Shaders This code uses GPU shader languages M-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants