|
| 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