diff --git a/src/core/camera.rs b/src/core/camera.rs index 1127e3f..f524083 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -49,7 +49,7 @@ impl Camera { } pub fn get_view_matrix(&self) -> cgmath::Matrix4 { - let (forward, _right, up) = self.get_vecs(); + let (_right, up, forward) = self.get_vecs(); return cgmath::Matrix4::look_to_rh(self.position, forward, up); } @@ -67,7 +67,7 @@ impl Camera { cgmath::Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize(); let right = cgmath::Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); let up = right.cross(forward); - return (forward, right, up); + return (right, up, forward); } pub fn update(&mut self, dt: Duration, controller: &CameraController) { @@ -83,7 +83,7 @@ impl Camera { self.yaw = 360.0 + self.yaw; } - let (forward, right, up) = self.get_vecs(); + let (right, up, forward) = self.get_vecs(); self.position += forward * (controller.move_forward - controller.move_backward) * controller.speed * dt; self.position += diff --git a/src/core/instance.rs b/src/core/instance.rs index 0f14037..30c60bf 100644 --- a/src/core/instance.rs +++ b/src/core/instance.rs @@ -1,3 +1,5 @@ +use super::model::Vertex; + pub struct Instance { pub position: cgmath::Vector3, pub rotation: cgmath::Quaternion, @@ -19,8 +21,8 @@ impl Instance { } } -impl InstanceRaw { - pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { +impl Vertex for InstanceRaw { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { use std::mem; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, @@ -31,14 +33,9 @@ impl InstanceRaw { attributes: &[ wgpu::VertexAttribute { offset: 0, - // While our vertex shader only uses locations 0, and 1 now, in later tutorials we'll - // be using 2, 3, and 4, for Vertex. We'll start at slot 5 not conflict with them later shader_location: 5, format: wgpu::VertexFormat::Float32x4, }, - // A mat4 takes up 4 vertex slots as it is technically 4 vec4s. We need to define a slot - // for each vec4. We'll have to reassemble the mat4 in - // the shader. wgpu::VertexAttribute { offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, shader_location: 6, diff --git a/src/core/model.rs b/src/core/model.rs index 53ecdd4..1e09a77 100644 --- a/src/core/model.rs +++ b/src/core/model.rs @@ -5,9 +5,52 @@ use crate::core::texture::Texture; pub struct Material { pub name: String, pub diffuse_texture: Texture, + pub normal_texture: Texture, pub bind_group: wgpu::BindGroup, } +impl Material { + pub fn new( + device: &wgpu::Device, + name: &str, + diffuse_texture: Texture, + normal_texture: Texture, + layout: &wgpu::BindGroupLayout, + ) -> Self { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + // diffuse + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + // normal + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&normal_texture.view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(&normal_texture.sampler), + }, + ], + label: None, + }); + + return Self { + name: String::from(name), + diffuse_texture: diffuse_texture, + normal_texture: normal_texture, + bind_group: bind_group, + }; + } +} + pub struct Mesh { pub name: String, pub vertex_buffer: wgpu::Buffer, @@ -31,6 +74,8 @@ pub struct ModelVertex { pub position: [f32; 3], pub tex_coords: [f32; 2], pub normal: [f32; 3], + pub tangent: [f32; 3], + pub bitangent: [f32; 3], } impl Vertex for ModelVertex { @@ -40,21 +85,36 @@ impl Vertex for ModelVertex { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ + // position wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x3, }, + // tex_coords wgpu::VertexAttribute { offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x2, }, + // normal wgpu::VertexAttribute { offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, shader_location: 2, format: wgpu::VertexFormat::Float32x3, }, + // tangent + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 3, + format: wgpu::VertexFormat::Float32x3, + }, + // bitangent + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, + shader_location: 4, + format: wgpu::VertexFormat::Float32x3, + }, ], } } diff --git a/src/core/resources.rs b/src/core/resources.rs index 0c9dcfd..2f5f03f 100644 --- a/src/core/resources.rs +++ b/src/core/resources.rs @@ -24,11 +24,12 @@ pub async fn load_binary(file_name: &str) -> anyhow::Result> { pub async fn load_texture( file_name: &str, + is_normal_map: bool, device: &wgpu::Device, queue: &wgpu::Queue, ) -> anyhow::Result { let data = load_binary(file_name).await?; - Texture::from_bytes(device, queue, &data, file_name) + return Texture::from_bytes(device, queue, &data, file_name, is_normal_map); } pub async fn load_model( @@ -57,33 +58,22 @@ pub async fn load_model( let mut materials = Vec::new(); for m in obj_materials? { - let diffuse_texture = load_texture(&m.diffuse_texture, device, queue).await?; - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: None, - }); + let diffuse_texture = load_texture(&m.diffuse_texture, true, device, queue).await?; + let normal_texture = load_texture(&m.normal_texture, false, device, queue).await?; - materials.push(Material { - name: m.name, + materials.push(Material::new( + device, + &m.name, diffuse_texture, - bind_group, - }) + normal_texture, + layout, + )); } let meshes = models .into_iter() .map(|m| { - let vertices = (0..m.mesh.positions.len() / 3) + let mut vertices = (0..m.mesh.positions.len() / 3) .map(|i| ModelVertex { position: [ m.mesh.positions[i * 3], @@ -96,9 +86,56 @@ pub async fn load_model( m.mesh.normals[i * 3 + 1], m.mesh.normals[i * 3 + 2], ], + tangent: [0.0; 3], + bitangent: [0.0; 3], }) .collect::>(); + let indices = &m.mesh.indices; + let mut triangles_included = vec![0; vertices.len()]; + + // tangents and bitangents from triangles + for chunk in indices.chunks(3) { + let v0 = vertices[chunk[0] as usize]; + let v1 = vertices[chunk[1] as usize]; + let v2 = vertices[chunk[2] as usize]; + + let pos0: cgmath::Vector3 = v0.position.into(); + let pos1: cgmath::Vector3 = v1.position.into(); + let pos2: cgmath::Vector3 = v2.position.into(); + + let uv0: cgmath::Vector2 = v0.tex_coords.into(); + let uv1: cgmath::Vector2 = v1.tex_coords.into(); + let uv2: cgmath::Vector2 = v2.tex_coords.into(); + + let delta_pos1 = pos1 - pos0; + let delta_pos2 = pos2 - pos0; + + let delta_uv1 = uv1 - uv0; + let delta_uv2 = uv2 - uv0; + + let r = 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv1.y * delta_uv2.x); + let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r; + let bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * -r; + + for i in 0..3 { + let sz = chunk[i] as usize; + vertices[sz].tangent = + (tangent + cgmath::Vector3::from(vertices[sz].tangent)).into(); + vertices[sz].bitangent = + (bitangent + cgmath::Vector3::from(vertices[sz].bitangent)).into(); + triangles_included[sz] += 1; + } + } + + // Average the tangents/bitangents + for (i, n) in triangles_included.into_iter().enumerate() { + let denom = 1.0 / n as f32; + let mut v = &mut vertices[i]; + v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into(); + v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into(); + } + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("{:?} Vertex Buffer", file_name)), contents: bytemuck::cast_slice(&vertices), @@ -110,13 +147,13 @@ pub async fn load_model( usage: wgpu::BufferUsages::INDEX, }); - Mesh { + return Mesh { name: file_name.to_string(), vertex_buffer, index_buffer, num_elements: m.mesh.indices.len() as u32, material: m.mesh.material_id.unwrap_or(0), - } + }; }) .collect::>(); diff --git a/src/core/state.rs b/src/core/state.rs index add42f2..0ebd34d 100644 --- a/src/core/state.rs +++ b/src/core/state.rs @@ -154,6 +154,7 @@ impl State { let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ + // diffuse wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, @@ -167,8 +168,23 @@ impl State { wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, - // This should match the filterable field of the - // corresponding Texture entry above. + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // normal + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, diff --git a/src/core/texture.rs b/src/core/texture.rs index 67db090..db7a14b 100644 --- a/src/core/texture.rs +++ b/src/core/texture.rs @@ -57,9 +57,10 @@ impl Texture { queue: &wgpu::Queue, bytes: &[u8], label: &str, + is_normal_map: bool, ) -> Result { let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label)) + return Self::from_image(device, queue, &img, Some(label), is_normal_map); } pub fn from_image( @@ -67,6 +68,7 @@ impl Texture { queue: &wgpu::Queue, img: &image::DynamicImage, label: Option<&str>, + is_normal_map: bool, ) -> Result { let rgba = img.to_rgba8(); let dimensions = img.dimensions(); @@ -82,7 +84,11 @@ impl Texture { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, + format: if is_normal_map { + wgpu::TextureFormat::Rgba8Unorm + } else { + wgpu::TextureFormat::Rgba8UnormSrgb + }, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }); diff --git a/src/shaders/test.wgsl b/src/shaders/test.wgsl index b8a262f..19073cf 100644 --- a/src/shaders/test.wgsl +++ b/src/shaders/test.wgsl @@ -1,10 +1,3 @@ -struct InstanceInput { - @location(5) model_matrix_0: vec4, - @location(6) model_matrix_1: vec4, - @location(7) model_matrix_2: vec4, - @location(8) model_matrix_3: vec4, -} - // Vertex shader struct CameraUniform { @@ -26,13 +19,23 @@ struct VertexInput { @location(0) position: vec3, @location(1) tex_coords: vec2, @location(2) normal: vec3, + @location(3) tangent: vec3, + @location(4) bitangent: vec3, +} + +struct InstanceInput { + @location(5) model_matrix_0: vec4, + @location(6) model_matrix_1: vec4, + @location(7) model_matrix_2: vec4, + @location(8) model_matrix_3: vec4, } struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) tex_coords: vec2, - @location(1) world_normal: vec3, - @location(2) world_position: vec3, + @location(1) tangent_position: vec3, + @location(2) tangent_light_position: vec3, + @location(3) tangent_view_position: vec3, } @vertex @@ -47,17 +50,23 @@ fn vs_main( instance.model_matrix_3, ); + let world_normal = normalize((model_matrix * vec4(model.normal, 0.0)).xyz); + let world_tangent = normalize((model_matrix * vec4(model.tangent, 0.0)).xyz); + let world_bitangent = normalize((model_matrix * vec4(model.bitangent, 0.0)).xyz); + let world_position = model_matrix * vec4(model.position, 1.0); + + let tangent_matrix = transpose(mat3x3( + world_tangent, + world_bitangent, + world_normal, + )); + var out: VertexOutput; - - out.tex_coords = model.tex_coords; - - out.world_normal = normalize((model_matrix * vec4(model.normal, 0.0)).xyz); - - var world_position: vec4 = model_matrix * vec4(model.position, 1.0); - out.world_position = world_position.xyz; - out.clip_position = camera.proj * camera.view * world_position; - + out.tex_coords = model.tex_coords; + out.tangent_position = tangent_matrix * world_position.xyz; + out.tangent_light_position = tangent_matrix * light.position; + out.tangent_view_position = tangent_matrix * camera.position.xyz; return out; } @@ -68,24 +77,32 @@ var t_diffuse: texture_2d; @group(0)@binding(1) var s_diffuse: sampler; +@group(0)@binding(2) +var t_normal: texture_2d; +@group(0) @binding(3) +var s_normal: sampler; + @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + let object_normal: vec4 = textureSample(t_normal, s_normal, in.tex_coords); - let light_dir = normalize(light.position - in.world_position); + // lighting vecs + let tangent_normal = object_normal.xyz * 2.0 - 1.0; + let light_dir = normalize(in.tangent_light_position - in.tangent_position); + let view_dir = normalize(in.tangent_view_position - in.tangent_position); + let half_dir = normalize(view_dir + light_dir); // ambient let ambient_strength = 0.05; let ambient_color = light.color * ambient_strength; // diffuse - let diffuse_strength = max(dot(in.world_normal, light_dir), 0.0); + let diffuse_strength = max(dot(tangent_normal, light_dir), 0.0); let diffuse_color = light.color * diffuse_strength; // specular - let view_dir = normalize(camera.position.xyz - in.world_position); - let half_dir = normalize(view_dir + light_dir); - let specular_strength = pow(max(dot(in.world_normal, half_dir), 0.0), 32.0); + let specular_strength = pow(max(dot(tangent_normal, half_dir), 0.0), 32.0); let specular_color = specular_strength * light.color; let result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;