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