Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1// Support code for clustered decals.
2//
3// This module provides an iterator API, which you may wish to use in your own
4// shaders if you want clustered decals to provide textures other than the base
5// color. The iterator API allows you to iterate over all decals affecting the
6// current fragment. Use `clustered_decal_iterator_new()` and
7// `clustered_decal_iterator_next()` as follows:
8//
9// let view_z = get_view_z(vec4(world_position, 1.0));
10// let is_orthographic = view_is_orthographic();
11//
12// let cluster_index =
13// clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic);
14// var clusterable_object_index_ranges =
15// clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
16//
17// var iterator = clustered_decal_iterator_new(world_position, &clusterable_object_index_ranges);
18// while (clustered_decal_iterator_next(&iterator)) {
19// ... sample from the texture at iterator.texture_index at iterator.uv ...
20// }
21//
22// In this way, in conjunction with a custom material, you can provide your own
23// texture arrays that mirror `mesh_view_bindings::clustered_decal_textures` in
24// order to support decals with normal maps, etc.
25//
26// Note that the order in which decals are returned is currently unpredictable,
27// though generally stable from frame to frame.
29#define_import_path bevy_pbr::decal::clustered
31#import bevy_pbr::clustered_forward
32#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges
33#import bevy_pbr::mesh_view_bindings
34#import bevy_pbr::pbr_functions
35#import bevy_pbr::pbr_types::PbrInput
36#import bevy_pbr::utils::porter_duff_over
37#import bevy_render::maths
39#ifdef MESHLET_MESH_MATERIAL_PASS
40#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput
41#else ifdef PREPASS_PIPELINE
42#import bevy_pbr::prepass_io::VertexOutput
43#else
44#import bevy_pbr::forward_io::VertexOutput
45#endif
47// An object that allows stepping through all clustered decals that affect a
48// single fragment.
49struct ClusteredDecalIterator {
50 // Public fields follow:
51 // The index of the decal base color texture in the binding array.
52 base_color_texture_index: i32,
53 // The index of the decal normal map texture in the binding array.
54 normal_map_texture_index: i32,
55 // The index of the decal metallic-roughness texture in the binding array.
56 metallic_roughness_texture_index: i32,
57 // The index of the decal emissive texture in the binding array.
58 emissive_texture_index: i32,
59 // The UV coordinates at which to sample that decal texture.
60 uv: vec2<f32>,
61 // A custom tag you can use for your own purposes.
62 tag: u32,
64 // Private fields follow:
65 // The current offset of the index in the `ClusterableObjectIndexRanges` list.
66 decal_index_offset: i32,
67 // The end offset of the index in the `ClusterableObjectIndexRanges` list.
68 end_offset: i32,
69 // The world-space position of the fragment.
70 world_position: vec3<f32>,
73#ifdef CLUSTERED_DECALS_ARE_USABLE
75// Creates a new iterator over the decals at the current fragment.
76//
77// You can retrieve `clusterable_object_index_ranges` as follows:
78//
79// let view_z = get_view_z(world_position);
80// let is_orthographic = view_is_orthographic();
81//
82// let cluster_index =
83// clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic);
84// var clusterable_object_index_ranges =
85// clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
86fn clustered_decal_iterator_new(
87 world_position: vec3<f32>,
88 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>
89) -> ClusteredDecalIterator {
90 return ClusteredDecalIterator(
91 -1,
92 -1,
93 -1,
94 -1,
95 vec2(0.0),
96 0u,
97 // We subtract 1 because the first thing `decal_iterator_next` does is
98 // add 1.
99 i32((*clusterable_object_index_ranges).first_decal_offset) - 1,
100 i32((*clusterable_object_index_ranges).last_clusterable_object_index_offset),
101 world_position,
102 );
105// Populates the `iterator.texture_index` and `iterator.uv` fields for the next
106// decal overlapping the current world position.
107//
108// Returns true if another decal was found or false if no more decals were found
109// for this position.
110fn clustered_decal_iterator_next(iterator: ptr<function, ClusteredDecalIterator>) -> bool {
111 if ((*iterator).decal_index_offset == (*iterator).end_offset) {
112 return false;
113 }
115 (*iterator).decal_index_offset += 1;
117 while ((*iterator).decal_index_offset < (*iterator).end_offset) {
118 let decal_index = i32(clustered_forward::get_clusterable_object_id(
119 u32((*iterator).decal_index_offset)
120 ));
121 let decal_space_vector =
122 (mesh_view_bindings::clustered_decals.decals[decal_index].local_from_world *
123 vec4((*iterator).world_position, 1.0)).xyz;
125 if (all(decal_space_vector >= vec3(-0.5)) && all(decal_space_vector <= vec3(0.5))) {
126 (*iterator).base_color_texture_index = i32(
127 mesh_view_bindings::clustered_decals.decals[decal_index].base_color_texture_index
128 );
129 (*iterator).normal_map_texture_index = i32(
130 mesh_view_bindings::clustered_decals.decals[decal_index].normal_map_texture_index
131 );
132 (*iterator).metallic_roughness_texture_index = i32(
133 mesh_view_bindings::clustered_decals.decals[
134 decal_index
135 ].metallic_roughness_texture_index
136 );
137 (*iterator).emissive_texture_index = i32(
138 mesh_view_bindings::clustered_decals.decals[decal_index].emissive_texture_index
139 );
140 (*iterator).uv = decal_space_vector.xy * vec2(1.0, -1.0) + vec2(0.5);
141 (*iterator).tag =
142 mesh_view_bindings::clustered_decals.decals[decal_index].tag;
143 return true;
144 }
146 (*iterator).decal_index_offset += 1;
147 }
149 return false;
152#endif // CLUSTERED_DECALS_ARE_USABLE
154// Returns the view-space Z coordinate for the given world position.
155fn get_view_z(world_position: vec3<f32>) -> f32 {
156 return dot(vec4<f32>(
157 mesh_view_bindings::view.view_from_world[0].z,
158 mesh_view_bindings::view.view_from_world[1].z,
159 mesh_view_bindings::view.view_from_world[2].z,
160 mesh_view_bindings::view.view_from_world[3].z
161 ), vec4(world_position, 1.0));
164// Returns true if the current view describes an orthographic projection or
165// false otherwise.
166fn view_is_orthographic() -> bool {
167 return mesh_view_bindings::view.clip_from_view[3].w == 1.0;
170fn apply_decals(pbr_input: ptr<function, PbrInput>) {
171 let world_position = (*pbr_input).world_position.xyz;
172 let world_normal = (*pbr_input).world_normal;
173 let frag_coord = (*pbr_input).frag_coord.xy;
175 var base_color = (*pbr_input).material.base_color;
176 var emissive = (*pbr_input).material.emissive;
177 var Nt = (*pbr_input).N;
178 var metallic = (*pbr_input).material.metallic;
179 var perceptual_roughness = (*pbr_input).material.perceptual_roughness;
181#ifdef CLUSTERED_DECALS_ARE_USABLE
182 // Fetch the clusterable object index ranges for this world position.
184 let view_z = get_view_z(world_position);
185 let is_orthographic = view_is_orthographic();
187 let cluster_index =
188 clustered_forward::fragment_cluster_index(frag_coord, view_z, is_orthographic);
189 var clusterable_object_index_ranges =
190 clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
192 // Iterate over decals.
194 var iterator = clustered_decal_iterator_new(world_position, &clusterable_object_index_ranges);
195 while (clustered_decal_iterator_next(&iterator)) {
196 // Apply base color and metallic/roughness.
197 if (iterator.base_color_texture_index >= 0) {
198 let decal_base_color = textureSampleLevel(
199 mesh_view_bindings::clustered_decal_textures[iterator.base_color_texture_index],
200 mesh_view_bindings::clustered_decal_sampler,
201 iterator.uv,
202 0.0
203 );
205 // Apply the metallic (blue channel) and the roughness (green channel) map.
206 if (iterator.metallic_roughness_texture_index >= 0) {
207 let metallic_roughness_sampler = textureSampleLevel(
208 mesh_view_bindings::clustered_decal_textures[
209 iterator.metallic_roughness_texture_index
210 ],
211 mesh_view_bindings::clustered_decal_sampler,
212 iterator.uv,
213 0.0
214 );
215 // Use OVER compositing using the base color alpha.
216 metallic = mix(
217 metallic * base_color.a,
218 metallic_roughness_sampler.b,
219 decal_base_color.a
220 );
221 perceptual_roughness = mix(
222 perceptual_roughness * base_color.a,
223 metallic_roughness_sampler.g,
224 decal_base_color.a
225 );
226 }
228 // Apply base color with the standard OVER compositing operator.
229 base_color = porter_duff_over(base_color, decal_base_color);
230 }
232#ifdef VERTEX_TANGENTS
233 if (iterator.normal_map_texture_index >= 0) {
234 let Nd = textureSampleLevel(
235 mesh_view_bindings::clustered_decal_textures[iterator.normal_map_texture_index],
236 mesh_view_bindings::clustered_decal_sampler,
237 iterator.uv,
238 0.0
239 ).rgb * 2.0 - 1.0;
240 // This is the *Whiteout* normal map blending operator from [1].
241 //
242 // [1]: https://blog.selfshadow.com/publications/blending-in-detail/
243 Nt = vec3(Nt.xy + Nd.xy, Nt.z * Nd.z);
244 }
245#endif // VERTEX_TANGENTS
247 // Apply emissive.
248 if (iterator.emissive_texture_index >= 0) {
249 let decal_emissive = textureSampleLevel(
250 mesh_view_bindings::clustered_decal_textures[iterator.emissive_texture_index],
251 mesh_view_bindings::clustered_decal_sampler,
252 iterator.uv,
253 0.0
254 );
255 emissive += vec4(decal_emissive.rgb, 0.0);
256 }
257 }
258#endif // CLUSTERED_DECALS_ARE_USABLE
260 (*pbr_input).material.base_color = base_color;
261 (*pbr_input).material.emissive = emissive;
262 (*pbr_input).N = normalize(Nt);
263 (*pbr_input).material.metallic = metallic;
264 (*pbr_input).material.perceptual_roughness = perceptual_roughness;
Contents