Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forward decals (port of bevy_contact_projective_decals) #16600

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,17 @@ description = "Illustrates bloom configuration using HDR and emissive materials"
category = "3D Rendering"
wasm = true

[[example]]
name = "decal"
path = "examples/3d/decal.rs"
doc-scrape-examples = true

[package.metadata.example.decal]
name = "Decal"
description = "Decal rendering"
category = "3D Rendering"
wasm = true

[[example]]
name = "deferred_rendering"
path = "examples/3d/deferred_rendering.rs"
Expand Down
128 changes: 128 additions & 0 deletions crates/bevy_pbr/src/decal/forward.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::{
ExtendedMaterial, Material, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline,
MaterialPlugin, StandardMaterial,
};
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
use bevy_ecs::component::Component;
use bevy_math::{prelude::Rectangle, Quat, Vec2, Vec3};
use bevy_reflect::{Reflect, TypePath};
use bevy_render::{
alpha::AlphaMode,
mesh::{Mesh, Mesh3d, MeshBuilder, MeshVertexBufferLayoutRef, Meshable},
render_resource::{
AsBindGroup, CompareFunction, RenderPipelineDescriptor, Shader,
SpecializedMeshPipelineError,
},
};

const FORWARD_DECAL_MESH_HANDLE: Handle<Mesh> = Handle::weak_from_u128(09376620402995522466);
const FORWARD_DECAL_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(19376620402995522466);

/// Plugin to render [`ForwardDecal`]s.
pub struct ForwardDecalPlugin;

impl Plugin for ForwardDecalPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
FORWARD_DECAL_SHADER_HANDLE,
"forward_decal.wgsl",
Shader::from_wgsl
);

app.register_type::<ForwardDecal>();

app.world_mut().resource_mut::<Assets<Mesh>>().insert(
FORWARD_DECAL_MESH_HANDLE.id(),
Rectangle::from_size(Vec2::ONE)
.mesh()
.build()
.rotated_by(Quat::from_rotation_arc(Vec3::Z, Vec3::Y))
.with_generated_tangents()
.unwrap(),
);

app.add_plugins(MaterialPlugin::<ForwardDecalMaterial<StandardMaterial>> {
prepass_enabled: false,
shadows_enabled: false,
..Default::default()
});
}
}

/// A decal that renders via a 1x1 transparent quad mesh, smoothly alpha-blending with the underlying
/// geometry towards the edges.
///
/// Because forward decals are meshes, you can use arbitrary materials to control their appearance.
///
/// # Usage Notes
///
/// * Spawn this component on an entity with a [`crate::MeshMaterial3d`] component holding a [`ForwardDecalMaterial`].
/// * Any camera rendering a forward decal must have the [`bevy_core_pipeline::DepthPrepass`] component.
/// * Looking at forward decals at a steep angle can cause distortion. This can be mitigated by padding your decal's
/// texture with extra transparent pixels on the edges.
#[derive(Component, Reflect)]
#[require(Mesh3d(|| Mesh3d(FORWARD_DECAL_MESH_HANDLE)))]
pub struct ForwardDecal;

/// Type alias for an extended material with a [`ForwardDecalMaterialExt`] extension.
///
/// Make sure to register the material [`MaterialPlugin`] for this material in your app setup.
///
/// [`StandardMaterial`] comes with out of the box support for forward decals.
#[expect(type_alias_bounds)]
pub type ForwardDecalMaterial<B: Material> = ExtendedMaterial<B, ForwardDecalMaterialExt>;

/// Material extension for a [`ForwardDecal`].
///
/// In addition to wrapping your material type with this extension, your shader must use
/// the `bevy_pbr::decal::forward::get_forward_decal_info` function.
///
/// The `FORWARD_DECAL` shader define will be made available to your shader so that you can gate
/// the forward decal code behind an ifdef.
#[derive(Asset, AsBindGroup, TypePath, Clone, Debug)]
pub struct ForwardDecalMaterialExt {
/// Controls how far away a surface must be before the decal will stop blending with it, and instead render as opaque.
///
/// Decreasing this value will cause the decal to blend only to surfaces closer to it.
///
/// Units are in meters.
#[uniform(200)]
pub depth_fade_factor: f32,
}

impl MaterialExtension for ForwardDecalMaterialExt {
fn alpha_mode() -> Option<AlphaMode> {
Some(AlphaMode::Blend)
}

fn specialize(
_pipeline: &MaterialExtensionPipeline,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayoutRef,
_key: MaterialExtensionKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
descriptor.depth_stencil.as_mut().unwrap().depth_compare = CompareFunction::Always;

descriptor.vertex.shader_defs.push("FORWARD_DECAL".into());

if let Some(fragment) = &mut descriptor.fragment {
fragment.shader_defs.push("FORWARD_DECAL".into());
}

if let Some(label) = &mut descriptor.label {
*label = format!("forward_decal_{}", label).into();
}

Ok(())
}
}

impl Default for ForwardDecalMaterialExt {
fn default() -> Self {
Self {
depth_fade_factor: 8.0,
}
}
}
61 changes: 61 additions & 0 deletions crates/bevy_pbr/src/decal/forward_decal.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#define_import_path bevy_pbr::decal::forward

#import bevy_pbr::{
forward_io::VertexOutput,
mesh_functions::get_world_from_local,
mesh_view_bindings::view,
pbr_functions::calculate_tbn_mikktspace,
prepass_utils::prepass_depth,
view_transformations::depth_ndc_to_view_z,
parallax_mapping::parallaxed_uv,
}

@group(2) @binding(200)
var<uniform> depth_fade_factor: f32;

struct ForwardDecalInformation {
world_position: vec4<f32>,
uv: vec2<f32>,
alpha: f32,
}

fn get_forward_decal_info(in: VertexOutput) -> ForwardDecalInformation {
let world_from_local = get_world_from_local(in.instance_index);
let scale = (world_from_local * vec4(1.0, 1.0, 1.0, 0.0)).xyz;
let scaled_tangent = vec4(in.world_tangent.xyz / scale, in.world_tangent.w);

let V = normalize(view.world_position - in.world_position.xyz);

// Transform V from fragment to camera in world space to tangent space.
let TBN = calculate_tbn_mikktspace(in.world_normal, scaled_tangent);
let T = TBN[0];
let B = TBN[1];
let N = TBN[2];
let Vt = vec3(dot(V, T), dot(V, B), dot(V, N));

let frag_depth = depth_ndc_to_view_z(in.position.z);
let depth_pass_depth = depth_ndc_to_view_z(prepass_depth(in.position, 0u));

let diff_depth = frag_depth - depth_pass_depth;
let diff_depth_abs = abs(diff_depth);

let contact_on_decal = project_onto(V * diff_depth, in.world_normal);
let normal_depth = length(contact_on_decal);
let uv = parallaxed_uv(
normal_depth,
1.0,
0u,
in.uv,
Vt,
);

let world_position = vec4(in.world_position.xyz + V * diff_depth_abs, in.world_position.w);
let alpha = saturate(1.0 - normal_depth * depth_fade_factor);

return ForwardDecalInformation(world_position, uv, alpha);
}

fn project_onto(lhs: vec3<f32>, rhs: vec3<f32>) -> vec3<f32> {
let other_len_sq_rcp = 1.0 / dot(rhs, rhs);
return rhs * dot(lhs, rhs) * other_len_sq_rcp;
}
10 changes: 10 additions & 0 deletions crates/bevy_pbr/src/decal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Decal rendering.
//!
//! Decals are a material that render on top of the surface that they're placed above.
//! They can be used to render signs, paint, snow, impact craters, and other effects on top of surfaces.

// TODO: Once other decal types are added, write a paragraph comparing the different types in the module docs.

mod forward;

pub use forward::*;
13 changes: 11 additions & 2 deletions crates/bevy_pbr/src/extended_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use bevy_asset::{Asset, Handle};
use bevy_ecs::system::SystemParamItem;
use bevy_reflect::{impl_type_path, Reflect};
use bevy_render::{
alpha::AlphaMode,
mesh::MeshVertexBufferLayoutRef,
render_resource::{
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
Expand Down Expand Up @@ -41,6 +42,11 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
ShaderRef::Default
}

// Returns this material’s AlphaMode. If None is returned, the base material alpha mode will be used.
fn alpha_mode() -> Option<AlphaMode> {
None
}

/// Returns this material's prepass vertex shader. If [`ShaderRef::Default`] is returned, the base material prepass vertex shader
/// will be used.
fn prepass_vertex_shader() -> ShaderRef {
Expand Down Expand Up @@ -199,8 +205,11 @@ impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
}
}

fn alpha_mode(&self) -> crate::AlphaMode {
B::alpha_mode(&self.base)
fn alpha_mode(&self) -> AlphaMode {
match E::alpha_mode() {
Some(specified) => specified,
None => B::alpha_mode(&self.base),
}
}

fn opaque_render_method(&self) -> crate::OpaqueRendererMethod {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod experimental {

mod bundle;
mod cluster;
pub mod decal;
pub mod deferred;
mod extended_material;
mod fog;
Expand Down Expand Up @@ -349,6 +350,7 @@ impl Plugin for PbrPlugin {
ScreenSpaceReflectionsPlugin,
))
.add_plugins((
decal::ForwardDecalPlugin,
SyncComponentPlugin::<DirectionalLight>::default(),
SyncComponentPlugin::<PointLight>::default(),
SyncComponentPlugin::<SpotLight>::default(),
Expand Down
26 changes: 21 additions & 5 deletions crates/bevy_pbr/src/render/pbr.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,38 @@
#import bevy_core_pipeline::oit::oit_draw
#endif // OIT_ENABLED

#ifdef FORWARD_DECAL
#import bevy_pbr::decal::forward::get_forward_decal_info
#endif

@fragment
fn fragment(
#ifdef MESHLET_MESH_MATERIAL_PASS
@builtin(position) frag_coord: vec4<f32>,
#else
in: VertexOutput,
vertex_output: VertexOutput,
@builtin(front_facing) is_front: bool,
#endif
) -> FragmentOutput {
#ifdef MESHLET_MESH_MATERIAL_PASS
let in = resolve_vertex_output(frag_coord);
let vertex_output = resolve_vertex_output(frag_coord);
let is_front = true;
#endif

var in = vertex_output;

// If we're in the crossfade section of a visibility range, conditionally
// discard the fragment according to the visibility pattern.
#ifdef VISIBILITY_RANGE_DITHER
pbr_functions::visibility_range_dither(in.position, in.visibility_range_dither);
#endif

#ifdef FORWARD_DECAL
let forward_decal_info = get_forward_decal_info(in);
in.world_position = forward_decal_info.world_position;
in.uv = forward_decal_info.uv;
#endif

// generate a PbrInput struct from the StandardMaterial bindings
var pbr_input = pbr_input_from_standard_material(in, is_front);

Expand Down Expand Up @@ -74,10 +86,14 @@ fn fragment(
let alpha_mode = pbr_input.material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS;
if alpha_mode != pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE {
// The fragments will only be drawn during the oit resolve pass.
oit_draw(in.position, out.color);
oit_draw(in.position, out.color);
discard;
}
}
#endif // OIT_ENABLED

return out;
#ifdef FORWARD_DECAL
out.color.a = min(forward_decal_info.alpha, out.color.a);
#endif

return out;
}
Loading
Loading