use crate::ecs::graphics::Atmosphere;
#[cfg(feature = "assets")]
use crate::render::wgpu::envmap_filter::filter_environment_map;
#[cfg(feature = "assets")]
use crate::render::wgpu::hdr::generate_cubemap_mipmaps;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
#[cfg(feature = "assets")]
const PROCEDURAL_CUBEMAP_SIZE: u32 = 1024;
#[cfg(feature = "assets")]
fn calculate_mip_count(size: u32) -> u32 {
(size as f32).log2().floor() as u32 + 1
}
fn create_dummy_cubemap(device: &wgpu::Device) -> (wgpu::Texture, wgpu::TextureView) {
let cubemap = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Dummy Cubemap Texture"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 6,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let cubemap_view = cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
(cubemap, cubemap_view)
}
pub struct SkyPass {
uniform_buffer: wgpu::Buffer,
procedural_bind_group: wgpu::BindGroup,
sky_pipeline: wgpu::RenderPipeline,
cloudy_sky_pipeline: wgpu::RenderPipeline,
space_pipeline: wgpu::RenderPipeline,
nebula_pipeline: wgpu::RenderPipeline,
sunset_pipeline: wgpu::RenderPipeline,
day_night_pipeline: wgpu::RenderPipeline,
hdr_bind_group: wgpu::BindGroup,
hdr_pipeline: wgpu::RenderPipeline,
hdr_lod_pipeline: wgpu::RenderPipeline,
#[cfg(feature = "assets")]
cubemap_texture: wgpu::Texture,
#[cfg(feature = "assets")]
cubemap_view: wgpu::TextureView,
#[cfg(feature = "assets")]
sampler: wgpu::Sampler,
hdr_loaded: bool,
#[cfg(feature = "assets")]
hdr_bind_group_layout: wgpu::BindGroupLayout,
irradiance_bind_group: wgpu::BindGroup,
prefilter_bind_group: wgpu::BindGroup,
#[cfg(feature = "assets")]
irradiance_texture: Option<wgpu::Texture>,
#[cfg(feature = "assets")]
prefilter_texture: Option<wgpu::Texture>,
#[cfg(feature = "assets")]
procedural_cubemap_pipeline: wgpu::ComputePipeline,
#[cfg(feature = "assets")]
procedural_cubemap_bind_group_layout: wgpu::BindGroupLayout,
#[cfg(feature = "assets")]
procedural_uniform_buffer: wgpu::Buffer,
pub ibl_snapshots: Vec<(f32, wgpu::Texture, wgpu::Texture)>,
pub ibl_snapshot_atmosphere: Option<crate::ecs::graphics::resources::Atmosphere>,
}
impl SkyPass {
pub fn new(
device: &wgpu::Device,
color_format: wgpu::TextureFormat,
_depth_format: wgpu::TextureFormat,
) -> Self {
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Sky Uniform Buffer"),
size: std::mem::size_of::<SkyUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
#[cfg(feature = "assets")]
let (cubemap_texture, cubemap_view) = create_dummy_cubemap(device);
#[cfg(not(feature = "assets"))]
let (_, cubemap_view) = create_dummy_cubemap(device);
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
let procedural_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sky Procedural Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let procedural_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &procedural_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
label: Some("Sky Procedural Bind Group"),
});
let hdr_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Sky HDR Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let hdr_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&cubemap_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: Some("Sky HDR Bind Group"),
});
let sky_shader = device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky.wgsl"));
let cloudy_sky_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_cloudy.wgsl"));
let space_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_space.wgsl"));
let nebula_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_nebula.wgsl"));
let sunset_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_sunset.wgsl"));
let day_night_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_day_night.wgsl"));
let hdr_shader =
device.create_shader_module(wgpu::include_wgsl!("../../shaders/sky_hdr.wgsl"));
let procedural_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sky Procedural Pipeline Layout"),
bind_group_layouts: &[Some(&procedural_bind_group_layout)],
immediate_size: 0,
});
let hdr_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Sky HDR Pipeline Layout"),
bind_group_layouts: &[Some(&hdr_bind_group_layout)],
immediate_size: 0,
});
let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sky Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &sky_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &sky_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let cloudy_sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Cloudy Sky Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &cloudy_sky_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &cloudy_sky_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let space_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Space Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &space_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &space_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let nebula_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Nebula Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &nebula_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &nebula_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let sunset_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sunset Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &sunset_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &sunset_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let day_night_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("DayNight Pipeline"),
layout: Some(&procedural_pipeline_layout),
vertex: wgpu::VertexState {
module: &day_night_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &day_night_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let hdr_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sky HDR Pipeline"),
layout: Some(&hdr_pipeline_layout),
vertex: wgpu::VertexState {
module: &hdr_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &hdr_shader,
entry_point: Some("fs_sky"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let hdr_lod_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sky HDR LOD Pipeline"),
layout: Some(&hdr_pipeline_layout),
vertex: wgpu::VertexState {
module: &hdr_shader,
entry_point: Some("vs_sky"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &hdr_shader,
entry_point: Some("fs_sky_lod"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let irradiance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&cubemap_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: Some("Sky Irradiance Bind Group"),
});
let prefilter_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&cubemap_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
label: Some("Sky Prefilter Bind Group"),
});
#[cfg(feature = "assets")]
let (
procedural_uniform_buffer,
procedural_cubemap_bind_group_layout,
procedural_cubemap_pipeline,
) = {
let procedural_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Procedural Cubemap Uniform Buffer"),
size: 16,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let procedural_cubemap_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Procedural to Cubemap Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::StorageTexture {
access: wgpu::StorageTextureAccess::WriteOnly,
format: wgpu::TextureFormat::Rgba16Float,
view_dimension: wgpu::TextureViewDimension::D2Array,
},
count: None,
},
],
});
let procedural_cubemap_shader = device.create_shader_module(wgpu::include_wgsl!(
"../../shaders/procedural_to_cubemap.wgsl"
));
let procedural_cubemap_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Procedural to Cubemap Pipeline Layout"),
bind_group_layouts: &[Some(&procedural_cubemap_bind_group_layout)],
immediate_size: 0,
});
let procedural_cubemap_pipeline =
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Procedural to Cubemap Pipeline"),
layout: Some(&procedural_cubemap_pipeline_layout),
module: &procedural_cubemap_shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
(
procedural_uniform_buffer,
procedural_cubemap_bind_group_layout,
procedural_cubemap_pipeline,
)
};
Self {
uniform_buffer,
procedural_bind_group,
sky_pipeline,
cloudy_sky_pipeline,
space_pipeline,
nebula_pipeline,
sunset_pipeline,
day_night_pipeline,
hdr_bind_group,
hdr_pipeline,
hdr_lod_pipeline,
#[cfg(feature = "assets")]
cubemap_texture,
#[cfg(feature = "assets")]
cubemap_view,
#[cfg(feature = "assets")]
sampler,
hdr_loaded: false,
#[cfg(feature = "assets")]
hdr_bind_group_layout,
irradiance_bind_group,
prefilter_bind_group,
#[cfg(feature = "assets")]
irradiance_texture: None,
#[cfg(feature = "assets")]
prefilter_texture: None,
#[cfg(feature = "assets")]
procedural_cubemap_pipeline,
#[cfg(feature = "assets")]
procedural_cubemap_bind_group_layout,
#[cfg(feature = "assets")]
procedural_uniform_buffer,
ibl_snapshots: Vec::new(),
ibl_snapshot_atmosphere: None,
}
}
#[cfg(feature = "assets")]
pub fn load_hdr_skybox(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
) -> (wgpu::TextureView, wgpu::TextureView) {
let result = crate::render::wgpu::hdr::load_hdri_texture(device, queue, bytes);
self.cubemap_texture = result.cubemap;
self.cubemap_view = result.cubemap_view;
self.hdr_loaded = true;
let irradiance_view = result.filtered_maps.irradiance_view;
let prefiltered_view = result.filtered_maps.prefiltered_view;
self.irradiance_texture = Some(result.filtered_maps.irradiance_texture);
self.prefilter_texture = Some(result.filtered_maps.prefiltered_texture);
let irradiance_view_for_sky =
self.irradiance_texture
.as_ref()
.unwrap()
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let prefilter_view_for_sky =
self.prefilter_texture
.as_ref()
.unwrap()
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
self.hdr_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.cubemap_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
label: Some("Sky HDR Bind Group"),
});
self.irradiance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&irradiance_view_for_sky),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
label: Some("Sky Irradiance Bind Group"),
});
self.prefilter_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&prefilter_view_for_sky),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
label: Some("Sky Prefilter Bind Group"),
});
(irradiance_view, prefiltered_view)
}
#[cfg(feature = "assets")]
pub fn capture_procedural_atmosphere(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
atmosphere: Atmosphere,
time: f32,
) -> Option<(wgpu::TextureView, wgpu::TextureView)> {
let atmosphere_type = atmosphere.as_procedural_cubemap_type()?;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ProceduralUniform {
time: f32,
atmosphere_type: u32,
_padding0: u32,
_padding1: u32,
}
let uniform = ProceduralUniform {
time,
atmosphere_type,
_padding0: 0,
_padding1: 0,
};
queue.write_buffer(
&self.procedural_uniform_buffer,
0,
bytemuck::cast_slice(&[uniform]),
);
let mip_level_count = calculate_mip_count(PROCEDURAL_CUBEMAP_SIZE);
let cubemap = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Procedural Sky Cubemap"),
size: wgpu::Extent3d {
width: PROCEDURAL_CUBEMAP_SIZE,
height: PROCEDURAL_CUBEMAP_SIZE,
depth_or_array_layers: 6,
},
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let cubemap_storage_view = cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2Array),
base_mip_level: 0,
mip_level_count: Some(1),
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Procedural to Cubemap Bind Group"),
layout: &self.procedural_cubemap_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.procedural_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&cubemap_storage_view),
},
],
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Procedural to Cubemap Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Procedural to Cubemap Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.procedural_cubemap_pipeline);
compute_pass.set_bind_group(0, &bind_group, &[]);
compute_pass.dispatch_workgroups(64, 64, 6);
}
queue.submit(Some(encoder.finish()));
generate_cubemap_mipmaps(device, queue, &cubemap);
let cubemap_view = cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let filtered_maps = filter_environment_map(device, queue, &cubemap, &cubemap_view);
let irradiance_view = filtered_maps.irradiance_view;
let prefiltered_view = filtered_maps.prefiltered_view;
self.irradiance_texture = Some(filtered_maps.irradiance_texture);
self.prefilter_texture = Some(filtered_maps.prefiltered_texture);
let irradiance_view_for_sky =
self.irradiance_texture
.as_ref()
.unwrap()
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let prefilter_view_for_sky =
self.prefilter_texture
.as_ref()
.unwrap()
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
self.irradiance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&irradiance_view_for_sky),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
label: Some("Sky Irradiance Bind Group"),
});
self.prefilter_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &self.hdr_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&prefilter_view_for_sky),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&self.sampler),
},
],
label: Some("Sky Prefilter Bind Group"),
});
Some((irradiance_view, prefiltered_view))
}
#[cfg(feature = "assets")]
pub fn capture_ibl_snapshots(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
atmosphere: Atmosphere,
hours: &[f32],
) {
self.ibl_snapshots.clear();
self.ibl_snapshot_atmosphere = Some(atmosphere);
let atmosphere_type = match atmosphere.as_procedural_cubemap_type() {
Some(t) => t,
None => return,
};
for &hour in hours {
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ProceduralUniform {
time: f32,
atmosphere_type: u32,
_padding0: u32,
_padding1: u32,
}
let uniform = ProceduralUniform {
time: hour,
atmosphere_type,
_padding0: 0,
_padding1: 0,
};
queue.write_buffer(
&self.procedural_uniform_buffer,
0,
bytemuck::cast_slice(&[uniform]),
);
let mip_level_count = calculate_mip_count(PROCEDURAL_CUBEMAP_SIZE);
let cubemap = device.create_texture(&wgpu::TextureDescriptor {
label: Some("IBL Snapshot Cubemap"),
size: wgpu::Extent3d {
width: PROCEDURAL_CUBEMAP_SIZE,
height: PROCEDURAL_CUBEMAP_SIZE,
depth_or_array_layers: 6,
},
mip_level_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::STORAGE_BINDING,
view_formats: &[],
});
let cubemap_storage_view = cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2Array),
base_mip_level: 0,
mip_level_count: Some(1),
..Default::default()
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("IBL Snapshot Cubemap Bind Group"),
layout: &self.procedural_cubemap_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: self.procedural_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&cubemap_storage_view),
},
],
});
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("IBL Snapshot Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("IBL Snapshot Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.procedural_cubemap_pipeline);
compute_pass.set_bind_group(0, &bind_group, &[]);
compute_pass.dispatch_workgroups(64, 64, 6);
}
queue.submit(Some(encoder.finish()));
generate_cubemap_mipmaps(device, queue, &cubemap);
let cubemap_view = cubemap.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
let filtered_maps = filter_environment_map(device, queue, &cubemap, &cubemap_view);
self.ibl_snapshots.push((
hour,
filtered_maps.irradiance_texture,
filtered_maps.prefiltered_texture,
));
}
}
pub fn select_ibl_bracket(
&self,
atmosphere: Atmosphere,
hour: f32,
) -> Option<(usize, usize, f32)> {
if self.ibl_snapshots.len() < 2 {
return None;
}
if self.ibl_snapshot_atmosphere != Some(atmosphere) {
return None;
}
let hour = hour - (hour / 24.0).floor() * 24.0;
let count = self.ibl_snapshots.len();
let mut lower_index = count - 1;
let mut upper_index = 0;
for index in 0..count {
if self.ibl_snapshots[index].0 <= hour {
lower_index = index;
}
}
for index in 0..count {
if self.ibl_snapshots[index].0 > hour {
upper_index = index;
break;
} else if index == count - 1 {
upper_index = 0;
}
}
let lower_hour = self.ibl_snapshots[lower_index].0;
let upper_hour = self.ibl_snapshots[upper_index].0;
let span = if upper_hour > lower_hour {
upper_hour - lower_hour
} else {
(24.0 - lower_hour) + upper_hour
};
let elapsed = if hour >= lower_hour {
hour - lower_hour
} else {
(24.0 - lower_hour) + hour
};
let blend = if span > 0.0 { elapsed / span } else { 0.0 };
Some((lower_index, upper_index, blend.clamp(0.0, 1.0)))
}
}
impl PassNode<crate::ecs::world::World> for SkyPass {
fn name(&self) -> &str {
"sky_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color"]
}
fn prepare(
&mut self,
_device: &wgpu::Device,
queue: &wgpu::Queue,
world: &crate::ecs::world::World,
) {
if let Some(camera_matrices) =
crate::ecs::camera::queries::query_active_camera_matrices(world)
{
let atmosphere = world.resources.graphics.atmosphere;
let uniform = SkyUniform::new(
camera_matrices.projection,
camera_matrices.view,
camera_matrices.camera_position,
world.resources.window.timing.uptime_milliseconds as f32 / 1000.0,
atmosphere.mip_level(),
world.resources.graphics.day_night.hour,
);
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniform]));
}
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
let atmosphere = context.configs.resources.graphics.atmosphere;
if atmosphere == Atmosphere::None {
return Ok(context.into_sub_graph_commands());
}
let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Sky Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: color_store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
match atmosphere {
Atmosphere::None => unreachable!(),
Atmosphere::Hdr if self.hdr_loaded => {
render_pass.set_pipeline(&self.hdr_pipeline);
render_pass.set_bind_group(0, &self.hdr_bind_group, &[]);
}
Atmosphere::Sky | Atmosphere::Hdr => {
render_pass.set_pipeline(&self.sky_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::Space => {
render_pass.set_pipeline(&self.space_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::Nebula => {
render_pass.set_pipeline(&self.nebula_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::Sunset => {
render_pass.set_pipeline(&self.sunset_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::DayNight => {
render_pass.set_pipeline(&self.day_night_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::CloudySky => {
render_pass.set_pipeline(&self.cloudy_sky_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
Atmosphere::Irradiance if self.hdr_loaded => {
render_pass.set_pipeline(&self.hdr_pipeline);
render_pass.set_bind_group(0, &self.irradiance_bind_group, &[]);
}
Atmosphere::PrefilterMip0
| Atmosphere::PrefilterMip1
| Atmosphere::PrefilterMip2
| Atmosphere::PrefilterMip3
| Atmosphere::PrefilterMip4
if self.hdr_loaded =>
{
render_pass.set_pipeline(&self.hdr_lod_pipeline);
render_pass.set_bind_group(0, &self.prefilter_bind_group, &[]);
}
Atmosphere::HdrMip0
| Atmosphere::HdrMip1
| Atmosphere::HdrMip2
| Atmosphere::HdrMip3
| Atmosphere::HdrMip4
if self.hdr_loaded =>
{
render_pass.set_pipeline(&self.hdr_lod_pipeline);
render_pass.set_bind_group(0, &self.hdr_bind_group, &[]);
}
Atmosphere::Irradiance
| Atmosphere::PrefilterMip0
| Atmosphere::PrefilterMip1
| Atmosphere::PrefilterMip2
| Atmosphere::PrefilterMip3
| Atmosphere::PrefilterMip4
| Atmosphere::HdrMip0
| Atmosphere::HdrMip1
| Atmosphere::HdrMip2
| Atmosphere::HdrMip3
| Atmosphere::HdrMip4 => {
render_pass.set_pipeline(&self.sky_pipeline);
render_pass.set_bind_group(0, &self.procedural_bind_group, &[]);
}
}
render_pass.draw(0..3, 0..1);
drop(render_pass);
Ok(context.into_sub_graph_commands())
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SkyUniform {
proj: [[f32; 4]; 4],
proj_inv: [[f32; 4]; 4],
view: [[f32; 4]; 4],
cam_pos: [f32; 4],
time: f32,
mip_level: f32,
hour: f32,
_padding: f32,
}
impl SkyUniform {
pub fn new(
projection: crate::ecs::world::Mat4,
view: crate::ecs::world::Mat4,
camera_position: crate::ecs::world::Vec3,
time: f32,
mip_level: f32,
hour: f32,
) -> Self {
let proj: [[f32; 4]; 4] = projection.into();
let proj_inv: [[f32; 4]; 4] = nalgebra_glm::inverse(&projection).into();
let view: [[f32; 4]; 4] = view.into();
let cam_pos = [camera_position.x, camera_position.y, camera_position.z, 1.0];
Self {
proj,
proj_inv,
view,
cam_pos,
time,
mip_level,
hour,
_padding: 0.0,
}
}
}