MAJOR rendering overhaul. Snow deformation, persistent light, flowmap out. Also ECS architexture overhaul
This commit is contained in:
465
src/snow.rs
465
src/snow.rs
@@ -5,11 +5,9 @@ use glam::{Vec2, Vec3};
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::{
|
||||
components::MeshComponent,
|
||||
entity::EntityHandle,
|
||||
mesh::{Mesh, Vertex},
|
||||
render,
|
||||
world::{Transform, World},
|
||||
loaders::mesh::{InstanceRaw, Mesh, Vertex},
|
||||
render::{self, DrawCall, Pipeline},
|
||||
texture::HeightmapTexture,
|
||||
};
|
||||
|
||||
pub struct SnowConfig
|
||||
@@ -22,16 +20,6 @@ pub struct SnowConfig
|
||||
|
||||
impl SnowConfig
|
||||
{
|
||||
pub fn new(depth_map_path: &str, heightmap_path: &str, terrain_size: Vec2, resolution: (u32, u32)) -> Self
|
||||
{
|
||||
Self {
|
||||
depth_map_path: depth_map_path.to_string(),
|
||||
heightmap_path: heightmap_path.to_string(),
|
||||
terrain_size,
|
||||
resolution,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
@@ -43,9 +31,33 @@ impl SnowConfig
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClipmapConfig
|
||||
{
|
||||
pub num_levels: u32,
|
||||
pub grid_size: u32,
|
||||
pub base_cell_size: f32,
|
||||
}
|
||||
|
||||
impl Default for ClipmapConfig
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
num_levels: 3,
|
||||
grid_size: 192,
|
||||
base_cell_size: 0.25,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClipmapLevel
|
||||
{
|
||||
mesh: Rc<Mesh>,
|
||||
instance_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
pub struct SnowLayer
|
||||
{
|
||||
pub entity: EntityHandle,
|
||||
pub depth_texture: wgpu::Texture,
|
||||
pub depth_texture_view: wgpu::TextureView,
|
||||
pub depth_bind_group: wgpu::BindGroup,
|
||||
@@ -54,11 +66,30 @@ pub struct SnowLayer
|
||||
pub deform_bind_group: wgpu::BindGroup,
|
||||
pub deform_pipeline: wgpu::ComputePipeline,
|
||||
pub deform_params_buffer: wgpu::Buffer,
|
||||
pub terrain_size: Vec2,
|
||||
|
||||
clipmap_config: ClipmapConfig,
|
||||
levels: Vec<ClipmapLevel>,
|
||||
displacement_bind_group: wgpu::BindGroup,
|
||||
heightmap_texture: HeightmapTexture,
|
||||
depth_sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
fn create_instance_buffer() -> wgpu::Buffer
|
||||
{
|
||||
render::with_device(|device| {
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("Snow Clipmap Instance Buffer"),
|
||||
size: std::mem::size_of::<InstanceRaw>() as u64,
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
impl SnowLayer
|
||||
{
|
||||
pub fn load(world: &mut World, config: &SnowConfig) -> anyhow::Result<Self>
|
||||
pub fn load(config: &SnowConfig) -> anyhow::Result<Self>
|
||||
{
|
||||
println!("\n=== Loading Snow Layer ===");
|
||||
println!("Depth map path: {}", config.depth_map_path);
|
||||
@@ -66,44 +97,42 @@ impl SnowLayer
|
||||
println!("Terrain size: {:?}", config.terrain_size);
|
||||
|
||||
let (depth_data, width, height) = Self::load_depth_map(&config.depth_map_path)?;
|
||||
let (heightmap_data, hm_width, hm_height) = Self::load_depth_map(&config.heightmap_path)?;
|
||||
|
||||
if width != hm_width || height != hm_height {
|
||||
anyhow::bail!("Snow depth map ({}×{}) and heightmap ({}×{}) dimensions don't match!",
|
||||
width, height, hm_width, hm_height);
|
||||
}
|
||||
|
||||
println!("Using EXR dimensions: {}×{}", width, height);
|
||||
|
||||
let (depth_texture, depth_texture_view, depth_bind_group) =
|
||||
Self::create_depth_texture(&depth_data, width, height);
|
||||
|
||||
let mesh = Self::generate_snow_mesh(&depth_data, &heightmap_data, width, height, config.terrain_size);
|
||||
let num_indices = mesh.num_indices;
|
||||
|
||||
let entity = world.spawn();
|
||||
world.transforms.insert(entity, Transform::IDENTITY);
|
||||
|
||||
if num_indices > 0 {
|
||||
world.meshes.insert(
|
||||
entity,
|
||||
MeshComponent {
|
||||
mesh: Rc::new(mesh),
|
||||
pipeline: render::Pipeline::Snow,
|
||||
instance_buffer: None,
|
||||
num_instances: 1,
|
||||
},
|
||||
);
|
||||
println!("Snow mesh created with {} indices", num_indices);
|
||||
} else {
|
||||
println!("⚠️ No snow mesh created - all depth values are zero");
|
||||
}
|
||||
|
||||
let (deform_pipeline, deform_bind_group, deform_params_buffer) =
|
||||
Self::create_deform_pipeline(&depth_texture_view);
|
||||
|
||||
let heightmap_texture = render::with_device(|device| {
|
||||
render::with_queue(|queue| {
|
||||
HeightmapTexture::load(device, queue, &config.heightmap_path)
|
||||
})
|
||||
})?;
|
||||
|
||||
let depth_sampler = render::with_device(|device| {
|
||||
device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("Snow Depth Sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Nearest,
|
||||
min_filter: wgpu::FilterMode::Nearest,
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
|
||||
let displacement_bind_group = Self::create_displacement_bind_group(
|
||||
&heightmap_texture,
|
||||
&depth_texture_view,
|
||||
&depth_sampler,
|
||||
);
|
||||
|
||||
let clipmap_config = ClipmapConfig::default();
|
||||
let levels = Vec::new();
|
||||
|
||||
Ok(Self {
|
||||
entity,
|
||||
depth_texture,
|
||||
depth_texture_view,
|
||||
depth_bind_group,
|
||||
@@ -112,6 +141,12 @@ impl SnowLayer
|
||||
deform_bind_group,
|
||||
deform_pipeline,
|
||||
deform_params_buffer,
|
||||
terrain_size: config.terrain_size,
|
||||
clipmap_config,
|
||||
levels,
|
||||
displacement_bind_group,
|
||||
heightmap_texture,
|
||||
depth_sampler,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -132,9 +167,20 @@ impl SnowLayer
|
||||
let height = layer.size.height() as u32;
|
||||
|
||||
println!(" Layer size: {}×{}", width, height);
|
||||
println!(" Available channels: {:?}", layer.channel_data.list.iter().map(|c| &c.name).collect::<Vec<_>>());
|
||||
println!(
|
||||
" Available channels: {:?}",
|
||||
layer
|
||||
.channel_data
|
||||
.list
|
||||
.iter()
|
||||
.map(|c| &c.name)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let channel = layer.channel_data.list.iter()
|
||||
let channel = layer
|
||||
.channel_data
|
||||
.list
|
||||
.iter()
|
||||
.find(|c| format!("{:?}", c.name).contains("\"R\""))
|
||||
.or_else(|| layer.channel_data.list.first())
|
||||
.ok_or_else(|| anyhow::anyhow!("No channels found in EXR"))?;
|
||||
@@ -149,12 +195,24 @@ impl SnowLayer
|
||||
let non_zero_count = depths.iter().filter(|&&v| v > 0.0001).count();
|
||||
|
||||
println!(" Total values: {}", depths.len());
|
||||
println!(" Min: {:.6}, Max: {:.6}, Avg: {:.6}", min_value, max_value, avg_value);
|
||||
println!(" Non-zero values: {} ({:.1}%)", non_zero_count, (non_zero_count as f32 / depths.len() as f32) * 100.0);
|
||||
println!(
|
||||
" Min: {:.6}, Max: {:.6}, Avg: {:.6}",
|
||||
min_value, max_value, avg_value
|
||||
);
|
||||
println!(
|
||||
" Non-zero values: {} ({:.1}%)",
|
||||
non_zero_count,
|
||||
(non_zero_count as f32 / depths.len() as f32) * 100.0
|
||||
);
|
||||
|
||||
if max_value < 0.0001 {
|
||||
println!(" ⚠️ WARNING: All values are effectively zero! Snow depth map may be invalid.");
|
||||
} else {
|
||||
if max_value < 0.0001
|
||||
{
|
||||
println!(
|
||||
" ⚠️ WARNING: All values are effectively zero! Snow depth map may be invalid."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
println!(" ✓ Snow depth data loaded successfully");
|
||||
}
|
||||
|
||||
@@ -237,96 +295,6 @@ impl SnowLayer
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_snow_mesh(
|
||||
depth_data: &[f32],
|
||||
heightmap_data: &[f32],
|
||||
width: u32,
|
||||
height: u32,
|
||||
terrain_size: Vec2,
|
||||
) -> Mesh
|
||||
{
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
let cell_size_x = terrain_size.x / (width - 1) as f32;
|
||||
let cell_size_z = terrain_size.y / (height - 1) as f32;
|
||||
let half_width = terrain_size.x / 2.0;
|
||||
let half_height = terrain_size.y / 2.0;
|
||||
|
||||
for z in 0..height
|
||||
{
|
||||
for x in 0..width
|
||||
{
|
||||
let index = (z * width + x) as usize;
|
||||
let snow_depth = depth_data.get(index).copied().unwrap_or(0.0);
|
||||
let terrain_height = heightmap_data.get(index).copied().unwrap_or(0.0);
|
||||
|
||||
let world_x = x as f32 * cell_size_x - half_width;
|
||||
let world_z = z as f32 * cell_size_z - half_height;
|
||||
let world_y = terrain_height + snow_depth;
|
||||
|
||||
vertices.push(Vertex {
|
||||
position: [world_x, world_y, world_z],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [x as f32 / width as f32, z as f32 / height as f32],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for z in 0..(height - 1)
|
||||
{
|
||||
for x in 0..(width - 1)
|
||||
{
|
||||
let index = (z * width + x) as usize;
|
||||
let depth_tl = depth_data.get(index).copied().unwrap_or(0.0);
|
||||
let depth_tr = depth_data.get(index + 1).copied().unwrap_or(0.0);
|
||||
let depth_bl = depth_data.get(index + width as usize).copied().unwrap_or(0.0);
|
||||
let depth_br = depth_data
|
||||
.get(index + width as usize + 1)
|
||||
.copied()
|
||||
.unwrap_or(0.0);
|
||||
|
||||
if depth_tl > 0.001
|
||||
|| depth_tr > 0.001
|
||||
|| depth_bl > 0.001
|
||||
|| depth_br > 0.001
|
||||
{
|
||||
let vertex_index = (z * width + x) as u32;
|
||||
|
||||
indices.push(vertex_index);
|
||||
indices.push(vertex_index + width);
|
||||
indices.push(vertex_index + 1);
|
||||
|
||||
indices.push(vertex_index + 1);
|
||||
indices.push(vertex_index + width);
|
||||
indices.push(vertex_index + width + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Snow Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
});
|
||||
|
||||
let index_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Snow Index Buffer"),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
})
|
||||
});
|
||||
|
||||
Mesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_deform_pipeline(
|
||||
depth_texture_view: &wgpu::TextureView,
|
||||
) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer)
|
||||
@@ -408,10 +376,215 @@ impl SnowLayer
|
||||
})
|
||||
}
|
||||
|
||||
fn create_displacement_bind_group(
|
||||
heightmap: &HeightmapTexture,
|
||||
depth_view: &wgpu::TextureView,
|
||||
depth_sampler: &wgpu::Sampler,
|
||||
) -> wgpu::BindGroup
|
||||
{
|
||||
render::with_device(|device| {
|
||||
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Snow Displacement Bind Group Layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Snow Displacement Bind Group"),
|
||||
layout: &layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&heightmap.view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&heightmap.sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(depth_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Sampler(depth_sampler),
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_clipmap_grid(&self, level: u32) -> Mesh
|
||||
{
|
||||
let cell_size = self.clipmap_config.base_cell_size * (1 << level) as f32;
|
||||
let grid_size = self.clipmap_config.grid_size;
|
||||
let half_extent = (grid_size as f32 * cell_size) / 2.0;
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
for z in 0..=grid_size
|
||||
{
|
||||
for x in 0..=grid_size
|
||||
{
|
||||
vertices.push(Vertex {
|
||||
position: [
|
||||
-half_extent + x as f32 * cell_size,
|
||||
0.0,
|
||||
-half_extent + z as f32 * cell_size,
|
||||
],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
uv: [0.0, 0.0],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let row = grid_size + 1;
|
||||
for z in 0..grid_size
|
||||
{
|
||||
for x in 0..grid_size
|
||||
{
|
||||
let i0 = z * row + x;
|
||||
let i1 = i0 + 1;
|
||||
let i2 = i0 + row;
|
||||
let i3 = i2 + 1;
|
||||
indices.push(i0);
|
||||
indices.push(i2);
|
||||
indices.push(i1);
|
||||
indices.push(i1);
|
||||
indices.push(i2);
|
||||
indices.push(i3);
|
||||
}
|
||||
}
|
||||
|
||||
let vertex_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Vertex Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
});
|
||||
|
||||
let index_buffer = render::with_device(|device| {
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("Snow Clipmap Level {} Index Buffer", level)),
|
||||
contents: bytemuck::cast_slice(&indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
})
|
||||
});
|
||||
|
||||
Mesh {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_indices: indices.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, center: Vec3)
|
||||
{
|
||||
if self.levels.is_empty()
|
||||
{
|
||||
for level in 0..self.clipmap_config.num_levels
|
||||
{
|
||||
let mesh = self.generate_clipmap_grid(level);
|
||||
let instance_buffer = create_instance_buffer();
|
||||
|
||||
self.levels.push(ClipmapLevel {
|
||||
mesh: Rc::new(mesh),
|
||||
instance_buffer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render::with_queue(|queue| {
|
||||
for (level, clipmap_level) in self.levels.iter().enumerate()
|
||||
{
|
||||
let cell_size = self.clipmap_config.base_cell_size * (1u32 << level) as f32;
|
||||
let snapped_x = (center.x / cell_size).round() * cell_size;
|
||||
let snapped_z = (center.z / cell_size).round() * cell_size;
|
||||
let y_bias = -(level as f32) * 0.005;
|
||||
let model =
|
||||
glam::Mat4::from_translation(glam::Vec3::new(snapped_x, y_bias, snapped_z));
|
||||
let instance_data = InstanceRaw {
|
||||
model: model.to_cols_array_2d(),
|
||||
dissolve_amount: 0.0,
|
||||
_padding: [0.0; 3],
|
||||
};
|
||||
queue.write_buffer(
|
||||
&clipmap_level.instance_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[instance_data]),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_draw_calls(&self) -> Vec<DrawCall>
|
||||
{
|
||||
self.levels
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|level| DrawCall {
|
||||
vertex_buffer: level.mesh.vertex_buffer.clone(),
|
||||
index_buffer: level.mesh.index_buffer.clone(),
|
||||
num_indices: level.mesh.num_indices,
|
||||
model: glam::Mat4::IDENTITY,
|
||||
pipeline: Pipeline::SnowClipmap,
|
||||
instance_buffer: Some(level.instance_buffer.clone()),
|
||||
num_instances: 1,
|
||||
tile_scale: 2.0,
|
||||
enable_dissolve: false,
|
||||
enable_snow_light: true,
|
||||
displacement_bind_group: Some(self.displacement_bind_group.clone()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32)
|
||||
{
|
||||
render::with_queue(|queue| {
|
||||
let params_data = [position.x, position.z, radius, depth];
|
||||
let params_data = [
|
||||
position.x,
|
||||
position.z,
|
||||
radius,
|
||||
depth,
|
||||
self.terrain_size.x,
|
||||
self.terrain_size.y,
|
||||
0.0,
|
||||
0.0,
|
||||
];
|
||||
let params_bytes: &[u8] = bytemuck::cast_slice(¶ms_data);
|
||||
|
||||
queue.write_buffer(&self.deform_params_buffer, 0, params_bytes);
|
||||
@@ -442,10 +615,4 @@ impl SnowLayer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn regenerate_mesh(&mut self, _world: &mut World, _config: &SnowConfig)
|
||||
{
|
||||
todo!("Implement regenerate_mesh with correct wgpu types for texture-to-buffer copy");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user