pub mod blit;
pub mod bloom;
pub mod clouds;
pub mod debug;
pub mod figure;
pub mod fluid;
pub mod lod_object;
pub mod lod_terrain;
pub mod particle;
pub mod postprocess;
pub mod rain_occlusion;
pub mod rope;
pub mod shadow;
pub mod skybox;
pub mod sprite;
pub mod terrain;
pub mod trail;
pub mod ui;
use super::{Consts, Renderer, Texture};
use crate::scene::camera::CameraMode;
use bytemuck::{Pod, Zeroable};
use common::{resources::TimeOfDay, terrain::BlockKind, util::srgb_to_linear};
use std::marker::PhantomData;
use vek::*;
pub use self::{figure::FigureSpriteAtlasData, terrain::TerrainAtlasData};
pub const MAX_POINT_LIGHT_COUNT: usize = 20;
pub const MAX_FIGURE_SHADOW_COUNT: usize = 24;
pub const MAX_DIRECTED_LIGHT_COUNT: usize = 6;
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Globals {
view_mat: [[f32; 4]; 4],
proj_mat: [[f32; 4]; 4],
all_mat: [[f32; 4]; 4],
cam_pos: [f32; 4],
focus_off: [f32; 4],
focus_pos: [f32; 4],
view_distance: [f32; 4],
time_of_day: [f32; 4], sun_dir: [f32; 4],
moon_dir: [f32; 4],
tick: [f32; 4],
screen_res: [f32; 4],
light_shadow_count: [u32; 4],
shadow_proj_factors: [f32; 4],
medium: [u32; 4],
select_pos: [i32; 4],
gamma_exposure: [f32; 4],
last_lightning: [f32; 4],
wind_vel: [f32; 2],
ambiance: f32,
cam_mode: u32,
sprite_render_distance: f32,
globals_dummy: [f32; 3],
}
const _: () = assert!(core::mem::size_of::<Globals>() % 16 == 0);
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Light {
pub pos: [f32; 4],
pub col: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Shadow {
pos_radius: [f32; 4],
}
pub const TIME_OVERFLOW: f64 = 300000.0;
impl Globals {
#[allow(clippy::too_many_arguments)]
pub fn new(
view_mat: Mat4<f32>,
proj_mat: Mat4<f32>,
cam_pos: Vec3<f32>,
focus_pos: Vec3<f32>,
view_distance: f32,
tgt_detail: f32,
map_bounds: Vec2<f32>,
time_of_day: f64,
tick: f64,
client_tick: f64,
screen_res: Vec2<u16>,
shadow_planes: Vec2<f32>,
light_count: usize,
shadow_count: usize,
directed_light_count: usize,
medium: BlockKind,
select_pos: Option<Vec3<i32>>,
gamma: f32,
exposure: f32,
last_lightning: (Vec3<f32>, f64),
wind_vel: Vec2<f32>,
ambiance: f32,
cam_mode: CameraMode,
sprite_render_distance: f32,
) -> Self {
Self {
view_mat: view_mat.into_col_arrays(),
proj_mat: proj_mat.into_col_arrays(),
all_mat: (proj_mat * view_mat).into_col_arrays(),
cam_pos: Vec4::from(cam_pos).into_array(),
focus_off: Vec4::from(focus_pos).map(|e: f32| e.trunc()).into_array(),
focus_pos: Vec4::from(focus_pos).map(|e: f32| e.fract()).into_array(),
view_distance: [view_distance, tgt_detail, map_bounds.x, map_bounds.y],
time_of_day: [
(time_of_day % (3600.0 * 24.0)) as f32,
(time_of_day / (3600.0 * 24.0) % 1000.0) as f32,
0.0,
0.0,
],
sun_dir: Vec4::from_direction(TimeOfDay::new(time_of_day).get_sun_dir()).into_array(),
moon_dir: Vec4::from_direction(TimeOfDay::new(time_of_day).get_moon_dir()).into_array(),
tick: [
(tick % TIME_OVERFLOW) as f32,
(tick / TIME_OVERFLOW).floor() as f32,
client_tick as f32,
0.0,
],
screen_res: [
screen_res.x as f32,
screen_res.y as f32,
shadow_planes.x,
shadow_planes.y,
],
light_shadow_count: [
usize::min(light_count, MAX_POINT_LIGHT_COUNT) as u32,
usize::min(shadow_count, MAX_FIGURE_SHADOW_COUNT) as u32,
usize::min(directed_light_count, MAX_DIRECTED_LIGHT_COUNT) as u32,
0,
],
shadow_proj_factors: [
shadow_planes.y / (shadow_planes.y - shadow_planes.x),
shadow_planes.y * shadow_planes.x / (shadow_planes.y - shadow_planes.x),
0.0,
0.0,
],
medium: [if medium.is_liquid() {
1
} else if medium.is_filled() {
2
} else {
0
}; 4],
select_pos: select_pos
.map(|sp| Vec4::from(sp) + Vec4::unit_w())
.unwrap_or_else(Vec4::zero)
.into_array(),
gamma_exposure: [gamma, exposure, 0.0, 0.0],
last_lightning: last_lightning
.0
.with_w((last_lightning.1 % TIME_OVERFLOW) as f32)
.into_array(),
wind_vel: wind_vel.into_array(),
ambiance: ambiance.clamped(0.0, 1.0),
cam_mode: cam_mode as u32,
sprite_render_distance,
globals_dummy: [0.0; 3],
}
}
}
impl Default for Globals {
fn default() -> Self {
Self::new(
Mat4::identity(),
Mat4::identity(),
Vec3::zero(),
Vec3::zero(),
0.0,
100.0,
Vec2::new(140.0, 2048.0),
0.0,
0.0,
0.0,
Vec2::new(800, 500),
Vec2::new(1.0, 25.0),
0,
0,
0,
BlockKind::Air,
None,
1.0,
1.0,
(Vec3::zero(), -1000.0),
Vec2::zero(),
1.0,
CameraMode::ThirdPerson,
250.0,
)
}
}
impl Light {
pub fn new(pos: Vec3<f32>, col: Rgb<f32>, strength: f32) -> Self {
let linearized_col = srgb_to_linear(col);
Self {
pos: Vec4::from(pos).into_array(),
col: (Rgba::new(linearized_col.r, linearized_col.g, linearized_col.b, 0.0) * strength)
.into_array(),
}
}
pub fn get_pos(&self) -> Vec3<f32> { Vec3::new(self.pos[0], self.pos[1], self.pos[2]) }
#[must_use]
pub fn with_strength(mut self, strength: f32) -> Self {
self.col = (Vec4::<f32>::from(self.col) * strength).into_array();
self
}
}
impl Default for Light {
fn default() -> Self { Self::new(Vec3::zero(), Rgb::zero(), 0.0) }
}
impl Shadow {
pub fn new(pos: Vec3<f32>, radius: f32) -> Self {
Self {
pos_radius: [pos.x, pos.y, pos.z, radius],
}
}
pub fn get_pos(&self) -> Vec3<f32> {
Vec3::new(self.pos_radius[0], self.pos_radius[1], self.pos_radius[2])
}
}
impl Default for Shadow {
fn default() -> Self { Self::new(Vec3::zero(), 0.0) }
}
pub struct GlobalModel {
pub globals: Consts<Globals>,
pub lights: Consts<Light>,
pub shadows: Consts<Shadow>,
pub shadow_mats: shadow::BoundLocals,
pub rain_occlusion_mats: rain_occlusion::BoundLocals,
pub point_light_matrices: Box<[shadow::PointLightMatrix; 126]>,
}
pub struct GlobalsBindGroup {
pub(super) bind_group: wgpu::BindGroup,
}
pub struct ShadowTexturesBindGroup {
pub(super) bind_group: wgpu::BindGroup,
}
pub struct GlobalsLayouts {
pub globals: wgpu::BindGroupLayout,
pub figure_sprite_atlas_layout: VoxelAtlasLayout<FigureSpriteAtlasData>,
pub terrain_atlas_layout: VoxelAtlasLayout<TerrainAtlasData>,
pub shadow_textures: wgpu::BindGroupLayout,
}
pub struct AtlasTextures<Locals, S: AtlasData>
where
[(); S::TEXTURES]:,
{
pub(super) bind_group: wgpu::BindGroup,
pub textures: [Texture; S::TEXTURES],
phantom: std::marker::PhantomData<Locals>,
}
pub struct VoxelAtlasLayout<S: AtlasData>(wgpu::BindGroupLayout, PhantomData<S>);
impl<S: AtlasData> VoxelAtlasLayout<S> {
pub fn new(device: &wgpu::Device) -> Self {
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &S::layout(),
});
Self(layout, PhantomData)
}
pub fn layout(&self) -> &wgpu::BindGroupLayout { &self.0 }
}
pub trait AtlasData {
const TEXTURES: usize;
type SliceMut<'a>: Iterator
where
Self: 'a;
fn blank_with_size(sz: Vec2<u16>) -> Self;
fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES];
fn layout() -> Vec<wgpu::BindGroupLayoutEntry>;
fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_>;
fn create_textures(
&self,
renderer: &mut Renderer,
atlas_size: Vec2<u16>,
) -> [Texture; Self::TEXTURES] {
self.as_texture_data().map(|(fmt, data)| {
let texture_info = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: u32::from(atlas_size.x),
height: u32::from(atlas_size.y),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: fmt,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
};
let sampler_info = wgpu::SamplerDescriptor {
label: None,
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::FilterMode::Nearest,
border_color: Some(wgpu::SamplerBorderColor::TransparentBlack),
..Default::default()
};
let view_info = wgpu::TextureViewDescriptor {
label: None,
format: Some(fmt),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
};
renderer.create_texture_with_data_raw(&texture_info, &view_info, &sampler_info, data)
})
}
}
impl GlobalsLayouts {
pub fn base_globals_layout() -> Vec<wgpu::BindGroupLayoutEntry> {
vec![
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::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
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: 4,
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: 5,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 8,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 9,
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: 10,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 11,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 12,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 13,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 14,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
]
}
pub fn new(device: &wgpu::Device) -> Self {
let globals = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Globals layout"),
entries: &Self::base_globals_layout(),
});
let shadow_textures = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::Cube,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
],
});
Self {
globals,
figure_sprite_atlas_layout: VoxelAtlasLayout::new(device),
terrain_atlas_layout: VoxelAtlasLayout::new(device),
shadow_textures,
}
}
pub fn bind_base_globals<'a>(
global_model: &'a GlobalModel,
lod_data: &'a lod_terrain::LodData,
noise: &'a Texture,
) -> Vec<wgpu::BindGroupEntry<'a>> {
vec![
wgpu::BindGroupEntry {
binding: 0,
resource: global_model.globals.buf().as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&noise.view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&noise.sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: global_model.lights.buf().as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 4,
resource: global_model.shadows.buf().as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&lod_data.alt.view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&lod_data.alt.sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(&lod_data.horizon.view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(&lod_data.horizon.sampler),
},
wgpu::BindGroupEntry {
binding: 9,
resource: global_model.shadow_mats.buf().as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(&lod_data.map.view),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(&lod_data.map.sampler),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::TextureView(&lod_data.weather.view),
},
wgpu::BindGroupEntry {
binding: 13,
resource: wgpu::BindingResource::Sampler(&lod_data.weather.sampler),
},
wgpu::BindGroupEntry {
binding: 14,
resource: global_model.rain_occlusion_mats.buf().as_entire_binding(),
},
]
}
pub fn bind(
&self,
device: &wgpu::Device,
global_model: &GlobalModel,
lod_data: &lod_terrain::LodData,
noise: &Texture,
) -> GlobalsBindGroup {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.globals,
entries: &Self::bind_base_globals(global_model, lod_data, noise),
});
GlobalsBindGroup { bind_group }
}
pub fn bind_shadow_textures(
&self,
device: &wgpu::Device,
point_shadow_map: &Texture,
directed_shadow_map: &Texture,
rain_occlusion_map: &Texture,
) -> ShadowTexturesBindGroup {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.shadow_textures,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&point_shadow_map.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&point_shadow_map.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&directed_shadow_map.view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&directed_shadow_map.sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&rain_occlusion_map.view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::Sampler(&rain_occlusion_map.sampler),
},
],
});
ShadowTexturesBindGroup { bind_group }
}
pub fn bind_atlas_textures<Locals, S: AtlasData>(
&self,
device: &wgpu::Device,
layout: &VoxelAtlasLayout<S>,
textures: [Texture; S::TEXTURES],
) -> AtlasTextures<Locals, S> {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: layout.layout(),
entries: &textures
.iter()
.enumerate()
.flat_map(|(i, tex)| {
[
wgpu::BindGroupEntry {
binding: i as u32 * 2,
resource: wgpu::BindingResource::TextureView(&tex.view),
},
wgpu::BindGroupEntry {
binding: i as u32 * 2 + 1,
resource: wgpu::BindingResource::Sampler(&tex.sampler),
},
]
})
.collect::<Vec<_>>(),
});
AtlasTextures {
textures,
bind_group,
phantom: std::marker::PhantomData,
}
}
}