Skip to main content

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, BindGroupLayoutDescriptor,
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: BindGroupLayoutDescriptor,
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    fullscreen_shader: Res<FullscreenShader>,
65    asset_server: Res<AssetServer>,
66) {
67    commands.insert_resource(SkyboxPrepassPipeline {
68        bind_group_layout: BindGroupLayoutDescriptor::new(
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        fullscreen_shader: fullscreen_shader.clone(),
79        fragment_shader: load_embedded_asset!(asset_server.as_ref(), "skybox_prepass.wgsl"),
80    });
81}
82
83impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
84    type Key = SkyboxPrepassPipelineKey;
85
86    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
87        RenderPipelineDescriptor {
88            label: Some("skybox_prepass_pipeline".into()),
89            layout: vec![self.bind_group_layout.clone()],
90            vertex: self.fullscreen_shader.to_vertex_state(),
91            depth_stencil: Some(DepthStencilState {
92                format: CORE_3D_DEPTH_FORMAT,
93                depth_write_enabled: false,
94                depth_compare: CompareFunction::GreaterEqual,
95                stencil: default(),
96                bias: default(),
97            }),
98            multisample: MultisampleState {
99                count: key.samples,
100                mask: !0,
101                alpha_to_coverage_enabled: false,
102            },
103            fragment: Some(FragmentState {
104                shader: self.fragment_shader.clone(),
105                targets: prepass_target_descriptors(key.normal_prepass, true, false),
106                ..default()
107            }),
108            ..default()
109        }
110    }
111}
112
113/// Specialize and cache the [`SkyboxPrepassPipeline`] for each camera with a [`Skybox`].
114pub fn prepare_skybox_prepass_pipelines(
115    mut commands: Commands,
116    pipeline_cache: Res<PipelineCache>,
117    mut pipelines: ResMut<SpecializedRenderPipelines<SkyboxPrepassPipeline>>,
118    pipeline: Res<SkyboxPrepassPipeline>,
119    views: Query<(Entity, Has<NormalPrepass>, &Msaa), (With<Skybox>, With<MotionVectorPrepass>)>,
120) {
121    for (entity, normal_prepass, msaa) in &views {
122        let pipeline_key = SkyboxPrepassPipelineKey {
123            samples: msaa.samples(),
124            normal_prepass,
125        };
126
127        let render_skybox_prepass_pipeline =
128            pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key);
129        commands
130            .entity(entity)
131            .insert(RenderSkyboxPrepassPipeline(render_skybox_prepass_pipeline));
132    }
133}
134
135/// Creates the required bind groups for the [`SkyboxPrepassPipeline`]. This binds the view uniforms
136/// from the CPU for access in the prepass shader on the GPU, allowing us to compute camera motion
137/// between frames. This is then stored in the [`SkyboxPrepassBindGroup`] component on the camera.
138pub fn prepare_skybox_prepass_bind_groups(
139    mut commands: Commands,
140    pipeline: Res<SkyboxPrepassPipeline>,
141    view_uniforms: Res<ViewUniforms>,
142    prev_view_uniforms: Res<PreviousViewUniforms>,
143    render_device: Res<RenderDevice>,
144    pipeline_cache: Res<PipelineCache>,
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_cache.get_bind_group_layout(&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}