stylized 1-bit rendering
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -8,4 +8,6 @@ pub struct MeshComponent
|
||||
{
|
||||
pub mesh: Rc<Mesh>,
|
||||
pub pipeline: Pipeline,
|
||||
pub instance_buffer: Option<wgpu::Buffer>,
|
||||
pub num_instances: u32,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
43
src/main.rs
43
src/main.rs
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
328
src/mesh.rs
328
src/mesh.rs
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
611
src/render.rs
611
src/render.rs
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
174
src/terrain.rs
174
src/terrain.rs
@@ -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
238
src/texture_loader.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user