Skip to main content

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::{Asset, Assets, Handle};
7use bevy_ecs::{
8    component::Component, lifecycle::HookContext, resource::Resource, world::DeferredWorld,
9};
10use bevy_math::{Quat, Vec2, Vec3};
11use bevy_mesh::{Mesh, Mesh3d, MeshBuilder, MeshVertexBufferLayoutRef, Meshable};
12use bevy_reflect::{Reflect, TypePath};
13use bevy_render::{
14    render_asset::RenderAssets,
15    render_resource::{
16        AsBindGroup, AsBindGroupShaderType, CompareFunction, RenderPipelineDescriptor, ShaderType,
17        SpecializedMeshPipelineError,
18    },
19    texture::GpuImage,
20    RenderDebugFlags,
21};
22use bevy_shader::load_shader_library;
23use bevy_shape::Rectangle;
24
25/// Plugin to render [`ForwardDecal`]s.
26pub struct ForwardDecalPlugin;
27
28impl Plugin for ForwardDecalPlugin {
29    fn build(&self, app: &mut App) {
30        load_shader_library!(app, "forward.wesl");
31
32        let mesh = app.world_mut().resource_mut::<Assets<Mesh>>().add(
33            Rectangle::from_size(Vec2::ONE)
34                .mesh()
35                .build()
36                .rotated_by(Quat::from_rotation_arc(Vec3::Z, Vec3::Y))
37                .with_generated_tangents()
38                .unwrap(),
39        );
40
41        app.insert_resource(ForwardDecalMesh(mesh));
42
43        app.add_plugins(MaterialPlugin::<ForwardDecalMaterial<StandardMaterial>> {
44            debug_flags: RenderDebugFlags::default(),
45            ..Default::default()
46        });
47    }
48}
49
50/// A decal that renders via a 1x1 quad mesh, smoothly alpha-blending with the underlying
51/// geometry towards the edges.
52///
53/// Because forward decals are meshes, you can use arbitrary materials to control their appearance.
54///
55/// # Usage Notes
56///
57/// * Spawn this component on an entity with a [`crate::MeshMaterial3d`] component holding a [`ForwardDecalMaterial`].
58/// * Any camera rendering a forward decal must have the [`bevy_core_pipeline::prepass::DepthPrepass`] component.
59/// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's
60///   texture with extra transparent pixels on the edges.
61/// * On Wasm, requires using WebGPU and disabling `Msaa` on your camera.
62#[derive(Component, Reflect)]
63#[require(Mesh3d)]
64#[component(on_add=forward_decal_set_mesh)]
65pub struct ForwardDecal;
66
67/// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension.
68///
69/// Make sure to register the [`MaterialPlugin`] for this material in your app setup.
70///
71/// [`StandardMaterial`] comes with out of the box support for forward decals.
72#[expect(type_alias_bounds, reason = "Type alias generics not yet stable")]
73pub type ForwardDecalMaterial<B: Material> = ExtendedMaterial<B, ForwardDecalMaterialExt>;
74
75/// Material extension for a [`ForwardDecal`].
76///
77/// In addition to wrapping your material type with this extension, your shader must use
78/// the `bevy_pbr::decal::forward::get_forward_decal_info` function.
79///
80/// The `FORWARD_DECAL` shader define will be made available to your shader so that you can gate
81/// the forward decal code behind an ifdef.
82#[derive(Asset, AsBindGroup, TypePath, Clone, Debug)]
83#[uniform(200, ForwardDecalMaterialExtUniform)]
84pub struct ForwardDecalMaterialExt {
85    /// Controls the distance threshold for decal blending with surfaces.
86    ///
87    /// This parameter determines how far away a surface can be before the decal no longer blends
88    /// with it and instead renders with full opacity.
89    ///
90    /// Lower values cause the decal to only blend with close surfaces, while higher values allow
91    /// blending with more distant surfaces.
92    ///
93    /// Units are in meters.
94    /// This has no effect if alpha mode is `Opaque`.
95    pub depth_fade_factor: f32,
96}
97
98#[derive(Clone, Default, ShaderType)]
99pub struct ForwardDecalMaterialExtUniform {
100    pub inv_depth_fade_factor: f32,
101}
102
103impl AsBindGroupShaderType<ForwardDecalMaterialExtUniform> for ForwardDecalMaterialExt {
104    fn as_bind_group_shader_type(
105        &self,
106        _images: &RenderAssets<GpuImage>,
107    ) -> ForwardDecalMaterialExtUniform {
108        ForwardDecalMaterialExtUniform {
109            inv_depth_fade_factor: 1.0 / self.depth_fade_factor.max(0.001),
110        }
111    }
112}
113
114impl MaterialExtension for ForwardDecalMaterialExt {
115    // Forward decal is incompatible with OIT as it needs to be rendered
116    // even it's occluded by opaque objects.
117    fn enable_oit() -> bool {
118        false
119    }
120
121    // Don't write forward decal's own depth if it is opaque.
122    fn enable_prepass() -> bool {
123        false
124    }
125
126    fn enable_shadows() -> bool {
127        false
128    }
129
130    fn specialize(
131        _pipeline: &MaterialExtensionPipeline,
132        descriptor: &mut RenderPipelineDescriptor,
133        _layout: &MeshVertexBufferLayoutRef,
134        _key: MaterialExtensionKey<Self>,
135    ) -> Result<(), SpecializedMeshPipelineError> {
136        descriptor.depth_stencil.as_mut().unwrap().depth_compare = Some(CompareFunction::Always);
137
138        descriptor.vertex.shader_defs.push("FORWARD_DECAL".into());
139
140        if let Some(fragment) = &mut descriptor.fragment {
141            fragment.shader_defs.push("FORWARD_DECAL".into());
142        }
143
144        if let Some(label) = &mut descriptor.label {
145            *label = format!("forward_decal_{label}").into();
146        }
147
148        Ok(())
149    }
150}
151
152impl Default for ForwardDecalMaterialExt {
153    fn default() -> Self {
154        Self {
155            depth_fade_factor: 8.0,
156        }
157    }
158}
159
160#[derive(Resource)]
161struct ForwardDecalMesh(Handle<Mesh>);
162
163// Note: We need to use a hook here instead of required components since we cannot access resources
164// with required components, and we can't otherwise get a handle to the asset from a required
165// component constructor, since the constructor must be a function pointer, and we intentionally do
166// not want to use `uuid_handle!`.
167fn forward_decal_set_mesh(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
168    let decal_mesh = world.resource::<ForwardDecalMesh>().0.clone();
169    let mut entity = world.entity_mut(entity);
170    let mut entity_mesh = entity.get_mut::<Mesh3d>().unwrap();
171    // Only replace the mesh handle if the mesh handle is defaulted.
172    if **entity_mesh == Handle::default() {
173        entity_mesh.0 = decal_mesh;
174    }
175}