bevy_core_pipeline/skybox/
prepass.rs

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