diff options
author | metamuffin <metamuffin@disroot.org> | 2025-01-19 16:44:06 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-01-19 16:44:06 +0100 |
commit | 2d6f319dfccf6339ed1a3bbfb003b8b2dde82383 (patch) | |
tree | 4229b75a26d6e02d1eb15c84096c7020875e650e /client | |
parent | 2c737d660cab38fdf4ff3e940395df396a75f959 (diff) | |
download | weareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar weareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar.bz2 weareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar.zst |
client: normal maps
Diffstat (limited to 'client')
-rw-r--r-- | client/Cargo.toml | 2 | ||||
-rw-r--r-- | client/src/main.rs | 1 | ||||
-rw-r--r-- | client/src/meshops.rs | 64 | ||||
-rw-r--r-- | client/src/renderer.rs | 2 | ||||
-rw-r--r-- | client/src/scene_prepare.rs | 191 | ||||
-rw-r--r-- | client/src/scene_render.rs | 29 | ||||
-rw-r--r-- | client/src/shader.wgsl | 29 |
7 files changed, 248 insertions, 70 deletions
diff --git a/client/Cargo.toml b/client/Cargo.toml index 823160e..762e711 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -15,7 +15,7 @@ wgpu = "23.0.1" winit = "0.30.8" weareshared = { path = "../shared" } rand = "0.9.0-beta.1" -glam = "0.29.2" +glam = { version = "0.29.2", features = ["bytemuck"] } image = "0.25.5" egui-wgpu = "0.30.0" egui = { version = "0.30.0", features = ["bytemuck"] } diff --git a/client/src/main.rs b/client/src/main.rs index a1213d1..cd77266 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -29,6 +29,7 @@ pub mod ui; pub mod window; pub mod audio; pub mod interfaces; +pub mod meshops; use anyhow::Result; use clap::Parser; diff --git a/client/src/meshops.rs b/client/src/meshops.rs new file mode 100644 index 0000000..e76e02a --- /dev/null +++ b/client/src/meshops.rs @@ -0,0 +1,64 @@ +use glam::{Vec2, Vec3, Vec3A}; + +pub fn generate_normals(index: &[[u32; 3]], position: &[Vec3]) -> Vec<Vec3> { + let mut normal_denom = vec![0; position.len()]; + let mut normal = vec![Vec3::ZERO; position.len()]; + + for &[a, b, c] in index { + let pos_a = position[a as usize]; + let pos_b = position[b as usize]; + let pos_c = position[c as usize]; + + // TODO is this right? + let norm = (pos_b - pos_a).cross(pos_c - pos_a).normalize(); + + normal[a as usize] += norm; + normal[b as usize] += norm; + normal[c as usize] += norm; + normal_denom[a as usize] += 1; + normal_denom[b as usize] += 1; + normal_denom[c as usize] += 1; + } + for (denom, tang) in normal_denom.iter().zip(normal.iter_mut()) { + *tang /= *denom as f32; + } + + normal +} + +pub fn generate_tangents(index: &[[u32; 3]], position: &[Vec3], texcoord: &[Vec2]) -> Vec<Vec3> { + let mut tangent_denom = vec![0; position.len()]; + let mut tangent = vec![Vec3::ZERO; position.len()]; + + for &[a, b, c] in index { + let (pos_a, uv_a) = (position[a as usize], texcoord[a as usize]); + let (pos_b, uv_b) = (position[b as usize], texcoord[b as usize]); + let (pos_c, uv_c) = (position[c as usize], texcoord[c as usize]); + + let pd_ba = pos_b - pos_a; + let pd_ca = pos_c - pos_a; + let td_ba = uv_b - uv_a; + let td_ca = uv_c - uv_a; + + let face_tangent = + (pd_ba * td_ca.y - pd_ca * td_ba.y) * (td_ba.x * td_ca.y - td_ba.y * td_ca.x); + + tangent[a as usize] += face_tangent; + tangent[b as usize] += face_tangent; + tangent[c as usize] += face_tangent; + tangent_denom[a as usize] += 1; + tangent_denom[b as usize] += 1; + tangent_denom[c as usize] += 1; + } + for (denom, tang) in tangent_denom.iter().zip(tangent.iter_mut()) { + *tang /= *denom as f32; + } + + tangent +} + +pub fn generate_texcoords(index: &[[u32; 3]], position: &[Vec3A]) -> Vec<Vec2> { + let _ = (index, position); + // TODO implement equirectangular projection + todo!() +} diff --git a/client/src/renderer.rs b/client/src/renderer.rs index 01d6fc4..6a37320 100644 --- a/client/src/renderer.rs +++ b/client/src/renderer.rs @@ -73,7 +73,7 @@ impl<'a> Renderer<'a> { &DeviceDescriptor { required_features: Features::PUSH_CONSTANTS, required_limits: Limits { - max_push_constant_size: 64, + max_push_constant_size: 112, max_vertex_buffers: 16, ..Limits::default() }, diff --git a/client/src/scene_prepare.rs b/client/src/scene_prepare.rs index 5c5102d..8941fa7 100644 --- a/client/src/scene_prepare.rs +++ b/client/src/scene_prepare.rs @@ -14,12 +14,13 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ -use crate::download::Downloader; +use crate::{download::Downloader, meshops::generate_tangents}; use anyhow::Result; use egui::{Grid, Widget}; +use glam::{Vec2, Vec3, Vec3A}; use humansize::DECIMAL; use image::ImageReader; -use log::debug; +use log::{debug, trace}; use std::{ collections::{HashMap, HashSet}, hash::Hash, @@ -85,10 +86,12 @@ pub struct ScenePreparer { texture_bgl: BindGroupLayout, textures: DemandMap<Resource<Image<'static>>, (Arc<Texture>, Arc<BindGroup>)>, - placeholder_textures: DemandMap<bool, (Arc<Texture>, Arc<BindGroup>)>, + placeholder_textures: DemandMap<TextureIdentityKind, (Arc<Texture>, Arc<BindGroup>)>, index_buffers: DemandMap<Resource<Vec<[u32; 3]>>, (Arc<Buffer>, u32)>, - vertex_buffers: DemandMap<Resource<Vec<f32>>, (Arc<Buffer>, u32)>, - placeholder_vertex_buffers: DemandMap<(u32, bool), Arc<Buffer>>, + vertex_buffers: DemandMap<Resource<Vec<f32>>, Arc<Buffer>>, + generated_tangent_buffers: DemandMap<TangentBufferSpec, Arc<Buffer>>, + generated_normal_buffers: DemandMap<NormalBufferSpec, Arc<Buffer>>, + generated_texcoord_buffers: DemandMap<TexcoordBufferSpec, Arc<Buffer>>, mesh_parts: DemandMap<Resource<MeshPart>, Arc<RMeshPart>>, pub prefabs: DemandMap<Resource<Prefab>, Arc<RPrefab>>, } @@ -99,12 +102,37 @@ pub struct RMeshPart { pub index: Arc<Buffer>, pub va_position: Arc<Buffer>, pub va_normal: Arc<Buffer>, + pub va_tangent: Arc<Buffer>, pub va_texcoord: Arc<Buffer>, pub tex_albedo: Arc<BindGroup>, pub tex_normal: Arc<BindGroup>, pub double_sided: bool, } +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct TangentBufferSpec { + index: Resource<Vec<[u32; 3]>>, + position: Resource<Vec<Vec3>>, + texcoord: Resource<Vec<Vec2>>, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct NormalBufferSpec { + index: Resource<Vec<[u32; 3]>>, + position: Resource<Vec<Vec3A>>, +} +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +struct TexcoordBufferSpec { + index: Resource<Vec<[u32; 3]>>, + position: Resource<Vec<Vec3A>>, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +enum TextureIdentityKind { + Normal, + Multiply, +} + impl ScenePreparer { pub fn new(device: Arc<Device>, queue: Arc<Queue>, texture_bgl: BindGroupLayout) -> Self { Self { @@ -114,8 +142,10 @@ impl ScenePreparer { mesh_parts: DemandMap::new(), prefabs: DemandMap::new(), textures: DemandMap::new(), - placeholder_vertex_buffers: DemandMap::new(), placeholder_textures: DemandMap::new(), + generated_tangent_buffers: DemandMap::new(), + generated_normal_buffers: DemandMap::new(), + generated_texcoord_buffers: DemandMap::new(), device, queue, } @@ -140,19 +170,14 @@ impl ScenePreparer { for pres in self.index_buffers.needed() { let start = Instant::now(); if let Some(buf) = dls.try_get(pres.clone())? { - let buf = buf - .into_iter() - .flatten() - .flat_map(u32::to_le_bytes) - .collect::<Vec<_>>(); let buffer = self.device.create_buffer_init(&BufferInitDescriptor { - contents: &buf, + contents: bytemuck::cast_slice(buf.as_slice()), label: None, usage: BufferUsages::INDEX | BufferUsages::COPY_DST, }); self.index_buffers.insert( pres.clone(), - (Arc::new(buffer), (buf.len() / size_of::<u32>()) as u32), + (Arc::new(buffer), (buf.len() * 3) as u32), buf.len(), ); debug!( @@ -166,20 +191,13 @@ impl ScenePreparer { for pres in self.vertex_buffers.needed() { let start = Instant::now(); if let Some(buf) = dls.try_get(pres.clone())? { - let buf = buf - .into_iter() - .flat_map(f32::to_le_bytes) - .collect::<Vec<_>>(); let buffer = self.device.create_buffer_init(&BufferInitDescriptor { - contents: &buf, + contents: bytemuck::cast_slice(buf.as_slice()), label: None, usage: BufferUsages::VERTEX | BufferUsages::COPY_DST, }); - self.vertex_buffers.insert( - pres.clone(), - (Arc::new(buffer), (buf.len() / size_of::<f32>()) as u32), - buf.len(), - ); + self.vertex_buffers + .insert(pres.clone(), Arc::new(buffer), buf.len()); debug!( "vertex attribute buffer created (len={}, took {:?}) {pres}", buf.len() / size_of::<f32>(), @@ -214,47 +232,62 @@ impl ScenePreparer { num_done += 1; } } - for variant in self.placeholder_textures.needed() { - let v = if variant { 255 } else { 0 }; - let tex_bg = create_texture( - &self.device, - &self.queue, - &self.texture_bgl, - &[v, v, v, 255], - 1, - 1, - ); - self.placeholder_textures.insert(variant, tex_bg, 4); + for kind in self.placeholder_textures.needed() { + let color = match kind { + TextureIdentityKind::Normal => [128, 128, 255, 255], + TextureIdentityKind::Multiply => [255, 255, 255, 255], + }; + let tex_bg = create_texture(&self.device, &self.queue, &self.texture_bgl, &color, 1, 1); + self.placeholder_textures.insert(kind, tex_bg, 4); num_done += 1; } + for spec in self.generated_tangent_buffers.needed() { + if let (Some(index), Some(position), Some(texcoord)) = ( + dls.try_get(spec.index.clone())?, + dls.try_get(spec.position.clone())?, + dls.try_get(spec.texcoord.clone())?, + ) { + let tangents = generate_tangents(&index, &position, &texcoord); + let buffer = self.device.create_buffer_init(&BufferInitDescriptor { + label: None, + usage: BufferUsages::COPY_DST | BufferUsages::VERTEX, + contents: bytemuck::cast_slice(tangents.as_slice()), + }); + self.generated_tangent_buffers.insert( + spec, + Arc::new(buffer), + size_of::<f32>() * tangents.len() * 3, + ); + } + } for pres in self.mesh_parts.needed() { let start = Instant::now(); if let Some(part) = dls.try_get(pres.clone())? { if let (Some(indexres), Some(positionres)) = (part.index, part.va_position) { - let index = self.index_buffers.try_get(indexres); + let index = self.index_buffers.try_get(indexres.clone()); let position = self .vertex_buffers .try_get(Resource(positionres.0, PhantomData)); - let vertex_count = position.as_ref().map(|(_, c)| *c / 3); - let normal = if let Some(res) = part.va_normal { - self.vertex_buffers - .try_get(Resource(res.0, PhantomData)) - .map(|e| e.0) + let normal = if let Some(res) = part.va_normal.clone() { + self.vertex_buffers.try_get(Resource(res.0, PhantomData)) } else { - vertex_count - .map(|vc| self.placeholder_vertex_buffers.try_get((vc * 4, false))) - .flatten() + todo!() }; - let texcoord = if let Some(res) = part.va_texcoord { - self.vertex_buffers - .try_get(Resource(res.0, PhantomData)) - .map(|e| e.0) + let texcoord = if let Some(res) = part.va_texcoord.clone() { + self.vertex_buffers.try_get(Resource(res.0, PhantomData)) } else { - vertex_count - .map(|vc| self.placeholder_vertex_buffers.try_get((vc * 2, false))) - .flatten() + todo!() + }; + let tangent = if let Some(res) = part.va_tangent.clone() { + self.vertex_buffers.try_get(Resource(res.0, PhantomData)) + } else { + self.generated_tangent_buffers.try_get(TangentBufferSpec { + index: indexres, + position: Resource(positionres.0, PhantomData), + texcoord: part.va_texcoord.expect("TODO"), + }) }; let mut tex_albedo = None; @@ -263,7 +296,10 @@ impl ScenePreparer { tex_albedo = Some(bg) } } else { - if let Some((_tex, bg)) = self.placeholder_textures.try_get(true) { + if let Some((_tex, bg)) = self + .placeholder_textures + .try_get(TextureIdentityKind::Multiply) + { tex_albedo = Some(bg) } } @@ -273,20 +309,25 @@ impl ScenePreparer { tex_normal = Some(bg) } } else { - if let Some((_tex, bg)) = self.placeholder_textures.try_get(false) { + if let Some((_tex, bg)) = self + .placeholder_textures + .try_get(TextureIdentityKind::Normal) + { tex_normal = Some(bg) } } if let ( - Some(va_normal), Some((index, index_count)), + Some(va_normal), + Some(va_tangent), Some(va_texcoord), - Some((va_position, _)), + Some(va_position), Some(tex_normal), Some(tex_albedo), - ) = (normal, index, texcoord, position, tex_normal, tex_albedo) - { + ) = ( + index, normal, tangent, texcoord, position, tex_normal, tex_albedo, + ) { debug!("part created (took {:?}) {pres}", start.elapsed()); self.mesh_parts.insert( pres, @@ -294,6 +335,7 @@ impl ScenePreparer { index_count, index, va_normal, + va_tangent, va_position, va_texcoord, tex_albedo, @@ -307,6 +349,7 @@ impl ScenePreparer { } } } + self.print_missing(); Ok(num_done) } } @@ -373,6 +416,34 @@ impl<K, V> Widget for &DemandMap<K, V> { ui.response() } } +impl ScenePreparer { + pub fn print_missing(&self) { + fn visit<K, V>(name: &str, m: &DemandMap<K, V>) + where + K: Clone, + K: Hash, + K: std::cmp::Eq, + V: Clone, + { + let nl = m.needed().len(); + if nl > 0 { + trace!("{name}: need {nl}") + } + } + visit("prefabs", &self.prefabs); + visit("mesh_parts", &self.mesh_parts); + visit("vertex_buffers", &self.vertex_buffers); + visit("index_buffers", &self.index_buffers); + visit("placeholder_textures", &self.placeholder_textures); + visit("generated_tangent_buffers", &self.generated_tangent_buffers); + visit("generated_normal_buffers", &self.generated_normal_buffers); + visit( + "generated_texcoord_buffers", + &self.generated_texcoord_buffers, + ); + visit("textures", &self.textures); + } +} impl Widget for &ScenePreparer { fn ui(self, ui: &mut egui::Ui) -> egui::Response { @@ -389,8 +460,12 @@ impl Widget for &ScenePreparer { self.index_buffers.ui(ui); ui.label("placeholder_textures"); self.placeholder_textures.ui(ui); - ui.label("placeholder_vertex_buffers"); - self.placeholder_vertex_buffers.ui(ui); + ui.label("generated_tangent_buffers"); + self.generated_tangent_buffers.ui(ui); + ui.label("generated_normal_buffers"); + self.generated_normal_buffers.ui(ui); + ui.label("generated_texcoord_buffers"); + self.generated_texcoord_buffers.ui(ui); ui.label("textures"); self.textures.ui(ui); }) diff --git a/client/src/scene_render.rs b/client/src/scene_render.rs index 03b077c..3ec96b3 100644 --- a/client/src/scene_render.rs +++ b/client/src/scene_render.rs @@ -65,7 +65,7 @@ impl ScenePipeline { label: None, bind_group_layouts: &[&bind_group_layout, &bind_group_layout], push_constant_ranges: &[PushConstantRange { - range: 0..(4 * 4 * size_of::<f32>() as u32), + range: 0..((4 * 4 + 3 * 4) * size_of::<f32>() as u32), stages: ShaderStages::VERTEX, }], }); @@ -87,6 +87,7 @@ impl ScenePipeline { module: &module, entry_point: Some("vs_main"), buffers: &[ + // position VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 3 * size_of::<f32>() as u64, @@ -96,6 +97,7 @@ impl ScenePipeline { shader_location: 0, }], }, + // normal VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 3 * size_of::<f32>() as u64, @@ -105,13 +107,24 @@ impl ScenePipeline { shader_location: 1, }], }, + // tangent + VertexBufferLayout { + step_mode: VertexStepMode::Vertex, + array_stride: 3 * size_of::<f32>() as u64, + attributes: &[VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 2, + }], + }, + // texcoord VertexBufferLayout { step_mode: VertexStepMode::Vertex, array_stride: 2 * size_of::<f32>() as u64, attributes: &[VertexAttribute { format: VertexFormat::Float32x2, offset: 0, - shader_location: 2, + shader_location: 3, }], }, ], @@ -195,6 +208,14 @@ impl ScenePipeline { * Mat4::from_translation(affine.translation.into()) * Mat4::from_mat3a(affine.matrix3); let projection = part_projection.to_cols_array().map(|v| v.to_le_bytes()); + let mb = affine.matrix3.to_cols_array(); // TODO apply object rotation + // add padding for gpu mat3x3 repr + let model_basis = [ + mb[0], mb[1], mb[2], 0., // + mb[3], mb[4], mb[5], 0., // + mb[6], mb[7], mb[8], 0., // + ]; + let model_basis = bytemuck::cast_slice(&model_basis); let pipeline = if part.double_sided { &self.pipeline_no_cull @@ -206,10 +227,12 @@ impl ScenePipeline { rpass.set_bind_group(0, &*part.tex_albedo, &[]); rpass.set_bind_group(1, &*part.tex_normal, &[]); rpass.set_push_constants(ShaderStages::VERTEX, 0, projection.as_flattened()); + rpass.set_push_constants(ShaderStages::VERTEX, 64, model_basis); rpass.set_index_buffer(part.index.slice(..), IndexFormat::Uint32); rpass.set_vertex_buffer(0, part.va_position.slice(..)); rpass.set_vertex_buffer(1, part.va_normal.slice(..)); - rpass.set_vertex_buffer(2, part.va_texcoord.slice(..)); + rpass.set_vertex_buffer(2, part.va_tangent.slice(..)); + rpass.set_vertex_buffer(3, part.va_texcoord.slice(..)); rpass.draw_indexed(0..part.index_count, 0, 0..1); } } diff --git a/client/src/shader.wgsl b/client/src/shader.wgsl index 993c9b5..e8d3276 100644 --- a/client/src/shader.wgsl +++ b/client/src/shader.wgsl @@ -16,39 +16,54 @@ struct VertexIn { @location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, - @location(2) texcoord: vec2<f32>, + @location(2) tangent: vec3<f32>, // TODO maybe compress this + @location(3) texcoord: vec2<f32>, } struct VertexOut { @builtin(position) clip: vec4<f32>, @location(0) normal: vec3<f32>, - @location(1) texcoord: vec2<f32>, + @location(1) tangent: vec3<f32>, + @location(2) texcoord: vec2<f32>, +} + +struct PushConst { + modelview: mat4x4<f32>, + model_basis: mat3x3<f32>, } @group(0) @binding(0) var tex_albedo: texture_2d<f32>; @group(0) @binding(1) var tex_albedo_sampler: sampler; @group(1) @binding(0) var tex_normal: texture_2d<f32>; @group(1) @binding(1) var tex_normal_sampler: sampler; -var<push_constant> project: mat4x4<f32>; + var<push_constant> pc: PushConst; const LIGHT: vec3<f32> = vec3(0.64, 0.64, 0.64); @vertex fn vs_main(vi: VertexIn) -> VertexOut { - var clip = project * vec4(vi.position, 1.); - let vo = VertexOut(clip, vi.normal, vi.texcoord); + let clip = pc.modelview * vec4(vi.position, 1.); + let vo = VertexOut( + clip, + normalize(pc.model_basis * vi.normal), + normalize(pc.model_basis * vi.tangent), + vi.texcoord + ); return vo; } + @fragment fn fs_main(vo: VertexOut) -> @location(0) vec4<f32> { let t_albedo = textureSample(tex_albedo, tex_albedo_sampler, vo.texcoord); let t_normal = textureSample(tex_normal, tex_normal_sampler, vo.texcoord); + let tangent_basis = mat3x3(vo.tangent, cross(vo.tangent, vo.normal), vo.normal); + let normal = tangent_basis * normalize(t_normal.rgb * 2. - 1.); - let lighting = mix(1., saturate(dot(LIGHT, vo.normal)), 0.9); + let lighting = mix(1., saturate(dot(LIGHT, normal)), 0.9); let alpha = t_albedo.a; let color = t_albedo.rgb * lighting; - // let color = vo.normal + t_normal.rgb; + // let color = normal * 0.5 + 0.5; if fract(dot(sin(vo.clip * 123.) * 1213., vec4(3., 2., 1., 4.))) > alpha { discard; |