MAJOR rendering overhaul. Snow deformation, persistent light, flowmap out. Also ECS architexture overhaul

This commit is contained in:
Jonas H
2026-03-03 19:30:41 +01:00
parent f615810509
commit 08ddaa2c5d
56 changed files with 2737 additions and 3463 deletions

View File

@@ -1,7 +1,6 @@
fn main()
{
let wesl = wesl::Wesl::new("src/shaders");
wesl.build_artifact(&"package::standard".parse().unwrap(), "standard");
wesl.build_artifact(&"package::main".parse().unwrap(), "main");
wesl.build_artifact(&"package::shadow".parse().unwrap(), "shadow");
wesl.build_artifact(&"package::terrain".parse().unwrap(), "terrain");
}

25
src/bundles/camera.rs Normal file
View File

@@ -0,0 +1,25 @@
use glam::Vec3;
use crate::bundles::Bundle;
use crate::components::CameraComponent;
use crate::entity::EntityHandle;
use crate::render;
use crate::world::{Transform, World};
pub struct CameraBundle
{
pub position: Vec3,
}
impl Bundle for CameraBundle
{
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
{
let camera_entity = world.spawn();
let camera_component = CameraComponent::new(render::aspect_ratio());
let transform = Transform::from_position(self.position);
world.cameras.insert(camera_entity, camera_component);
world.transforms.insert(camera_entity, transform);
Ok(camera_entity)
}
}

12
src/bundles/mod.rs Normal file
View File

@@ -0,0 +1,12 @@
pub mod camera;
pub mod player;
pub mod spotlight;
pub mod terrain;
use crate::entity::EntityHandle;
use crate::world::World;
pub trait Bundle
{
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>;
}

207
src/bundles/player.rs Normal file
View File

@@ -0,0 +1,207 @@
use std::f32::consts::PI;
use std::rc::Rc;
use glam::Vec3;
use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::bundles::Bundle;
use crate::components::jump::JumpComponent;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::{InputComponent, MeshComponent, MovementComponent, PhysicsComponent};
use crate::entity::EntityHandle;
use crate::loaders::mesh::Mesh;
use crate::physics::PhysicsManager;
use crate::render::Pipeline;
use crate::state::StateMachine;
use crate::systems::player_states::{
PlayerFallingState, PlayerIdleState, PlayerJumpingState, PlayerWalkingState,
};
use crate::world::{Transform, World};
pub struct PlayerBundle
{
pub position: Vec3,
}
impl Bundle for PlayerBundle
{
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
{
let entity = world.spawn();
let spawn_transform = Transform::from_position(self.position);
let rigidbody = RigidBodyBuilder::kinematic_position_based()
.translation(spawn_transform.position.into())
.build();
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
let _controller = KinematicCharacterController {
slide: true,
autostep: Some(CharacterAutostep::default()),
max_slope_climb_angle: 45.0,
..Default::default()
};
let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
let mesh = Mesh::load_mesh("meshes/player_mesh.glb")
.map_err(|e| format!("missing player mesh: {}", e))?;
let falling_state = PlayerFallingState { entity };
let idle_state = PlayerIdleState { entity };
let walking_state = PlayerWalkingState {
entity,
enter_time_stamp: 0.0,
};
let jumping_state = PlayerJumpingState {
entity,
enter_time_stamp: 0.0,
};
let mut state_machine = StateMachine::new(Box::new(falling_state));
state_machine.add_state(walking_state);
state_machine.add_state(idle_state);
state_machine.add_state(jumping_state);
let entity_id = entity;
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
!is_grounded
});
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
!is_grounded
});
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<PlayerJumpingState, PlayerFallingState>(move |world| {
world
.jumps
.with(entity_id, |jump| {
jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration
})
.unwrap_or(true)
});
world.transforms.insert(entity, spawn_transform);
world.movements.insert(entity, MovementComponent::new());
world.jumps.insert(entity, JumpComponent::new());
world.inputs.insert(entity, InputComponent::default());
world.physics.insert(
entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: Pipeline::Standard,
instance_buffer: None,
num_instances: 1,
tile_scale: 4.0,
enable_dissolve: false,
enable_snow_light: false,
},
);
world.player_tags.insert(entity, ());
world.state_machines.insert(entity, state_machine);
let outer_angle = PI / 2.0 * 0.9;
world.spotlights.insert(
entity,
SpotlightComponent::new(
Vec3::new(1.0, 2.5, 1.0),
Vec3::new(0.0, -1.0, 0.0),
100.0,
outer_angle * 0.5,
outer_angle,
),
);
Ok(entity)
}
}

53
src/bundles/spotlight.rs Normal file
View File

@@ -0,0 +1,53 @@
use glam::Vec3;
use crate::bundles::Bundle;
use crate::components::RotateComponent;
use crate::entity::EntityHandle;
use crate::loaders::lights::LightData;
use crate::world::{Transform, World};
pub struct SpotlightBundle
{
pub light_data: LightData,
}
impl Bundle for SpotlightBundle
{
fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
{
let entity = world.spawn();
let transform = Transform::from_matrix(self.light_data.transform);
world.transforms.insert(entity, transform);
world.spotlights.insert(entity, self.light_data.component);
if let Some(tag) = self.light_data.tag
{
if tag == "lighthouse"
{
world
.rotates
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
}
}
Ok(entity)
}
}
pub fn spawn_spotlights(world: &mut World, spotlights: Vec<LightData>)
{
for light_data in spotlights
{
let entity = world.spawn();
let transform = Transform::from_matrix(light_data.transform);
world.transforms.insert(entity, transform);
world.spotlights.insert(entity, light_data.component);
if let Some(tag) = light_data.tag
{
if tag == "lighthouse"
{
world
.rotates
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
}
}
}
}

View File

@@ -1,22 +1,16 @@
use std::rc::Rc;
use exr::prelude::{ReadChannels, ReadLayers};
use glam::Vec2;
use nalgebra::{vector, DMatrix};
use rapier3d::{
math::Isometry,
prelude::{ColliderBuilder, RigidBodyBuilder},
};
use wesl::Wesl;
use nalgebra::vector;
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};
use crate::{
components::{MeshComponent, PhysicsComponent},
entity::EntityHandle,
mesh::{InstanceData, InstanceRaw, Mesh, Vertex},
physics::PhysicsManager,
render,
world::{Transform, World},
};
use crate::components::{MeshComponent, PhysicsComponent};
use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
use crate::loaders::terrain::load_heightfield_from_exr;
use crate::physics::PhysicsManager;
use crate::render;
use crate::world::{Transform, World};
pub struct TerrainConfig
{
@@ -46,9 +40,9 @@ impl TerrainConfig
}
}
pub struct Terrain;
pub struct TerrainBundle;
impl Terrain
impl TerrainBundle
{
pub fn spawn(
world: &mut World,
@@ -76,15 +70,18 @@ impl Terrain
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: render::Pipeline::Terrain,
pipeline: render::Pipeline::Standard,
instance_buffer: None,
num_instances: 1,
tile_scale: 2.0,
enable_dissolve: false,
enable_snow_light: true,
},
);
if !physics_added
{
let heights = Self::load_heightfield_from_exr(&config.heightmap_path)?;
let heights = load_heightfield_from_exr(&config.heightmap_path)?;
let height_scale = 1.0;
let scale = vector![config.size.x, height_scale, config.size.y];
@@ -137,9 +134,12 @@ impl Terrain
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: render::Pipeline::Environment,
pipeline: render::Pipeline::Standard,
instance_buffer: Some(instance_buffer),
num_instances: num_instances as u32,
tile_scale: 4.0,
enable_dissolve: true,
enable_snow_light: false,
},
);
}
@@ -147,94 +147,4 @@ impl Terrain
first_entity.ok_or_else(|| anyhow::anyhow!("No meshes found in glTF file"))
}
fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
{
let image = exr::prelude::read()
.no_deep_data()
.largest_resolution_level()
.all_channels()
.all_layers()
.all_attributes()
.from_file(path)?;
let layer = &image.layer_data[0];
let channel = &layer.channel_data.list[0];
let width = layer.size.width();
let height = layer.size.height();
let heights: Vec<f32> = channel.sample_data.values_as_f32().collect();
Ok(DMatrix::from_row_slice(height, width, &heights))
}
}
pub fn create_terrain_render_pipeline(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::terrain".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Terrain Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Terrain Render Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Terrain Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}

View File

@@ -1,51 +0,0 @@
use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Vec3};
use crate::components::CameraComponent;
use crate::entity::EntityHandle;
use crate::render;
use crate::world::{Transform, World};
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct CameraUniforms
{
pub model: [[f32; 4]; 4],
pub view: [[f32; 4]; 4],
pub projection: [[f32; 4]; 4],
pub light_direction: [f32; 3],
pub _padding: f32,
}
impl CameraUniforms
{
pub fn new(model: Mat4, view: Mat4, projection: Mat4, light_direction: Vec3) -> Self
{
Self {
model: model.to_cols_array_2d(),
view: view.to_cols_array_2d(),
projection: projection.to_cols_array_2d(),
light_direction: light_direction.to_array(),
_padding: 0.0,
}
}
}
pub struct Camera;
impl Camera
{
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
{
let camera_entity = world.spawn();
let camera_component = CameraComponent::new(render::aspect_ratio());
let transform = Transform::from_position(position);
world.cameras.insert(camera_entity, camera_component);
world.transforms.insert(camera_entity, transform);
camera_entity
}
}

View File

@@ -1,18 +0,0 @@
use glam::Vec3;
pub struct DirectionallightComponent
{
pub offset: Vec3,
pub direction: Vec3,
}
impl DirectionallightComponent
{
pub fn new(offset: Vec3, direction: Vec3) -> Self
{
Self {
offset,
direction: direction.normalize(),
}
}
}

View File

@@ -1,3 +1 @@
pub mod directional;
pub mod point;
pub mod spot;

View File

@@ -1,14 +0,0 @@
use glam::Vec3;
pub struct PointlightComponent
{
pub offset: Vec3,
}
impl PointlightComponent
{
pub fn new(offset: Vec3) -> Self
{
Self { offset }
}
}

View File

@@ -12,7 +12,13 @@ pub struct SpotlightComponent
impl SpotlightComponent
{
pub fn new(offset: Vec3, direction: Vec3, range: f32, inner_angle: f32, outer_angle: f32) -> Self
pub fn new(
offset: Vec3,
direction: Vec3,
range: f32,
inner_angle: f32,
outer_angle: f32,
) -> Self
{
Self {
offset,

View File

@@ -1,6 +1,6 @@
use std::rc::Rc;
use crate::mesh::Mesh;
use crate::loaders::mesh::Mesh;
use crate::render::Pipeline;
#[derive(Clone)]
@@ -10,4 +10,7 @@ pub struct MeshComponent
pub pipeline: Pipeline,
pub instance_buffer: Option<wgpu::Buffer>,
pub num_instances: u32,
pub tile_scale: f32,
pub enable_dissolve: bool,
pub enable_snow_light: bool,
}

View File

@@ -6,11 +6,9 @@ pub mod jump;
pub mod lights;
pub mod mesh;
pub mod movement;
pub mod noclip;
pub mod physics;
pub mod player_tag;
pub mod rotate;
pub mod state_machine;
pub mod tree_tag;
pub use camera::CameraComponent;
pub use dissolve::DissolveComponent;

1
src/components/noclip.rs Normal file
View File

@@ -0,0 +1 @@
pub struct NoclipTag;

View File

@@ -1,2 +0,0 @@
#[derive(Copy, Clone, Debug)]
pub struct PlayerTag;

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@
pub struct TreeTag;

View File

@@ -6,7 +6,7 @@ use nalgebra::DMatrix;
use rapier3d::parry::shape::HeightField;
use crate::{
mesh::{InstanceRaw, Mesh, Vertex},
loaders::mesh::{InstanceRaw, Mesh, Vertex},
physics::PhysicsManager,
render::{self, DrawCall, Pipeline},
};
@@ -143,9 +143,13 @@ pub fn render_collider_debug() -> Vec<DrawCall>
index_buffer: wireframe_box.index_buffer.clone(),
num_indices: wireframe_box.num_indices,
model,
pipeline: Pipeline::Wireframe,
pipeline: Pipeline::Standard,
instance_buffer: Some(instance_buffer),
num_instances: 1,
tile_scale: 4.0,
enable_dissolve: false,
enable_snow_light: false,
displacement_bind_group: None,
});
}
});
@@ -172,9 +176,13 @@ pub fn render_collider_debug() -> Vec<DrawCall>
index_buffer: heightfield_mesh.index_buffer.clone(),
num_indices: heightfield_mesh.num_indices,
model: Mat4::IDENTITY,
pipeline: Pipeline::Wireframe,
pipeline: Pipeline::Standard,
instance_buffer: Some(instance_buffer),
num_instances: 1,
tile_scale: 4.0,
enable_dissolve: false,
enable_snow_light: false,
displacement_bind_group: None,
});
}
});

View File

@@ -1,3 +1 @@
pub mod collider_debug;
pub use collider_debug::render_collider_debug;

View File

@@ -1,59 +0,0 @@
use glam::Vec3;
use crate::camera::Camera;
use crate::utility::input::InputState;
use crate::world::World;
pub fn update_noclip_camera(camera: &mut Camera, input_state: &InputState, delta: f32)
{
camera.update_rotation(input_state.mouse_delta, 0.0008);
let mut input_vec = Vec3::ZERO;
if input_state.w
{
input_vec.z += 1.0;
}
if input_state.s
{
input_vec.z -= 1.0;
}
if input_state.d
{
input_vec.x += 1.0;
}
if input_state.a
{
input_vec.x -= 1.0;
}
if input_state.space
{
input_vec.y += 1.0;
}
if input_vec.length_squared() > 0.0
{
input_vec = input_vec.normalize();
}
let mut speed = 10.0 * delta;
if input_state.shift
{
speed *= 2.0;
}
camera.update_noclip(input_vec, speed);
}
pub fn update_follow_camera(camera: &mut Camera, world: &World, input_state: &InputState)
{
let player_entities = world.player_tags.all();
if let Some(&player_entity) = player_entities.first()
{
if let Some(player_transform) = world.transforms.get(player_entity)
{
camera.update_follow(player_transform.position, input_state.mouse_delta, 0.0008);
}
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use crate::entity::EntityHandle;
use crate::mesh::Mesh;
use crate::loaders::mesh::Mesh;
use crate::render::{DrawCall, Pipeline};
pub type DrawHandle = usize;

View File

@@ -80,10 +80,7 @@ impl Empties
Self::load_gltf_empties(path)
}
pub fn get_empty_by_name(
gltf_path: &str,
name: &str,
) -> anyhow::Result<Option<crate::empty::EmptyNode>>
pub fn get_empty_by_name(gltf_path: &str, name: &str) -> anyhow::Result<Option<EmptyNode>>
{
let empties = Self::load_empties(gltf_path)
.map_err(|e| anyhow::anyhow!("Failed to load empty nodes: {}", e))?;

View File

@@ -1,11 +1,7 @@
use glam::{Mat4, Vec3};
use gltf::json::Extras;
use std::{ops::Deref, path::Path};
use std::path::Path;
use crate::{
components::lights::spot::SpotlightComponent,
world::{Transform, World},
};
use crate::components::lights::spot::SpotlightComponent;
pub struct LightData
{
@@ -104,7 +100,7 @@ impl Lights
transform: global_transform,
tag,
});
},
}
}
}
@@ -126,28 +122,4 @@ impl Lights
{
crate::render::with_device(|_device| Lights::load_gltf_lights(path))
}
pub fn spawn_lights(world: &mut World, spotlights: Vec<LightData>)
{
use crate::components::RotateComponent;
for light_data in spotlights
{
let entity = world.spawn();
let transform = Transform::from_matrix(light_data.transform);
world.transforms.insert(entity, transform);
world.spotlights.insert(entity, light_data.component);
if let Some(tag) = light_data.tag
{
if tag == "lighthouse"
{
world
.rotates
.insert(entity, RotateComponent::new(Vec3::Y, 1.0));
}
}
}
}
}

View File

@@ -1,9 +1,6 @@
use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Quat, Vec3};
use std::path::Path;
use std::rc::Rc;
use crate::utility::transform::Transform;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]

6
src/loaders/mod.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod empty;
pub mod heightmap;
pub mod lights;
pub mod mesh;
pub mod scene;
pub mod terrain;

View File

@@ -1,12 +1,10 @@
use anyhow::Result;
use glam::Vec3;
use crate::{
empty::Empties,
light::{LightData, Lights},
mesh::{InstanceData, Mesh},
render,
};
use crate::loaders::empty::Empties;
use crate::loaders::lights::{LightData, Lights};
use crate::loaders::mesh::{InstanceData, Mesh};
use crate::render;
pub const CAMERA_SPAWN_OFFSET: Vec3 = Vec3::new(15.0, 15.0, 15.0);
@@ -21,9 +19,8 @@ impl Space
{
pub fn load_space(gltf_path: &str) -> Result<Space>
{
let mesh_data = render::with_device(|device| {
Mesh::load_gltf_with_instances(device, gltf_path)
})?;
let mesh_data =
render::with_device(|device| Mesh::load_gltf_with_instances(device, gltf_path))?;
let lights = Lights::load_lights(gltf_path)
.map_err(|e| anyhow::anyhow!("Failed to load lights: {}", e))?;
@@ -45,7 +42,8 @@ impl Space
if let Some(empty_node) = empty
{
let (_scale, _rotation, translation) = empty_node.transform.to_scale_rotation_translation();
let (_scale, _rotation, translation) =
empty_node.transform.to_scale_rotation_translation();
Ok(translation)
}
else

23
src/loaders/terrain.rs Normal file
View File

@@ -0,0 +1,23 @@
use exr::prelude::{ReadChannels, ReadLayers};
use nalgebra::DMatrix;
pub fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
{
let image = exr::prelude::read()
.no_deep_data()
.largest_resolution_level()
.all_channels()
.all_layers()
.all_attributes()
.from_file(path)?;
let layer = &image.layer_data[0];
let channel = &layer.channel_data.list[0];
let width = layer.size.width();
let height = layer.size.height();
let heights: Vec<f32> = channel.sample_data.values_as_f32().collect();
Ok(DMatrix::from_row_slice(height, width, &heights))
}

View File

@@ -1,24 +1,17 @@
mod camera;
mod bundles;
mod components;
mod debug;
mod draw;
mod empty;
mod entity;
mod event;
mod heightmap;
mod light;
mod mesh;
mod loaders;
mod physics;
mod player;
mod postprocess;
mod render;
mod shader;
mod snow;
mod snow_light;
mod space;
mod state;
mod systems;
mod terrain;
mod texture;
mod utility;
mod world;
@@ -28,24 +21,22 @@ use std::time::{Duration, Instant};
use glam::Vec3;
use render::Renderer;
use utility::input::InputState;
use world::{Transform, World};
use world::World;
use crate::camera::Camera;
use crate::components::CameraComponent;
use crate::debug::render_collider_debug;
use crate::entity::EntityHandle;
use crate::light::Lights;
use crate::bundles::camera::CameraBundle;
use crate::bundles::player::PlayerBundle;
use crate::bundles::spotlight::spawn_spotlights;
use crate::bundles::terrain::{TerrainBundle, TerrainConfig};
use crate::bundles::Bundle;
use crate::loaders::scene::Space;
use crate::physics::PhysicsManager;
use crate::player::Player;
use crate::space::Space;
use crate::systems::{
camera_follow_system, camera_input_system, camera_noclip_system, physics_sync_system,
player_input_system, render_system, rotate_system, spotlight_sync_system,
start_camera_following, state_machine_physics_system, state_machine_system,
stop_camera_following,
};
use crate::snow::{SnowConfig, SnowLayer};
use crate::terrain::{Terrain, TerrainConfig};
use crate::systems::{
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
noclip_toggle_system, 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,
};
use crate::utility::time::Time;
fn main() -> Result<(), Box<dyn std::error::Error>>
@@ -68,11 +59,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
let player_spawn = space.player_spawn;
let camera_spawn = space.camera_spawn_position();
let tree_positions: Vec<Vec3> = space
.mesh_data
.iter()
.flat_map(|(_, instances)| instances.iter().map(|inst| inst.position))
.collect();
let mut world = World::new();
let _player_entity = Player::spawn(&mut world, player_spawn);
let _terrain_entity = Terrain::spawn(&mut world, space.mesh_data, &terrain_config)?;
Lights::spawn_lights(&mut world, space.spotlights);
let _player_entity = PlayerBundle {
position: player_spawn,
}
.spawn(&mut world)
.unwrap();
let _terrain_entity = TerrainBundle::spawn(&mut world, space.mesh_data, &terrain_config)?;
spawn_spotlights(&mut world, space.spotlights);
render::set_terrain_data();
@@ -83,15 +84,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
);
let snow_config = SnowConfig::default();
let snow_layer = SnowLayer::load(&mut world, &snow_config)?;
let mut snow_layer = SnowLayer::load(&snow_config)?;
for pos in &tree_positions
{
snow_layer.deform_at_position(*pos, 5.0, 50.0);
}
println!("Snow layer loaded successfully");
render::set_snow_depth(&snow_layer.depth_texture_view);
let mut noclip_mode = true;
let camera_entity = Camera::spawn(&mut world, camera_spawn);
if noclip_mode == false
let camera_entity = CameraBundle {
position: camera_spawn,
}
.spawn(&mut world)
.unwrap();
if !noclip_mode
{
start_camera_following(&mut world, camera_entity);
}
@@ -135,19 +144,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
input_state.process_post_events();
if input_state.noclip_just_pressed
{
noclip_mode = !noclip_mode;
if noclip_mode
{
stop_camera_following(&mut world, camera_entity);
}
else
{
start_camera_following(&mut world, camera_entity);
}
}
noclip_toggle_system(&mut world, &input_state, camera_entity, &mut noclip_mode);
camera_input_system(&mut world, &input_state);
@@ -181,34 +178,31 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
let spotlights = spotlight_sync_system(&world);
render::update_spotlights(spotlights);
let mut draw_calls = render_system(&world);
draw_calls.extend(render_collider_debug());
snow_system(&world, &mut snow_layer, noclip_mode);
if let Some((camera_entity, camera_component)) = world.cameras.get_active()
let mut draw_calls = render_system(&world);
draw_calls.extend(snow_layer.get_draw_calls());
if let Some((camera_entity, camera_component)) = world.active_camera()
{
if let Some(camera_transform) = world.transforms.get(camera_entity)
{
let view =
get_view_matrix(&world, camera_entity, camera_transform, camera_component);
let projection = camera_component.projection_matrix();
let player_pos = world.player_position();
let player_pos = world
.player_tags
.all()
.first()
.and_then(|e| world.transforms.get(*e))
.map(|t| t.position)
.unwrap_or(Vec3::ZERO);
if let Some(view) = camera_view_matrix(&world)
{
let projection = camera_component.projection_matrix();
render::render(
&view,
&projection,
camera_transform.position,
player_pos,
&draw_calls,
time,
delta,
);
render::render(
&view,
&projection,
camera_transform.position,
player_pos,
&draw_calls,
time,
delta,
);
}
}
}
@@ -223,27 +217,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
Ok(())
}
fn get_view_matrix(
world: &World,
camera_entity: EntityHandle,
camera_transform: &Transform,
camera_component: &CameraComponent,
) -> glam::Mat4
{
if let Some(follow) = world.follows.get(camera_entity)
{
if let Some(target_transform) = world.transforms.get(follow.target)
{
return glam::Mat4::look_at_rh(
camera_transform.position,
target_transform.position,
Vec3::Y,
);
}
}
let forward = camera_component.get_forward();
let target = camera_transform.position + forward;
glam::Mat4::look_at_rh(camera_transform.position, target, Vec3::Y)
}

File diff suppressed because it is too large Load Diff

113
src/render/bind_group.rs Normal file
View File

@@ -0,0 +1,113 @@
use crate::texture::{DitherTextures, FlowmapTexture};
use super::Renderer;
impl Renderer
{
pub(super) fn create_bind_group_from_parts(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
uniform_buffer: &wgpu::Buffer,
shadow_map_view: &wgpu::TextureView,
shadow_map_sampler: &wgpu::Sampler,
dither_textures: &DitherTextures,
flowmap_texture: &FlowmapTexture,
blue_noise_view: &wgpu::TextureView,
blue_noise_sampler: &wgpu::Sampler,
snow_light_view: &wgpu::TextureView,
snow_light_sampler: &wgpu::Sampler,
) -> wgpu::BindGroup
{
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Bind Group"),
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(shadow_map_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(shadow_map_sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&dither_textures.view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Sampler(&dither_textures.sampler),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&flowmap_texture.view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&flowmap_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::TextureView(blue_noise_view),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::Sampler(blue_noise_sampler),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::TextureView(snow_light_view),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::Sampler(snow_light_sampler),
},
],
})
}
pub(super) fn rebuild_bind_group_with_snow_light(&mut self)
{
let dither_textures = self
.dither_textures
.as_ref()
.expect("Dither textures required");
let flowmap_texture = self
.flowmap_texture
.as_ref()
.expect("Flowmap texture required");
let snow_light_sampler = self.device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Snow Light Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let snow_light_view = self
.snow_light_accumulation
.as_ref()
.map(|s| s.read_view())
.unwrap_or(&self.dummy_snow_light_view);
self.bind_group = Self::create_bind_group_from_parts(
&self.device,
&self.bind_group_layout,
&self.uniform_buffer,
&self.shadow_map_view,
&self.shadow_map_sampler,
dither_textures,
flowmap_texture,
&self.blue_noise_view,
&self.blue_noise_sampler,
snow_light_view,
&snow_light_sampler,
);
}
}

885
src/render/mod.rs Normal file
View File

@@ -0,0 +1,885 @@
mod bind_group;
mod pipeline;
mod shadow;
mod types;
pub use types::{DrawCall, Pipeline, Spotlight, SpotlightRaw, Uniforms, MAX_SPOTLIGHTS};
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
use crate::texture::{DitherTextures, FlowmapTexture};
use std::cell::RefCell;
use pipeline::{create_main_pipeline, create_snow_clipmap_pipeline};
pub struct Renderer
{
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub surface: wgpu::Surface<'static>,
pub config: wgpu::SurfaceConfiguration,
framebuffer: LowResFramebuffer,
standard_pipeline: wgpu::RenderPipeline,
snow_clipmap_pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
quad_vb: wgpu::Buffer,
quad_ib: wgpu::Buffer,
quad_num_indices: u32,
blit_pipeline: wgpu::RenderPipeline,
blit_bind_group: wgpu::BindGroup,
shadow_pipeline: Option<wgpu::RenderPipeline>,
shadow_bind_group_layout: wgpu::BindGroupLayout,
shadow_bind_group: Option<wgpu::BindGroup>,
terrain_height_scale: f32,
pub spotlights: Vec<Spotlight>,
pub shadow_bias: f32,
shadow_map_texture: wgpu::Texture,
shadow_map_view: wgpu::TextureView,
shadow_map_sampler: wgpu::Sampler,
dither_textures: Option<DitherTextures>,
flowmap_texture: Option<FlowmapTexture>,
blue_noise_view: wgpu::TextureView,
blue_noise_sampler: wgpu::Sampler,
dummy_snow_light_view: wgpu::TextureView,
dummy_snow_light_sampler: wgpu::Sampler,
snow_light_accumulation: Option<crate::snow_light::SnowLightAccumulation>,
snow_light_bound: bool,
}
impl Renderer
{
pub async fn new(
window: &sdl3::video::Window,
render_scale: u32,
) -> Result<Self, Box<dyn std::error::Error>>
{
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::VULKAN,
..Default::default()
});
let surface = unsafe {
let target = wgpu::SurfaceTargetUnsafe::from_window(window)?;
instance.create_surface_unsafe(target)?
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.map_err(|_| "Failed to find adapter")?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor::default())
.await?;
let size = window.size();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface.get_capabilities(&adapter).formats[0],
width: size.0,
height: size.1,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
let low_res_width = config.width / render_scale;
let low_res_height = config.height / render_scale;
let framebuffer =
LowResFramebuffer::new(&device, low_res_width, low_res_height, config.format);
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"),
size: std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let dither_textures = match DitherTextures::load_octaves(&device, &queue)
{
Ok(textures) =>
{
println!("Loaded dither textures successfully");
Some(textures)
}
Err(e) =>
{
eprintln!(
"Warning: Could not load dither textures: {}. Rendering may look incorrect.",
e
);
None
}
};
let flowmap_texture =
match FlowmapTexture::load(&device, &queue, "textures/terrain_flowmap.exr")
{
Ok(texture) =>
{
println!("Loaded terrain flowmap successfully");
Some(texture)
}
Err(e) =>
{
eprintln!(
"Warning: Could not load terrain flowmap: {}. Path lighting will not work.",
e
);
None
}
};
let blue_noise_data = image::open("textures/blue_noise.png")
.expect("Failed to load blue noise texture")
.to_luma8();
let blue_noise_size = blue_noise_data.dimensions();
let blue_noise_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Blue Noise"),
size: wgpu::Extent3d {
width: blue_noise_size.0,
height: blue_noise_size.1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &blue_noise_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&blue_noise_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(blue_noise_size.0),
rows_per_image: Some(blue_noise_size.1),
},
wgpu::Extent3d {
width: blue_noise_size.0,
height: blue_noise_size.1,
depth_or_array_layers: 1,
},
);
let blue_noise_view =
blue_noise_texture.create_view(&wgpu::TextureViewDescriptor::default());
let blue_noise_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Blue Noise Sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let shadow_map_size = 4096;
let shadow_map_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Shadow Map"),
size: wgpu::Extent3d {
width: shadow_map_size,
height: shadow_map_size,
depth_or_array_layers: MAX_SPOTLIGHTS as u32,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
let shadow_map_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Shadow Map Sampler"),
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,
compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default()
});
let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Dummy Snow Light"),
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &dummy_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[0u8; 2],
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(2),
rows_per_image: Some(1),
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
let dummy_snow_light_view =
dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
let dummy_snow_light_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Snow Light Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::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::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 4,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: 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: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 7,
visibility: 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: 8,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 9,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 10,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let bind_group = Self::create_bind_group_from_parts(
&device,
&bind_group_layout,
&uniform_buffer,
&shadow_map_view,
&shadow_map_sampler,
dither_textures.as_ref().expect("Dither textures required"),
flowmap_texture.as_ref().expect("Flowmap texture required"),
&blue_noise_view,
&blue_noise_sampler,
&dummy_snow_light_view,
&dummy_snow_light_sampler,
);
let standard_pipeline = create_main_pipeline(
&device,
config.format,
&bind_group_layout,
wgpu::DepthBiasState::default(),
"Standard Pipeline",
);
let snow_clipmap_pipeline =
create_snow_clipmap_pipeline(&device, config.format, &bind_group_layout);
let (quad_vb, quad_ib, quad_num_indices) = create_fullscreen_quad(&device);
let blit_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Blit Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let blit_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Blit Bind Group"),
layout: &blit_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&framebuffer.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&framebuffer.sampler),
},
],
});
let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout);
let shadow_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Shadow Bind Group Layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
Ok(Self {
device,
queue,
surface,
config,
framebuffer,
standard_pipeline,
snow_clipmap_pipeline,
uniform_buffer,
bind_group_layout,
bind_group,
quad_vb,
quad_ib,
quad_num_indices,
blit_pipeline,
blit_bind_group,
shadow_pipeline: None,
shadow_bind_group_layout,
shadow_bind_group: None,
terrain_height_scale: 10.0,
spotlights: vec![Spotlight::new(
glam::Vec3::new(0.0, 50.0, 0.0),
glam::Vec3::new(-0.5, -1.0, 1.0).normalize(),
0.4,
1.0,
100.0,
)],
shadow_bias: 0.005,
shadow_map_texture,
shadow_map_view,
shadow_map_sampler,
dither_textures,
flowmap_texture,
blue_noise_view,
blue_noise_sampler,
dummy_snow_light_view,
dummy_snow_light_sampler,
snow_light_accumulation: None,
snow_light_bound: false,
})
}
pub fn render(
&mut self,
view: &glam::Mat4,
projection: &glam::Mat4,
camera_position: glam::Vec3,
player_position: glam::Vec3,
draw_calls: &[DrawCall],
time: f32,
delta_time: f32,
)
{
let light_view_projections = self.calculate_light_view_projections();
if let Some(ref mut snow_light_accum) = self.snow_light_accumulation
{
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Snow Light Accumulation Encoder"),
});
snow_light_accum.render(
&mut encoder,
&self.queue,
&self.spotlights,
delta_time,
&light_view_projections,
self.shadow_bias,
self.terrain_height_scale,
);
self.queue.submit(std::iter::once(encoder.finish()));
}
if self.snow_light_accumulation.is_some() && !self.snow_light_bound
{
self.rebuild_bind_group_with_snow_light();
self.snow_light_bound = true;
}
self.render_shadow_pass(draw_calls, &light_view_projections, player_position, time);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
for (i, draw_call) in draw_calls.iter().enumerate()
{
let uniforms = Uniforms::new(
draw_call.model,
*view,
*projection,
&light_view_projections,
camera_position,
player_position,
self.terrain_height_scale,
time,
self.shadow_bias,
draw_call.tile_scale,
draw_call.enable_dissolve,
draw_call.enable_snow_light,
&self.spotlights,
);
self.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
{
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("3D Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.framebuffer.view,
resolve_target: None,
ops: wgpu::Operations {
load: if i == 0
{
wgpu::LoadOp::Clear(wgpu::Color {
r: 0.2,
g: 0.2,
b: 0.2,
a: 1.0,
})
}
else
{
wgpu::LoadOp::Load
},
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.framebuffer.depth_view,
depth_ops: Some(wgpu::Operations {
load: if i == 0
{
wgpu::LoadOp::Clear(1.0)
}
else
{
wgpu::LoadOp::Load
},
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
let pipeline = match draw_call.pipeline
{
Pipeline::Standard => &self.standard_pipeline,
Pipeline::SnowClipmap => &self.snow_clipmap_pipeline,
};
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, &self.bind_group, &[]);
if let Some(ref displacement_bind_group) = draw_call.displacement_bind_group
{
render_pass.set_bind_group(1, displacement_bind_group, &[]);
}
render_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
if let Some(ref instance_buffer) = draw_call.instance_buffer
{
render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
}
render_pass
.set_index_buffer(draw_call.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..draw_call.num_instances);
}
}
self.queue.submit(std::iter::once(encoder.finish()));
let frame = match self.surface.get_current_texture()
{
Ok(frame) => frame,
Err(_) =>
{
self.surface.configure(&self.device, &self.config);
self.surface
.get_current_texture()
.expect("Failed to acquire next surface texture")
}
};
let screen_view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut blit_encoder =
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Blit Encoder"),
});
{
let mut blit_pass = blit_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Blit Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &screen_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
blit_pass.set_pipeline(&self.blit_pipeline);
blit_pass.set_bind_group(0, &self.blit_bind_group, &[]);
blit_pass.set_vertex_buffer(0, self.quad_vb.slice(..));
blit_pass.set_index_buffer(self.quad_ib.slice(..), wgpu::IndexFormat::Uint16);
blit_pass.draw_indexed(0..self.quad_num_indices, 0, 0..1);
}
self.queue.submit(std::iter::once(blit_encoder.finish()));
frame.present();
}
pub fn render_scale(&self) -> (u32, u32)
{
(
self.config.width / self.framebuffer.width,
self.config.height / self.framebuffer.height,
)
}
pub fn set_terrain_data(&mut self)
{
let shadow_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Shadow Bind Group"),
layout: &self.shadow_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: self.uniform_buffer.as_entire_binding(),
}],
});
let shadow_pipeline =
pipeline::create_shadow_pipeline(&self.device, &self.shadow_bind_group_layout);
self.shadow_bind_group = Some(shadow_bind_group);
self.shadow_pipeline = Some(shadow_pipeline);
self.terrain_height_scale = 1.0;
}
pub fn init_snow_light_accumulation(&mut self, terrain_min: glam::Vec2, terrain_max: glam::Vec2)
{
let snow_light_accumulation = crate::snow_light::SnowLightAccumulation::new(
&self.device,
terrain_min,
terrain_max,
512,
);
self.snow_light_accumulation = Some(snow_light_accumulation);
}
pub fn set_snow_depth(&mut self, snow_depth_view: &wgpu::TextureView)
{
println!("set_snow_depth() called");
if let Some(ref mut snow_light_accum) = self.snow_light_accumulation
{
println!("Snow light accumulation exists, setting up bind groups");
let snow_depth_sampler = self.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()
});
match crate::texture::HeightmapTexture::load(
&self.device,
&self.queue,
"textures/terrain_heightmap.exr",
)
{
Ok(heightmap) =>
{
snow_light_accum.set_heightmap(
&self.device,
&heightmap.view,
&heightmap.sampler,
&self.shadow_map_view,
&self.shadow_map_sampler,
snow_depth_view,
&snow_depth_sampler,
);
}
Err(e) =>
{
eprintln!("Failed to load heightmap for snow depth setup: {}", e);
}
}
}
}
pub fn aspect_ratio(&self) -> f32
{
self.config.width as f32 / self.config.height as f32
}
}
thread_local! {
static GLOBAL_RENDERER: RefCell<Option<Renderer>> = RefCell::new(None);
}
pub fn init(renderer: Renderer)
{
GLOBAL_RENDERER.with(|r| *r.borrow_mut() = Some(renderer));
}
pub fn with_device<F, R>(f: F) -> R
where
F: FnOnce(&wgpu::Device) -> R,
{
GLOBAL_RENDERER.with(|r| {
let renderer = r.borrow();
let renderer = renderer.as_ref().expect("Renderer not set");
f(&renderer.device)
})
}
pub fn with_queue<F, R>(f: F) -> R
where
F: FnOnce(&wgpu::Queue) -> R,
{
GLOBAL_RENDERER.with(|r| {
let renderer = r.borrow();
let renderer = renderer.as_ref().expect("Renderer not set");
f(&renderer.queue)
})
}
pub fn set_terrain_data()
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.set_terrain_data();
});
}
pub fn init_snow_light_accumulation(terrain_min: glam::Vec2, terrain_max: glam::Vec2)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.init_snow_light_accumulation(terrain_min, terrain_max);
});
}
pub fn set_snow_depth(snow_depth_view: &wgpu::TextureView)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.set_snow_depth(snow_depth_view);
});
}
pub fn aspect_ratio() -> f32
{
GLOBAL_RENDERER.with(|r| {
let renderer = r.borrow();
let renderer = renderer.as_ref().expect("Renderer not set");
renderer.aspect_ratio()
})
}
pub fn render(
view: &glam::Mat4,
projection: &glam::Mat4,
camera_position: glam::Vec3,
player_position: glam::Vec3,
draw_calls: &[DrawCall],
time: f32,
delta_time: f32,
)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.render(
view,
projection,
camera_position,
player_position,
draw_calls,
time,
delta_time,
);
});
}
pub fn set_shadow_bias(bias: f32)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.shadow_bias = bias;
});
}
pub fn update_spotlights(spotlights: Vec<Spotlight>)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.spotlights = spotlights;
});
}

252
src/render/pipeline.rs Normal file
View File

@@ -0,0 +1,252 @@
use wesl::Wesl;
pub fn create_shadow_pipeline(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::shadow".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shadow Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Shadow Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Shadow Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
crate::loaders::mesh::Vertex::desc(),
crate::loaders::mesh::InstanceRaw::desc(),
],
compilation_options: Default::default(),
},
fragment: None,
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}
pub fn create_main_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
bind_group_layout: &wgpu::BindGroupLayout,
depth_bias: wgpu::DepthBiasState,
label: &str,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::main".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Main Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
crate::loaders::mesh::Vertex::desc(),
crate::loaders::mesh::InstanceRaw::desc(),
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: depth_bias,
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}
pub fn create_snow_clipmap_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
main_bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::main".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Snow Clipmap Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let displacement_bind_group_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,
},
],
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Snow Clipmap Pipeline Layout"),
bind_group_layouts: &[main_bind_group_layout, &displacement_bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Snow Clipmap Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_snow_main"),
buffers: &[
crate::loaders::mesh::Vertex::desc(),
crate::loaders::mesh::InstanceRaw::desc(),
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState {
constant: -1,
slope_scale: -1.0,
clamp: 0.0,
},
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}

139
src/render/shadow.rs Normal file
View File

@@ -0,0 +1,139 @@
use glam::Mat4;
use super::types::{DrawCall, Uniforms, MAX_SPOTLIGHTS};
use super::Renderer;
impl Renderer
{
pub(super) fn render_shadow_pass(
&mut self,
draw_calls: &[DrawCall],
light_view_projections: &[Mat4],
player_position: glam::Vec3,
time: f32,
)
{
let spotlight_count = self.spotlights.len().min(MAX_SPOTLIGHTS);
for layer in 0..spotlight_count
{
let shadow_uniforms = Uniforms::new(
Mat4::IDENTITY,
Mat4::IDENTITY,
light_view_projections[layer],
light_view_projections,
glam::Vec3::ZERO,
player_position,
self.terrain_height_scale,
time,
self.shadow_bias,
2.0,
false,
false,
&self.spotlights,
);
self.queue.write_buffer(
&self.uniform_buffer,
0,
bytemuck::cast_slice(&[shadow_uniforms]),
);
let layer_view = self
.shadow_map_texture
.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D2),
base_array_layer: layer as u32,
array_layer_count: Some(1),
..Default::default()
});
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Shadow Pass Encoder"),
});
{
let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Shadow Pass"),
color_attachments: &[],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &layer_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
shadow_pass.set_pipeline(
self.shadow_pipeline
.as_ref()
.expect("shadow pipeline missing"),
);
shadow_pass.set_bind_group(
0,
self.shadow_bind_group
.as_ref()
.expect("shadow bind group missing"),
&[],
);
for draw_call in draw_calls.iter()
{
shadow_pass.set_vertex_buffer(0, draw_call.vertex_buffer.slice(..));
if let Some(ref instance_buffer) = draw_call.instance_buffer
{
shadow_pass.set_vertex_buffer(1, instance_buffer.slice(..));
}
shadow_pass.set_index_buffer(
draw_call.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
shadow_pass.draw_indexed(
0..draw_call.num_indices,
0,
0..draw_call.num_instances,
);
}
}
self.queue.submit(std::iter::once(encoder.finish()));
}
}
pub(super) fn calculate_light_view_projections(&self) -> Vec<Mat4>
{
self.spotlights
.iter()
.take(MAX_SPOTLIGHTS)
.map(|spotlight| {
let light_position = spotlight.position;
let light_target = spotlight.position + spotlight.direction;
let up_vector = if spotlight.direction.y.abs() > 0.99
{
glam::Vec3::Z
}
else
{
glam::Vec3::Y
};
let light_view = Mat4::look_at_rh(light_position, light_target, up_vector);
let fov = spotlight.outer_angle * 2.0;
let near = 0.1;
let far = 150.0;
let light_projection = Mat4::perspective_rh(fov, 1.0, near, far);
light_projection * light_view
})
.collect()
}
}

157
src/render/types.rs Normal file
View File

@@ -0,0 +1,157 @@
use bytemuck::{Pod, Zeroable};
use glam::Mat4;
pub const MAX_SPOTLIGHTS: usize = 4;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable, Default)]
pub struct SpotlightRaw
{
pub position: [f32; 3],
pub inner_angle: f32,
pub direction: [f32; 3],
pub outer_angle: f32,
pub range: f32,
pub _padding: [f32; 7],
}
pub struct Spotlight
{
pub position: glam::Vec3,
pub direction: glam::Vec3,
pub inner_angle: f32,
pub outer_angle: f32,
pub range: f32,
}
impl Spotlight
{
pub fn new(
position: glam::Vec3,
direction: glam::Vec3,
inner_angle: f32,
outer_angle: f32,
range: f32,
) -> Self
{
Self {
position,
direction: direction.normalize(),
inner_angle,
outer_angle,
range,
}
}
pub fn to_raw(&self) -> SpotlightRaw
{
SpotlightRaw {
position: self.position.to_array(),
inner_angle: self.inner_angle,
direction: self.direction.to_array(),
outer_angle: self.outer_angle,
range: self.range,
_padding: [0.0; 7],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Uniforms
{
pub model: [[f32; 4]; 4],
pub view: [[f32; 4]; 4],
pub projection: [[f32; 4]; 4],
pub light_view_projections: [[[f32; 4]; 4]; MAX_SPOTLIGHTS],
pub camera_position: [f32; 3],
pub height_scale: f32,
pub player_position: [f32; 3],
pub time: f32,
pub shadow_bias: f32,
pub tile_scale: f32,
pub enable_dissolve: u32,
pub enable_snow_light: u32,
pub spotlight_count: u32,
pub _padding1: u32,
pub _padding2: u32,
pub _padding3: u32,
pub spotlights: [SpotlightRaw; MAX_SPOTLIGHTS],
}
impl Uniforms
{
pub fn new(
model: Mat4,
view: Mat4,
projection: Mat4,
light_view_projections: &[Mat4],
camera_position: glam::Vec3,
player_position: glam::Vec3,
height_scale: f32,
time: f32,
shadow_bias: f32,
tile_scale: f32,
enable_dissolve: bool,
enable_snow_light: bool,
spotlights: &[Spotlight],
) -> Self
{
let mut spotlight_array = [SpotlightRaw::default(); MAX_SPOTLIGHTS];
for (i, spotlight) in spotlights.iter().take(MAX_SPOTLIGHTS).enumerate()
{
spotlight_array[i] = spotlight.to_raw();
}
let mut lvp_array = [[[0.0f32; 4]; 4]; MAX_SPOTLIGHTS];
for (i, mat) in light_view_projections
.iter()
.take(MAX_SPOTLIGHTS)
.enumerate()
{
lvp_array[i] = mat.to_cols_array_2d();
}
Self {
model: model.to_cols_array_2d(),
view: view.to_cols_array_2d(),
projection: projection.to_cols_array_2d(),
light_view_projections: lvp_array,
camera_position: camera_position.to_array(),
height_scale,
player_position: player_position.to_array(),
time,
shadow_bias,
tile_scale,
enable_dissolve: if enable_dissolve { 1 } else { 0 },
enable_snow_light: if enable_snow_light { 1 } else { 0 },
spotlight_count: spotlights.len().min(MAX_SPOTLIGHTS) as u32,
_padding1: 0,
_padding2: 0,
_padding3: 0,
spotlights: spotlight_array,
}
}
}
#[derive(Clone, Copy)]
pub enum Pipeline
{
Standard,
SnowClipmap,
}
pub struct DrawCall
{
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_indices: u32,
pub model: Mat4,
pub pipeline: Pipeline,
pub instance_buffer: Option<wgpu::Buffer>,
pub num_instances: u32,
pub tile_scale: f32,
pub enable_dissolve: bool,
pub enable_snow_light: bool,
pub displacement_bind_group: Option<wgpu::BindGroup>,
}

View File

@@ -1,140 +0,0 @@
use crate::mesh::{InstanceRaw, Vertex};
use wesl::{include_wesl, Wesl};
pub fn create_render_pipeline(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::standard".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}
pub fn create_environment_pipeline(
device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let compiler = Wesl::new("src/shaders");
let shader_source = compiler
.compile(&"package::environment".parse().unwrap())
.inspect_err(|e| eprintln!("WESL error: {e}"))
.unwrap()
.to_string();
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Environment Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Environment Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Environment Pipeline"),
layout: Some(&render_pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}

View File

@@ -1,95 +0,0 @@
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, blue_noise_texture, blue_noise_sampler};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let instance_model = mat4x4<f32>(
input.instance_model_0,
input.instance_model_1,
input.instance_model_2,
input.instance_model_3
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
output.world_position = world_pos.xyz;
output.clip_position = uniforms.projection * uniforms.view * world_pos;
let normal_matrix = mat3x3<f32>(
instance_model[0].xyz,
instance_model[1].xyz,
instance_model[2].xyz
);
output.world_normal = normalize(normal_matrix * input.normal);
output.light_space_position = uniforms.light_view_projection * world_pos;
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);
var dissolve_amount = 0.0;
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;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let debug = 0u;
if debug == 1u {
return vec4<f32>(input.dissolve_amount);
}
if input.dissolve_amount > 0.0 {
let screen_pos = input.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 < input.dissolve_amount {
discard;
}
}
let shadow = sample_shadow_map(input.light_space_position);
let tile_scale = 4.0;
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
let brightness = spotlight_strokes;
return vec4<f32>(brightness, brightness, brightness, 1.0);
}

177
src/shaders/main.wesl Normal file
View File

@@ -0,0 +1,177 @@
import package::util::{
VertexInput,
VertexOutput,
uniforms,
all_spotlights_lighting,
blue_noise_texture,
blue_noise_sampler,
snow_light_texture,
snow_light_sampler,
TERRAIN_BOUNDS
};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let instance_model = mat4x4<f32>(
input.instance_model_0,
input.instance_model_1,
input.instance_model_2,
input.instance_model_3
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
output.world_position = world_pos.xyz;
output.clip_position = uniforms.projection * uniforms.view * world_pos;
let normal_matrix = mat3x3<f32>(
instance_model[0].xyz,
instance_model[1].xyz,
instance_model[2].xyz
);
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;
return output;
}
@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 {
discard;
}
}
let tile_scale = uniforms.tile_scale;
let spotlight_strokes = all_spotlights_lighting(in.world_position, in.clip_position, in.world_normal, tile_scale);
var brightness = spotlight_strokes;
if uniforms.enable_snow_light == 1u {
let terrain_uv = (vec2<f32>(in.world_position.x, in.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let snow_light = textureSample(snow_light_texture, snow_light_sampler, terrain_uv).r;
if snow_light > 0.05 {
let screen_pos = in.clip_position.xy / in.clip_position.w;
let blue_noise_uv = screen_pos * 0.5 + 0.5;
let blue_noise = textureSample(blue_noise_texture, blue_noise_sampler, blue_noise_uv * 10.0).r;
let blue_step = step(blue_noise, snow_light / 30.0);
brightness = max(brightness, blue_step);
}
}
return vec4<f32>(brightness, brightness, brightness, 1.0);
}
@group(1) @binding(0)
var heightmap_texture: texture_2d<f32>;
@group(1) @binding(1)
var heightmap_sampler: sampler;
@group(1) @binding(2)
var snow_depth_texture: texture_2d<f32>;
@group(1) @binding(3)
var snow_depth_sampler: sampler;
fn sample_snow_depth_bilinear(uv: vec2<f32>) -> f32 {
let tex_size = vec2<f32>(textureDimensions(snow_depth_texture, 0));
let texel_coord = uv * tex_size - 0.5;
let base = vec2<i32>(floor(texel_coord));
let f = fract(texel_coord);
let s00 = textureLoad(snow_depth_texture, clamp(base, vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
let s10 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(1, 0), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
let s01 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(0, 1), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
let s11 = textureLoad(snow_depth_texture, clamp(base + vec2<i32>(1, 1), vec2<i32>(0), vec2<i32>(tex_size) - 1), 0).r;
return mix(mix(s00, s10, f.x), mix(s01, s11, f.x), f.y);
}
@vertex
fn vs_snow_main(in: VertexInput) -> VertexOutput {
let instance_model = mat4x4<f32>(
in.instance_model_0,
in.instance_model_1,
in.instance_model_2,
in.instance_model_3,
);
let instance_world = instance_model * vec4<f32>(in.position, 1.0);
let world_xz = instance_world.xz;
let level_bias = instance_world.y;
let uv = (world_xz + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let terrain_height = textureSampleLevel(heightmap_texture, heightmap_sampler, uv, 0.0).r;
let snow_depth = sample_snow_depth_bilinear(uv) * 5.0;
let world_y = terrain_height + snow_depth + 0.2 + level_bias;
let world_position = vec3<f32>(world_xz.x, world_y, world_xz.y);
let texel_size = 1.0 / vec2<f32>(textureDimensions(heightmap_texture, 0));
let h_right = textureSampleLevel(heightmap_texture, heightmap_sampler, uv + vec2<f32>(texel_size.x, 0.0), 0.0).r + sample_snow_depth_bilinear(uv + vec2<f32>(texel_size.x, 0.0));
let h_left = textureSampleLevel(heightmap_texture, heightmap_sampler, uv - vec2<f32>(texel_size.x, 0.0), 0.0).r + sample_snow_depth_bilinear(uv - vec2<f32>(texel_size.x, 0.0));
let h_up = textureSampleLevel(heightmap_texture, heightmap_sampler, uv + vec2<f32>(0.0, texel_size.y), 0.0).r + sample_snow_depth_bilinear(uv + vec2<f32>(0.0, texel_size.y));
let h_down = textureSampleLevel(heightmap_texture, heightmap_sampler, uv - vec2<f32>(0.0, texel_size.y), 0.0).r + sample_snow_depth_bilinear(uv - vec2<f32>(0.0, texel_size.y));
let dx = h_right - h_left;
let dz = h_up - h_down;
let normal = normalize(vec3<f32>(-dx, 2.0 * texel_size.x * TERRAIN_BOUNDS.x, -dz));
var out: VertexOutput;
out.world_position = world_position;
out.world_normal = normal;
out.clip_position = uniforms.projection * uniforms.view * vec4<f32>(world_position, 1.0);
out.dissolve_amount = in.instance_dissolve;
return out;
}

View File

@@ -1,4 +1,4 @@
import package::shared::{ VertexInput, uniforms };
import package::util::{ VertexInput, uniforms };
@vertex
fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
@@ -10,5 +10,5 @@ fn vs_main(input: VertexInput) -> @builtin(position) vec4<f32> {
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
return uniforms.light_view_projection * world_pos;
return uniforms.projection * world_pos;
}

View File

@@ -1,72 +0,0 @@
import package::shared::{
VertexInput,
VertexOutput,
uniforms,
sample_shadow_map,
all_spotlights_lighting,
is_in_any_spotlight_light_area,
blue_noise_texture,
blue_noise_sampler,
TERRAIN_BOUNDS,
Spotlight
};
@group(1) @binding(0)
var persistent_light_texture: texture_2d<f32>;
@group(1) @binding(1)
var persistent_light_sampler: sampler;
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let instance_model = mat4x4<f32>(
input.instance_model_0,
input.instance_model_1,
input.instance_model_2,
input.instance_model_3
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
output.world_position = world_pos.xyz;
output.clip_position = uniforms.projection * uniforms.view * world_pos;
let normal_matrix = mat3x3<f32>(
instance_model[0].xyz,
instance_model[1].xyz,
instance_model[2].xyz
);
output.world_normal = normalize(normal_matrix * input.normal);
output.light_space_position = uniforms.light_view_projection * world_pos;
return output;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let shadow = sample_shadow_map(in.light_space_position);
let tile_scale = 2.0;
let in_spotlight_light_area = is_in_any_spotlight_light_area(in.world_position, in.world_normal, tile_scale, shadow);
let spotlight_strokes = all_spotlights_lighting(in.world_position, in.clip_position, in.world_normal, tile_scale, shadow);
var brightness = spotlight_strokes;
if !in_spotlight_light_area {
let terrain_uv = (vec2<f32>(in.world_position.x, in.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let persistent_light = textureSample(persistent_light_texture, persistent_light_sampler, terrain_uv).r;
if persistent_light > 0.05 {
let screen_pos = in.clip_position.xy / in.clip_position.w;
let blue_noise_uv = screen_pos * 0.5 + 0.5;
let blue_noise = textureSample(blue_noise_texture, blue_noise_sampler, blue_noise_uv * 10.0).r;
let blue_step = step(blue_noise, persistent_light / 30.0);
brightness = max(brightness, blue_step);
}
}
return vec4<f32>(brightness, brightness, brightness, 1.0);
}

View File

@@ -9,6 +9,10 @@ struct DeformParams {
position_z: f32,
radius: f32,
depth: f32,
terrain_width: f32,
terrain_height: f32,
_padding1: f32,
_padding2: f32,
}
@compute @workgroup_size(16, 16, 1)
@@ -21,7 +25,7 @@ fn deform(@builtin(global_invocation_id) global_id: vec3<u32>) {
let coords = vec2<i32>(i32(global_id.x), i32(global_id.y));
let terrain_size = vec2<f32>(1000.0, 1000.0);
let terrain_size = vec2<f32>(params.terrain_width, params.terrain_height);
let half_size = terrain_size / 2.0;
let uv = vec2<f32>(f32(global_id.x) / f32(texture_size.x), f32(global_id.y) / f32(texture_size.y));
@@ -34,9 +38,7 @@ fn deform(@builtin(global_invocation_id) global_id: vec3<u32>) {
let current_depth = textureLoad(snow_depth, coords).r;
let falloff = 1.0 - (distance / params.radius);
let falloff_smooth = falloff * falloff;
let deform_amount = params.depth * falloff_smooth;
let deform_amount = params.depth * falloff * falloff;
let new_depth = max(0.0, current_depth - deform_amount);
textureStore(snow_depth, coords, vec4<f32>(new_depth, 0.0, 0.0, 0.0));

View File

@@ -1,4 +1,4 @@
import package::shared::{Spotlight, MAX_SPOTLIGHTS, calculate_spotlight_data};
import package::util::{Spotlight, MAX_SPOTLIGHTS, calculate_spotlight_data};
struct AccumulationUniforms {
terrain_min_xz: vec2<f32>,
@@ -7,12 +7,12 @@ struct AccumulationUniforms {
delta_time: f32,
spotlight_count: u32,
_padding: u32,
light_view_projection: mat4x4<f32>,
light_view_projections: array<mat4x4<f32>, MAX_SPOTLIGHTS>,
shadow_bias: f32,
terrain_height_scale: f32,
_padding3: f32,
_padding4: f32,
spotlights: array<Spotlight, 4>,
spotlights: array<Spotlight, MAX_SPOTLIGHTS>,
}
@group(0) @binding(0)
@@ -31,7 +31,7 @@ var heightmap: texture_2d<f32>;
var heightmap_sampler: sampler;
@group(0) @binding(5)
var shadow_map: texture_depth_2d;
var shadow_map: texture_depth_2d_array;
@group(0) @binding(6)
var shadow_sampler: sampler_comparison;
@@ -59,7 +59,8 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
return out;
}
fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
fn sample_shadow_map_local(world_pos: vec3<f32>, spotlight_index: u32) -> f32 {
let light_space_pos = uniforms.light_view_projections[spotlight_index] * vec4<f32>(world_pos, 1.0);
let proj_coords = light_space_pos.xyz / light_space_pos.w;
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
@@ -70,7 +71,7 @@ fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
}
let depth = ndc_coords.z - uniforms.shadow_bias;
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, spotlight_index, depth);
return shadow;
}
@@ -85,15 +86,13 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let snow_surface_height = terrain_height + depth;
let snow_surface_pos = vec3<f32>(world_xz.x, snow_surface_height, world_xz.y);
let light_space_position = uniforms.light_view_projection * vec4<f32>(snow_surface_pos, 1.0);
let shadow = sample_shadow_map(light_space_position);
var current_light = 0.0;
if shadow > 0.0 {
let tile_scale = 2.0;
let surface_normal = vec3<f32>(0.0, 1.0, 0.0);
let tile_scale = 2.0;
let surface_normal = vec3<f32>(0.0, 1.0, 0.0);
for (var i = 0u; i < uniforms.spotlight_count; i++) {
for (var i = 0u; i < uniforms.spotlight_count; i++) {
let shadow = sample_shadow_map_local(snow_surface_pos, i);
if shadow > 0.0 {
let spotlight = uniforms.spotlights[i];
let data = calculate_spotlight_data(snow_surface_pos, surface_normal, spotlight, tile_scale, shadow);
let light = f32(data.is_lit);

View File

@@ -1,66 +0,0 @@
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, blue_noise_texture, blue_noise_sampler};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let instance_model = mat4x4<f32>(
input.instance_model_0,
input.instance_model_1,
input.instance_model_2,
input.instance_model_3
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
output.world_position = world_pos.xyz;
output.clip_position = uniforms.projection * uniforms.view * world_pos;
let normal_matrix = mat3x3<f32>(
instance_model[0].xyz,
instance_model[1].xyz,
instance_model[2].xyz
);
output.world_normal = normalize(normal_matrix * input.normal);
output.light_space_position = uniforms.light_view_projection * world_pos;
output.dissolve_amount = 0.0;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let shadow = sample_shadow_map(input.light_space_position);
let debug = 0u;
if debug == 3u {
return vec4<f32>(shadow, shadow, shadow, 1.0);
}
if debug == 2u {
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
return vec4<f32>(proj_coords.x, proj_coords.y, proj_coords.z, 1.0);
}
if debug == 1u {
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
let in_bounds = ndc_coords.x >= 0.0 && ndc_coords.x <= 1.0 &&
ndc_coords.y >= 0.0 && ndc_coords.y <= 1.0 &&
ndc_coords.z >= 0.0 && ndc_coords.z <= 1.0;
if in_bounds {
return vec4<f32>(ndc_coords.x, ndc_coords.y, ndc_coords.z, 1.0);
} else {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
}
let tile_scale = 4.0;
let flowmap_strokes = flowmap_path_lighting_with_shadow(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
// let brightness = max(flowmap_strokes, spotlight_strokes);
let brightness = 0.0;
return vec4<f32>(brightness, brightness, brightness, 1.0);
}

View File

@@ -1,89 +0,0 @@
import package::shared::{VertexInput, VertexOutput, sample_shadow_map, flowmap_path_lighting_with_shadow, all_spotlights_lighting, uniforms, TERRAIN_BOUNDS, flowmap_texture, flowmap_sampler};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var output: VertexOutput;
let instance_model = mat4x4<f32>(
input.instance_model_0,
input.instance_model_1,
input.instance_model_2,
input.instance_model_3
);
let world_pos = instance_model * vec4<f32>(input.position, 1.0);
output.world_position = world_pos.xyz;
output.clip_position = uniforms.projection * uniforms.view * world_pos;
let normal_matrix = mat3x3<f32>(
instance_model[0].xyz,
instance_model[1].xyz,
instance_model[2].xyz
);
output.world_normal = normalize(normal_matrix * input.normal);
output.light_space_position = uniforms.light_view_projection * world_pos;
return output;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
let debug = 0u;
if debug == 4u {
let proj_coords = input.light_space_position.xyz / input.light_space_position.w;
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
let in_bounds = ndc_coords.x >= 0.0 && ndc_coords.x <= 1.0 &&
ndc_coords.y >= 0.0 && ndc_coords.y <= 1.0 &&
ndc_coords.z >= 0.0 && ndc_coords.z <= 1.0;
if in_bounds {
return vec4<f32>(ndc_coords.x, ndc_coords.y, ndc_coords.z, 1.0);
} else {
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
}
}
if debug == 3u {
let shadow = sample_shadow_map(input.light_space_position);
return vec4<f32>(shadow, shadow, shadow, 1.0);
}
if debug == 1u {
let flowmap_uv = (vec2<f32>(input.world_position.x, input.world_position.z) + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0).rgb;
return vec4<f32>(flowmap_sample, 1.0);
}
if debug == 2u {
let world_pos_2d = vec2<f32>(input.world_position.x, input.world_position.z);
let tile_size = 10.0;
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0).rgb;
let x = (flowmap_sample.r) * 2.0 - 1.0;
let y = flowmap_sample.g * 2.0 - 1.0;
let direction_to_path = normalize(vec2<f32>(x, y));
let perpendicular_to_path = normalize(vec2<f32>(-direction_to_path.y, direction_to_path.x));
let local_pos = world_pos_2d - tile_center;
let arrow_scale = 0.05;
let parallel_coord = dot(local_pos, direction_to_path) * arrow_scale;
let perpendicular_coord = dot(local_pos, perpendicular_to_path) * arrow_scale;
let to_path = step(0.95, fract(parallel_coord));
let to_perp = step(0.95, fract(perpendicular_coord));
return vec4<f32>(to_perp, to_perp, to_perp, 1.0);
}
let shadow = sample_shadow_map(input.light_space_position);
let tile_scale = 2.0;
let spotlight_strokes = all_spotlights_lighting(input.world_position, input.clip_position, input.world_normal, tile_scale, shadow);
let brightness = spotlight_strokes;
return vec4<f32>(brightness, brightness, brightness, 1.0);
}

View File

@@ -13,8 +13,7 @@ struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec3<f32>,
@location(1) world_normal: vec3<f32>,
@location(2) light_space_position: vec4<f32>,
@location(3) dissolve_amount: f32,
@location(2) dissolve_amount: f32,
}
const MAX_SPOTLIGHTS: u32 = 4u;
@@ -38,15 +37,19 @@ struct Uniforms {
model: mat4x4<f32>,
view: mat4x4<f32>,
projection: mat4x4<f32>,
light_view_projection: mat4x4<f32>,
light_view_projections: array<mat4x4<f32>, 4>,
camera_position: vec3<f32>,
height_scale: f32,
player_position: vec3<f32>,
time: f32,
shadow_bias: f32,
tile_scale: f32,
enable_dissolve: u32,
enable_snow_light: u32,
spotlight_count: u32,
_padding1: u32,
_padding2: u32,
_padding3: u32,
spotlights: array<Spotlight, 4>,
}
@@ -54,29 +57,23 @@ struct Uniforms {
var<uniform> uniforms: Uniforms;
@group(0) @binding(1)
var shadow_map: texture_depth_2d;
var shadow_map: texture_depth_2d_array;
@group(0) @binding(2)
var shadow_sampler: sampler_comparison;
@group(0) @binding(3)
var dither_texture_array: texture_2d_array<f32>;
@group(0) @binding(4)
var dither_sampler: sampler;
@group(0) @binding(5)
var flowmap_texture: texture_2d<f32>;
@group(0) @binding(6)
var flowmap_sampler: sampler;
@group(0) @binding(7)
var blue_noise_texture: texture_2d<f32>;
@group(0) @binding(8)
var blue_noise_sampler: sampler;
@group(0) @binding(9)
var snow_light_texture: texture_2d<f32>;
@group(0) @binding(10)
var snow_light_sampler: sampler;
const PI: f32 = 3.14159265359;
const TERRAIN_BOUNDS: vec2<f32> = vec2<f32>(1000.0, 1000.0);
const LINE_THICKNESS: f32 = 0.1;
@@ -116,7 +113,8 @@ fn compute_rotation_t(distance_to_light: f32) -> f32 {
return pow(min(max(distance_to_light - 1.0 / OCTAVE_STEPS, 0.0) * OCTAVE_STEPS, 1.0), 2.0);
}
fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
fn sample_shadow_map(world_pos: vec3<f32>, spotlight_index: u32) -> f32 {
let light_space_pos = uniforms.light_view_projections[spotlight_index] * vec4<f32>(world_pos, 1.0);
let proj_coords = light_space_pos.xyz / light_space_pos.w;
let ndc_coords = proj_coords * vec3<f32>(0.5, -0.5, 1.0) + vec3<f32>(0.5, 0.5, 0.0);
@@ -125,7 +123,7 @@ fn sample_shadow_map(light_space_pos: vec4<f32>) -> f32 {
}
let depth = ndc_coords.z - uniforms.shadow_bias;
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, depth);
let shadow = textureSampleCompare(shadow_map, shadow_sampler, ndc_coords.xy, spotlight_index, depth);
return shadow;
}
@@ -270,36 +268,18 @@ fn calculate_spotlight_data(world_pos: vec3<f32>, normal: vec3<f32>, spotlight:
return data;
}
fn is_in_spotlight_light_area(world_pos: vec3<f32>, normal: vec3<f32>, spotlight: Spotlight, tile_scale: f32, shadow: f32) -> bool {
return calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow).is_lit;
}
fn is_in_any_spotlight_light_area(world_pos: vec3<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> bool {
for (var i = 0u; i < uniforms.spotlight_count; i++) {
if is_in_spotlight_light_area(world_pos, normal, uniforms.spotlights[i], tile_scale, shadow) {
return true;
}
}
return false;
}
fn spotlight_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, spotlight: Spotlight, tile_scale: f32, shadow: f32) -> f32 {
let data = calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow);
if !data.is_lit {
return 0.0;
}
return hatching_lighting(world_pos, clip_pos, tile_scale, data.direction_normalized, data.combined_distance);
}
fn all_spotlights_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> f32 {
fn all_spotlights_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32) -> f32 {
var max_lighting = 0.0;
for (var i = 0u; i < uniforms.spotlight_count; i++) {
let spotlight = uniforms.spotlights[i];
let lighting = spotlight_lighting(world_pos, clip_pos, normal, spotlight, tile_scale, shadow);
max_lighting = max(max_lighting, lighting);
let shadow = sample_shadow_map(world_pos, i);
let data = calculate_spotlight_data(world_pos, normal, spotlight, tile_scale, shadow);
if data.is_lit {
let lighting = hatching_lighting(world_pos, clip_pos, tile_scale, data.direction_normalized, data.combined_distance);
max_lighting = max(max_lighting, lighting);
}
}
return max_lighting;
@@ -344,40 +324,3 @@ fn line_stroke_lighting(data: StrokeData, clip_pos: vec4<f32>) -> f32 {
return step(line_half_width, effective_distance);
}
fn flowmap_path_lighting(world_pos: vec3<f32>, clip_pos: vec4<f32>, tile_scale: f32) -> f32 {
let world_pos_2d = vec2<f32>(world_pos.x, world_pos.z);
let tile_size = 1.0 / tile_scale;
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0);
let x = flowmap_sample.r * 2.0 - 1.0;
let y = flowmap_sample.g * 2.0 - 1.0;
let direction_to_path = normalize(vec2<f32>(x, y));
let distance_to_path = flowmap_sample.b;
return hatching_lighting(world_pos, clip_pos, tile_scale, direction_to_path, distance_to_path);
}
fn flowmap_path_lighting_with_shadow(world_pos: vec3<f32>, clip_pos: vec4<f32>, normal: vec3<f32>, tile_scale: f32, shadow: f32) -> f32 {
let world_pos_2d = vec2<f32>(world_pos.x, world_pos.z);
let tile_size = 1.0 / tile_scale;
let tile_center = floor(world_pos_2d / tile_size) * tile_size + tile_size * 0.5;
let flowmap_uv = (tile_center + TERRAIN_BOUNDS * 0.5) / TERRAIN_BOUNDS;
let flowmap_sample = textureSampleLevel(flowmap_texture, flowmap_sampler, flowmap_uv, 0.0);
let x = flowmap_sample.r * 2.0 - 1.0;
let y = flowmap_sample.g * 2.0 - 1.0;
let direction_to_path = normalize(vec2<f32>(x, y));
let distance_to_path = flowmap_sample.b;
let light_dir_3d = normalize(vec3<f32>(-x, 100.0, -y));
let diffuse = max(0.0, dot(normalize(normal), light_dir_3d));
let lighting_intensity = diffuse * shadow;
let darkness = 1.0 - lighting_intensity;
let combined_distance = min(distance_to_path + darkness * 0.5, 1.0);
return hatching_lighting(world_pos, clip_pos, tile_scale, direction_to_path, combined_distance);
}

View File

@@ -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(&params_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");
}
}

View File

@@ -12,7 +12,7 @@ struct AccumulationUniforms
delta_time: f32,
spotlight_count: u32,
_padding: u32,
light_view_projection: [[f32; 4]; 4],
light_view_projections: [[[f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS],
shadow_bias: f32,
terrain_height_scale: f32,
_padding3: f32,
@@ -48,12 +48,8 @@ pub struct SnowLightAccumulation
impl SnowLightAccumulation
{
pub fn new(
device: &wgpu::Device,
terrain_min: Vec2,
terrain_max: Vec2,
resolution: u32,
) -> Self
pub fn new(device: &wgpu::Device, terrain_min: Vec2, terrain_max: Vec2, resolution: u32)
-> Self
{
let size = wgpu::Extent3d {
width: resolution,
@@ -68,8 +64,7 @@ impl SnowLightAccumulation
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R16Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
@@ -145,7 +140,7 @@ impl SnowLightAccumulation
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Depth,
view_dimension: wgpu::TextureViewDimension::D2,
view_dimension: wgpu::TextureViewDimension::D2Array,
multisampled: false,
},
count: None,
@@ -232,9 +227,7 @@ impl SnowLightAccumulation
});
let vertices: &[f32] = &[
-1.0, -1.0, 0.0, 1.0,
3.0, -1.0, 2.0, 1.0,
-1.0, 3.0, 0.0, -1.0,
-1.0, -1.0, 0.0, 1.0, 3.0, -1.0, 2.0, 1.0, -1.0, 3.0, 0.0, -1.0,
];
let quad_vb = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
@@ -419,7 +412,7 @@ impl SnowLightAccumulation
queue: &wgpu::Queue,
spotlights: &[crate::render::Spotlight],
delta_time: f32,
light_view_projection: &glam::Mat4,
light_view_projections: &[glam::Mat4],
shadow_bias: f32,
terrain_height_scale: f32,
)
@@ -430,8 +423,12 @@ impl SnowLightAccumulation
self.needs_clear = false;
}
let mut spotlight_array = [crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS];
for (i, spotlight) in spotlights.iter().take(crate::render::MAX_SPOTLIGHTS).enumerate()
let mut spotlight_array =
[crate::render::SpotlightRaw::default(); crate::render::MAX_SPOTLIGHTS];
for (i, spotlight) in spotlights
.iter()
.take(crate::render::MAX_SPOTLIGHTS)
.enumerate()
{
spotlight_array[i] = spotlight.to_raw();
}
@@ -443,7 +440,17 @@ impl SnowLightAccumulation
delta_time,
spotlight_count: spotlights.len().min(crate::render::MAX_SPOTLIGHTS) as u32,
_padding: 0,
light_view_projection: light_view_projection.to_cols_array_2d(),
light_view_projections: {
let mut arr = [[[0.0f32; 4]; 4]; crate::render::MAX_SPOTLIGHTS];
for (i, mat) in light_view_projections
.iter()
.take(crate::render::MAX_SPOTLIGHTS)
.enumerate()
{
arr[i] = mat.to_cols_array_2d();
}
arr
},
shadow_bias,
terrain_height_scale,
_padding3: 0.0,
@@ -453,10 +460,26 @@ impl SnowLightAccumulation
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
let write_view = if self.current { &self.view_ping } else { &self.view_pong };
let bind_group = if self.current { self.bind_group_ping.as_ref() } else { self.bind_group_pong.as_ref() };
let write_view = if self.current
{
&self.view_ping
}
else
{
&self.view_pong
};
let bind_group = if self.current
{
self.bind_group_ping.as_ref()
}
else
{
self.bind_group_pong.as_ref()
};
let Some(bind_group) = bind_group else {
let Some(bind_group) = bind_group
else
{
return;
};
@@ -489,7 +512,14 @@ impl SnowLightAccumulation
pub fn read_view(&self) -> &wgpu::TextureView
{
if self.current { &self.view_pong } else { &self.view_ping }
if self.current
{
&self.view_pong
}
else
{
&self.view_ping
}
}
pub fn create_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout

View File

@@ -4,6 +4,32 @@ use crate::components::FollowComponent;
use crate::utility::input::InputState;
use crate::world::{Transform, World};
pub fn camera_view_matrix(world: &World) -> Option<glam::Mat4>
{
let (camera_entity, camera_component) = world.active_camera()?;
let camera_transform = world.transforms.get(camera_entity)?;
if let Some(follow) = world.follows.get(camera_entity)
{
if let Some(target_transform) = world.transforms.get(follow.target)
{
return Some(glam::Mat4::look_at_rh(
camera_transform.position,
target_transform.position,
glam::Vec3::Y,
));
}
}
let forward = camera_component.get_forward();
let target = camera_transform.position + forward;
Some(glam::Mat4::look_at_rh(
camera_transform.position,
target,
glam::Vec3::Y,
))
}
pub fn camera_input_system(world: &mut World, input_state: &InputState)
{
let cameras: Vec<_> = world.cameras.all();

View File

@@ -5,14 +5,11 @@ use crate::world::World;
pub fn player_input_system(world: &mut World, input_state: &InputState)
{
let active_camera = world.cameras.get_active();
if active_camera.is_none()
let Some((_, camera)) = world.active_camera()
else
{
return;
}
let (_, camera) = active_camera.unwrap();
};
let forward = camera.get_forward_horizontal();
let right = camera.get_right_horizontal();

View File

@@ -1,22 +1,25 @@
pub mod camera;
pub mod follow;
pub mod input;
pub mod noclip;
pub mod physics_sync;
pub mod player_states;
pub mod render;
pub mod rotate;
pub mod snow;
pub mod spotlight_sync;
pub mod state_machine;
pub mod tree_dissolve;
pub use camera::{
camera_follow_system, camera_input_system, camera_noclip_system, start_camera_following,
stop_camera_following,
camera_follow_system, camera_input_system, camera_noclip_system, camera_view_matrix,
start_camera_following,
};
pub use follow::follow_system;
pub use input::player_input_system;
pub use noclip::noclip_toggle_system;
pub use physics_sync::physics_sync_system;
pub use render::render_system;
pub use rotate::rotate_system;
pub use snow::snow_system;
pub use spotlight_sync::spotlight_sync_system;
pub use state_machine::{state_machine_physics_system, state_machine_system};
pub use tree_dissolve::{tree_dissolve_update_system, tree_occlusion_system};

25
src/systems/noclip.rs Normal file
View File

@@ -0,0 +1,25 @@
use crate::entity::EntityHandle;
use crate::systems::camera::{start_camera_following, stop_camera_following};
use crate::utility::input::InputState;
use crate::world::World;
pub fn noclip_toggle_system(
world: &mut World,
input_state: &InputState,
camera_entity: EntityHandle,
noclip_mode: &mut bool,
)
{
if input_state.noclip_just_pressed
{
*noclip_mode = !*noclip_mode;
if *noclip_mode
{
stop_camera_following(world, camera_entity);
}
else
{
start_camera_following(world, camera_entity);
}
}
}

View File

@@ -1,216 +1,16 @@
use std::{f32::consts::PI, rc::Rc};
use glam::Vec3;
use kurbo::ParamCurve;
use rapier3d::{
control::{CharacterAutostep, KinematicCharacterController},
math::Vector,
prelude::{ColliderBuilder, RigidBodyBuilder},
};
use rapier3d::math::Vector;
use crate::{
components::{
jump::JumpComponent, lights::spot::SpotlightComponent, InputComponent, MeshComponent,
MovementComponent, PhysicsComponent,
},
entity::EntityHandle,
mesh::Mesh,
physics::PhysicsManager,
render::Pipeline,
state::{State, StateMachine},
world::{Transform, World},
};
pub struct Player;
impl Player
{
pub fn spawn(world: &mut World, position: Vec3) -> EntityHandle
{
let entity = world.spawn();
let spawn_transform = Transform::from_position(position);
let rigidbody = RigidBodyBuilder::kinematic_position_based()
.translation(spawn_transform.position.into())
.build();
let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
let _controller = KinematicCharacterController {
slide: true,
autostep: Some(CharacterAutostep::default()),
max_slope_climb_angle: 45.0,
..Default::default()
};
let rigidbody_handle = PhysicsManager::add_rigidbody(rigidbody);
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
let mesh = Mesh::load_mesh("meshes/player_mesh.glb").expect("missing player mesh");
let falling_state = PlayerFallingState { entity };
let idle_state = PlayerIdleState { entity };
let walking_state = PlayerWalkingState {
entity,
enter_time_stamp: 0.0,
};
let jumping_state = PlayerJumpingState {
entity,
enter_time_stamp: 0.0,
};
let mut state_machine = StateMachine::new(Box::new(falling_state));
state_machine.add_state(walking_state);
state_machine.add_state(idle_state);
state_machine.add_state(jumping_state);
let entity_id = entity;
state_machine.add_transition::<PlayerFallingState, PlayerIdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<PlayerFallingState, PlayerWalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<PlayerIdleState, PlayerWalkingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && has_input
});
state_machine.add_transition::<PlayerWalkingState, PlayerIdleState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let has_input = world
.inputs
.with(entity_id, |i| i.move_direction.length() > 0.01)
.unwrap_or(false);
is_grounded && !has_input
});
state_machine.add_transition::<PlayerIdleState, PlayerFallingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
!is_grounded
});
state_machine.add_transition::<PlayerWalkingState, PlayerFallingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
!is_grounded
});
state_machine.add_transition::<PlayerIdleState, PlayerJumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<PlayerWalkingState, PlayerJumpingState>(move |world| {
let is_grounded = world
.movements
.with(entity_id, |m| m.movement_config.movement_context.is_floored)
.unwrap_or(false);
let jump_pressed = world
.inputs
.with(entity_id, |i| i.jump_just_pressed)
.unwrap_or(false);
is_grounded && jump_pressed
});
state_machine.add_transition::<PlayerJumpingState, PlayerFallingState>(move |world| {
world
.jumps
.with(entity_id, |jump| {
jump.jump_config.jump_context.duration >= jump.jump_config.jump_duration
})
.unwrap_or(true)
});
world
.transforms
.insert(entity, spawn_transform);
world.movements.insert(entity, MovementComponent::new());
world.jumps.insert(entity, JumpComponent::new());
world.inputs.insert(entity, InputComponent::default());
world.physics.insert(
entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(mesh),
pipeline: Pipeline::Standard,
instance_buffer: None,
num_instances: 1,
},
);
world.player_tags.insert(entity);
world.state_machines.insert(entity, state_machine);
let outer_angle = PI / 2.0 * 0.9;
world.spotlights.insert(
entity,
SpotlightComponent::new(
Vec3::new(1.0, 2.0, 1.0),
Vec3::new(0.0, -1.0, 0.0),
100.0,
outer_angle * 0.5,
outer_angle,
),
);
entity
}
pub fn despawn(world: &mut World, entity: EntityHandle)
{
world.despawn(entity);
}
}
use crate::entity::EntityHandle;
use crate::physics::PhysicsManager;
use crate::state::State;
use crate::utility::time::Time;
use crate::world::World;
pub struct PlayerFallingState
{
entity: EntityHandle,
pub entity: EntityHandle,
}
impl State for PlayerFallingState
@@ -220,14 +20,14 @@ impl State for PlayerFallingState
"PlayerFallingState"
}
fn on_state_enter(&mut self, world: &mut World)
fn on_state_enter(&mut self, _world: &mut World)
{
println!("entered falling");
}
fn on_state_exit(&mut self, world: &mut World) {}
fn on_state_exit(&mut self, _world: &mut World) {}
fn on_state_update(&mut self, world: &mut World, delta: f32) {}
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
{
@@ -297,7 +97,7 @@ impl State for PlayerFallingState
pub struct PlayerIdleState
{
entity: EntityHandle,
pub entity: EntityHandle,
}
impl State for PlayerIdleState
@@ -338,7 +138,7 @@ impl State for PlayerIdleState
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
fn on_state_physics_update(&mut self, world: &mut World, _delta: f32)
{
const GROUND_CHECK_DISTANCE: f32 = 0.6;
@@ -380,8 +180,8 @@ impl State for PlayerIdleState
pub struct PlayerWalkingState
{
entity: EntityHandle,
enter_time_stamp: f32,
pub entity: EntityHandle,
pub enter_time_stamp: f32,
}
impl State for PlayerWalkingState
@@ -393,19 +193,16 @@ impl State for PlayerWalkingState
fn on_state_enter(&mut self, _world: &mut World)
{
use crate::utility::time::Time;
self.enter_time_stamp = Time::get_time_elapsed();
println!("entered walking");
}
fn on_state_exit(&mut self, _world: &mut World) {}
fn on_state_update(&mut self, world: &mut World, delta: f32) {}
fn on_state_update(&mut self, _world: &mut World, _delta: f32) {}
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
{
use crate::utility::time::Time;
let (movement_input, walking_config) = world
.movements
.with(self.entity, |movement| {
@@ -519,8 +316,8 @@ impl State for PlayerWalkingState
pub struct PlayerJumpingState
{
entity: EntityHandle,
enter_time_stamp: f32,
pub entity: EntityHandle,
pub enter_time_stamp: f32,
}
impl State for PlayerJumpingState
@@ -532,7 +329,6 @@ impl State for PlayerJumpingState
fn on_state_enter(&mut self, world: &mut World)
{
use crate::utility::time::Time;
self.enter_time_stamp = Time::get_time_elapsed();
let current_position = world.transforms.get(self.entity).unwrap().position;
@@ -562,8 +358,6 @@ impl State for PlayerJumpingState
fn on_state_physics_update(&mut self, world: &mut World, delta: f32)
{
use crate::utility::time::Time;
let current_time = Time::get_time_elapsed();
world.jumps.with_mut(self.entity, |jump| {

View File

@@ -1,4 +1,4 @@
use crate::mesh::InstanceRaw;
use crate::loaders::mesh::InstanceRaw;
use crate::render::DrawCall;
use crate::world::World;
use bytemuck::cast_slice;
@@ -56,6 +56,10 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
pipeline: mesh_component.pipeline,
instance_buffer,
num_instances,
tile_scale: mesh_component.tile_scale,
enable_dissolve: mesh_component.enable_dissolve,
enable_snow_light: mesh_component.enable_snow_light,
displacement_bind_group: None,
})
})
.collect()

13
src/systems/snow.rs Normal file
View File

@@ -0,0 +1,13 @@
use crate::snow::SnowLayer;
use crate::world::World;
pub fn snow_system(world: &World, snow_layer: &mut SnowLayer, noclip: bool)
{
let camera_pos = world.active_camera_position();
let player_pos = world.player_position();
if !noclip
{
snow_layer.deform_at_position(player_pos, 1.5, 10.0);
}
snow_layer.update(camera_pos);
}

View File

@@ -1,6 +1,5 @@
use crate::components::DissolveComponent;
use crate::world::World;
use glam::Vec3;
pub fn tree_dissolve_update_system(world: &mut World, delta: f32)
{
@@ -22,22 +21,9 @@ pub fn tree_occlusion_system(world: &mut World)
if let Some(player_pos) = player_pos
{
let camera_entity = world.cameras.get_active().map(|(e, _)| e);
let camera_entity = world.active_camera().map(|(e, _)| e);
let camera_pos = camera_entity.and_then(|e| world.transforms.get(e).map(|t| t.position));
let tree_count = world.tree_tags.all().len();
if tree_count > 0
{
static mut FRAME_COUNT: u32 = 0;
unsafe {
FRAME_COUNT += 1;
if FRAME_COUNT % 60 == 0
{
println!("Tree occlusion system: {} trees detected", tree_count);
}
}
}
if let Some(camera_pos) = camera_pos
{
let to_player = player_pos - camera_pos;
@@ -74,18 +60,6 @@ pub fn tree_occlusion_system(world: &mut World)
let dissolve_amount =
1.0 - (perp_distance / occlusion_radius).clamp(0.0, 1.0);
static mut DEBUG_FRAME: u32 = 0;
unsafe {
DEBUG_FRAME += 1;
if DEBUG_FRAME % 60 == 0
{
println!(
"Tree occluding! perp_dist: {:.2}, dissolve: {:.2}",
perp_distance, dissolve_amount
);
}
}
if let Some(dissolve) = world.dissolves.get_mut(tree_entity)
{
dissolve.target_amount = dissolve_amount;

View File

@@ -13,12 +13,12 @@ use crate::state::StateMachine;
pub use crate::utility::transform::Transform;
pub struct TransformStorage
pub struct Storage<T>
{
pub components: HashMap<EntityHandle, Transform>,
pub components: HashMap<EntityHandle, T>,
}
impl TransformStorage
impl<T> Storage<T>
{
pub fn new() -> Self
{
@@ -27,31 +27,31 @@ impl TransformStorage
}
}
pub fn insert(&mut self, entity: EntityHandle, component: Transform)
pub fn insert(&mut self, entity: EntityHandle, component: T)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&Transform>
pub fn get(&self, entity: EntityHandle) -> Option<&T>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut Transform>
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut T>
{
self.components.get_mut(&entity)
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&Transform) -> R,
F: FnOnce(&T) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut Transform) -> R,
F: FnOnce(&mut T) -> R,
{
self.components.get_mut(&entity).map(f)
}
@@ -67,554 +67,23 @@ impl TransformStorage
}
}
pub struct MeshStorage
{
pub components: HashMap<EntityHandle, MeshComponent>,
}
impl MeshStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: MeshComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&MeshComponent>
{
self.components.get(&entity)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct PhysicsStorage
{
pub components: HashMap<EntityHandle, PhysicsComponent>,
}
impl PhysicsStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: PhysicsComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<PhysicsComponent>
{
self.components.get(&entity).copied()
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&PhysicsComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct MovementStorage
{
pub components: HashMap<EntityHandle, MovementComponent>,
}
impl MovementStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: MovementComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&MovementComponent>
{
self.components.get(&entity)
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&MovementComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut MovementComponent) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct JumpStorage
{
pub components: HashMap<EntityHandle, JumpComponent>,
}
impl JumpStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: JumpComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&JumpComponent>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut JumpComponent>
{
self.components.get_mut(&entity)
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&JumpComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut JumpComponent) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct InputStorage
{
pub components: HashMap<EntityHandle, InputComponent>,
}
impl InputStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: InputComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&InputComponent>
{
self.components.get(&entity)
}
pub fn with<F, R>(&self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&InputComponent) -> R,
{
self.components.get(&entity).map(f)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut InputComponent) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct PlayerTagStorage
{
pub components: HashMap<EntityHandle, ()>,
}
impl PlayerTagStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle)
{
self.components.insert(entity, ());
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct StateMachineStorage
{
pub components: HashMap<EntityHandle, StateMachine>,
}
impl StateMachineStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: StateMachine)
{
self.components.insert(entity, component);
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut StateMachine) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct CameraStorage
{
pub components: HashMap<EntityHandle, CameraComponent>,
}
impl CameraStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: CameraComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&CameraComponent>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut CameraComponent>
{
self.components.get_mut(&entity)
}
pub fn with_mut<F, R>(&mut self, entity: EntityHandle, f: F) -> Option<R>
where
F: FnOnce(&mut CameraComponent) -> R,
{
self.components.get_mut(&entity).map(f)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
pub fn get_active(&self) -> Option<(EntityHandle, &CameraComponent)>
{
self.components
.iter()
.find(|(_, cam)| cam.is_active)
.map(|(e, c)| (*e, c))
}
}
pub struct SpotlightStorage
{
pub components: HashMap<EntityHandle, SpotlightComponent>,
}
impl SpotlightStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: SpotlightComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&SpotlightComponent>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut SpotlightComponent>
{
self.components.get_mut(&entity)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct TreeTagStorage
{
pub components: HashMap<EntityHandle, ()>,
}
impl TreeTagStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle)
{
self.components.insert(entity, ());
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct DissolveStorage
{
pub components: HashMap<EntityHandle, DissolveComponent>,
}
impl DissolveStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: DissolveComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&DissolveComponent>
{
self.components.get(&entity)
}
pub fn get_mut(&mut self, entity: EntityHandle) -> Option<&mut DissolveComponent>
{
self.components.get_mut(&entity)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct FollowStorage
{
pub components: HashMap<EntityHandle, FollowComponent>,
}
impl FollowStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: FollowComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&FollowComponent>
{
self.components.get(&entity)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct RotateStorage
{
pub components: HashMap<EntityHandle, RotateComponent>,
}
impl RotateStorage
{
pub fn new() -> Self
{
Self {
components: HashMap::new(),
}
}
pub fn insert(&mut self, entity: EntityHandle, component: RotateComponent)
{
self.components.insert(entity, component);
}
pub fn get(&self, entity: EntityHandle) -> Option<&RotateComponent>
{
self.components.get(&entity)
}
pub fn remove(&mut self, entity: EntityHandle)
{
self.components.remove(&entity);
}
pub fn all(&self) -> Vec<EntityHandle>
{
self.components.keys().copied().collect()
}
}
pub struct World
{
pub entities: EntityManager,
pub transforms: TransformStorage,
pub meshes: MeshStorage,
pub physics: PhysicsStorage,
pub movements: MovementStorage,
pub jumps: JumpStorage,
pub inputs: InputStorage,
pub player_tags: PlayerTagStorage,
pub state_machines: StateMachineStorage,
pub cameras: CameraStorage,
pub spotlights: SpotlightStorage,
pub tree_tags: TreeTagStorage,
pub dissolves: DissolveStorage,
pub follows: FollowStorage,
pub rotates: RotateStorage,
pub transforms: Storage<Transform>,
pub meshes: Storage<MeshComponent>,
pub physics: Storage<PhysicsComponent>,
pub movements: Storage<MovementComponent>,
pub jumps: Storage<JumpComponent>,
pub inputs: Storage<InputComponent>,
pub player_tags: Storage<()>,
pub state_machines: Storage<StateMachine>,
pub cameras: Storage<CameraComponent>,
pub spotlights: Storage<SpotlightComponent>,
pub tree_tags: Storage<()>,
pub dissolves: Storage<DissolveComponent>,
pub follows: Storage<FollowComponent>,
pub rotates: Storage<RotateComponent>,
}
impl World
@@ -623,20 +92,20 @@ impl World
{
Self {
entities: EntityManager::new(),
transforms: TransformStorage::new(),
meshes: MeshStorage::new(),
physics: PhysicsStorage::new(),
movements: MovementStorage::new(),
jumps: JumpStorage::new(),
inputs: InputStorage::new(),
player_tags: PlayerTagStorage::new(),
state_machines: StateMachineStorage::new(),
cameras: CameraStorage::new(),
spotlights: SpotlightStorage::new(),
tree_tags: TreeTagStorage::new(),
dissolves: DissolveStorage::new(),
follows: FollowStorage::new(),
rotates: RotateStorage::new(),
transforms: Storage::new(),
meshes: Storage::new(),
physics: Storage::new(),
movements: Storage::new(),
jumps: Storage::new(),
inputs: Storage::new(),
player_tags: Storage::new(),
state_machines: Storage::new(),
cameras: Storage::new(),
spotlights: Storage::new(),
tree_tags: Storage::new(),
dissolves: Storage::new(),
follows: Storage::new(),
rotates: Storage::new(),
}
}
@@ -663,4 +132,31 @@ impl World
self.rotates.remove(entity);
self.entities.despawn(entity);
}
pub fn active_camera(&self) -> Option<(EntityHandle, &CameraComponent)>
{
self.cameras
.components
.iter()
.find(|(_, cam)| cam.is_active)
.map(|(e, c)| (*e, c))
}
pub fn active_camera_position(&self) -> glam::Vec3
{
self.active_camera()
.and_then(|(entity, _)| self.transforms.get(entity))
.map(|t| t.position)
.unwrap_or(glam::Vec3::ZERO)
}
pub fn player_position(&self) -> glam::Vec3
{
self.player_tags
.all()
.first()
.and_then(|e| self.transforms.get(*e))
.map(|t| t.position)
.unwrap_or(glam::Vec3::ZERO)
}
}