mod constructor;
mod execute;
mod prepare;
use crate::ecs::mesh::components::Vertex;
use crate::ecs::world::World;
use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::collections::HashMap;
pub const INITIAL_WATER_VERTEX_BUFFER_SIZE: usize = 10_000;
pub const INITIAL_WATER_INDEX_BUFFER_SIZE: usize = 30_000;
pub const INITIAL_WATER_INSTANCES: usize = 256;
pub const INITIAL_WATER_MATERIALS: usize = 32;
pub const INITIAL_WATER_OBJECTS: usize = 256;
pub const INITIAL_WATER_BATCHES: usize = 32;
pub const BUFFER_GROWTH_FACTOR: f32 = 2.0;
const VOLUMETRIC_CUBE_MESH_NAME: &str = "__volumetric_cube__";
fn create_unit_cube_mesh() -> (Vec<Vertex>, Vec<u32>) {
let half = 0.5;
let vertices = vec![
Vertex {
position: [-half, -half, half],
normal: [0.0, 0.0, 1.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, half],
normal: [0.0, 0.0, 1.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, half],
normal: [0.0, 0.0, 1.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, half],
normal: [0.0, 0.0, 1.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, -half, -half],
normal: [0.0, 0.0, -1.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [-1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, -half],
normal: [0.0, 0.0, -1.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [-1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, -half],
normal: [0.0, 0.0, -1.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [-1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, -half],
normal: [0.0, 0.0, -1.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [-1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, -half],
normal: [0.0, 1.0, 0.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, -half],
normal: [0.0, 1.0, 0.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, half],
normal: [0.0, 1.0, 0.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, half],
normal: [0.0, 1.0, 0.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, -half, -half],
normal: [0.0, -1.0, 0.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, -half],
normal: [0.0, -1.0, 0.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, half],
normal: [0.0, -1.0, 0.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, -half, half],
normal: [0.0, -1.0, 0.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [1.0, 0.0, 0.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, -half],
normal: [1.0, 0.0, 0.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, 1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, -half],
normal: [1.0, 0.0, 0.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, 1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, half, half],
normal: [1.0, 0.0, 0.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, 1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [half, -half, half],
normal: [1.0, 0.0, 0.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, 1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, -half, -half],
normal: [-1.0, 0.0, 0.0],
tex_coords: [1.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, -1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, -half],
normal: [-1.0, 0.0, 0.0],
tex_coords: [1.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, -1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, half, half],
normal: [-1.0, 0.0, 0.0],
tex_coords: [0.0, 1.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, -1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
Vertex {
position: [-half, -half, half],
normal: [-1.0, 0.0, 0.0],
tex_coords: [0.0, 0.0],
tex_coords_1: [0.0, 0.0],
tangent: [0.0, 0.0, -1.0, 1.0],
color: [1.0, 1.0, 1.0, 1.0],
},
];
let indices = vec![
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 8, 9, 10, 8, 10, 11, 12, 14, 13, 12, 15, 14, 16, 17,
18, 16, 18, 19, 20, 22, 21, 20, 23, 22,
];
(vertices, indices)
}
fn compute_volumetric_bounds(volume_size: [f32; 3]) -> MeshBoundsData {
let half_size = [
volume_size[0] * 0.5,
volume_size[1] * 0.5,
volume_size[2] * 0.5,
];
let radius =
(half_size[0] * half_size[0] + half_size[1] * half_size[1] + half_size[2] * half_size[2])
.sqrt();
MeshBoundsData {
center: [0.0, 0.0, 0.0],
radius,
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct WaterUniforms {
view: [[f32; 4]; 4],
projection: [[f32; 4]; 4],
view_projection: [[f32; 4]; 4],
camera_position: [f32; 4],
time: f32,
_padding: [f32; 3],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, PartialEq)]
struct WaterMaterialData {
base_color: [f32; 4],
water_color: [f32; 4],
wave_height: f32,
choppy: f32,
speed: f32,
freq: f32,
specular_strength: f32,
fresnel_power: f32,
volume_shape: u32,
volume_flow_type: u32,
volume_size: [f32; 3],
is_volumetric: u32,
flow_direction: [f32; 2],
flow_strength: f32,
_flow_padding: f32,
}
impl std::hash::Hash for WaterMaterialData {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.base_color.iter().for_each(|v| v.to_bits().hash(state));
self.water_color
.iter()
.for_each(|v| v.to_bits().hash(state));
self.wave_height.to_bits().hash(state);
self.choppy.to_bits().hash(state);
self.speed.to_bits().hash(state);
self.freq.to_bits().hash(state);
self.specular_strength.to_bits().hash(state);
self.fresnel_power.to_bits().hash(state);
self.volume_shape.hash(state);
self.volume_flow_type.hash(state);
self.volume_size
.iter()
.for_each(|v| v.to_bits().hash(state));
self.is_volumetric.hash(state);
self.flow_direction
.iter()
.for_each(|v| v.to_bits().hash(state));
self.flow_strength.to_bits().hash(state);
}
}
impl Eq for WaterMaterialData {}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct WaterInstance {
model: [[f32; 4]; 4],
material_index: u32,
_padding: [u32; 3],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct WaterObjectData {
instance_index: u32,
mesh_id: u32,
batch_id: u32,
_padding: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct MeshBoundsData {
center: [f32; 3],
radius: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct CullingUniforms {
frustum_planes: [[f32; 4]; 6],
object_count: u32,
_padding: [u32; 3],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct DrawIndexedIndirect {
index_count: u32,
instance_count: u32,
first_index: u32,
base_vertex: i32,
first_instance: u32,
}
#[derive(Debug, Clone)]
struct WaterMeshData {
vertex_offset: u32,
index_offset: u32,
index_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WaterType {
Horizontal,
Vertical,
Volumetric,
}
pub struct WaterMeshPass {
horizontal_pipeline: wgpu::RenderPipeline,
vertical_pipeline: wgpu::RenderPipeline,
volumetric_pipeline: wgpu::RenderPipeline,
culling_pipeline: wgpu::ComputePipeline,
uniform_buffer: wgpu::Buffer,
culling_uniform_buffer: wgpu::Buffer,
render_bind_group_layout: wgpu::BindGroupLayout,
render_bind_group: wgpu::BindGroup,
culling_bind_group_layout: wgpu::BindGroupLayout,
culling_bind_group: Option<wgpu::BindGroup>,
material_buffer: wgpu::Buffer,
instance_buffer: wgpu::Buffer,
object_buffer: wgpu::Buffer,
mesh_bounds_buffer: wgpu::Buffer,
visible_indices_buffer: wgpu::Buffer,
indirect_buffer: wgpu::Buffer,
indirect_reset_buffer: wgpu::Buffer,
vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
vertex_buffer_size: usize,
index_buffer_size: usize,
instance_buffer_size: usize,
material_buffer_size: usize,
object_buffer_size: usize,
mesh_bounds_buffer_size: usize,
visible_indices_buffer_size: usize,
indirect_buffer_size: usize,
current_vertex_offset: u32,
current_index_offset: u32,
meshes: HashMap<String, (u32, WaterMeshData)>,
next_mesh_id: u32,
horizontal_batches: Vec<usize>,
vertical_batches: Vec<usize>,
volumetric_batches: Vec<(usize, [f32; 3])>,
batch_count: usize,
object_count: u32,
indirect_reset_count: usize,
camera_position: [f32; 3],
}
fn extract_frustum_planes(view_projection: &nalgebra_glm::Mat4) -> [[f32; 4]; 6] {
let m = view_projection;
let row0 = [m[(0, 0)], m[(0, 1)], m[(0, 2)], m[(0, 3)]];
let row1 = [m[(1, 0)], m[(1, 1)], m[(1, 2)], m[(1, 3)]];
let row2 = [m[(2, 0)], m[(2, 1)], m[(2, 2)], m[(2, 3)]];
let row3 = [m[(3, 0)], m[(3, 1)], m[(3, 2)], m[(3, 3)]];
let planes = [
[
row3[0] + row0[0],
row3[1] + row0[1],
row3[2] + row0[2],
row3[3] + row0[3],
],
[
row3[0] - row0[0],
row3[1] - row0[1],
row3[2] - row0[2],
row3[3] - row0[3],
],
[
row3[0] + row1[0],
row3[1] + row1[1],
row3[2] + row1[2],
row3[3] + row1[3],
],
[
row3[0] - row1[0],
row3[1] - row1[1],
row3[2] - row1[2],
row3[3] - row1[3],
],
[
row3[0] + row2[0],
row3[1] + row2[1],
row3[2] + row2[2],
row3[3] + row2[3],
],
[
row3[0] - row2[0],
row3[1] - row2[1],
row3[2] - row2[2],
row3[3] - row2[3],
],
];
planes.map(|p| {
let len = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
if len > 0.0001 {
[p[0] / len, p[1] / len, p[2] / len, p[3] / len]
} else {
p
}
})
}
impl PassNode<World> for WaterMeshPass {
fn name(&self) -> &str {
"water_mesh_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color", "depth"]
}
fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, world: &World) {
self.prepare_pass(device, queue, world);
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
self.execute_pass(context)
}
}