Skip to content

Commit b45786d

Browse files
aevyriepcwalton
andauthored
Add Skybox Motion Vectors (#13617)
# Objective - Add motion vector support to the skybox - This fixes the last remaining "gap" to complete the motion blur feature ## Solution - Add a pipeline for the skybox to write motion vectors to the prepass ## Testing - Used examples to test motion vectors using motion blur https://github.com/bevyengine/bevy/assets/2632925/74c0778a-7e77-4e68-8111-05791e4bfdd2 --------- Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
1 parent 7d3fcd5 commit b45786d

File tree

9 files changed

+317
-65
lines changed

9 files changed

+317
-65
lines changed

crates/bevy_core_pipeline/src/prepass/mod.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,23 @@ use std::ops::Range;
3131

3232
use bevy_asset::AssetId;
3333
use bevy_ecs::prelude::*;
34+
use bevy_math::Mat4;
3435
use bevy_reflect::Reflect;
3536
use bevy_render::{
3637
mesh::Mesh,
3738
render_phase::{
3839
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
3940
PhaseItemExtraIndex,
4041
},
41-
render_resource::{BindGroupId, CachedRenderPipelineId, Extent3d, TextureFormat, TextureView},
42+
render_resource::{
43+
BindGroupId, CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer,
44+
Extent3d, ShaderType, TextureFormat, TextureView,
45+
},
4246
texture::ColorAttachment,
4347
};
4448

49+
use crate::deferred::{DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT};
50+
4551
pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm;
4652
pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float;
4753

@@ -63,6 +69,22 @@ pub struct MotionVectorPrepass;
6369
#[derive(Component, Default, Reflect)]
6470
pub struct DeferredPrepass;
6571

72+
#[derive(Component, ShaderType, Clone)]
73+
pub struct PreviousViewData {
74+
pub inverse_view: Mat4,
75+
pub view_proj: Mat4,
76+
}
77+
78+
#[derive(Resource, Default)]
79+
pub struct PreviousViewUniforms {
80+
pub uniforms: DynamicUniformBuffer<PreviousViewData>,
81+
}
82+
83+
#[derive(Component)]
84+
pub struct PreviousViewUniformOffset {
85+
pub offset: u32,
86+
}
87+
6688
/// Textures that are written to by the prepass.
6789
///
6890
/// This component will only be present if any of the relevant prepass components are also present.
@@ -270,3 +292,32 @@ impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass {
270292
self.key.pipeline
271293
}
272294
}
295+
296+
pub fn prepass_target_descriptors(
297+
normal_prepass: bool,
298+
motion_vector_prepass: bool,
299+
deferred_prepass: bool,
300+
) -> Vec<Option<ColorTargetState>> {
301+
vec![
302+
normal_prepass.then_some(ColorTargetState {
303+
format: NORMAL_PREPASS_FORMAT,
304+
blend: None,
305+
write_mask: ColorWrites::ALL,
306+
}),
307+
motion_vector_prepass.then_some(ColorTargetState {
308+
format: MOTION_VECTOR_PREPASS_FORMAT,
309+
blend: None,
310+
write_mask: ColorWrites::ALL,
311+
}),
312+
deferred_prepass.then_some(ColorTargetState {
313+
format: DEFERRED_PREPASS_FORMAT,
314+
blend: None,
315+
write_mask: ColorWrites::ALL,
316+
}),
317+
deferred_prepass.then_some(ColorTargetState {
318+
format: DEFERRED_LIGHTING_PASS_ID_FORMAT,
319+
blend: None,
320+
write_mask: ColorWrites::ALL,
321+
}),
322+
]
323+
}

crates/bevy_core_pipeline/src/prepass/node.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@ use bevy_render::{
55
diagnostic::RecordDiagnostics,
66
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
77
render_phase::{TrackedRenderPass, ViewBinnedRenderPhases},
8-
render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp},
8+
render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp},
99
renderer::RenderContext,
10-
view::ViewDepthTexture,
10+
view::{ViewDepthTexture, ViewUniformOffset},
1111
};
1212
#[cfg(feature = "trace")]
1313
use bevy_utils::tracing::info_span;
1414

15-
use super::{AlphaMask3dPrepass, DeferredPrepass, Opaque3dPrepass, ViewPrepassTextures};
15+
use crate::skybox::prepass::{RenderSkyboxPrepassPipeline, SkyboxPrepassBindGroup};
16+
17+
use super::{
18+
AlphaMask3dPrepass, DeferredPrepass, Opaque3dPrepass, PreviousViewUniformOffset,
19+
ViewPrepassTextures,
20+
};
1621

1722
/// Render node used by the prepass.
1823
///
@@ -26,17 +31,28 @@ impl ViewNode for PrepassNode {
2631
&'static ExtractedCamera,
2732
&'static ViewDepthTexture,
2833
&'static ViewPrepassTextures,
34+
&'static ViewUniformOffset,
2935
Option<&'static DeferredPrepass>,
36+
Option<&'static RenderSkyboxPrepassPipeline>,
37+
Option<&'static SkyboxPrepassBindGroup>,
38+
Option<&'static PreviousViewUniformOffset>,
3039
);
3140

3241
fn run<'w>(
3342
&self,
3443
graph: &mut RenderGraphContext,
3544
render_context: &mut RenderContext<'w>,
36-
(view, camera, view_depth_texture, view_prepass_textures, deferred_prepass): QueryItem<
37-
'w,
38-
Self::ViewQuery,
39-
>,
45+
(
46+
view,
47+
camera,
48+
view_depth_texture,
49+
view_prepass_textures,
50+
view_uniform_offset,
51+
deferred_prepass,
52+
skybox_prepass_pipeline,
53+
skybox_prepass_bind_group,
54+
view_prev_uniform_offset,
55+
): QueryItem<'w, Self::ViewQuery>,
4056
world: &'w World,
4157
) -> Result<(), NodeRunError> {
4258
let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = (
@@ -119,6 +135,30 @@ impl ViewNode for PrepassNode {
119135
alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity);
120136
}
121137

138+
// Skybox draw using a fullscreen triangle
139+
if let (
140+
Some(skybox_prepass_pipeline),
141+
Some(skybox_prepass_bind_group),
142+
Some(view_prev_uniform_offset),
143+
) = (
144+
skybox_prepass_pipeline,
145+
skybox_prepass_bind_group,
146+
view_prev_uniform_offset,
147+
) {
148+
let pipeline_cache = world.resource::<PipelineCache>();
149+
if let Some(pipeline) =
150+
pipeline_cache.get_render_pipeline(skybox_prepass_pipeline.0)
151+
{
152+
render_pass.set_render_pipeline(pipeline);
153+
render_pass.set_bind_group(
154+
0,
155+
&skybox_prepass_bind_group.0,
156+
&[view_uniform_offset.offset, view_prev_uniform_offset.offset],
157+
);
158+
render_pass.draw(0..3, 0..1);
159+
}
160+
}
161+
122162
pass_span.end(&mut render_pass);
123163
drop(render_pass);
124164

crates/bevy_core_pipeline/src/skybox/mod.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,25 @@ use bevy_render::{
2222
view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms},
2323
Render, RenderApp, RenderSet,
2424
};
25+
use prepass::{SkyboxPrepassPipeline, SKYBOX_PREPASS_SHADER_HANDLE};
2526

2627
use crate::core_3d::CORE_3D_DEPTH_FORMAT;
2728

2829
const SKYBOX_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(55594763423201);
2930

31+
pub mod prepass;
32+
3033
pub struct SkyboxPlugin;
3134

3235
impl Plugin for SkyboxPlugin {
3336
fn build(&self, app: &mut App) {
3437
load_internal_asset!(app, SKYBOX_SHADER_HANDLE, "skybox.wgsl", Shader::from_wgsl);
38+
load_internal_asset!(
39+
app,
40+
SKYBOX_PREPASS_SHADER_HANDLE,
41+
"skybox_prepass.wgsl",
42+
Shader::from_wgsl
43+
);
3544

3645
app.add_plugins((
3746
ExtractComponentPlugin::<Skybox>::default(),
@@ -43,11 +52,15 @@ impl Plugin for SkyboxPlugin {
4352
};
4453
render_app
4554
.init_resource::<SpecializedRenderPipelines<SkyboxPipeline>>()
55+
.init_resource::<SpecializedRenderPipelines<SkyboxPrepassPipeline>>()
4656
.add_systems(
4757
Render,
4858
(
4959
prepare_skybox_pipelines.in_set(RenderSet::Prepare),
60+
prepass::prepare_skybox_prepass_pipelines.in_set(RenderSet::Prepare),
5061
prepare_skybox_bind_groups.in_set(RenderSet::PrepareBindGroups),
62+
prepass::prepare_skybox_prepass_bind_groups
63+
.in_set(RenderSet::PrepareBindGroups),
5164
),
5265
);
5366
}
@@ -57,7 +70,9 @@ impl Plugin for SkyboxPlugin {
5770
return;
5871
};
5972
let render_device = render_app.world().resource::<RenderDevice>().clone();
60-
render_app.insert_resource(SkyboxPipeline::new(&render_device));
73+
render_app
74+
.insert_resource(SkyboxPipeline::new(&render_device))
75+
.init_resource::<SkyboxPrepassPipeline>();
6176
}
6277
}
6378

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#![warn(missing_docs)]
2+
3+
//! Adds motion vector support to skyboxes. See [`SkyboxPrepassPipeline`] for details.
4+
5+
use bevy_asset::Handle;
6+
use bevy_ecs::{
7+
component::Component,
8+
entity::Entity,
9+
query::{Has, With},
10+
system::{Commands, Query, Res, ResMut, Resource},
11+
world::{FromWorld, World},
12+
};
13+
use bevy_render::{
14+
render_resource::{
15+
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
16+
BindGroupLayoutEntries, CachedRenderPipelineId, CompareFunction, DepthStencilState,
17+
FragmentState, MultisampleState, PipelineCache, RenderPipelineDescriptor, Shader,
18+
ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines,
19+
},
20+
renderer::RenderDevice,
21+
view::{Msaa, ViewUniform, ViewUniforms},
22+
};
23+
use bevy_utils::prelude::default;
24+
25+
use crate::{
26+
core_3d::CORE_3D_DEPTH_FORMAT,
27+
prepass::{
28+
prepass_target_descriptors, MotionVectorPrepass, NormalPrepass, PreviousViewData,
29+
PreviousViewUniforms,
30+
},
31+
Skybox,
32+
};
33+
34+
pub const SKYBOX_PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(376510055324461154);
35+
36+
/// This pipeline writes motion vectors to the prepass for all [`Skybox`]es.
37+
///
38+
/// This allows features like motion blur and TAA to work correctly on the skybox. Without this, for
39+
/// example, motion blur would not be applied to the skybox when the camera is rotated and motion
40+
/// blur is enabled.
41+
#[derive(Resource)]
42+
pub struct SkyboxPrepassPipeline {
43+
bind_group_layout: BindGroupLayout,
44+
}
45+
46+
/// Used to specialize the [`SkyboxPrepassPipeline`].
47+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
48+
pub struct SkyboxPrepassPipelineKey {
49+
samples: u32,
50+
normal_prepass: bool,
51+
}
52+
53+
/// Stores the ID for a camera's specialized pipeline, so it can be retrieved from the
54+
/// [`PipelineCache`].
55+
#[derive(Component)]
56+
pub struct RenderSkyboxPrepassPipeline(pub CachedRenderPipelineId);
57+
58+
/// Stores the [`SkyboxPrepassPipeline`] bind group for a camera. This is later used by the prepass
59+
/// render graph node to add this binding to the prepass's render pass.
60+
#[derive(Component)]
61+
pub struct SkyboxPrepassBindGroup(pub BindGroup);
62+
63+
impl FromWorld for SkyboxPrepassPipeline {
64+
fn from_world(world: &mut World) -> Self {
65+
let render_device = world.resource::<RenderDevice>();
66+
67+
Self {
68+
bind_group_layout: render_device.create_bind_group_layout(
69+
"skybox_prepass_bind_group_layout",
70+
&BindGroupLayoutEntries::sequential(
71+
ShaderStages::FRAGMENT,
72+
(
73+
uniform_buffer::<ViewUniform>(true),
74+
uniform_buffer::<PreviousViewData>(true),
75+
),
76+
),
77+
),
78+
}
79+
}
80+
}
81+
82+
impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
83+
type Key = SkyboxPrepassPipelineKey;
84+
85+
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
86+
RenderPipelineDescriptor {
87+
label: Some("skybox_prepass_pipeline".into()),
88+
layout: vec![self.bind_group_layout.clone()],
89+
push_constant_ranges: vec![],
90+
vertex: crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state(),
91+
primitive: default(),
92+
depth_stencil: Some(DepthStencilState {
93+
format: CORE_3D_DEPTH_FORMAT,
94+
depth_write_enabled: false,
95+
depth_compare: CompareFunction::GreaterEqual,
96+
stencil: default(),
97+
bias: default(),
98+
}),
99+
multisample: MultisampleState {
100+
count: key.samples,
101+
mask: !0,
102+
alpha_to_coverage_enabled: false,
103+
},
104+
fragment: Some(FragmentState {
105+
shader: SKYBOX_PREPASS_SHADER_HANDLE,
106+
shader_defs: vec![],
107+
entry_point: "fragment".into(),
108+
targets: prepass_target_descriptors(key.normal_prepass, true, false),
109+
}),
110+
}
111+
}
112+
}
113+
114+
/// Specialize and cache the [`SkyboxPrepassPipeline`] for each camera with a [`Skybox`].
115+
pub fn prepare_skybox_prepass_pipelines(
116+
mut commands: Commands,
117+
pipeline_cache: Res<PipelineCache>,
118+
mut pipelines: ResMut<SpecializedRenderPipelines<SkyboxPrepassPipeline>>,
119+
msaa: Res<Msaa>,
120+
pipeline: Res<SkyboxPrepassPipeline>,
121+
views: Query<(Entity, Has<NormalPrepass>), (With<Skybox>, With<MotionVectorPrepass>)>,
122+
) {
123+
for (entity, normal_prepass) in &views {
124+
let pipeline_key = SkyboxPrepassPipelineKey {
125+
samples: msaa.samples(),
126+
normal_prepass,
127+
};
128+
129+
let render_skybox_prepass_pipeline =
130+
pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key);
131+
commands
132+
.entity(entity)
133+
.insert(RenderSkyboxPrepassPipeline(render_skybox_prepass_pipeline));
134+
}
135+
}
136+
137+
/// Creates the required bind groups for the [`SkyboxPrepassPipeline`]. This binds the view uniforms
138+
/// from the CPU for access in the prepass shader on the GPU, allowing us to compute camera motion
139+
/// between frames. This is then stored in the [`SkyboxPrepassBindGroup`] component on the camera.
140+
pub fn prepare_skybox_prepass_bind_groups(
141+
mut commands: Commands,
142+
pipeline: Res<SkyboxPrepassPipeline>,
143+
view_uniforms: Res<ViewUniforms>,
144+
prev_view_uniforms: Res<PreviousViewUniforms>,
145+
render_device: Res<RenderDevice>,
146+
views: Query<Entity, (With<Skybox>, With<MotionVectorPrepass>)>,
147+
) {
148+
for entity in &views {
149+
let (Some(view_uniforms), Some(prev_view_uniforms)) = (
150+
view_uniforms.uniforms.binding(),
151+
prev_view_uniforms.uniforms.binding(),
152+
) else {
153+
continue;
154+
};
155+
let bind_group = render_device.create_bind_group(
156+
"skybox_prepass_bind_group",
157+
&pipeline.bind_group_layout,
158+
&BindGroupEntries::sequential((view_uniforms, prev_view_uniforms)),
159+
);
160+
161+
commands
162+
.entity(entity)
163+
.insert(SkyboxPrepassBindGroup(bind_group));
164+
}
165+
}

0 commit comments

Comments
 (0)