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::view_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.
29import package::render::clustered_forward;
30import package::render::clustered_forward::ClusterableObjectIndexRanges;
31import package::render::mesh_view_bindings;
32import package::render::pbr_functions;
33import package::render::pbr_types::PbrInput;
34import package::render::utils::porter_duff_over;
35import bevy_render::maths;
37@if(MESHLET_MESH_MATERIAL_PASS) import package::meshlet::visibility_buffer_resolve::VertexOutput;
38@elif(PREPASS_PIPELINE) import package::prepass::io::VertexOutput;
39@else import package::render::forward_io::VertexOutput;
41/// An object that allows stepping through all clustered decals that affect a
42/// single fragment.
43struct ClusteredDecalIterator {
44 /// Public fields follow:
45 /// The index of the decal base color texture in the binding array.
46 base_color_texture_index: i32,
47 /// The index of the decal normal map texture in the binding array.
48 normal_map_texture_index: i32,
49 /// The index of the decal metallic-roughness texture in the binding array.
50 metallic_roughness_texture_index: i32,
51 /// The index of the decal emissive texture in the binding array.
52 emissive_texture_index: i32,
53 /// The UV coordinates at which to sample that decal texture.
54 uv: vec2<f32>,
55 /// A custom tag you can use for your own purposes.
56 tag: u32,
58 /// Private fields follow:
59 /// The current offset of the index in the `ClusterableObjectIndexRanges` list.
60 decal_index_offset: i32,
61 /// The end offset of the index in the `ClusterableObjectIndexRanges` list.
62 end_offset: i32,
63 /// The world-space position of the fragment.
64 world_position: vec3<f32>,
67@if(CLUSTERED_DECALS_ARE_USABLE)
69/// Creates a new iterator over the decals at the current fragment.
70///
71/// You can retrieve `clusterable_object_index_ranges` as follows:
72///
73/// let view_z = get_view_z(world_position);
74/// let is_orthographic = view_is_orthographic();
75///
76/// let cluster_index =
77/// clustered_forward::view_fragment_cluster_index(frag_coord, view_z, is_orthographic);
78/// var clusterable_object_index_ranges =
79/// clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
80fn clustered_decal_iterator_new(
81 world_position: vec3<f32>,
82 clusterable_object_index_ranges: ptr<function, ClusterableObjectIndexRanges>
83) -> ClusteredDecalIterator {
84 return ClusteredDecalIterator(
85 -1,
86 -1,
87 -1,
88 -1,
89 vec2(0.0),
90 0u,
91 // We subtract 1 because the first thing `decal_iterator_next` does is
92 // add 1.
93 i32((*clusterable_object_index_ranges).first_decal_offset) - 1,
94 i32((*clusterable_object_index_ranges).last_clusterable_object_index_offset),
95 world_position,
96 );
99@if(CLUSTERED_DECALS_ARE_USABLE)
100/// Populates the `iterator.texture_index` and `iterator.uv` fields for the next
101/// decal overlapping the current world position.
102///
103/// Returns true if another decal was found or false if no more decals were found
104/// for this position.
105fn clustered_decal_iterator_next(iterator: ptr<function, ClusteredDecalIterator>) -> bool {
106 if ((*iterator).decal_index_offset == (*iterator).end_offset) {
107 return false;
108 }
110 (*iterator).decal_index_offset += 1;
112 while ((*iterator).decal_index_offset < (*iterator).end_offset) {
113 let decal_index = i32(clustered_forward::get_clusterable_object_id(
114 u32((*iterator).decal_index_offset)
115 ));
116 let decal_space_vector =
117 (mesh_view_bindings::clustered_decals.decals[decal_index].local_from_world *
118 vec4((*iterator).world_position, 1.0)).xyz;
120 if (all(decal_space_vector >= vec3(-0.5)) && all(decal_space_vector <= vec3(0.5))) {
121 (*iterator).base_color_texture_index = i32(
122 mesh_view_bindings::clustered_decals.decals[decal_index].base_color_texture_index
123 );
124 (*iterator).normal_map_texture_index = i32(
125 mesh_view_bindings::clustered_decals.decals[decal_index].normal_map_texture_index
126 );
127 (*iterator).metallic_roughness_texture_index = i32(
128 mesh_view_bindings::clustered_decals.decals[
129 decal_index
130 ].metallic_roughness_texture_index
131 );
132 (*iterator).emissive_texture_index = i32(
133 mesh_view_bindings::clustered_decals.decals[decal_index].emissive_texture_index
134 );
135 (*iterator).uv = decal_space_vector.xy * vec2(1.0, -1.0) + vec2(0.5);
136 (*iterator).tag =
137 mesh_view_bindings::clustered_decals.decals[decal_index].tag;
138 return true;
139 }
141 (*iterator).decal_index_offset += 1;
142 }
144 return false;
147/// Returns the view-space Z coordinate for the given world position.
148fn get_view_z(world_position: vec3<f32>) -> f32 {
149 return dot(vec4<f32>(
150 mesh_view_bindings::view.view_from_world[0].z,
151 mesh_view_bindings::view.view_from_world[1].z,
152 mesh_view_bindings::view.view_from_world[2].z,
153 mesh_view_bindings::view.view_from_world[3].z
154 ), vec4(world_position, 1.0));
157/// Returns true if the current view describes an orthographic projection or
158/// false otherwise.
159fn view_is_orthographic() -> bool {
160 return mesh_view_bindings::view.clip_from_view[3].w == 1.0;
163fn apply_decals(pbr_input: ptr<function, PbrInput>) {
164 let world_position = (*pbr_input).world_position.xyz;
165 let world_normal = (*pbr_input).world_normal;
166 let frag_coord = (*pbr_input).frag_coord.xy;
168 var base_color = (*pbr_input).material.base_color;
169 var emissive = (*pbr_input).material.emissive;
170 var Nt = (*pbr_input).N;
171 var metallic = (*pbr_input).material.metallic;
172 var perceptual_roughness = (*pbr_input).material.perceptual_roughness;
174@if(CLUSTERED_DECALS_ARE_USABLE) {
175 // Fetch the clusterable object index ranges for this world position.
177 let view_z = get_view_z(world_position);
178 let is_orthographic = view_is_orthographic();
180 let cluster_index =
181 clustered_forward::view_fragment_cluster_index(frag_coord, view_z, is_orthographic);
182 var clusterable_object_index_ranges =
183 clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
185 // Iterate over decals.
187 var iterator = clustered_decal_iterator_new(world_position, &clusterable_object_index_ranges);
188 while (clustered_decal_iterator_next(&iterator)) {
189 // Apply base color and metallic/roughness.
190 if (iterator.base_color_texture_index >= 0) {
191 let decal_base_color = textureSampleLevel(
192 mesh_view_bindings::clustered_decal_textures[iterator.base_color_texture_index],
193 mesh_view_bindings::clustered_decal_sampler,
194 iterator.uv,
195 0.0
196 );
198 // Apply the metallic (blue channel) and the roughness (green channel) map.
199 if (iterator.metallic_roughness_texture_index >= 0) {
200 let metallic_roughness_sampler = textureSampleLevel(
201 mesh_view_bindings::clustered_decal_textures[
202 iterator.metallic_roughness_texture_index
203 ],
204 mesh_view_bindings::clustered_decal_sampler,
205 iterator.uv,
206 0.0
207 );
208 // Use OVER compositing using the base color alpha.
209 metallic = mix(
210 metallic * base_color.a,
211 metallic_roughness_sampler.b,
212 decal_base_color.a
213 );
214 perceptual_roughness = mix(
215 perceptual_roughness * base_color.a,
216 metallic_roughness_sampler.g,
217 decal_base_color.a
218 );
219 }
221 // Apply base color with the standard OVER compositing operator.
222 base_color = porter_duff_over(base_color, decal_base_color);
223 }
225 @if(VERTEX_TANGENTS)
226 if (iterator.normal_map_texture_index >= 0) {
227 let Nd = textureSampleLevel(
228 mesh_view_bindings::clustered_decal_textures[iterator.normal_map_texture_index],
229 mesh_view_bindings::clustered_decal_sampler,
230 iterator.uv,
231 0.0
232 ).rgb * 2.0 - 1.0;
233 // This is the *Whiteout* normal map blending operator from [1].
234 //
235 // [1]: https://blog.selfshadow.com/publications/blending-in-detail/
236 Nt = vec3(Nt.xy + Nd.xy, Nt.z * Nd.z);
237 }
239 // Apply emissive.
240 if (iterator.emissive_texture_index >= 0) {
241 let decal_emissive = textureSampleLevel(
242 mesh_view_bindings::clustered_decal_textures[iterator.emissive_texture_index],
243 mesh_view_bindings::clustered_decal_sampler,
244 iterator.uv,
245 0.0
246 );
247 emissive += vec4(decal_emissive.rgb, 0.0);
248 }
249 }
252 (*pbr_input).material.base_color = base_color;
253 (*pbr_input).material.emissive = emissive;
254 (*pbr_input).N = normalize(Nt);
255 (*pbr_input).material.metallic = metallic;
256 (*pbr_input).material.perceptual_roughness = perceptual_roughness;
Contents