tree dissolve + instances

This commit is contained in:
Jonas H
2026-03-05 15:06:29 +01:00
parent 350fddc2af
commit c37a9fd5dd
7 changed files with 124 additions and 83 deletions

View File

@@ -4,7 +4,7 @@ use glam::Vec2;
use nalgebra::vector;
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::components::{MeshComponent, PhysicsComponent};
use crate::components::{MeshComponent, PhysicsComponent, TreeInstancesComponent};
use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
use crate::loaders::terrain::load_heightfield_from_exr;
@@ -78,6 +78,7 @@ impl TerrainBundle
enable_snow_light: true,
},
);
world.names.insert(entity, "Terrain".to_string());
if !physics_added
{
@@ -125,7 +126,7 @@ impl TerrainBundle
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tree Instance Buffer"),
contents: bytemuck::cast_slice(&instance_raw),
usage: wgpu::BufferUsages::VERTEX,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
})
});
@@ -135,13 +136,18 @@ impl TerrainBundle
MeshComponent {
mesh: Rc::new(mesh),
pipeline: render::Pipeline::Standard,
instance_buffer: Some(instance_buffer),
instance_buffer: Some(instance_buffer.clone()),
num_instances: num_instances as u32,
tile_scale: 4.0,
enable_dissolve: true,
enable_snow_light: false,
},
);
world.tree_instances.insert(
entity,
TreeInstancesComponent::new(instances, Rc::new(instance_buffer)),
);
world.names.insert(entity, "Trees".to_string());
}
}

View File

@@ -150,6 +150,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
enable_dissolve: false,
enable_snow_light: false,
displacement_bind_group: None,
entity: None,
});
}
});
@@ -183,6 +184,7 @@ pub fn render_collider_debug() -> Vec<DrawCall>
enable_dissolve: false,
enable_snow_light: false,
displacement_bind_group: None,
entity: None,
});
}
});

View File

@@ -2,6 +2,26 @@ use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Quat, Vec3};
use std::path::Path;
fn compute_aabb(vertices: &[Vertex]) -> (Vec3, Vec3)
{
let mut aabb_min = Vec3::splat(f32::MAX);
let mut aabb_max = Vec3::splat(f32::MIN);
for v in vertices
{
let p = Vec3::from(v.position);
aabb_min = aabb_min.min(p);
aabb_max = aabb_max.max(p);
}
if vertices.is_empty()
{
(Vec3::ZERO, Vec3::ZERO)
}
else
{
(aabb_min, aabb_max)
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Vertex
@@ -113,6 +133,10 @@ pub struct Mesh
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_indices: u32,
pub aabb_min: Vec3,
pub aabb_max: Vec3,
pub cpu_positions: Vec<[f32; 3]>,
pub cpu_indices: Vec<u32>,
}
impl Mesh
@@ -121,6 +145,8 @@ impl Mesh
{
use wgpu::util::DeviceExt;
let (aabb_min, aabb_max) = compute_aabb(vertices);
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertices),
@@ -137,6 +163,10 @@ impl Mesh
vertex_buffer,
index_buffer,
num_indices: indices.len() as u32,
aabb_min,
aabb_max,
cpu_positions: vertices.iter().map(|v| v.position).collect(),
cpu_indices: indices.to_vec(),
}
}

View File

@@ -41,6 +41,7 @@ use crate::systems::{
camera_follow_system, camera_input_system, camera_view_matrix, physics_sync_system,
player_input_system, render_system, rotate_system, snow_system, spotlight_sync_system,
start_camera_following, state_machine_physics_system, state_machine_system,
tree_dissolve_update_system, tree_instance_buffer_update_system, tree_occlusion_system,
trigger_system,
};
use crate::systems::camera::stop_camera_following;
@@ -295,6 +296,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
rotate_system(&mut world, delta);
tree_occlusion_system(&mut world);
tree_dissolve_update_system(&mut world, delta);
tree_instance_buffer_update_system(&mut world);
let spotlights = spotlight_sync_system(&world);
render::update_spotlights(spotlights);

View File

@@ -32,46 +32,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
);
output.world_normal = normalize(normal_matrix * input.normal);
var dissolve_amount = 0.0;
if uniforms.enable_dissolve == 1u {
let instance_position = vec3<f32>(
instance_model[3][0],
instance_model[3][1],
instance_model[3][2]
);
let to_player = uniforms.player_position - uniforms.camera_position;
let distance_to_player = length(to_player);
if distance_to_player > 0.01 {
let ray_dir = to_player / distance_to_player;
let ray_origin = uniforms.camera_position;
let tree_height = 16.0;
let occlusion_radius = 6.5;
let w = instance_position - ray_origin;
let projection_t = dot(w, ray_dir);
if projection_t > 0.0 && projection_t < distance_to_player {
let closest_on_ray = ray_origin + ray_dir * projection_t;
let diff = closest_on_ray - instance_position;
let height_on_trunk = clamp(diff.y, 0.0, tree_height);
let closest_on_trunk = instance_position + vec3<f32>(0.0, height_on_trunk, 0.0);
let perp_distance = length(closest_on_trunk - closest_on_ray);
if perp_distance < occlusion_radius {
let dissolve_t = pow(perp_distance / occlusion_radius, 0.5);
dissolve_amount = 1.0 - clamp(dissolve_t, 0.0, 1.0);
}
}
}
}
output.dissolve_amount = dissolve_amount;
output.dissolve_amount = input.instance_dissolve;
output.uv = input.uv;
return output;
@@ -80,11 +41,10 @@ fn vs_main(input: VertexInput) -> VertexOutput {
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if uniforms.enable_dissolve == 1u && in.dissolve_amount > 0.0 {
let screen_pos = in.clip_position.xy;
let noise_uv = fract(screen_pos / 128.0);
let noise_value = textureSampleLevel(blue_noise_texture, blue_noise_sampler, noise_uv, 0.0).r;
if noise_value < in.dissolve_amount {
let screen_pos = in.clip_position.xy / in.clip_position.w;
let noise_uv = screen_pos * 0.5 + 0.5;
let noise = textureSample(blue_noise_texture, blue_noise_sampler, noise_uv * 10.0).r;
if in.dissolve_amount > noise {
discard;
}
}

View File

@@ -505,10 +505,23 @@ impl SnowLayer
})
});
let mut aabb_min = Vec3::splat(f32::MAX);
let mut aabb_max = Vec3::splat(f32::MIN);
for v in &vertices
{
let p = Vec3::from(v.position);
aabb_min = aabb_min.min(p);
aabb_max = aabb_max.max(p);
}
Mesh {
vertex_buffer,
index_buffer,
num_indices: indices.len() as u32,
aabb_min,
aabb_max,
cpu_positions: Vec::new(),
cpu_indices: Vec::new(),
}
}
@@ -568,6 +581,7 @@ impl SnowLayer
enable_dissolve: false,
enable_snow_light: true,
displacement_bind_group: Some(self.displacement_bind_group.clone()),
entity: None,
})
.collect()
}

View File

@@ -1,15 +1,21 @@
use crate::components::DissolveComponent;
use crate::loaders::mesh::InstanceRaw;
use crate::world::World;
use bytemuck::cast_slice;
pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
{
for entity in world.dissolves.all()
for entity in world.tree_instances.all()
{
if let Some(dissolve) = world.dissolves.get_mut(entity)
if let Some(tree_instances) = world.tree_instances.get_mut(entity)
{
let diff = dissolve.target_amount - dissolve.amount;
dissolve.amount += diff * dissolve.transition_speed * delta;
dissolve.amount = dissolve.amount.clamp(0.0, 1.0);
for i in 0..tree_instances.dissolve_amounts.len()
{
let diff = tree_instances.dissolve_targets[i] - tree_instances.dissolve_amounts[i];
tree_instances.dissolve_amounts[i] +=
diff * tree_instances.transition_speed * delta;
tree_instances.dissolve_amounts[i] =
tree_instances.dissolve_amounts[i].clamp(0.0, 1.0);
}
}
}
}
@@ -35,52 +41,70 @@ pub fn tree_occlusion_system(world: &mut World)
}
let to_player_normalized = to_player.normalize();
let occlusion_radius = 10.0;
for tree_entity in world.tree_tags.all()
for tree_entity in world.tree_instances.all()
{
if let Some(tree_transform) = world.transforms.get(tree_entity)
if let Some(tree_instances) = world.tree_instances.get_mut(tree_entity)
{
let tree_pos = tree_transform.position;
let to_tree = tree_pos - camera_pos;
let distance_to_tree = to_tree.length();
if distance_to_tree < distance_to_player
for (idx, instance) in tree_instances.instances.iter().enumerate()
{
let projection = to_tree.dot(to_player_normalized);
let instance_pos = instance.position;
let to_instance = instance_pos - camera_pos;
let distance_to_instance = to_instance.length();
if projection > 0.0
if distance_to_instance < distance_to_player
{
let perpendicular_vec = to_tree - to_player_normalized * projection;
let perp_distance = perpendicular_vec.length();
let projection = to_instance.dot(to_player_normalized);
let occlusion_radius = 2.5;
if perp_distance < occlusion_radius
if projection > 0.0 && projection <= distance_to_player
{
let dissolve_amount =
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
let perpendicular_vec =
to_instance - to_player_normalized * projection;
let perp_distance = perpendicular_vec.length();
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
if perp_distance <= occlusion_radius
{
dissolve.target_amount = dissolve_amount;
let dissolve_amount =
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
tree_instances.dissolve_targets[idx] = dissolve_amount;
continue;
}
else
{
let mut dissolve = DissolveComponent::new();
dissolve.target_amount = dissolve_amount;
world.dissolves.insert(tree_entity, dissolve);
}
continue;
}
}
}
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
{
dissolve.target_amount = 0.0;
tree_instances.dissolve_targets[idx] = 0.0;
}
}
}
}
}
}
pub fn tree_instance_buffer_update_system(world: &mut World)
{
for entity in world.tree_instances.all()
{
if let Some(tree_instances) = world.tree_instances.get(entity)
{
let instance_data_vec: Vec<InstanceRaw> = tree_instances
.instances
.iter()
.enumerate()
.map(|(idx, instance)| InstanceRaw {
model: instance.to_raw().model,
dissolve_amount: tree_instances.dissolve_amounts[idx],
_padding: [0.0; 3],
})
.collect();
crate::render::with_queue(|queue| {
queue.write_buffer(
&tree_instances.instance_buffer,
0,
cast_slice(&instance_data_vec),
);
});
}
}
}