Skip to content

Commit b3224e1

Browse files
IceSentryJMS55
andcommitted
Add depth and normal prepass (#6284)
# Objective - Add a configurable prepass - A depth prepass is useful for various shader effects and to reduce overdraw. It can be expansive depending on the scene so it's important to be able to disable it if you don't need any effects that uses it or don't suffer from excessive overdraw. - The goal is to eventually use it for things like TAA, Ambient Occlusion, SSR and various other techniques that can benefit from having a prepass. ## Solution The prepass node is inserted before the main pass. It runs for each `Camera3d` with a prepass component (`DepthPrepass`, `NormalPrepass`). The presence of one of those components is used to determine which textures are generated in the prepass. When any prepass is enabled, the depth buffer generated will be used by the main pass to reduce overdraw. The prepass runs for each `Material` created with the `MaterialPlugin::prepass_enabled` option set to `true`. You can overload the shader used by the prepass by using `Material::prepass_vertex_shader()` and/or `Material::prepass_fragment_shader()`. It will also use the `Material::specialize()` for more advanced use cases. It is enabled by default on all materials. The prepass works on opaque materials and materials using an alpha mask. Transparent materials are ignored. The `StandardMaterial` overloads the prepass fragment shader to support alpha mask and normal maps. --- ## Changelog - Add a new `PrepassNode` that runs before the main pass - Add a `PrepassPlugin` to extract/prepare/queue the necessary data - Add a `DepthPrepass` and `NormalPrepass` component to control which textures will be created by the prepass and available in later passes. - Add a new `prepass_enabled` flag to the `MaterialPlugin` that will control if a material uses the prepass or not. - Add a new `prepass_enabled` flag to the `PbrPlugin` to control if the StandardMaterial uses the prepass. Currently defaults to false. - Add `Material::prepass_vertex_shader()` and `Material::prepass_fragment_shader()` to control the prepass from the `Material` ## Notes In bevy's sample 3d scene, the performance is actually worse when enabling the prepass, but on more complex scenes the performance is generally better. I would like more testing on this, but @DGriffin91 has reported a very noticeable improvements in some scenes. The prepass is also used by @JMS55 for TAA and GTAO discord thread: <https://discord.com/channels/691052431525675048/1011624228627419187> This PR was built on top of the work of multiple people Co-Authored-By: @superdump Co-Authored-By: @robtfm Co-Authored-By: @JMS55 Co-authored-by: Charles <[email protected]> Co-authored-by: JMS55 <[email protected]>
1 parent 519f6f4 commit b3224e1

File tree

22 files changed

+1833
-155
lines changed

22 files changed

+1833
-155
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,17 @@ description = "A shader and a material that uses it"
12201220
category = "Shaders"
12211221
wasm = true
12221222

1223+
[[example]]
1224+
name = "shader_prepass"
1225+
path = "examples/shader/shader_prepass.rs"
1226+
1227+
[package.metadata.example.shader_prepass]
1228+
name = "Material Prepass"
1229+
description = "A shader that uses the depth texture generated in a prepass"
1230+
category = "Shaders"
1231+
wasm = false
1232+
1233+
12231234
[[example]]
12241235
name = "shader_material_screenspace_texture"
12251236
path = "examples/shader/shader_material_screenspace_texture.rs"

assets/shaders/show_prepass.wgsl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#import bevy_pbr::mesh_types
2+
#import bevy_pbr::mesh_view_bindings
3+
#import bevy_pbr::utils
4+
5+
@group(1) @binding(0)
6+
var<uniform> show_depth: f32;
7+
@group(1) @binding(1)
8+
var<uniform> show_normal: f32;
9+
10+
@fragment
11+
fn fragment(
12+
@builtin(position) frag_coord: vec4<f32>,
13+
@builtin(sample_index) sample_index: u32,
14+
#import bevy_pbr::mesh_vertex_output
15+
) -> @location(0) vec4<f32> {
16+
if show_depth == 1.0 {
17+
let depth = prepass_depth(frag_coord, sample_index);
18+
return vec4(depth, depth, depth, 1.0);
19+
} else if show_normal == 1.0 {
20+
let normal = prepass_normal(frag_coord, sample_index);
21+
return vec4(normal, 1.0);
22+
} else {
23+
// transparent
24+
return vec4(0.0);
25+
}
26+
}

crates/bevy_core_pipeline/src/core_3d/main_pass_3d_node.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
22
clear_color::{ClearColor, ClearColorConfig},
33
core_3d::{AlphaMask3d, Camera3d, Opaque3d, Transparent3d},
4+
prepass::{DepthPrepass, NormalPrepass},
45
};
56
use bevy_ecs::prelude::*;
67
use bevy_render::{
@@ -14,6 +15,8 @@ use bevy_render::{
1415
#[cfg(feature = "trace")]
1516
use bevy_utils::tracing::info_span;
1617

18+
use super::Camera3dDepthLoadOp;
19+
1720
pub struct MainPass3dNode {
1821
query: QueryState<
1922
(
@@ -24,6 +27,8 @@ pub struct MainPass3dNode {
2427
&'static Camera3d,
2528
&'static ViewTarget,
2629
&'static ViewDepthTexture,
30+
Option<&'static DepthPrepass>,
31+
Option<&'static NormalPrepass>,
2732
),
2833
With<ExtractedView>,
2934
>,
@@ -55,13 +60,20 @@ impl Node for MainPass3dNode {
5560
world: &World,
5661
) -> Result<(), NodeRunError> {
5762
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
58-
let (camera, opaque_phase, alpha_mask_phase, transparent_phase, camera_3d, target, depth) =
59-
match self.query.get_manual(world, view_entity) {
60-
Ok(query) => query,
61-
Err(_) => {
62-
return Ok(());
63-
} // No window
64-
};
63+
let Ok((
64+
camera,
65+
opaque_phase,
66+
alpha_mask_phase,
67+
transparent_phase,
68+
camera_3d,
69+
target,
70+
depth,
71+
depth_prepass,
72+
normal_prepass,
73+
)) = self.query.get_manual(world, view_entity) else {
74+
// No window
75+
return Ok(());
76+
};
6577

6678
// Always run opaque pass to ensure screen is cleared
6779
{
@@ -88,8 +100,15 @@ impl Node for MainPass3dNode {
88100
view: &depth.view,
89101
// NOTE: The opaque main pass loads the depth buffer and possibly overwrites it
90102
depth_ops: Some(Operations {
91-
// NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections.
92-
load: camera_3d.depth_load_op.clone().into(),
103+
load: if depth_prepass.is_some() || normal_prepass.is_some() {
104+
// if any prepass runs, it will generate a depth buffer so we should use it,
105+
// even if only the normal_prepass is used.
106+
Camera3dDepthLoadOp::Load
107+
} else {
108+
// NOTE: 0.0 is the far plane due to bevy's use of reverse-z projections.
109+
camera_3d.depth_load_op.clone()
110+
}
111+
.into(),
93112
store: true,
94113
}),
95114
stencil_ops: None,

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod graph {
77
pub const VIEW_ENTITY: &str = "view_entity";
88
}
99
pub mod node {
10+
pub const PREPASS: &str = "prepass";
1011
pub const MAIN_PASS: &str = "main_pass";
1112
pub const BLOOM: &str = "bloom";
1213
pub const TONEMAPPING: &str = "tonemapping";
@@ -43,7 +44,11 @@ use bevy_render::{
4344
};
4445
use bevy_utils::{FloatOrd, HashMap};
4546

46-
use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
47+
use crate::{
48+
prepass::{node::PrepassNode, DepthPrepass},
49+
tonemapping::TonemappingNode,
50+
upscaling::UpscalingNode,
51+
};
4752

4853
pub struct Core3dPlugin;
4954

@@ -68,20 +73,29 @@ impl Plugin for Core3dPlugin {
6873
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<AlphaMask3d>)
6974
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
7075

76+
let prepass_node = PrepassNode::new(&mut render_app.world);
7177
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
7278
let tonemapping = TonemappingNode::new(&mut render_app.world);
7379
let upscaling = UpscalingNode::new(&mut render_app.world);
7480
let mut graph = render_app.world.resource_mut::<RenderGraph>();
7581

7682
let mut draw_3d_graph = RenderGraph::default();
83+
draw_3d_graph.add_node(graph::node::PREPASS, prepass_node);
7784
draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d);
7885
draw_3d_graph.add_node(graph::node::TONEMAPPING, tonemapping);
7986
draw_3d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode);
8087
draw_3d_graph.add_node(graph::node::UPSCALING, upscaling);
88+
8189
let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new(
8290
graph::input::VIEW_ENTITY,
8391
SlotType::Entity,
8492
)]);
93+
draw_3d_graph.add_slot_edge(
94+
input_node_id,
95+
graph::input::VIEW_ENTITY,
96+
graph::node::PREPASS,
97+
PrepassNode::IN_VIEW,
98+
);
8599
draw_3d_graph.add_slot_edge(
86100
input_node_id,
87101
graph::input::VIEW_ENTITY,
@@ -100,6 +114,7 @@ impl Plugin for Core3dPlugin {
100114
graph::node::UPSCALING,
101115
UpscalingNode::IN_VIEW,
102116
);
117+
draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::MAIN_PASS);
103118
draw_3d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING);
104119
draw_3d_graph.add_node_edge(
105120
graph::node::TONEMAPPING,
@@ -253,7 +268,7 @@ pub fn prepare_core_3d_depth_textures(
253268
msaa: Res<Msaa>,
254269
render_device: Res<RenderDevice>,
255270
views_3d: Query<
256-
(Entity, &ExtractedCamera),
271+
(Entity, &ExtractedCamera, Option<&DepthPrepass>),
257272
(
258273
With<RenderPhase<Opaque3d>>,
259274
With<RenderPhase<AlphaMask3d>>,
@@ -262,34 +277,46 @@ pub fn prepare_core_3d_depth_textures(
262277
>,
263278
) {
264279
let mut textures = HashMap::default();
265-
for (entity, camera) in &views_3d {
266-
if let Some(physical_target_size) = camera.physical_target_size {
267-
let cached_texture = textures
268-
.entry(camera.target.clone())
269-
.or_insert_with(|| {
270-
texture_cache.get(
271-
&render_device,
272-
TextureDescriptor {
273-
label: Some("view_depth_texture"),
274-
size: Extent3d {
275-
depth_or_array_layers: 1,
276-
width: physical_target_size.x,
277-
height: physical_target_size.y,
278-
},
279-
mip_level_count: 1,
280-
sample_count: msaa.samples,
281-
dimension: TextureDimension::D2,
282-
format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24
283-
* bit depth for better performance */
284-
usage: TextureUsages::RENDER_ATTACHMENT,
285-
},
286-
)
287-
})
288-
.clone();
289-
commands.entity(entity).insert(ViewDepthTexture {
290-
texture: cached_texture.texture,
291-
view: cached_texture.default_view,
292-
});
293-
}
280+
for (entity, camera, depth_prepass) in &views_3d {
281+
let Some(physical_target_size) = camera.physical_target_size else {
282+
continue;
283+
};
284+
285+
let cached_texture = textures
286+
.entry(camera.target.clone())
287+
.or_insert_with(|| {
288+
// Default usage required to write to the depth texture
289+
let mut usage = TextureUsages::RENDER_ATTACHMENT;
290+
if depth_prepass.is_some() {
291+
// Required to read the output of the prepass
292+
usage |= TextureUsages::COPY_SRC;
293+
}
294+
295+
// The size of the depth texture
296+
let size = Extent3d {
297+
depth_or_array_layers: 1,
298+
width: physical_target_size.x,
299+
height: physical_target_size.y,
300+
};
301+
302+
let descriptor = TextureDescriptor {
303+
label: Some("view_depth_texture"),
304+
size,
305+
mip_level_count: 1,
306+
sample_count: msaa.samples,
307+
dimension: TextureDimension::D2,
308+
// PERF: vulkan docs recommend using 24 bit depth for better performance
309+
format: TextureFormat::Depth32Float,
310+
usage,
311+
};
312+
313+
texture_cache.get(&render_device, descriptor)
314+
})
315+
.clone();
316+
317+
commands.entity(entity).insert(ViewDepthTexture {
318+
texture: cached_texture.texture,
319+
view: cached_texture.default_view,
320+
});
294321
}
295322
}

crates/bevy_core_pipeline/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod core_2d;
44
pub mod core_3d;
55
pub mod fullscreen_vertex_shader;
66
pub mod fxaa;
7+
pub mod prepass;
78
pub mod tonemapping;
89
pub mod upscaling;
910

@@ -23,6 +24,7 @@ use crate::{
2324
core_3d::Core3dPlugin,
2425
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
2526
fxaa::FxaaPlugin,
27+
prepass::{DepthPrepass, NormalPrepass},
2628
tonemapping::TonemappingPlugin,
2729
upscaling::UpscalingPlugin,
2830
};
@@ -44,6 +46,8 @@ impl Plugin for CorePipelinePlugin {
4446

4547
app.register_type::<ClearColor>()
4648
.register_type::<ClearColorConfig>()
49+
.register_type::<DepthPrepass>()
50+
.register_type::<NormalPrepass>()
4751
.init_resource::<ClearColor>()
4852
.add_plugin(ExtractResourcePlugin::<ClearColor>::default())
4953
.add_plugin(Core2dPlugin)

0 commit comments

Comments
 (0)