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