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.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 a42/// 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>,65}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 is92 // 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 );97}99@if(CLUSTERED_DECALS_ARE_USABLE)100/// Populates the `iterator.texture_index` and `iterator.uv` fields for the next101/// decal overlapping the current world position.102///103/// Returns true if another decal was found or false if no more decals were found104/// 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_index123 );124 (*iterator).normal_map_texture_index = i32(125 mesh_view_bindings::clustered_decals.decals[decal_index].normal_map_texture_index126 );127 (*iterator).metallic_roughness_texture_index = i32(128 mesh_view_bindings::clustered_decals.decals[129 decal_index130 ].metallic_roughness_texture_index131 );132 (*iterator).emissive_texture_index = i32(133 mesh_view_bindings::clustered_decals.decals[decal_index].emissive_texture_index134 );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;145}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].z154 ), vec4(world_position, 1.0));155}157/// Returns true if the current view describes an orthographic projection or158/// false otherwise.159fn view_is_orthographic() -> bool {160 return mesh_view_bindings::view.clip_from_view[3].w == 1.0;161}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.0196 );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_index203 ],204 mesh_view_bindings::clustered_decal_sampler,205 iterator.uv,206 0.0207 );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.a213 );214 perceptual_roughness = mix(215 perceptual_roughness * base_color.a,216 metallic_roughness_sampler.g,217 decal_base_color.a218 );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.0232 ).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.0246 );247 emissive += vec4(decal_emissive.rgb, 0.0);248 }249 }250}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;257}