Forward decals (port of bevy_contact_projective_decals) (#16600)
# Objective - Implement ForwardDecal as outlined in https://github.com/bevyengine/bevy/issues/2401 ## Solution - Port https://github.com/naasblod/bevy_contact_projective_decals, and cleanup the API a little. ## Testing - Ran the new decal example. --- ## Showcase  ## Changelog * Added ForwardDecal and associated types * Added MaterialExtension::alpha_mode() --------- Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
parent
26bb0b40d2
commit
e8e2426058
11
Cargo.toml
11
Cargo.toml
@ -951,6 +951,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"
|
||||
|
128
crates/bevy_pbr/src/decal/forward.rs
Normal file
128
crates/bevy_pbr/src/decal/forward.rs
Normal file
@ -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::{require, 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(19376620402995522466);
|
||||
const FORWARD_DECAL_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(29376620402995522466);
|
||||
|
||||
/// 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 [`MaterialPlugin`] for this material in your app setup.
|
||||
///
|
||||
/// [`StandardMaterial`] comes with out of the box support for forward decals.
|
||||
#[expect(type_alias_bounds, reason = "Type alias generics not yet stable")]
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
52
crates/bevy_pbr/src/decal/forward_decal.wgsl
Normal file
52
crates/bevy_pbr/src/decal/forward_decal.wgsl
Normal file
@ -0,0 +1,52 @@
|
||||
#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,
|
||||
}
|
||||
#import bevy_render::maths::project_onto
|
||||
|
||||
@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);
|
||||
|
||||
// Apply UV parallax
|
||||
let contact_on_decal = project_onto(V * diff_depth, in.world_normal);
|
||||
let normal_depth = length(contact_on_decal);
|
||||
let view_steepness = abs(Vt.z);
|
||||
let delta_uv = normal_depth * Vt.xy * vec2(1.0, -1.0) / view_steepness;
|
||||
let uv = in.uv + delta_uv;
|
||||
|
||||
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);
|
||||
}
|
10
crates/bevy_pbr/src/decal/mod.rs
Normal file
10
crates/bevy_pbr/src/decal/mod.rs
Normal file
@ -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::*;
|
@ -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,
|
||||
@ -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 {
|
||||
@ -230,8 +236,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 {
|
||||
|
@ -26,6 +26,7 @@ pub mod experimental {
|
||||
|
||||
mod cluster;
|
||||
mod components;
|
||||
pub mod decal;
|
||||
pub mod deferred;
|
||||
mod extended_material;
|
||||
mod fog;
|
||||
@ -335,6 +336,7 @@ impl Plugin for PbrPlugin {
|
||||
ScreenSpaceReflectionsPlugin,
|
||||
))
|
||||
.add_plugins((
|
||||
decal::ForwardDecalPlugin,
|
||||
SyncComponentPlugin::<DirectionalLight>::default(),
|
||||
SyncComponentPlugin::<PointLight>::default(),
|
||||
SyncComponentPlugin::<SpotLight>::default(),
|
||||
|
@ -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);
|
||||
|
||||
@ -79,5 +91,9 @@ fn fragment(
|
||||
}
|
||||
#endif // OIT_ENABLED
|
||||
|
||||
return out;
|
||||
#ifdef FORWARD_DECAL
|
||||
out.color.a = min(forward_decal_info.alpha, out.color.a);
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
@ -93,3 +93,9 @@ fn sphere_intersects_plane_half_space(
|
||||
fn powsafe(color: vec3<f32>, power: f32) -> vec3<f32> {
|
||||
return pow(abs(color), vec3(power)) * sign(color);
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
|
||||
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;
|
||||
}
|
||||
|
96
examples/3d/decal.rs
Normal file
96
examples/3d/decal.rs
Normal file
@ -0,0 +1,96 @@
|
||||
//! Decal rendering.
|
||||
|
||||
#[path = "../helpers/camera_controller.rs"]
|
||||
mod camera_controller;
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::prepass::DepthPrepass,
|
||||
pbr::decal::{ForwardDecal, ForwardDecalMaterial, ForwardDecalMaterialExt},
|
||||
prelude::*,
|
||||
};
|
||||
use camera_controller::{CameraController, CameraControllerPlugin};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins, CameraControllerPlugin))
|
||||
.add_systems(Startup, setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut decal_standard_materials: ResMut<Assets<ForwardDecalMaterial<StandardMaterial>>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Spawn the forward decal
|
||||
commands.spawn((
|
||||
Name::new("Decal"),
|
||||
ForwardDecal,
|
||||
MeshMaterial3d(decal_standard_materials.add(ForwardDecalMaterial {
|
||||
base: StandardMaterial {
|
||||
base_color_texture: Some(asset_server.load("textures/uv_checker_bw.png")),
|
||||
..default()
|
||||
},
|
||||
extension: ForwardDecalMaterialExt {
|
||||
depth_fade_factor: 1.0,
|
||||
},
|
||||
})),
|
||||
Transform::from_scale(Vec3::splat(4.0)),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Camera"),
|
||||
Camera3d::default(),
|
||||
CameraController::default(),
|
||||
DepthPrepass, // Must enable the depth prepass to render forward decals
|
||||
Transform::from_xyz(2.0, 9.5, 2.5).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
|
||||
let white_material = standard_materials.add(Color::WHITE);
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Floor"),
|
||||
Mesh3d(meshes.add(Rectangle::from_length(10.0))),
|
||||
MeshMaterial3d(white_material.clone()),
|
||||
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
|
||||
));
|
||||
|
||||
// Spawn a few cube with random rotations to showcase how the decals behave with non-flat geometry
|
||||
let num_obs = 10;
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
|
||||
for i in 0..num_obs {
|
||||
for j in 0..num_obs {
|
||||
let rotation_axis: [f32; 3] = rng.gen();
|
||||
let rotation_vec: Vec3 = rotation_axis.into();
|
||||
let rotation: u32 = rng.gen_range(0..360);
|
||||
let transform = Transform::from_xyz(
|
||||
(-num_obs + 1) as f32 / 2.0 + i as f32,
|
||||
-0.2,
|
||||
(-num_obs + 1) as f32 / 2.0 + j as f32,
|
||||
)
|
||||
.with_rotation(Quat::from_axis_angle(
|
||||
rotation_vec.normalize_or_zero(),
|
||||
(rotation as f32).to_radians(),
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Cuboid::from_length(0.6))),
|
||||
MeshMaterial3d(white_material.clone()),
|
||||
transform,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Light"),
|
||||
PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
));
|
||||
}
|
@ -145,6 +145,7 @@ Example | Description
|
||||
[Camera sub view](../examples/3d/camera_sub_view.rs) | Demonstrates using different sub view effects on a camera
|
||||
[Clearcoat](../examples/3d/clearcoat.rs) | Demonstrates the clearcoat PBR feature
|
||||
[Color grading](../examples/3d/color_grading.rs) | Demonstrates color grading
|
||||
[Decal](../examples/3d/decal.rs) | Decal rendering
|
||||
[Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines
|
||||
[Depth of field](../examples/3d/depth_of_field.rs) | Demonstrates depth of field
|
||||
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
||||
|
Loading…
Reference in New Issue
Block a user