stylized 1-bit rendering

This commit is contained in:
Jonas H
2026-01-21 11:04:55 +01:00
parent 5d2eca0393
commit 2422106725
40 changed files with 2859 additions and 366 deletions

View File

@@ -8,16 +8,20 @@ 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) -> Self
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,
}
}
}

View File

@@ -20,7 +20,7 @@ impl CameraComponent
fov: 45.0_f32.to_radians(),
aspect,
near: 0.1,
far: 100.0,
far: 2000.0,
yaw: -135.0_f32.to_radians(),
pitch: -30.0_f32.to_radians(),
is_active: true,

View File

@@ -8,4 +8,6 @@ pub struct MeshComponent
{
pub mesh: Rc<Mesh>,
pub pipeline: Pipeline,
pub instance_buffer: Option<wgpu::Buffer>,
pub num_instances: u32,
}

View File

@@ -6,10 +6,12 @@ use nalgebra::DMatrix;
use rapier3d::parry::shape::HeightField;
use crate::{
mesh::{Mesh, Vertex},
mesh::{InstanceRaw, Mesh, Vertex},
physics::PhysicsManager,
render::{self, DrawCall, Pipeline},
};
use bytemuck::cast_slice;
use wgpu::util::DeviceExt;
thread_local! {
static WIREFRAME_BOX: OnceCell<Rc<Mesh>> = OnceCell::new();
@@ -122,12 +124,26 @@ pub fn render_collider_debug() -> Vec<DrawCall>
let translation = Mat4::from_translation(center);
let model = translation * scale;
let instance_data = InstanceRaw {
model: model.to_cols_array_2d(),
};
let instance_buffer = render::with_device(|device| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Debug Instance Buffer"),
contents: cast_slice(&[instance_data]),
usage: wgpu::BufferUsages::VERTEX,
})
});
draw_calls.push(DrawCall {
vertex_buffer: wireframe_box.vertex_buffer.clone(),
index_buffer: wireframe_box.index_buffer.clone(),
num_indices: wireframe_box.num_indices,
model,
pipeline: Pipeline::Wireframe,
instance_buffer: Some(instance_buffer),
num_instances: 1,
});
}
});
@@ -135,12 +151,26 @@ pub fn render_collider_debug() -> Vec<DrawCall>
DEBUG_HEIGHTFIELD.with(|cell| {
if let Some(Some(heightfield_mesh)) = cell.get()
{
let instance_data = InstanceRaw {
model: Mat4::IDENTITY.to_cols_array_2d(),
};
let instance_buffer = render::with_device(|device| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Heightfield Debug Instance Buffer"),
contents: cast_slice(&[instance_data]),
usage: wgpu::BufferUsages::VERTEX,
})
});
draw_calls.push(DrawCall {
vertex_buffer: heightfield_mesh.vertex_buffer.clone(),
index_buffer: heightfield_mesh.index_buffer.clone(),
num_indices: heightfield_mesh.num_indices,
model: Mat4::IDENTITY,
pipeline: Pipeline::Wireframe,
instance_buffer: Some(instance_buffer),
num_instances: 1,
});
}
});

View File

@@ -15,6 +15,7 @@ mod shader;
mod state;
mod systems;
mod terrain;
mod texture_loader;
mod utility;
mod world;
@@ -35,7 +36,7 @@ use crate::systems::{
player_input_system, render_system, start_camera_following, state_machine_physics_system,
state_machine_system, stop_camera_following,
};
use crate::terrain::Terrain;
use crate::terrain::{Terrain, TerrainConfig};
use crate::utility::time::Time;
fn main() -> Result<(), Box<dyn std::error::Error>>
@@ -53,29 +54,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
let renderer = pollster::block_on(Renderer::new(&window, 1))?;
render::init(renderer);
let terrain_data = render::with_device(|device| {
render::with_queue(|queue| {
let height_map =
heightmap::load_exr_heightmap(device, queue, "textures/height_map_x0_y0.exr");
let (height_texture, height_view, height_sampler) = height_map.unwrap();
render::TerrainData {
height_texture,
height_view,
height_sampler,
}
})
});
render::set_terrain_data(terrain_data);
let terrain_config = TerrainConfig::default();
let mut world = World::new();
let player_entity = Player::spawn(&mut world);
let _terrain_entity = Terrain::spawn(&mut world, "textures/height_map_x0_y0.exr", 10.0)?;
let _terrain_entity = Terrain::spawn(&mut world, &terrain_config)?;
render::set_terrain_data();
let mut noclip_mode = true;
let camera_entity = spawn_camera(&mut world, player_entity);
start_camera_following(&mut world, camera_entity);
let mut noclip_mode = false;
if noclip_mode == false
{
start_camera_following(&mut world, camera_entity);
}
let mut event_pump = sdl_context.event_pump()?;
let mut input_state = InputState::new();
@@ -142,7 +135,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
player_input_system(&mut world, &input_state);
}
physics_accumulator += delta;
while physics_accumulator >= FIXED_TIMESTEP
@@ -165,10 +157,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>>
{
if let Some(camera_transform) = world.transforms.get(camera_entity)
{
let view = get_view_matrix(&world, camera_entity, camera_transform, camera_component);
let view =
get_view_matrix(&world, camera_entity, camera_transform, camera_component);
let projection = camera_component.projection_matrix();
render::render_with_matrices(&view, &projection, &draw_calls, time);
render::render_with_matrices(
&view,
&projection,
camera_transform.position,
&draw_calls,
time,
);
}
}

View File

@@ -1,5 +1,5 @@
use bytemuck::{Pod, Zeroable};
use glam::{Mat4, Vec3};
use glam::{Mat4, Quat, Vec3};
use std::path::Path;
use std::rc::Rc;
@@ -42,6 +42,65 @@ impl Vertex
}
}
#[derive(Debug, Clone)]
pub struct InstanceData
{
pub position: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
impl InstanceData
{
pub fn to_raw(&self) -> InstanceRaw
{
let model = Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
InstanceRaw {
model: model.to_cols_array_2d(),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct InstanceRaw
{
pub model: [[f32; 4]; 4],
}
impl InstanceRaw
{
pub fn desc() -> wgpu::VertexBufferLayout<'static>
{
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<InstanceRaw>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 3,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (std::mem::size_of::<[f32; 4]>() * 2) as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: (std::mem::size_of::<[f32; 4]>() * 3) as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
}
pub struct Mesh
{
pub vertex_buffer: wgpu::Buffer,
@@ -411,4 +470,271 @@ impl Mesh
{
crate::render::with_device(|device| Mesh::load_gltf_mesh(device, path))
}
pub fn load_gltf_with_instances(
device: &wgpu::Device,
path: impl AsRef<Path>,
) -> anyhow::Result<Vec<(Mesh, Vec<InstanceData>)>>
{
let path = path.as_ref();
let gltf_str = std::fs::read_to_string(path)?;
let gltf_json: serde_json::Value = serde_json::from_str(&gltf_str)?;
let (document, buffers, _images) = gltf::import(path)?;
let mut result = Vec::new();
let nodes = gltf_json["nodes"]
.as_array()
.ok_or_else(|| anyhow::anyhow!("Missing nodes array"))?;
for (node_index, json_node) in nodes.iter().enumerate()
{
let node = document
.nodes()
.nth(node_index)
.ok_or_else(|| anyhow::anyhow!("Node index mismatch"))?;
if let Some(mesh_data) = node.mesh()
{
let has_instancing = json_node
.get("extensions")
.and_then(|ext| ext.get("EXT_mesh_gpu_instancing"))
.is_some();
if has_instancing
{
let extensions = json_node.get("extensions").unwrap();
let instancing_ext = extensions.get("EXT_mesh_gpu_instancing").unwrap();
let mut mesh_vertices = Vec::new();
let mut mesh_indices = Vec::new();
for primitive in mesh_data.primitives()
{
let reader = primitive
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
let positions = reader
.read_positions()
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
.collect::<Vec<[f32; 3]>>();
let normals = reader
.read_normals()
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
.collect::<Vec<[f32; 3]>>();
let uvs = reader
.read_tex_coords(0)
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let base_index = mesh_vertices.len() as u32;
for ((pos, normal), uv) in
positions.iter().zip(normals.iter()).zip(uvs.iter())
{
mesh_vertices.push(Vertex {
position: *pos,
normal: *normal,
uv: *uv,
});
}
if let Some(indices_reader) = reader.read_indices()
{
mesh_indices
.extend(indices_reader.into_u32().map(|i| i + base_index));
}
}
let attributes = instancing_ext
.get("attributes")
.and_then(|v| v.as_object())
.ok_or_else(|| anyhow::anyhow!("Missing attributes in EXT_mesh_gpu_instancing"))?;
let translation_accessor_index = attributes
.get("TRANSLATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing TRANSLATION in instancing extension"))? as usize;
let rotation_accessor_index = attributes
.get("ROTATION")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing ROTATION in instancing extension"))? as usize;
let scale_accessor_index = attributes
.get("SCALE")
.and_then(|v| v.as_u64())
.ok_or_else(|| anyhow::anyhow!("Missing SCALE in instancing extension"))? as usize;
let translations = Self::read_vec3_accessor(
&document,
&buffers,
translation_accessor_index,
)?;
let rotations =
Self::read_quat_accessor(&document, &buffers, rotation_accessor_index)?;
let scales =
Self::read_vec3_accessor(&document, &buffers, scale_accessor_index)?;
let instances: Vec<InstanceData> = translations
.into_iter()
.zip(rotations.into_iter())
.zip(scales.into_iter())
.map(|((position, rotation), scale)| InstanceData {
position,
rotation,
scale,
})
.collect();
let mesh = Mesh::new(device, &mesh_vertices, &mesh_indices);
result.push((mesh, instances));
}
else
{
let mut mesh_vertices = Vec::new();
let mut mesh_indices = Vec::new();
for primitive in mesh_data.primitives()
{
let reader = primitive
.reader(|buffer| buffers.get(buffer.index()).map(|data| &data[..]));
let positions = reader
.read_positions()
.ok_or_else(|| anyhow::anyhow!("Missing position data"))?
.collect::<Vec<[f32; 3]>>();
let normals = reader
.read_normals()
.ok_or_else(|| anyhow::anyhow!("Missing normal data"))?
.collect::<Vec<[f32; 3]>>();
let uvs = reader
.read_tex_coords(0)
.map(|iter| iter.into_f32().collect::<Vec<[f32; 2]>>())
.unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
let base_index = mesh_vertices.len() as u32;
for ((pos, normal), uv) in
positions.iter().zip(normals.iter()).zip(uvs.iter())
{
mesh_vertices.push(Vertex {
position: *pos,
normal: *normal,
uv: *uv,
});
}
if let Some(indices_reader) = reader.read_indices()
{
mesh_indices.extend(indices_reader.into_u32().map(|i| i + base_index));
}
}
let mesh = Mesh::new(device, &mesh_vertices, &mesh_indices);
result.push((mesh, Vec::new()));
}
}
}
Ok(result)
}
fn read_vec3_accessor(
document: &gltf::Document,
buffers: &[gltf::buffer::Data],
accessor_index: usize,
) -> anyhow::Result<Vec<Vec3>>
{
let accessor = document
.accessors()
.nth(accessor_index)
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
let buffer_view = accessor.view().ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer = &buffers[buffer_view.buffer().index()];
let start = buffer_view.offset() + accessor.offset();
let stride = buffer_view.stride().unwrap_or(12);
let mut result = Vec::new();
for i in 0..accessor.count()
{
let offset = start + i * stride;
let x = f32::from_le_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
]);
let y = f32::from_le_bytes([
buffer[offset + 4],
buffer[offset + 5],
buffer[offset + 6],
buffer[offset + 7],
]);
let z = f32::from_le_bytes([
buffer[offset + 8],
buffer[offset + 9],
buffer[offset + 10],
buffer[offset + 11],
]);
result.push(Vec3::new(x, y, z));
}
Ok(result)
}
fn read_quat_accessor(
document: &gltf::Document,
buffers: &[gltf::buffer::Data],
accessor_index: usize,
) -> anyhow::Result<Vec<Quat>>
{
let accessor = document
.accessors()
.nth(accessor_index)
.ok_or_else(|| anyhow::anyhow!("Invalid accessor index"))?;
let buffer_view = accessor.view().ok_or_else(|| anyhow::anyhow!("Missing buffer view"))?;
let buffer = &buffers[buffer_view.buffer().index()];
let start = buffer_view.offset() + accessor.offset();
let stride = buffer_view.stride().unwrap_or(16);
let mut result = Vec::new();
for i in 0..accessor.count()
{
let offset = start + i * stride;
let x = f32::from_le_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
]);
let y = f32::from_le_bytes([
buffer[offset + 4],
buffer[offset + 5],
buffer[offset + 6],
buffer[offset + 7],
]);
let z = f32::from_le_bytes([
buffer[offset + 8],
buffer[offset + 9],
buffer[offset + 10],
buffer[offset + 11],
]);
let w = f32::from_le_bytes([
buffer[offset + 12],
buffer[offset + 13],
buffer[offset + 14],
buffer[offset + 15],
]);
result.push(Quat::from_xyzw(x, y, z, w));
}
Ok(result)
}
}

View File

@@ -179,6 +179,8 @@ impl Player
MeshComponent {
mesh: Rc::new(mesh),
pipeline: Pipeline::Render,
instance_buffer: None,
num_instances: 1,
},
);
world.player_tags.insert(entity);

View File

@@ -3,6 +3,7 @@ use crate::mesh::Mesh;
use crate::postprocess::{create_blit_pipeline, create_fullscreen_quad, LowResFramebuffer};
use crate::shader::create_render_pipeline;
use crate::terrain::create_terrain_render_pipeline;
use crate::texture_loader::{DitherTextures, FlowmapTexture};
use crate::utility::transform::Transform;
use bytemuck::{Pod, Zeroable};
use glam::Mat4;
@@ -16,22 +17,42 @@ struct TerrainUniforms
model: [[f32; 4]; 4],
view: [[f32; 4]; 4],
projection: [[f32; 4]; 4],
light_view_projection: [[f32; 4]; 4],
camera_position: [f32; 3],
height_scale: f32,
time: f32,
_padding: [f32; 2],
shadow_bias: f32,
_padding1: [f32; 2],
light_direction: [f32; 3],
_padding2: u32,
}
impl TerrainUniforms
{
fn new(model: Mat4, view: Mat4, projection: Mat4, height_scale: f32, time: f32) -> Self
fn new(
model: Mat4,
view: Mat4,
projection: Mat4,
light_view_projection: Mat4,
camera_position: glam::Vec3,
height_scale: f32,
time: f32,
shadow_bias: f32,
light_direction: glam::Vec3,
) -> Self
{
Self {
model: model.to_cols_array_2d(),
view: view.to_cols_array_2d(),
projection: projection.to_cols_array_2d(),
light_view_projection: light_view_projection.to_cols_array_2d(),
camera_position: camera_position.to_array(),
height_scale,
time,
_padding: [0.0; 2],
shadow_bias,
_padding1: [0.0; 2],
light_direction: light_direction.to_array(),
_padding2: 0,
}
}
}
@@ -51,13 +72,8 @@ pub struct DrawCall
pub num_indices: u32,
pub model: Mat4,
pub pipeline: Pipeline,
}
pub struct TerrainData
{
pub height_texture: wgpu::Texture,
pub height_view: wgpu::TextureView,
pub height_sampler: wgpu::Sampler,
pub instance_buffer: Option<wgpu::Buffer>,
pub num_instances: u32,
}
pub struct Renderer
@@ -85,7 +101,25 @@ pub struct Renderer
terrain_bind_group: Option<wgpu::BindGroup>,
terrain_height_scale: f32,
shadow_pipeline: Option<wgpu::RenderPipeline>,
shadow_bind_group_layout: wgpu::BindGroupLayout,
shadow_bind_group: Option<wgpu::BindGroup>,
wireframe_pipeline: wgpu::RenderPipeline,
pub light_direction: glam::Vec3,
pub shadow_focus_point: glam::Vec3,
pub shadow_ortho_size: f32,
pub shadow_distance: f32,
pub shadow_bias: f32,
shadow_map_texture: wgpu::Texture,
shadow_map_view: wgpu::TextureView,
shadow_map_sampler: wgpu::Sampler,
shadow_map_size: u32,
dither_textures: Option<DitherTextures>,
flowmap_texture: Option<FlowmapTexture>,
}
impl Renderer
@@ -140,34 +174,181 @@ impl Renderer
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Uniform Buffer"),
size: std::mem::size_of::<CameraUniforms>() as wgpu::BufferAddress,
size: std::mem::size_of::<TerrainUniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
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,
}],
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 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: 1,
},
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 bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Bind Group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
let shadow_map_view = shadow_map_texture.create_view(&wgpu::TextureViewDescriptor::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 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::D2,
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: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 6,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let bind_group = if let (Some(ref dither_tex), Some(ref flowmap)) = (&dither_textures, &flowmap_texture)
{
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Bind Group"),
layout: &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_tex.view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::Sampler(&dither_tex.sampler),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(&flowmap.view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::Sampler(&flowmap.sampler),
},
],
})
}
else
{
panic!("Cannot create renderer without dither textures and flowmap");
};
let render_pipeline = create_render_pipeline(&device, &config, &bind_group_layout);
let (quad_vb, quad_ib, quad_num_indices) = create_fullscreen_quad(&device);
@@ -212,9 +393,9 @@ impl Renderer
let blit_pipeline = create_blit_pipeline(&device, config.format, &blit_bind_group_layout);
let terrain_bind_group_layout =
let shadow_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Terrain Bind Group Layout"),
label: Some("Shadow Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
@@ -226,32 +407,9 @@ impl Renderer
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
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: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering),
count: None,
},
],
});
let terrain_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Terrain Uniform Buffer"),
size: std::mem::size_of::<TerrainUniforms>() as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let wireframe_pipeline =
create_wireframe_pipeline(&device, config.format, &bind_group_layout);
@@ -262,19 +420,33 @@ impl Renderer
config,
framebuffer,
render_pipeline,
uniform_buffer,
bind_group,
uniform_buffer: uniform_buffer.clone(),
bind_group: bind_group.clone(),
quad_vb,
quad_ib,
quad_num_indices,
blit_pipeline,
blit_bind_group,
terrain_pipeline: None,
terrain_bind_group_layout,
terrain_uniform_buffer,
terrain_bind_group: None,
terrain_bind_group_layout: bind_group_layout,
terrain_uniform_buffer: uniform_buffer,
terrain_bind_group: Some(bind_group),
terrain_height_scale: 10.0,
shadow_pipeline: None,
shadow_bind_group_layout,
shadow_bind_group: None,
wireframe_pipeline,
light_direction: glam::Vec3::new(-1.0, -0.5, 1.0).normalize(),
shadow_focus_point: glam::Vec3::ZERO,
shadow_ortho_size: 600.0,
shadow_distance: 1000.0,
shadow_bias: 0.001,
shadow_map_texture,
shadow_map_view,
shadow_map_sampler,
shadow_map_size,
dither_textures,
flowmap_texture,
})
}
@@ -282,14 +454,28 @@ impl Renderer
{
let view = camera.view_matrix();
let projection = camera.projection_matrix();
let light_view_projection = self.calculate_light_view_projection();
self.render_shadow_pass(draw_calls, light_view_projection, time);
for (i, draw_call) in draw_calls.iter().enumerate()
{
let uniforms = TerrainUniforms::new(
draw_call.model,
view,
projection,
light_view_projection,
camera.position,
self.terrain_height_scale,
time,
self.shadow_bias,
self.light_direction,
);
match draw_call.pipeline
{
Pipeline::Render | Pipeline::Wireframe =>
{
let uniforms = CameraUniforms::new(draw_call.model, view, projection);
self.queue.write_buffer(
&self.uniform_buffer,
0,
@@ -298,13 +484,6 @@ impl Renderer
}
Pipeline::Terrain =>
{
let uniforms = TerrainUniforms::new(
draw_call.model,
view,
projection,
self.terrain_height_scale,
time,
);
self.queue.write_buffer(
&self.terrain_uniform_buffer,
0,
@@ -384,9 +563,15 @@ impl Renderer
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, 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..1);
render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..draw_call.num_instances);
}
self.queue.submit(std::iter::once(encoder.finish()));
@@ -446,17 +631,22 @@ impl Renderer
&mut self,
view: &glam::Mat4,
projection: &glam::Mat4,
camera_position: glam::Vec3,
draw_calls: &[DrawCall],
time: f32,
)
{
let light_view_projection = self.calculate_light_view_projection();
self.render_shadow_pass(draw_calls, light_view_projection, time);
for (i, draw_call) in draw_calls.iter().enumerate()
{
match draw_call.pipeline
{
Pipeline::Render | Pipeline::Wireframe =>
{
let uniforms = CameraUniforms::new(draw_call.model, *view, *projection);
let uniforms = CameraUniforms::new(draw_call.model, *view, *projection, self.light_direction);
self.queue.write_buffer(
&self.uniform_buffer,
0,
@@ -469,8 +659,12 @@ impl Renderer
draw_call.model,
*view,
*projection,
light_view_projection,
camera_position,
self.terrain_height_scale,
time,
self.shadow_bias,
self.light_direction,
);
self.queue.write_buffer(
&self.terrain_uniform_buffer,
@@ -552,11 +746,17 @@ impl Renderer
render_pass.set_bind_group(0, 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..1);
render_pass.draw_indexed(0..draw_call.num_indices, 0, 0..draw_call.num_instances);
}
self.queue.submit(std::iter::once(encoder.finish()));
@@ -620,8 +820,18 @@ impl Renderer
)
}
pub fn set_terrain_data(&mut self, terrain_data: TerrainData)
pub fn set_terrain_data(&mut self)
{
let dither_textures = self
.dither_textures
.as_ref()
.expect("Dither textures should be loaded during initialization");
let flowmap_texture = self
.flowmap_texture
.as_ref()
.expect("Flowmap texture should be loaded during initialization");
let terrain_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Terrain Bind Group"),
layout: &self.terrain_bind_group_layout,
@@ -632,11 +842,38 @@ impl Renderer
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&terrain_data.height_view),
resource: wgpu::BindingResource::TextureView(&self.shadow_map_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(&terrain_data.height_sampler),
resource: wgpu::BindingResource::Sampler(&self.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),
},
],
});
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.terrain_uniform_buffer.as_entire_binding(),
},
],
});
@@ -647,8 +884,13 @@ impl Renderer
&self.terrain_bind_group_layout,
);
let shadow_pipeline = create_shadow_pipeline(&self.device, &self.shadow_bind_group_layout);
self.terrain_bind_group = Some(terrain_bind_group);
self.terrain_pipeline = Some(terrain_pipeline);
self.shadow_bind_group = Some(shadow_bind_group);
self.shadow_pipeline = Some(shadow_pipeline);
self.terrain_height_scale = 1.0;
}
pub fn get_device(&self) -> &wgpu::Device
@@ -660,6 +902,119 @@ impl Renderer
{
self.config.width as f32 / self.config.height as f32
}
fn render_shadow_pass(
&mut self,
draw_calls: &[DrawCall],
light_view_projection: Mat4,
time: f32,
)
{
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: &self.shadow_map_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()
{
if !matches!(draw_call.pipeline, Pipeline::Terrain)
{
continue;
}
let uniforms = TerrainUniforms::new(
draw_call.model,
Mat4::IDENTITY,
light_view_projection,
light_view_projection,
glam::Vec3::ZERO,
self.terrain_height_scale,
time,
self.shadow_bias,
self.light_direction,
);
self.queue.write_buffer(
&self.terrain_uniform_buffer,
0,
bytemuck::cast_slice(&[uniforms]),
);
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()));
}
fn calculate_light_view_projection(&self) -> Mat4
{
let light_dir = self.light_direction.normalize();
let light_position = self.shadow_focus_point - light_dir * self.shadow_distance;
let light_view = Mat4::look_at_rh(
light_position,
self.shadow_focus_point,
glam::Vec3::Y,
);
let far_plane = self.shadow_distance * 2.0 + 50.0;
let light_projection = Mat4::orthographic_rh(
-self.shadow_ortho_size,
self.shadow_ortho_size,
-self.shadow_ortho_size,
self.shadow_ortho_size,
0.1,
far_plane,
);
println!("Shadow Frustum - Size: {:.1}×{:.1}, Coverage: {:.1}×{:.1}, Depth: 0.1-{:.1}, Focus: {:?}, Light: {:?}",
self.shadow_ortho_size * 2.0, self.shadow_ortho_size * 2.0,
self.shadow_ortho_size, self.shadow_ortho_size,
far_plane, self.shadow_focus_point, light_position);
light_projection * light_view
}
}
thread_local! {
@@ -693,12 +1048,12 @@ where
})
}
pub fn set_terrain_data(terrain_data: TerrainData)
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(terrain_data);
renderer.set_terrain_data();
});
}
@@ -723,6 +1078,7 @@ pub fn render(camera: &Camera, draw_calls: &[DrawCall], time: f32)
pub fn render_with_matrices(
view: &glam::Mat4,
projection: &glam::Mat4,
camera_position: glam::Vec3,
draw_calls: &[DrawCall],
time: f32,
)
@@ -730,18 +1086,115 @@ pub fn render_with_matrices(
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.render_with_matrices(view, projection, draw_calls, time);
renderer.render_with_matrices(view, projection, camera_position, draw_calls, time);
});
}
pub fn set_shadow_focus_point(focus_point: glam::Vec3)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.shadow_focus_point = focus_point;
});
}
pub fn set_shadow_ortho_size(size: f32)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.shadow_ortho_size = size;
});
}
pub fn set_shadow_distance(distance: f32)
{
GLOBAL_RENDERER.with(|r| {
let mut renderer = r.borrow_mut();
let renderer = renderer.as_mut().expect("Renderer not set");
renderer.shadow_distance = distance;
});
}
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;
});
}
fn create_shadow_pipeline(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let terrain_source =
std::fs::read_to_string("shaders/terrain.wgsl").expect("Failed to read terrain shader");
let shader_source = format!("{}\n{}", shared_source, terrain_source);
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::mesh::Vertex::desc(), crate::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,
})
}
fn create_wireframe_pipeline(
device: &wgpu::Device,
format: wgpu::TextureFormat,
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shader_source =
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let standard_source =
std::fs::read_to_string("shaders/standard.wgsl").expect("Failed to read shader");
let shader_source = format!("{}\n{}", shared_source, standard_source);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Wireframe Shader"),
@@ -760,7 +1213,7 @@ fn create_wireframe_pipeline(
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[crate::mesh::Vertex::desc()],
buffers: &[crate::mesh::Vertex::desc(), crate::mesh::InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {

View File

@@ -1,4 +1,4 @@
use crate::mesh::Vertex;
use crate::mesh::{InstanceRaw, Vertex};
pub fn create_render_pipeline(
device: &wgpu::Device,
@@ -6,8 +6,11 @@ pub fn create_render_pipeline(
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shader_source =
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let standard_source =
std::fs::read_to_string("shaders/standard.wgsl").expect("Failed to read standard shader");
let shader_source = format!("{}\n{}", shared_source, standard_source);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"),
@@ -26,7 +29,7 @@ pub fn create_render_pipeline(
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc()],
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {

View File

@@ -126,7 +126,7 @@ pub fn camera_noclip_system(world: &mut World, input_state: &InputState, delta:
}
if input_state.space
{
input_vec.y += 1.0;
input_vec.y += 10.0;
}
if input_vec.length_squared() > 0.0
@@ -137,7 +137,7 @@ pub fn camera_noclip_system(world: &mut World, input_state: &InputState, delta:
let mut speed = 10.0 * delta;
if input_state.shift
{
speed *= 2.0;
speed *= 10.0;
}
if let Some(camera_transform) = world.transforms.get_mut(camera_entity)

View File

@@ -1,5 +1,9 @@
use crate::mesh::InstanceRaw;
use crate::render::DrawCall;
use crate::world::World;
use bytemuck::cast_slice;
use glam::Mat4;
use wgpu::util::DeviceExt;
pub fn render_system(world: &World) -> Vec<DrawCall>
{
@@ -11,12 +15,38 @@ pub fn render_system(world: &World) -> Vec<DrawCall>
let transform = world.transforms.get(entity)?;
let mesh_component = world.meshes.get(entity)?;
let model_matrix = transform.to_matrix();
let (instance_buffer, num_instances) = if let Some(ref buffer) =
mesh_component.instance_buffer
{
(Some(buffer.clone()), mesh_component.num_instances)
}
else
{
let instance_data = InstanceRaw {
model: model_matrix.to_cols_array_2d(),
};
let buffer = crate::render::with_device(|device| {
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Instance Buffer"),
contents: cast_slice(&[instance_data]),
usage: wgpu::BufferUsages::VERTEX,
})
});
(Some(buffer), 1)
};
Some(DrawCall {
vertex_buffer: mesh_component.mesh.vertex_buffer.clone(),
index_buffer: mesh_component.mesh.index_buffer.clone(),
num_indices: mesh_component.mesh.num_indices,
model: transform.to_matrix(),
model: model_matrix,
pipeline: mesh_component.pipeline,
instance_buffer,
num_instances,
})
})
.collect()

View File

@@ -1,7 +1,7 @@
use std::rc::Rc;
use exr::prelude::{ReadChannels, ReadLayers};
use glam::{Vec2, Vec3};
use glam::Vec2;
use nalgebra::{vector, DMatrix};
use rapier3d::{
math::Isometry,
@@ -11,75 +11,152 @@ use rapier3d::{
use crate::{
components::{MeshComponent, PhysicsComponent},
entity::EntityHandle,
mesh::{Mesh, Vertex},
mesh::{InstanceRaw, Mesh, Vertex},
physics::PhysicsManager,
render,
world::{Transform, World},
};
pub struct TerrainConfig
{
pub gltf_path: String,
pub heightmap_path: String,
pub size: Vec2,
}
impl TerrainConfig
{
pub fn new(gltf_path: &str, heightmap_path: &str, size: Vec2) -> Self
{
Self {
gltf_path: gltf_path.to_string(),
heightmap_path: heightmap_path.to_string(),
size,
}
}
pub fn default() -> Self
{
Self {
gltf_path: "meshes/terrain.gltf".to_string(),
heightmap_path: "textures/terrain.exr".to_string(),
size: Vec2::new(1000.0, 1000.0),
}
}
}
pub struct Terrain;
impl Terrain
{
pub fn spawn(
world: &mut World,
heightmap_path: &str,
height_scale: f32,
) -> anyhow::Result<EntityHandle>
pub fn spawn(world: &mut World, config: &TerrainConfig) -> anyhow::Result<EntityHandle>
{
let entity = world.spawn();
let plane_size = Vec2::new(100.0, 100.0);
let plane_mesh = render::with_device(|device| {
Mesh::create_plane_mesh(device, plane_size.x, plane_size.y, 100, 100)
});
let gltf_data = render::with_device(|device| {
Mesh::load_gltf_with_instances(device, &config.gltf_path)
})?;
let terrain_entity = world.spawn();
let transform = Transform::IDENTITY;
world.transforms.insert(entity, transform);
world.meshes.insert(
entity,
MeshComponent {
mesh: Rc::new(plane_mesh),
pipeline: render::Pipeline::Terrain,
},
);
let mut terrain_mesh = None;
let mut tree_mesh = None;
let mut tree_instances = None;
let heights = Self::load_heightfield_data(heightmap_path)?;
for (mesh, instances) in gltf_data
{
if instances.is_empty()
{
if terrain_mesh.is_none()
{
terrain_mesh = Some(mesh);
}
}
else
{
tree_mesh = Some(mesh);
tree_instances = Some(instances);
}
}
println!(
"Heightmap dimensions: {} rows × {} cols",
heights.nrows(),
heights.ncols()
);
if let Some(terrain_mesh) = terrain_mesh
{
world.transforms.insert(terrain_entity, transform);
world.meshes.insert(
terrain_entity,
MeshComponent {
mesh: Rc::new(terrain_mesh),
pipeline: render::Pipeline::Terrain,
instance_buffer: None,
num_instances: 1,
},
);
let scale = vector![plane_size.x, height_scale, plane_size.y,];
let heights = Self::load_heightfield_from_exr(&config.heightmap_path)?;
let body = RigidBodyBuilder::fixed()
.translation(transform.get_position().into())
.build();
println!(
"Loaded terrain: {} rows × {} cols heightfield from EXR",
heights.nrows(),
heights.ncols()
);
let rigidbody_handle = PhysicsManager::add_rigidbody(body);
let height_scale = 1.0;
let scale = vector![config.size.x, height_scale, config.size.y];
let collider = ColliderBuilder::heightfield(heights.clone(), scale).build();
let body = RigidBodyBuilder::fixed()
.translation(transform.get_position().into())
.build();
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
let rigidbody_handle = PhysicsManager::add_rigidbody(body);
PhysicsManager::set_heightfield_data(heights, scale, transform.get_position().into());
let collider = ColliderBuilder::heightfield(heights.clone(), scale).build();
world.physics.insert(
entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
let collider_handle = PhysicsManager::add_collider(collider, Some(rigidbody_handle));
Ok(entity)
PhysicsManager::set_heightfield_data(heights, scale, transform.get_position().into());
world.physics.insert(
terrain_entity,
PhysicsComponent {
rigidbody: rigidbody_handle,
collider: Some(collider_handle),
},
);
}
if let (Some(tree_mesh), Some(instances)) = (tree_mesh, tree_instances)
{
let num_instances = instances.len();
println!("Loaded {} tree instances", num_instances);
let tree_entity = world.spawn();
let instance_raw: Vec<InstanceRaw> = instances.iter().map(|i| i.to_raw()).collect();
let instance_buffer = render::with_device(|device| {
use wgpu::util::DeviceExt;
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Tree Instance Buffer"),
contents: bytemuck::cast_slice(&instance_raw),
usage: wgpu::BufferUsages::VERTEX,
})
});
world.transforms.insert(tree_entity, Transform::IDENTITY);
world.meshes.insert(
tree_entity,
MeshComponent {
mesh: Rc::new(tree_mesh),
pipeline: render::Pipeline::Render,
instance_buffer: Some(instance_buffer),
num_instances: num_instances as u32,
},
);
}
Ok(terrain_entity)
}
fn load_heightfield_data(path: &str) -> anyhow::Result<DMatrix<f32>>
fn load_heightfield_from_exr(path: &str) -> anyhow::Result<DMatrix<f32>>
{
let image = exr::prelude::read()
.no_deep_data()
@@ -107,8 +184,11 @@ pub fn create_terrain_render_pipeline(
bind_group_layout: &wgpu::BindGroupLayout,
) -> wgpu::RenderPipeline
{
let shader_source =
let shared_source =
std::fs::read_to_string("shaders/shared.wgsl").expect("Failed to read shared shader");
let terrain_source =
std::fs::read_to_string("shaders/terrain.wgsl").expect("Failed to read terrain shader");
let shader_source = format!("{}\n{}", shared_source, terrain_source);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Terrain Shader"),
@@ -127,7 +207,7 @@ pub fn create_terrain_render_pipeline(
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Vertex::desc()],
buffers: &[Vertex::desc(), InstanceRaw::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {

238
src/texture_loader.rs Normal file
View File

@@ -0,0 +1,238 @@
use anyhow::Result;
use exr::prelude::{ReadChannels, ReadLayers};
use half::f16;
pub struct DitherTextures
{
pub texture_array: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
pub struct FlowmapTexture
{
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl DitherTextures
{
pub fn load_octaves(device: &wgpu::Device, queue: &wgpu::Queue) -> Result<Self>
{
let octave_paths = [
"textures/dither/octave_0.png",
"textures/dither/octave_1.png",
"textures/dither/octave_2.png",
"textures/dither/octave_3.png",
];
let mut images = Vec::new();
let mut texture_size = 0;
for path in &octave_paths
{
let img = image::open(path)?.to_luma8();
let (width, height) = img.dimensions();
if texture_size == 0
{
texture_size = width;
}
else if width != texture_size || height != texture_size
{
return Err(anyhow::anyhow!(
"All dither textures must be the same size. Expected {}x{}, got {}x{}",
texture_size,
texture_size,
width,
height
));
}
if width != height
{
return Err(anyhow::anyhow!(
"Dither textures must be square. Got {}x{}",
width,
height
));
}
images.push(img);
}
let texture_array = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Dither Texture Array"),
size: wgpu::Extent3d {
width: texture_size,
height: texture_size,
depth_or_array_layers: 4,
},
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: &[],
});
for (i, img) in images.iter().enumerate()
{
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture_array,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: i as u32,
},
aspect: wgpu::TextureAspect::All,
},
img.as_raw(),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(texture_size),
rows_per_image: Some(texture_size),
},
wgpu::Extent3d {
width: texture_size,
height: texture_size,
depth_or_array_layers: 1,
},
);
}
let view = texture_array.create_view(&wgpu::TextureViewDescriptor {
label: Some("Dither Texture Array View"),
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Dither Sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Ok(Self {
texture_array,
view,
sampler,
})
}
}
impl FlowmapTexture
{
pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, path: &str) -> Result<Self>
{
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 width = layer.size.width();
let height = layer.size.height();
if width != height
{
return Err(anyhow::anyhow!(
"Flowmap texture must be square. Got {}x{}",
width,
height
));
}
let mut rgba_data: Vec<f32> = vec![1.0; width * height * 4];
for channel in &layer.channel_data.list
{
let channel_name = channel.name.to_string();
let values: Vec<f32> = channel.sample_data.values_as_f32().collect();
let target_channel = match channel_name.as_str()
{
"R" => 0,
"G" => 1,
"B" => 2,
"A" => 3,
_ => continue,
};
for (i, &value) in values.iter().enumerate()
{
rgba_data[i * 4 + target_channel] = value;
}
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Flowmap Texture"),
size: wgpu::Extent3d {
width: width as u32,
height: height as u32,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba16Float,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let rgba_data_f16: Vec<u16> = rgba_data
.iter()
.map(|&f| f16::from_f32(f).to_bits())
.collect();
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
bytemuck::cast_slice(&rgba_data_f16),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(8 * width as u32),
rows_per_image: Some(height as u32),
},
wgpu::Extent3d {
width: width as u32,
height: height as u32,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Flowmap Sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Ok(Self {
texture,
view,
sampler,
})
}
}