use super::super::types::{
BUFFER_GROWTH_FACTOR, CLUSTER_GRID_X, CLUSTER_GRID_Y, CLUSTER_GRID_Z, ClusterUniforms,
CullingUniforms, MAX_LIGHTS, MeshUniforms,
};
use super::MeshPass;
impl MeshPass {
pub(in super::super) fn prepare_uniforms_and_lights(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &crate::ecs::world::World,
) {
use super::super::super::projection::*;
let light_result = collect_lights(world, MAX_LIGHTS);
let mut lights_data = light_result.lights_data;
let directional_light = light_result.directional_light;
let entity_to_lights_index = light_result.entity_to_index;
{
let state = self.world_states.get_mut(&self.current_world_id).unwrap();
state.num_directional_lights = light_result.num_directional_lights;
state.num_total_lights = lights_data.len() as u32;
}
let directional_light_direction = directional_light
.as_ref()
.map(|(_light, transform)| {
let dir = transform.forward_vector();
[dir.x, dir.y, dir.z, 0.0]
})
.unwrap_or([0.0, -1.0, 0.0, 0.0]);
let cascade_result = calculate_cascade_shadows(world, directional_light.as_ref());
let cascade_view_projections = cascade_result.cascade_view_projections;
let cascade_diameters = cascade_result.cascade_diameters;
let light_view_projection = cascade_result.light_view_projection;
let shadow_bias = cascade_result.shadow_bias;
let shadows_enabled = cascade_result.shadows_enabled;
let cascade_texture_resolution = if cfg!(target_arch = "wasm32") {
2048.0
} else {
4096.0
};
let cascade_atlas_offsets: [[f32; 4]; crate::render::wgpu::passes::NUM_SHADOW_CASCADES] = [
[
0.0,
0.0,
cascade_diameters[0] / cascade_texture_resolution,
0.0,
],
[
0.5,
0.0,
cascade_diameters[1] / cascade_texture_resolution,
0.0,
],
[
0.0,
0.5,
cascade_diameters[2] / cascade_texture_resolution,
0.0,
],
[
0.5,
0.5,
cascade_diameters[3] / cascade_texture_resolution,
0.0,
],
];
if let Some(camera_matrices) =
crate::ecs::camera::queries::query_active_camera_matrices(world)
{
let global_unlit = if world.resources.graphics.unlit_mode {
1.0
} else {
0.0
};
let (snap_resolution, snap_enabled) =
if let Some(ref vertex_snap) = world.resources.graphics.vertex_snap {
(vertex_snap.resolution, 1)
} else {
([320.0, 240.0], 0)
};
let affine_enabled = if world.resources.graphics.affine_texture_mapping {
1
} else {
0
};
let (fog_color, fog_enabled, fog_start, fog_end) =
if let Some(ref fog) = world.resources.graphics.fog {
(fog.color, 1, fog.start, fog.end)
} else {
([0.5, 0.5, 0.6], 0, 5.0, 30.0)
};
let time = world.resources.window.timing.uptime_milliseconds as f32 / 1000.0;
let uniforms = MeshUniforms {
view: camera_matrices.view.into(),
projection: camera_matrices.projection.into(),
camera_position: [
camera_matrices.camera_position.x,
camera_matrices.camera_position.y,
camera_matrices.camera_position.z,
1.0,
],
num_lights: [lights_data.len() as u32, 0, 0, 0],
ambient_light: world.resources.graphics.ambient_light,
light_view_projection,
shadow_bias,
shadows_enabled,
global_unlit,
shadow_normal_bias: 1.8,
snap_resolution,
snap_enabled,
affine_enabled,
fog_color,
fog_enabled,
fog_start,
fog_end,
cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
directional_light_size: 1.0,
cascade_view_projections,
cascade_split_distances: CASCADE_SPLIT_DISTANCES,
cascade_atlas_offsets,
cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
time,
pbr_debug_mode: world.resources.graphics.pbr_debug_mode.as_u32(),
texture_debug_stripes: world.resources.graphics.texture_debug_stripes as u32,
texture_debug_stripes_speed: world.resources.graphics.texture_debug_stripes_speed,
directional_light_direction,
ibl_blend_factor: 0.0,
_padding3: [0.0; 19],
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
let overlay_uniforms = MeshUniforms {
view: camera_matrices.view.into(),
projection: camera_matrices.projection.into(),
camera_position: [
camera_matrices.camera_position.x,
camera_matrices.camera_position.y,
camera_matrices.camera_position.z,
1.0,
],
num_lights: [lights_data.len() as u32, 0, 0, 0],
ambient_light: world.resources.graphics.ambient_light,
light_view_projection,
shadow_bias,
shadows_enabled: 0.0,
global_unlit,
shadow_normal_bias: 1.8,
snap_resolution,
snap_enabled,
affine_enabled,
fog_color,
fog_enabled,
fog_start,
fog_end,
cascade_count: crate::render::wgpu::passes::NUM_SHADOW_CASCADES as u32,
directional_light_size: 1.0,
cascade_view_projections,
cascade_split_distances: CASCADE_SPLIT_DISTANCES,
cascade_atlas_offsets,
cascade_atlas_scale: [0.5, 0.5, 0.0, 0.0],
time,
pbr_debug_mode: world.resources.graphics.pbr_debug_mode.as_u32(),
texture_debug_stripes: world.resources.graphics.texture_debug_stripes as u32,
texture_debug_stripes_speed: world.resources.graphics.texture_debug_stripes_speed,
directional_light_direction,
ibl_blend_factor: 0.0,
_padding3: [0.0; 19],
};
queue.write_buffer(
&self.overlay_uniform_buffer,
0,
bytemuck::cast_slice(&[overlay_uniforms]),
);
let view_proj = camera_matrices.projection * camera_matrices.view;
let (culling_frustum_planes, culling_view_proj) =
if let Some(culling_camera) = world.resources.graphics.culling_camera_override {
if let Some(culling_matrices) =
crate::ecs::camera::queries::query_camera_matrices(world, culling_camera)
{
let culling_vp = culling_matrices.projection * culling_matrices.view;
(extract_frustum_planes(&culling_vp), culling_vp)
} else {
(extract_frustum_planes(&view_proj), view_proj)
}
} else {
(extract_frustum_planes(&view_proj), view_proj)
};
let (screen_width, screen_height) = self.hiz_pass.screen_size();
let projection_scale_y = camera_matrices.projection[(1, 1)];
let min_screen_pixel_size = world.resources.graphics.min_screen_pixel_size;
let occlusion_enabled =
screen_width > 0 && world.resources.graphics.occlusion_culling_enabled;
let object_count = self
.world_states
.get(&self.current_world_id)
.unwrap()
.object_count;
let phase1_culling_uniforms = CullingUniforms {
frustum_planes: culling_frustum_planes.map(|v| [v.x, v.y, v.z, v.w]),
view_projection: culling_view_proj.into(),
screen_size: [screen_width as f32, screen_height as f32],
hiz_mip_count: self.hiz_pass.mip_count() as f32,
occlusion_enabled: 0,
object_count,
min_screen_pixel_size,
projection_scale_y,
_padding: 0,
};
queue.write_buffer(
&self.phase1_culling_uniform_buffer,
0,
bytemuck::cast_slice(&[phase1_culling_uniforms]),
);
let culling_uniforms = CullingUniforms {
frustum_planes: culling_frustum_planes.map(|v| [v.x, v.y, v.z, v.w]),
view_projection: culling_view_proj.into(),
screen_size: [screen_width as f32, screen_height as f32],
hiz_mip_count: self.hiz_pass.mip_count() as f32,
occlusion_enabled: if occlusion_enabled { 1 } else { 0 },
object_count,
min_screen_pixel_size,
projection_scale_y,
_padding: 0,
};
queue.write_buffer(
&self.culling_uniform_buffer,
0,
bytemuck::cast_slice(&[culling_uniforms]),
);
let hiz_view = self.hiz_pass.hiz_view_or_dummy();
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
if gpu.culling_bind_group.is_none() {
gpu.culling_bind_group =
Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Mesh Culling Bind Group"),
layout: &self.culling_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: gpu.transform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: gpu.object_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.culling_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.mesh_bounds_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: gpu.indirect_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: gpu.visible_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(hiz_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: self.mesh_aabbs_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 8,
resource: self.mesh_lod_buffer.as_entire_binding(),
},
],
}));
}
if gpu.phase1_culling_bind_group.is_none() {
gpu.phase1_culling_bind_group =
Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Phase 1 Culling Bind Group"),
layout: &self.culling_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: gpu.transform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: gpu.object_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: self.phase1_culling_uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: self.mesh_bounds_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: gpu.phase1_indirect_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: gpu.phase1_visible_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(hiz_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: self.mesh_aabbs_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 8,
resource: self.mesh_lod_buffer.as_entire_binding(),
},
],
}));
}
let (z_near, z_far) = world
.resources
.active_camera
.and_then(|entity| world.core.get_camera(entity))
.map(|camera| match &camera.projection {
crate::ecs::camera::components::Projection::Perspective(persp) => {
(persp.z_near, persp.z_far.unwrap_or(1000.0))
}
crate::ecs::camera::components::Projection::Orthographic(ortho) => {
(ortho.z_near, ortho.z_far)
}
})
.unwrap_or((0.1, 1000.0));
let tile_size_x = (screen_width as f32) / (CLUSTER_GRID_X as f32);
let tile_size_y = (screen_height as f32) / (CLUSTER_GRID_Y as f32);
let inverse_projection: [[f32; 4]; 4] =
nalgebra_glm::inverse(&camera_matrices.projection).into();
let num_directional_lights = self
.world_states
.get(&self.current_world_id)
.unwrap()
.num_directional_lights;
let cluster_uniforms = ClusterUniforms {
inverse_projection,
screen_size: [screen_width as f32, screen_height as f32],
z_near,
z_far,
cluster_count: [CLUSTER_GRID_X, CLUSTER_GRID_Y, CLUSTER_GRID_Z, 0],
tile_size: [tile_size_x, tile_size_y],
num_lights: lights_data.len() as u32,
num_directional_lights,
};
let cluster_uniforms_buffer = &self
.world_states
.get(&self.current_world_id)
.unwrap()
.gpu_buffers
.as_ref()
.unwrap()
.cluster_uniforms_buffer;
queue.write_buffer(
cluster_uniforms_buffer,
0,
bytemuck::cast_slice(&[cluster_uniforms]),
);
let view_matrix: [[f32; 4]; 4] = camera_matrices.view.into();
queue.write_buffer(
&self.view_matrix_buffer,
0,
bytemuck::cast_slice(&[view_matrix]),
);
let view_proj_hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
for row in &inverse_projection {
for val in row {
val.to_bits().hash(&mut hasher);
}
}
screen_width.hash(&mut hasher);
screen_height.hash(&mut hasher);
hasher.finish()
};
{
let state = self.world_states.get_mut(&self.current_world_id).unwrap();
state.camera_changed = view_proj_hash != state.last_camera_hash;
if state.camera_changed {
state.last_camera_hash = view_proj_hash;
}
}
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
gpu.cluster_assign_bind_group =
Some(device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Cluster Light Assign Bind Group (Per-World)"),
layout: &self.cluster_assign_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: gpu.cluster_uniforms_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: self.cluster_bounds_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 2,
resource: gpu.light_grid_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 3,
resource: gpu.light_indices_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: gpu.lights_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: self.view_matrix_buffer.as_entire_binding(),
},
],
}));
}
let camera_position = world
.resources
.active_camera
.and_then(|cam| world.core.get_global_transform(cam))
.map(|t| nalgebra_glm::vec3(t.0[(0, 3)], t.0[(1, 3)], t.0[(2, 3)]))
.unwrap_or_else(|| nalgebra_glm::vec3(0.0, 0.0, 0.0));
let spotlight_result = collect_spotlight_shadows(world, camera_position);
apply_spotlight_shadow_indices(
&mut lights_data,
&spotlight_result.entity_to_shadow_index,
&entity_to_lights_index,
);
if !spotlight_result.shadow_data.is_empty() {
queue.write_buffer(
&self.spotlight_shadow_buffer,
0,
bytemuck::cast_slice(&spotlight_result.shadow_data),
);
}
let point_shadow_result = collect_point_light_shadows(
world,
camera_position,
&mut lights_data,
&entity_to_lights_index,
);
if !point_shadow_result.is_empty() {
queue.write_buffer(
&self.point_shadow_buffer,
0,
bytemuck::cast_slice(&point_shadow_result),
);
}
if !lights_data.is_empty() {
let lights_buffer_size = self
.world_states
.get(&self.current_world_id)
.unwrap()
.gpu_buffers
.as_ref()
.unwrap()
.lights_buffer_size;
if lights_data.len() > lights_buffer_size {
let new_size = (lights_data.len() as f32 * BUFFER_GROWTH_FACTOR).ceil() as usize;
let new_size = new_size.min(MAX_LIGHTS);
let new_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Mesh Lights Buffer (Resized)"),
size: (std::mem::size_of::<LightData>() * new_size) as u64,
usage: wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST
| wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
{
let world_state = self.world_states.get_mut(&self.current_world_id).unwrap();
let gpu = world_state.gpu_buffers.as_mut().unwrap();
gpu.lights_buffer = new_buffer;
gpu.lights_buffer_size = new_size;
}
self.rebuild_instance_bind_group(device);
}
let lights_buffer = &self
.world_states
.get(&self.current_world_id)
.unwrap()
.gpu_buffers
.as_ref()
.unwrap()
.lights_buffer;
queue.write_buffer(lights_buffer, 0, bytemuck::cast_slice(&lights_data));
}
}
}