diff --git a/crates/bevy_core_pipeline/src/debug_gradient.wgsl b/crates/bevy_core_pipeline/src/debug_gradient.wgsl new file mode 100644 index 0000000000000..ee92b426a4a5c --- /dev/null +++ b/crates/bevy_core_pipeline/src/debug_gradient.wgsl @@ -0,0 +1,41 @@ +#define_import_path bevy_core_pipeline::debug_gradient + +// Define a function to map a range of values to a high contrast color gradient +// optimized for representational clarity. +// this uses the matplotlib plasma color map. +fn get_color(value: f32) -> vec3 { + let plasma_colors = array( + vec3(0.868,0.941,0.011), // #f0f921 + vec3(0.966,0.386,0.0326), // #fca636 + vec3(0.753,0.126,0.121), // #e16462 + vec3(0.444,0.0188,0.282), // #b12a90 + vec3(0.144,0.0,0.396), // #6a00a8 + vec3(0.00142,0.000488,0.245), // #0d0887 + ); + if value < 1.0 { + return plasma_colors[0]; + } else if value < 2.0 { + return plasma_colors[1]; + } else if value < 3.0 { + return plasma_colors[2]; + } else if value < 4.0 { + return plasma_colors[3]; + } else if value < 5.0 { + return plasma_colors[4]; + } else { + return plasma_colors[5]; + } +} + +// Return color sampled from the plasma color map +fn debug_gradient(value: f32) -> vec4 { + let colors_offset = clamp(value, 0.0, 1.0) * 5.0; + let low_ratio = fract(colors_offset); + + let low_color_index = u32(floor(colors_offset)); + let low_color = get_color(colors_offset); + let high_color = get_color(colors_offset + 1.0); + + return vec4(mix(low_color, high_color, low_ratio), 1.0); +} + diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index f169cdd89e08d..48eada5e915ca 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -15,6 +15,7 @@ mod taa; pub mod tonemapping; pub mod upscaling; +use bevy_reflect::TypeUuid; pub use skybox::Skybox; /// Experimental features that are not yet finished. Please report any issues you encounter! @@ -48,9 +49,12 @@ use crate::{ upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; -use bevy_asset::load_internal_asset; +use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_render::{extract_resource::ExtractResourcePlugin, prelude::Shader}; +pub const DEBUG_GRADIENT_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17293936987160423658); + #[derive(Default)] pub struct CorePipelinePlugin; @@ -62,6 +66,12 @@ impl Plugin for CorePipelinePlugin { "fullscreen_vertex_shader/fullscreen.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + DEBUG_GRADIENT_HANDLE, + "debug_gradient.wgsl", + Shader::from_wgsl + ); app.register_type::() .register_type::() diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 0659f20a3290a..61f2d43c7daac 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -393,7 +393,7 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1)); } - if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { + if true || key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2)); shader_defs.push("NORMAL_PREPASS".into()); @@ -410,13 +410,6 @@ where shader_defs.push("MOTION_VECTOR_PREPASS".into()); } - if key - .mesh_key - .intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS) - { - shader_defs.push("PREPASS_FRAGMENT".into()); - } - if layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) { @@ -431,39 +424,38 @@ where let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 + let has_normal = key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS); + let has_motion_vectors = key + .mesh_key + .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS); + let has_prepass_fragment = true || has_normal || has_motion_vectors; let mut targets = vec![]; - targets.push( - key.mesh_key - .contains(MeshPipelineKey::NORMAL_PREPASS) - .then_some(ColorTargetState { - format: NORMAL_PREPASS_FORMAT, - blend: Some(BlendState::REPLACE), - write_mask: ColorWrites::ALL, - }), - ); - targets.push( - key.mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) - .then_some(ColorTargetState { - format: MOTION_VECTOR_PREPASS_FORMAT, - blend: Some(BlendState::REPLACE), - write_mask: ColorWrites::ALL, - }), - ); - if targets.iter().all(Option::is_none) { - // if no targets are required then clear the list, so that no fragment shader is required - // (though one may still be used for discarding depth buffer writes) + targets.push(has_normal.then_some(ColorTargetState { + format: NORMAL_PREPASS_FORMAT, + blend: Some(BlendState::REPLACE), + write_mask: ColorWrites::ALL, + })); + targets.push(has_motion_vectors.then_some(ColorTargetState { + format: MOTION_VECTOR_PREPASS_FORMAT, + blend: Some(BlendState::REPLACE), + write_mask: ColorWrites::ALL, + })); + if targets.iter().all(|opt| opt.is_none()) { targets.clear(); } // The fragment shader is only used when the normal prepass or motion vectors prepass // is enabled or the material uses alpha cutoff values and doesn't rely on the standard // prepass shader - let fragment_required = !targets.is_empty() - || ((key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK) - || blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA - || blend_key == MeshPipelineKey::BLEND_ALPHA) - && self.material_fragment_shader.is_some()); + let has_alpha = key.mesh_key.contains(MeshPipelineKey::ALPHA_MASK) + || blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA + || blend_key == MeshPipelineKey::BLEND_ALPHA; + let has_prepass_material = has_alpha && self.material_fragment_shader.is_some(); + let fragment_required = has_prepass_fragment || has_prepass_material; + + if fragment_required { + shader_defs.push("PREPASS_FRAGMENT".into()); + } let fragment = fragment_required.then(|| { // Use the fragment shader from the material diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index d624cf0e0fcd2..1930c062ea459 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -1,5 +1,6 @@ #import bevy_pbr::prepass_bindings #import bevy_pbr::mesh_functions +#import bevy_pbr::pbr_bindings // Most of these attributes are not used in the default prepass fragment shader, but they are still needed so we can // pass them to custom prepass shaders like pbr_prepass.wgsl. @@ -25,6 +26,7 @@ struct Vertex { struct VertexOutput { @builtin(position) clip_position: vec4, + @location(3) world_position: vec4, #ifdef VERTEX_UVS @location(0) uv: vec2, @@ -38,7 +40,6 @@ struct VertexOutput { #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - @location(3) world_position: vec4, @location(4) previous_world_position: vec4, #endif // MOTION_VECTOR_PREPASS } @@ -53,6 +54,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { var model = mesh.model; #endif // SKINNED + out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); #ifdef DEPTH_CLAMP_ORTHO out.clip_position.z = min(out.clip_position.z, 1.0); @@ -75,7 +77,6 @@ fn vertex(vertex: Vertex) -> VertexOutput { #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); out.previous_world_position = mesh_position_local_to_world(mesh.previous_model, vec4(vertex.position, 1.0)); #endif // MOTION_VECTOR_PREPASS @@ -84,8 +85,12 @@ fn vertex(vertex: Vertex) -> VertexOutput { #ifdef PREPASS_FRAGMENT struct FragmentInput { +#ifdef VERTEX_UVS + @location(0) uv: vec2, +#endif // VERTEX_UVS #ifdef NORMAL_PREPASS @location(1) world_normal: vec3, + @location(2) world_tangent: vec4, #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS @@ -95,6 +100,7 @@ struct FragmentInput { } struct FragmentOutput { + @builtin(frag_depth) depth: f32, #ifdef NORMAL_PREPASS @location(0) normal: vec4, #endif // NORMAL_PREPASS diff --git a/crates/bevy_pbr/src/render/parallax_mapping.wgsl b/crates/bevy_pbr/src/render/parallax_mapping.wgsl index 884b5e23c6d69..26423aa956012 100644 --- a/crates/bevy_pbr/src/render/parallax_mapping.wgsl +++ b/crates/bevy_pbr/src/render/parallax_mapping.wgsl @@ -24,9 +24,9 @@ fn parallaxed_uv( uv: vec2, // The vector from the camera to the fragment at the surface in tangent space Vt: vec3, -) -> vec2 { +) -> vec3 { if max_layer_count < 1.0 { - return uv; + return vec3(uv, 0.0); } var uv = uv; @@ -110,7 +110,12 @@ fn parallaxed_uv( current_layer_depth += mix(next_depth, previous_depth, weight); #endif - // Note: `current_layer_depth` is not returned, but may be useful - // for light computation later on in future improvements of the pbr shader. - return uv; + return vec3(uv, sample_depth_map(uv) * depth_scale); +} + +/// Additional depth in tangent space resulting from parallaxing. +/// +/// This is a simple pythagorean theorem. +fn additional_depth(old_uv: vec2, new_uv: vec2, depth: f32) -> f32 { + return length(vec3(new_uv - old_uv, depth)); } diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index f058a83ff8b53..ec416ef1fcf1f 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -10,12 +10,14 @@ #import bevy_pbr::fog #import bevy_pbr::pbr_functions #import bevy_pbr::parallax_mapping +#import bevy_core_pipeline::debug_gradient #import bevy_pbr::prepass_utils struct FragmentInput { @builtin(front_facing) is_front: bool, @builtin(position) frag_coord: vec4, + @builtin(sample_index) sample_index: u32, #import bevy_pbr::mesh_vertex_output }; @@ -23,6 +25,7 @@ struct FragmentInput { fn fragment(in: FragmentInput) -> @location(0) vec4 { let is_orthographic = view.projection[3].w == 1.0; let V = calculate_view(in.world_position, is_orthographic); + var my_depth = 100000.0; #ifdef VERTEX_UVS var uv = in.uv; #ifdef VERTEX_TANGENTS @@ -31,8 +34,9 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { let T = in.world_tangent.xyz; let B = in.world_tangent.w * cross(N, T); // Transform V from fragment to camera in world space to tangent space. - let Vt = vec3(dot(V, T), dot(V, B), dot(V, N)); - uv = parallaxed_uv( + let TBN = mat3x3(T, B, N); + let Vt = V * TBN; //vec3(dot(V, T), dot(V, B), dot(V, N)); + let parallaxed = parallaxed_uv( material.parallax_depth_scale, material.max_parallax_layer_count, material.max_relief_mapping_search_steps, @@ -42,6 +46,15 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { // about. -Vt, ); + uv = parallaxed.xy; + if parallaxed.z > 0.0001 { + let NBT = transpose(mat3x3(T, B, N)); + let tangent_extra_depth = Vt * additional_depth(in.uv, uv, parallaxed.z); + let extra_depth = tangent_extra_depth * NBT; + let parallaxed_view_position = view.view_proj * (in.world_position - vec4(extra_depth, 0.0)); + // let parallaxed_view_position = view.view_proj * in.world_position; + my_depth = parallaxed_view_position.z / parallaxed_view_position.w; + } } #endif #endif @@ -151,5 +164,14 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #ifdef PREMULTIPLY_ALPHA output_color = premultiply_alpha(material.flags, output_color); #endif + let depth = prepass_depth(in.frag_coord, in.sample_index); + // if depth > my_depth { + // discard; + // } + // var depth = my_depth; + // if depth == 0.0 { + // depth = in.frag_coord.z; + // } + // output_color = debug_gradient(1.0 - depth * 14.0); return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index d96a23b845d4b..ab248da2265bf 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -1,12 +1,28 @@ #import bevy_pbr::prepass_bindings #import bevy_pbr::pbr_bindings -#ifdef NORMAL_PREPASS #import bevy_pbr::pbr_functions +#import bevy_pbr::parallax_mapping +#ifdef NORMAL_PREPASS #endif // NORMAL_PREPASS + +// This is a workaround since the preprocessor does not support +// #if defined(ALPHA_MASK) || defined(BLEND_PREMULTIPLIED_ALPHA) +#ifndef ALPHA_MASK +#ifndef BLEND_PREMULTIPLIED_ALPHA +#ifndef BLEND_ALPHA + +#define NO_ALPHA_DISCARD + +#endif // BLEND_ALPHA +#endif // BLEND_PREMULTIPLIED_ALPHA not defined +#endif // ALPHA_MASK not defined + + struct FragmentInput { @builtin(front_facing) is_front: bool, @builtin(position) frag_coord: vec4, + @location(3) world_position: vec4, #ifdef VERTEX_UVS @location(0) uv: vec2, #endif // VERTEX_UVS @@ -19,30 +35,31 @@ struct FragmentInput { #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS - @location(3) world_position: vec4, @location(4) previous_world_position: vec4, #endif // MOTION_VECTOR_PREPASS }; +#ifdef PREPASS_FRAGMENT +struct FragmentOutput { + @builtin(frag_depth) depth: f32, +#ifdef NORMAL_PREPASS + @location(0) normal: vec4, +#endif // NORMAL_PREPASS + +#ifdef MOTION_VECTOR_PREPASS + @location(1) motion_vector: vec2, +#endif // MOTION_VECTOR_PREPASS +}; +#endif PREPASS_FRAGMENT + + // Cutoff used for the premultiplied alpha modes BLEND and ADD. const PREMULTIPLIED_ALPHA_CUTOFF = 0.05; // We can use a simplified version of alpha_discard() here since we only need to handle the alpha_cutoff fn prepass_alpha_discard(in: FragmentInput) { -// This is a workaround since the preprocessor does not support -// #if defined(ALPHA_MASK) || defined(BLEND_PREMULTIPLIED_ALPHA) -#ifndef ALPHA_MASK -#ifndef BLEND_PREMULTIPLIED_ALPHA -#ifndef BLEND_ALPHA - -#define EMPTY_PREPASS_ALPHA_DISCARD - -#endif // BLEND_ALPHA -#endif // BLEND_PREMULTIPLIED_ALPHA not defined -#endif // ALPHA_MASK not defined - -#ifndef EMPTY_PREPASS_ALPHA_DISCARD +#ifndef NO_ALPHA_DISCARD var output_color: vec4 = material.base_color; #ifdef VERTEX_UVS @@ -52,39 +69,63 @@ fn prepass_alpha_discard(in: FragmentInput) { #endif // VERTEX_UVS #ifdef ALPHA_MASK - if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) && output_color.a < material.alpha_cutoff { + let has_alpha = (material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u; + if has_alpha && output_color.a < material.alpha_cutoff { discard; } #else // BLEND_PREMULTIPLIED_ALPHA || BLEND_ALPHA let alpha_mode = material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; - if (alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD) - && output_color.a < PREMULTIPLIED_ALPHA_CUTOFF { + let blend_or_add = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND | STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ADD; + let cuts_off = output_color.a < PREMULTIPLIED_ALPHA_CUTOFF; + if ((alpha_mode & blend_or_add) != 0u) && cuts_off { discard; } else if alpha_mode == STANDARD_MATERIAL_FLAGS_ALPHA_MODE_PREMULTIPLIED && all(output_color < vec4(PREMULTIPLIED_ALPHA_CUTOFF)) { discard; } -#endif // !ALPHA_MASK +#endif // ALPHA_MASK -#endif // EMPTY_PREPASS_ALPHA_DISCARD not defined +#endif // NO_ALPHA_DISCARD not defined } #ifdef PREPASS_FRAGMENT -struct FragmentOutput { -#ifdef NORMAL_PREPASS - @location(0) normal: vec4, -#endif // NORMAL_PREPASS - -#ifdef MOTION_VECTOR_PREPASS - @location(1) motion_vector: vec2, -#endif // MOTION_VECTOR_PREPASS -} - @fragment fn fragment(in: FragmentInput) -> FragmentOutput { + var out: FragmentOutput; prepass_alpha_discard(in); - var out: FragmentOutput; + out.depth = in.frag_coord.z; + +#ifdef VERTEX_TANGENTS + if ((material.flags & STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) { + let is_orthographic = view.projection[3].w == 1.0; + let V = calculate_view(in.world_position, is_orthographic); + + let N = in.world_normal; + let T = in.world_tangent.xyz; + let B = in.world_tangent.w * cross(N, T); + // Transform V from fragment to camera in world space to tangent space. + var Vt = vec3(dot(V, T), dot(V, B), dot(V, N)); + let parallaxed = parallaxed_uv( + material.parallax_depth_scale, + material.max_parallax_layer_count, + material.max_relief_mapping_search_steps, + in.uv, + // Flip the direction of Vt to go toward the surface to make the + // parallax mapping algorithm easier to understand and reason + // about. + -Vt, + ); + if parallaxed.z > 0.0001 { + let NBT = transpose(mat3x3(T, B, N)); + let tangent_extra_depth = Vt * additional_depth(in.uv, parallaxed.xy, parallaxed.z); + let extra_depth = tangent_extra_depth * NBT; + let parallaxed_view_position = view.view_proj * (in.world_position - vec4(extra_depth, 0.0)); + // let parallaxed_view_position = view.view_proj * in.world_position; + out.depth = parallaxed_view_position.z / parallaxed_view_position.w; + } + } +#endif #ifdef NORMAL_PREPASS // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit @@ -132,7 +173,8 @@ fn fragment(in: FragmentInput) -> FragmentOutput { } #else @fragment -fn fragment(in: FragmentInput) { +fn fragment(in: FragmentInput) -> @builtin(frag_depth) f32 { prepass_alpha_discard(in); + return in.frag_coord.z; } #endif // PREPASS_FRAGMENT diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 16a893260a1b7..3c312ff5d76a4 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -3,12 +3,18 @@ use std::fmt; -use bevy::{prelude::*, render::render_resource::TextureFormat, window::close_on_esc}; +use bevy::{ + core_pipeline::{core_3d::Camera3dDepthLoadOp, prepass::DepthPrepass}, + prelude::*, + render::render_resource::TextureFormat, + window::close_on_esc, +}; fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(Normal(None)) + .insert_resource(Msaa::Off) .add_systems(Startup, setup) .add_systems( Update, @@ -215,9 +221,14 @@ fn setup( commands.spawn(( Camera3dBundle { transform: Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y), + camera_3d: Camera3d { + depth_load_op: Camera3dDepthLoadOp::Load, + ..default() + }, ..default() }, CameraController, + DepthPrepass, )); // light @@ -260,7 +271,7 @@ fn setup( reflectance: 0.18, ..Color::rgb_u8(0, 80, 0).into() }), - transform: Transform::from_xyz(0.0, -1.0, 0.0), + transform: Transform::from_xyz(0.0, -0.1, 0.0), ..default() }); diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index a1519fbcda2e1..fd25ba94766d4 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -6,6 +6,7 @@ //! With no arguments it will load the `FlightHelmet` glTF model from the repository assets subdirectory. use bevy::{ + core_pipeline::prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass}, math::Vec3A, prelude::*, render::primitives::{Aabb, Sphere}, @@ -63,6 +64,7 @@ fn parse_scene(scene_path: String) -> (String, usize) { } fn setup(mut commands: Commands, asset_server: Res) { + commands.insert_resource(Msaa::Off); let scene_path = std::env::args() .nth(1) .unwrap_or_else(|| "assets/models/FlightHelmet/FlightHelmet.gltf".to_string()); @@ -135,6 +137,9 @@ fn setup_scene_after_load( .load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), }, camera_controller, + DepthPrepass, + NormalPrepass, + MotionVectorPrepass, )); // Spawn a default light if the scene does not have one