1// Support code for clustered decals.2//3// This module provides an iterator API, which you may wish to use in your own4// shaders if you want clustered decals to provide textures other than the base5// color. The iterator API allows you to iterate over all decals affecting the6// current fragment. Use `clustered_decal_iterator_new()` and7// `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 own23// texture arrays that mirror `mesh_view_bindings::clustered_decal_textures` in24// 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::clustered31#import bevy_pbr::clustered_forward32#import bevy_pbr::clustered_forward::ClusterableObjectIndexRanges33#import bevy_pbr::mesh_view_bindings34#import bevy_pbr::pbr_functions35#import bevy_pbr::pbr_types::PbrInput36#import bevy_pbr::utils::porter_duff_over37#import bevy_render::maths39#ifdef MESHLET_MESH_MATERIAL_PASS40#import bevy_pbr::meshlet_visibility_buffer_resolve::VertexOutput41#else ifdef PREPASS_PIPELINE42#import bevy_pbr::prepass_io::VertexOutput43#else44#import bevy_pbr::forward_io::VertexOutput45#endif47// An object that allows stepping through all clustered decals that affect a48// 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>,71}73#ifdef CLUSTERED_DECALS_ARE_USABLE75// 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::view_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 is98 // 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 );103}105// Populates the `iterator.texture_index` and `iterator.uv` fields for the next106// decal overlapping the current world position.107//108// Returns true if another decal was found or false if no more decals were found109// 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_index128 );129 (*iterator).normal_map_texture_index = i32(130 mesh_view_bindings::clustered_decals.decals[decal_index].normal_map_texture_index131 );132 (*iterator).metallic_roughness_texture_index = i32(133 mesh_view_bindings::clustered_decals.decals[134 decal_index135 ].metallic_roughness_texture_index136 );137 (*iterator).emissive_texture_index = i32(138 mesh_view_bindings::clustered_decals.decals[decal_index].emissive_texture_index139 );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;150}152#endif // CLUSTERED_DECALS_ARE_USABLE154// 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].z161 ), vec4(world_position, 1.0));162}164// Returns true if the current view describes an orthographic projection or165// false otherwise.166fn view_is_orthographic() -> bool {167 return mesh_view_bindings::view.clip_from_view[3].w == 1.0;168}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_USABLE182 // 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::view_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.0203 );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_index210 ],211 mesh_view_bindings::clustered_decal_sampler,212 iterator.uv,213 0.0214 );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.a220 );221 perceptual_roughness = mix(222 perceptual_roughness * base_color.a,223 metallic_roughness_sampler.g,224 decal_base_color.a225 );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_TANGENTS233 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.0239 ).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_TANGENTS247 // 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.0254 );255 emissive += vec4(decal_emissive.rgb, 0.0);256 }257 }258#endif // CLUSTERED_DECALS_ARE_USABLE260 (*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;265}