bevy_pbr/decal/
forward.rs

1use crate::{
2    ExtendedMaterial, Material, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline,
3    MaterialPlugin, StandardMaterial,
4};
5use bevy_app::{App, Plugin};
6use bevy_asset::{load_internal_asset, weak_handle, Asset, Assets, Handle};
7use bevy_ecs::component::Component;
8use bevy_math::{prelude::Rectangle, Quat, Vec2, Vec3};
9use bevy_reflect::{Reflect, TypePath};
10use bevy_render::render_asset::RenderAssets;
11use bevy_render::render_resource::{AsBindGroupShaderType, ShaderType};
12use bevy_render::texture::GpuImage;
13use bevy_render::{
14    alpha::AlphaMode,
15    mesh::{Mesh, Mesh3d, MeshBuilder, MeshVertexBufferLayoutRef, Meshable},
16    render_resource::{
17        AsBindGroup, CompareFunction, RenderPipelineDescriptor, Shader,
18        SpecializedMeshPipelineError,
19    },
20    RenderDebugFlags,
21};
22
23const FORWARD_DECAL_MESH_HANDLE: Handle<Mesh> =
24    weak_handle!("afa817f9-1869-4e0c-ac0d-d8cd1552d38a");
25const FORWARD_DECAL_SHADER_HANDLE: Handle<Shader> =
26    weak_handle!("f8dfbef4-d88b-42ae-9af4-d9661e9f1648");
27
28/// Plugin to render [`ForwardDecal`]s.
29pub struct ForwardDecalPlugin;
30
31impl Plugin for ForwardDecalPlugin {
32    fn build(&self, app: &mut App) {
33        load_internal_asset!(
34            app,
35            FORWARD_DECAL_SHADER_HANDLE,
36            "forward_decal.wgsl",
37            Shader::from_wgsl
38        );
39
40        app.register_type::<ForwardDecal>();
41
42        app.world_mut().resource_mut::<Assets<Mesh>>().insert(
43            FORWARD_DECAL_MESH_HANDLE.id(),
44            Rectangle::from_size(Vec2::ONE)
45                .mesh()
46                .build()
47                .rotated_by(Quat::from_rotation_arc(Vec3::Z, Vec3::Y))
48                .with_generated_tangents()
49                .unwrap(),
50        );
51
52        app.add_plugins(MaterialPlugin::<ForwardDecalMaterial<StandardMaterial>> {
53            prepass_enabled: false,
54            shadows_enabled: false,
55            debug_flags: RenderDebugFlags::default(),
56            ..Default::default()
57        });
58    }
59}
60
61/// A decal that renders via a 1x1 transparent quad mesh, smoothly alpha-blending with the underlying
62/// geometry towards the edges.
63///
64/// Because forward decals are meshes, you can use arbitrary materials to control their appearance.
65///
66/// # Usage Notes
67///
68/// * Spawn this component on an entity with a [`crate::MeshMaterial3d`] component holding a [`ForwardDecalMaterial`].
69/// * Any camera rendering a forward decal must have the [`bevy_core_pipeline::prepass::DepthPrepass`] component.
70/// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's
71///   texture with extra transparent pixels on the edges.
72#[derive(Component, Reflect)]
73#[require(Mesh3d(FORWARD_DECAL_MESH_HANDLE))]
74pub struct ForwardDecal;
75
76/// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension.
77///
78/// Make sure to register the [`MaterialPlugin`] for this material in your app setup.
79///
80/// [`StandardMaterial`] comes with out of the box support for forward decals.
81#[expect(type_alias_bounds, reason = "Type alias generics not yet stable")]
82pub type ForwardDecalMaterial<B: Material> = ExtendedMaterial<B, ForwardDecalMaterialExt>;
83
84/// Material extension for a [`ForwardDecal`].
85///
86/// In addition to wrapping your material type with this extension, your shader must use
87/// the `bevy_pbr::decal::forward::get_forward_decal_info` function.
88///
89/// The `FORWARD_DECAL` shader define will be made available to your shader so that you can gate
90/// the forward decal code behind an ifdef.
91#[derive(Asset, AsBindGroup, TypePath, Clone, Debug)]
92#[uniform(200, ForwardDecalMaterialExtUniform)]
93pub struct ForwardDecalMaterialExt {
94    /// Controls the distance threshold for decal blending with surfaces.
95    ///
96    /// This parameter determines how far away a surface can be before the decal no longer blends
97    /// with it and instead renders with full opacity.
98    ///
99    /// Lower values cause the decal to only blend with close surfaces, while higher values allow
100    /// blending with more distant surfaces.
101    ///
102    /// Units are in meters.
103    pub depth_fade_factor: f32,
104}
105
106#[derive(Clone, Default, ShaderType)]
107pub struct ForwardDecalMaterialExtUniform {
108    pub inv_depth_fade_factor: f32,
109}
110
111impl AsBindGroupShaderType<ForwardDecalMaterialExtUniform> for ForwardDecalMaterialExt {
112    fn as_bind_group_shader_type(
113        &self,
114        _images: &RenderAssets<GpuImage>,
115    ) -> ForwardDecalMaterialExtUniform {
116        ForwardDecalMaterialExtUniform {
117            inv_depth_fade_factor: 1.0 / self.depth_fade_factor.max(0.001),
118        }
119    }
120}
121
122impl MaterialExtension for ForwardDecalMaterialExt {
123    fn alpha_mode() -> Option<AlphaMode> {
124        Some(AlphaMode::Blend)
125    }
126
127    fn specialize(
128        _pipeline: &MaterialExtensionPipeline,
129        descriptor: &mut RenderPipelineDescriptor,
130        _layout: &MeshVertexBufferLayoutRef,
131        _key: MaterialExtensionKey<Self>,
132    ) -> Result<(), SpecializedMeshPipelineError> {
133        descriptor.depth_stencil.as_mut().unwrap().depth_compare = CompareFunction::Always;
134
135        descriptor.vertex.shader_defs.push("FORWARD_DECAL".into());
136
137        if let Some(fragment) = &mut descriptor.fragment {
138            fragment.shader_defs.push("FORWARD_DECAL".into());
139        }
140
141        if let Some(label) = &mut descriptor.label {
142            *label = format!("forward_decal_{}", label).into();
143        }
144
145        Ok(())
146    }
147}
148
149impl Default for ForwardDecalMaterialExt {
150    fn default() -> Self {
151        Self {
152            depth_fade_factor: 8.0,
153        }
154    }
155}