summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-01-19 16:44:06 +0100
committermetamuffin <metamuffin@disroot.org>2025-01-19 16:44:06 +0100
commit2d6f319dfccf6339ed1a3bbfb003b8b2dde82383 (patch)
tree4229b75a26d6e02d1eb15c84096c7020875e650e /client
parent2c737d660cab38fdf4ff3e940395df396a75f959 (diff)
downloadweareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar
weareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar.bz2
weareserver-2d6f319dfccf6339ed1a3bbfb003b8b2dde82383.tar.zst
client: normal maps
Diffstat (limited to 'client')
-rw-r--r--client/Cargo.toml2
-rw-r--r--client/src/main.rs1
-rw-r--r--client/src/meshops.rs64
-rw-r--r--client/src/renderer.rs2
-rw-r--r--client/src/scene_prepare.rs191
-rw-r--r--client/src/scene_render.rs29
-rw-r--r--client/src/shader.wgsl29
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;