1use bevy_app::{App, Plugin};
2use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
3use bevy_camera::Exposure;
4use bevy_ecs::{
5 prelude::{Component, Entity},
6 query::{QueryItem, With},
7 reflect::ReflectComponent,
8 resource::Resource,
9 schedule::IntoScheduleConfigs,
10 system::{Commands, Query, Res, ResMut},
11};
12use bevy_image::{BevyDefault, Image};
13use bevy_math::{Mat4, Quat};
14use bevy_reflect::{std_traits::ReflectDefault, Reflect};
15use bevy_render::{
16 extract_component::{
17 ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin,
18 UniformComponentPlugin,
19 },
20 render_asset::RenderAssets,
21 render_resource::{
22 binding_types::{sampler, texture_cube, uniform_buffer},
23 *,
24 },
25 renderer::RenderDevice,
26 texture::GpuImage,
27 view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms},
28 Render, RenderApp, RenderStartup, RenderSystems,
29};
30use bevy_shader::Shader;
31use bevy_transform::components::Transform;
32use bevy_utils::default;
33use prepass::SkyboxPrepassPipeline;
34
35use crate::{
36 core_3d::CORE_3D_DEPTH_FORMAT, prepass::PreviousViewUniforms,
37 skybox::prepass::init_skybox_prepass_pipeline,
38};
39
40pub mod prepass;
41
42pub struct SkyboxPlugin;
43
44impl Plugin for SkyboxPlugin {
45 fn build(&self, app: &mut App) {
46 embedded_asset!(app, "skybox.wgsl");
47 embedded_asset!(app, "skybox_prepass.wgsl");
48
49 app.add_plugins((
50 ExtractComponentPlugin::<Skybox>::default(),
51 UniformComponentPlugin::<SkyboxUniforms>::default(),
52 ));
53
54 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
55 return;
56 };
57 render_app
58 .init_resource::<SpecializedRenderPipelines<SkyboxPipeline>>()
59 .init_resource::<SpecializedRenderPipelines<SkyboxPrepassPipeline>>()
60 .init_resource::<PreviousViewUniforms>()
61 .add_systems(
62 RenderStartup,
63 (init_skybox_pipeline, init_skybox_prepass_pipeline),
64 )
65 .add_systems(
66 Render,
67 (
68 prepare_skybox_pipelines.in_set(RenderSystems::Prepare),
69 prepass::prepare_skybox_prepass_pipelines.in_set(RenderSystems::Prepare),
70 prepare_skybox_bind_groups.in_set(RenderSystems::PrepareBindGroups),
71 prepass::prepare_skybox_prepass_bind_groups
72 .in_set(RenderSystems::PrepareBindGroups),
73 ),
74 );
75 }
76}
77
78#[derive(Component, Clone, Reflect)]
85#[reflect(Component, Default, Clone)]
86pub struct Skybox {
87 pub image: Handle<Image>,
88 pub brightness: f32,
92
93 pub rotation: Quat,
97}
98
99impl Default for Skybox {
100 fn default() -> Self {
101 Skybox {
102 image: Handle::default(),
103 brightness: 0.0,
104 rotation: Quat::IDENTITY,
105 }
106 }
107}
108
109impl ExtractComponent for Skybox {
110 type QueryData = (&'static Self, Option<&'static Exposure>);
111 type QueryFilter = ();
112 type Out = (Self, SkyboxUniforms);
113
114 fn extract_component(
115 (skybox, exposure): QueryItem<'_, '_, Self::QueryData>,
116 ) -> Option<Self::Out> {
117 let exposure = exposure
118 .map(Exposure::exposure)
119 .unwrap_or_else(|| Exposure::default().exposure());
120
121 Some((
122 skybox.clone(),
123 SkyboxUniforms {
124 brightness: skybox.brightness * exposure,
125 transform: Transform::from_rotation(skybox.rotation.inverse()).to_matrix(),
126 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
127 _wasm_padding_8b: 0,
128 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
129 _wasm_padding_12b: 0,
130 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
131 _wasm_padding_16b: 0,
132 },
133 ))
134 }
135}
136
137#[derive(Component, ShaderType, Clone)]
139pub struct SkyboxUniforms {
140 brightness: f32,
141 transform: Mat4,
142 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
143 _wasm_padding_8b: u32,
144 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
145 _wasm_padding_12b: u32,
146 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
147 _wasm_padding_16b: u32,
148}
149
150#[derive(Resource)]
151struct SkyboxPipeline {
152 bind_group_layout: BindGroupLayout,
153 shader: Handle<Shader>,
154}
155
156impl SkyboxPipeline {
157 fn new(render_device: &RenderDevice, shader: Handle<Shader>) -> Self {
158 Self {
159 bind_group_layout: render_device.create_bind_group_layout(
160 "skybox_bind_group_layout",
161 &BindGroupLayoutEntries::sequential(
162 ShaderStages::FRAGMENT,
163 (
164 texture_cube(TextureSampleType::Float { filterable: true }),
165 sampler(SamplerBindingType::Filtering),
166 uniform_buffer::<ViewUniform>(true)
167 .visibility(ShaderStages::VERTEX_FRAGMENT),
168 uniform_buffer::<SkyboxUniforms>(true),
169 ),
170 ),
171 ),
172 shader,
173 }
174 }
175}
176
177fn init_skybox_pipeline(
178 mut commands: Commands,
179 render_device: Res<RenderDevice>,
180 asset_server: Res<AssetServer>,
181) {
182 let shader = load_embedded_asset!(asset_server.as_ref(), "skybox.wgsl");
183 commands.insert_resource(SkyboxPipeline::new(&render_device, shader));
184}
185
186#[derive(PartialEq, Eq, Hash, Clone, Copy)]
187struct SkyboxPipelineKey {
188 hdr: bool,
189 samples: u32,
190 depth_format: TextureFormat,
191}
192
193impl SpecializedRenderPipeline for SkyboxPipeline {
194 type Key = SkyboxPipelineKey;
195
196 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
197 RenderPipelineDescriptor {
198 label: Some("skybox_pipeline".into()),
199 layout: vec![self.bind_group_layout.clone()],
200 vertex: VertexState {
201 shader: self.shader.clone(),
202 ..default()
203 },
204 depth_stencil: Some(DepthStencilState {
205 format: key.depth_format,
206 depth_write_enabled: false,
207 depth_compare: CompareFunction::GreaterEqual,
208 stencil: StencilState {
209 front: StencilFaceState::IGNORE,
210 back: StencilFaceState::IGNORE,
211 read_mask: 0,
212 write_mask: 0,
213 },
214 bias: DepthBiasState {
215 constant: 0,
216 slope_scale: 0.0,
217 clamp: 0.0,
218 },
219 }),
220 multisample: MultisampleState {
221 count: key.samples,
222 mask: !0,
223 alpha_to_coverage_enabled: false,
224 },
225 fragment: Some(FragmentState {
226 shader: self.shader.clone(),
227 targets: vec![Some(ColorTargetState {
228 format: if key.hdr {
229 ViewTarget::TEXTURE_FORMAT_HDR
230 } else {
231 TextureFormat::bevy_default()
232 },
233 blend: None,
235 write_mask: ColorWrites::ALL,
236 })],
237 ..default()
238 }),
239 ..default()
240 }
241 }
242}
243
244#[derive(Component)]
245pub struct SkyboxPipelineId(pub CachedRenderPipelineId);
246
247fn prepare_skybox_pipelines(
248 mut commands: Commands,
249 pipeline_cache: Res<PipelineCache>,
250 mut pipelines: ResMut<SpecializedRenderPipelines<SkyboxPipeline>>,
251 pipeline: Res<SkyboxPipeline>,
252 views: Query<(Entity, &ExtractedView, &Msaa), With<Skybox>>,
253) {
254 for (entity, view, msaa) in &views {
255 let pipeline_id = pipelines.specialize(
256 &pipeline_cache,
257 &pipeline,
258 SkyboxPipelineKey {
259 hdr: view.hdr,
260 samples: msaa.samples(),
261 depth_format: CORE_3D_DEPTH_FORMAT,
262 },
263 );
264
265 commands
266 .entity(entity)
267 .insert(SkyboxPipelineId(pipeline_id));
268 }
269}
270
271#[derive(Component)]
272pub struct SkyboxBindGroup(pub (BindGroup, u32));
273
274fn prepare_skybox_bind_groups(
275 mut commands: Commands,
276 pipeline: Res<SkyboxPipeline>,
277 view_uniforms: Res<ViewUniforms>,
278 skybox_uniforms: Res<ComponentUniforms<SkyboxUniforms>>,
279 images: Res<RenderAssets<GpuImage>>,
280 render_device: Res<RenderDevice>,
281 views: Query<(Entity, &Skybox, &DynamicUniformIndex<SkyboxUniforms>)>,
282) {
283 for (entity, skybox, skybox_uniform_index) in &views {
284 if let (Some(skybox), Some(view_uniforms), Some(skybox_uniforms)) = (
285 images.get(&skybox.image),
286 view_uniforms.uniforms.binding(),
287 skybox_uniforms.binding(),
288 ) {
289 let bind_group = render_device.create_bind_group(
290 "skybox_bind_group",
291 &pipeline.bind_group_layout,
292 &BindGroupEntries::sequential((
293 &skybox.texture_view,
294 &skybox.sampler,
295 view_uniforms,
296 skybox_uniforms,
297 )),
298 );
299
300 commands
301 .entity(entity)
302 .insert(SkyboxBindGroup((bind_group, skybox_uniform_index.index())));
303 }
304 }
305}