-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement basic clustered decal projectors.
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.
- Loading branch information
Showing
17 changed files
with
1,266 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// Support code for clustered decal projectors. | ||
// | ||
// This module provides an iterator API, which you may wish to use in your own | ||
// shaders if you want clustered decals to provide textures other than the base | ||
// color. The iterator API allows you to iterate over all decals affecting the | ||
// current fragment. Use `clustered_decal_iterator_new()` and | ||
// `clustered_decal_iterator_next()` as follows: | ||
// | ||
// let view_z = get_view_z(vec4(world_position, 1.0)); | ||
// let is_orthographic = view_is_orthographic(); | ||
// | ||
// let cluster_index = | ||
// clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic); | ||
// var clusterable_object_index_ranges = | ||
// clustered_forward::unpack_clusterable_object_index_ranges(cluster_index); | ||
// | ||
// var iterator = clustered_decal_iterator_new(world_position, &clusterable_object_index_ranges); | ||
// while (clustered_decal_iterator_next(&iterator)) { | ||
// ... sample from the texture at iterator.texture_index at iterator.uv ... | ||
// } | ||
// | ||
// In this way, in conjunction with a custom material, you can provide your own | ||
// texture arrays that mirror `mesh_view_bindings::clustered_decal_textures` in | ||
// order to support decals with normal maps, etc. | ||
// | ||
// Note that the order in which decals are returned is currently unpredictable, | ||
// though generally stable from frame to frame. | ||
|
||
#define_import_path bevy_pbr::decal::clustered | ||
|
||
#import bevy_pbr::clustered_forward | ||
#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges | ||
#import bevy_pbr::mesh_view_bindings | ||
#import bevy_render::maths | ||
|
||
// An object that allows stepping through all clustered decals that affect a | ||
// single fragment. | ||
struct ClusteredDecalIterator { | ||
// Public fields follow: | ||
// The index of the decal texture in the binding array. | ||
texture_index: i32, | ||
// The UV coordinates at which to sample that decal texture. | ||
uv: vec2<f32>, | ||
|
||
// Private fields follow: | ||
// The current offset of the index in the `ClusterableObjectIndexRanges` list. | ||
decal_index_offset: i32, | ||
// The end offset of the index in the `ClusterableObjectIndexRanges` list. | ||
end_offset: i32, | ||
// The world-space position of the fragment. | ||
world_position: vec3<f32>, | ||
} | ||
|
||
#ifdef CLUSTERED_DECALS_ARE_USABLE | ||
|
||
// Creates a new iterator over the decals at the current fragment. | ||
// | ||
// You can retrieve `clusterable_object_index_ranges` as follows: | ||
// | ||
// let view_z = get_view_z(world_position); | ||
// let is_orthographic = view_is_orthographic(); | ||
// | ||
// let cluster_index = | ||
// clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic); | ||
// var clusterable_object_index_ranges = | ||
// clustered_forward::unpack_clusterable_object_index_ranges(cluster_index); | ||
fn clustered_decal_iterator_new( | ||
world_position: vec3<f32>, | ||
clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges> | ||
) -> ClusteredDecalIterator { | ||
return ClusteredDecalIterator( | ||
-1, | ||
vec2(0.0), | ||
// We subtract 1 because the first thing `decal_iterator_next` does is | ||
// add 1. | ||
i32((*clusterable_object_index_ranges).first_decal_offset) - 1, | ||
i32((*clusterable_object_index_ranges).last_clusterable_object_index_offset), | ||
world_position, | ||
); | ||
} | ||
|
||
// Populates the `iterator.texture_index` and `iterator.uv` fields for the next | ||
// decal overlapping the current world position. | ||
// | ||
// 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 { | ||
if ((*iterator).decal_index_offset == (*iterator).end_offset) { | ||
return false; | ||
} | ||
|
||
(*iterator).decal_index_offset += 1; | ||
|
||
while ((*iterator).decal_index_offset < (*iterator).end_offset) { | ||
let decal_index = i32(clustered_forward::get_clusterable_object_id( | ||
u32((*iterator).decal_index_offset) | ||
)); | ||
let decal_space_vector = | ||
(mesh_view_bindings::clustered_decals.decals[decal_index].local_from_world * | ||
vec4((*iterator).world_position, 1.0)).xyz; | ||
|
||
if (all(decal_space_vector >= vec3(-0.5)) && all(decal_space_vector <= vec3(0.5))) { | ||
(*iterator).texture_index = | ||
i32(mesh_view_bindings::clustered_decals.decals[decal_index].image_index); | ||
(*iterator).uv = decal_space_vector.xy * vec2(1.0, -1.0) + vec2(0.5); | ||
return true; | ||
} | ||
|
||
(*iterator).decal_index_offset += 1; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
#endif // CLUSTERED_DECALS_ARE_USABLE | ||
|
||
// Returns the view-space Z coordinate for the given world position. | ||
fn get_view_z(world_position: vec3<f32>) -> f32 { | ||
return dot(vec4<f32>( | ||
mesh_view_bindings::view.view_from_world[0].z, | ||
mesh_view_bindings::view.view_from_world[1].z, | ||
mesh_view_bindings::view.view_from_world[2].z, | ||
mesh_view_bindings::view.view_from_world[3].z | ||
), vec4(world_position, 1.0)); | ||
} | ||
|
||
// Returns true if the current view describes an orthographic projection or | ||
// false otherwise. | ||
fn view_is_orthographic() -> bool { | ||
return mesh_view_bindings::view.clip_from_view[3].w == 1.0; | ||
} | ||
|
||
// Modifies the base color at the given position to account for decals. | ||
// | ||
// Returns the new base color with decals taken into account. If no decals | ||
// overlap the current world position, returns the supplied base color | ||
// unmodified. | ||
fn apply_decal_base_color( | ||
world_position: vec3<f32>, | ||
frag_coord: vec2<f32>, | ||
initial_base_color: vec4<f32>, | ||
) -> vec4<f32> { | ||
var base_color = initial_base_color; | ||
|
||
#ifdef CLUSTERED_DECALS_ARE_USABLE | ||
// Fetch the clusterable object index ranges for this world position. | ||
|
||
let view_z = get_view_z(world_position); | ||
let is_orthographic = view_is_orthographic(); | ||
|
||
let cluster_index = | ||
clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic); | ||
var clusterable_object_index_ranges = | ||
clustered_forward::unpack_clusterable_object_index_ranges(cluster_index); | ||
|
||
// Iterate over decals. | ||
|
||
var iterator = clustered_decal_iterator_new(world_position, &clusterable_object_index_ranges); | ||
while (clustered_decal_iterator_next(&iterator)) { | ||
// Sample the current decal. | ||
let decal_base_color = textureSampleLevel( | ||
mesh_view_bindings::clustered_decal_textures[iterator.texture_index], | ||
mesh_view_bindings::clustered_decal_sampler, | ||
iterator.uv, | ||
0.0 | ||
); | ||
|
||
// Blend with the accumulated fragment. | ||
base_color = vec4( | ||
mix(base_color.rgb, decal_base_color.rgb, decal_base_color.a), | ||
base_color.a + decal_base_color.a | ||
); | ||
} | ||
#endif // CLUSTERED_DECALS_ARE_USABLE | ||
|
||
return base_color; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
//! Decals, textures that can be projected onto surfaces. | ||
pub mod projector; |
Oops, something went wrong.