diff --git a/readme.md b/readme.md index 76296f7..07e6b42 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ # doing the wgpu-rs tutorial -[current chapter](https://sotrh.github.io/learn-wgpu/intermediate/tutorial11-normals/#tangent-space-to-world-space) +[current chapter](https://sotrh.github.io/learn-wgpu/intermediate/tutorial12-camera/) ## stuff to maybe do after the tutorial is done * [glsl preprocessor using tera](https://codecrash.me/an-opengl-preprocessor-for-rust) -* [line drawing](https://vladjuckov.github.io/hqlines/) \ No newline at end of file +* [line drawing](https://vladjuckov.github.io/hqlines/) diff --git a/src/main.rs b/src/main.rs index 861cb74..4f230f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,6 +184,7 @@ impl State { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("texture_bind_group_layout"), entries: &[ + // Diffuse texture wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStage::FRAGMENT, @@ -200,6 +201,23 @@ impl State { ty: wgpu::BindingType::Sampler { comparison: false }, count: None, }, + // Normal map texture + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + dimension: wgpu::TextureViewDimension::D2, + component_type: wgpu::TextureComponentType::Uint, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { comparison: false }, + count: None, + }, ], }); diff --git a/src/model.rs b/src/model.rs index fd64483..ef988a0 100644 --- a/src/model.rs +++ b/src/model.rs @@ -14,6 +14,8 @@ pub struct ModelVertex { position: cgmath::Vector3, tex_coords: cgmath::Vector2, normal: cgmath::Vector3, + tangent: cgmath::Vector3, + bitangent: cgmath::Vector3, } impl Vertex for ModelVertex { @@ -29,17 +31,26 @@ impl Vertex for ModelVertex { format: wgpu::VertexFormat::Float3, }, wgpu::VertexAttributeDescriptor { - offset: mem::size_of::>() as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float2, }, wgpu::VertexAttributeDescriptor { - offset: (mem::size_of::>() - + mem::size_of::>()) - as wgpu::BufferAddress, + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, shader_location: 2, format: wgpu::VertexFormat::Float3, }, + // Tangent & Bitangent + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, + shader_location: 3, + format: wgpu::VertexFormat::Float3, + }, + wgpu::VertexAttributeDescriptor { + offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, + shader_location: 4, + format: wgpu::VertexFormat::Float3, + }, ], } } @@ -51,9 +62,50 @@ unsafe impl bytemuck::Zeroable for ModelVertex {} pub struct Material { pub name: String, pub diffuse_texture: texture::Texture, + pub normal_texture: texture::Texture, pub bind_group: wgpu::BindGroup, } +impl Material { + pub fn new( + device: &wgpu::Device, + name: &str, + diffuse_texture: texture::Texture, + normal_texture: texture::Texture, + layout: &wgpu::BindGroupLayout, + ) -> Self { + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some(name), + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(&normal_texture.view), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::Sampler(&normal_texture.sampler), + }, + ], + }); + + Material { + name: name.to_string(), + diffuse_texture, + normal_texture, + bind_group, + } + } +} + pub struct Mesh { pub name: String, pub vertex_buffer: wgpu::Buffer, @@ -85,28 +137,22 @@ impl Model { device, queue, containing_folder.join(mat.diffuse_texture), + false, + )?; + let normal_texture = texture::Texture::load( + device, + queue, + containing_folder.join(mat.normal_texture), + true, )?; - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - }); - - Ok(Material { - name: mat.name, + Ok(Material::new( + device, + &mat.name, diffuse_texture, - bind_group, - }) + normal_texture, + layout, + )) }) .collect(); @@ -129,9 +175,35 @@ impl Model { m.mesh.normals[i * 3 + 2], ) .into(), + tangent: [0.0; 3].into(), + bitangent: [0.0; 3].into(), }); } + for c in m.mesh.indices.chunks(3) { + let v0 = vertices[c[0] as usize]; + let v1 = vertices[c[1] as usize]; + let v2 = vertices[c[2] as usize]; + + let delta_pos1 = v1.position - v0.position; + let delta_pos2 = v2.position - v0.position; + + let delta_uv1 = v1.tex_coords - v0.tex_coords; + let delta_uv2 = v2.tex_coords - v0.tex_coords; + + 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; + + vertices[c[0] as usize].tangent = tangent; + vertices[c[1] as usize].tangent = tangent; + vertices[c[2] as usize].tangent = tangent; + + vertices[c[0] as usize].bitangent = bitangent; + vertices[c[1] as usize].bitangent = bitangent; + vertices[c[2] as usize].bitangent = bitangent; + } + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&format!("{:?} Vertex Buffer", path.as_ref())), contents: bytemuck::cast_slice(&vertices), diff --git a/src/shader.frag b/src/shader.frag index 611786b..ac7d54a 100644 --- a/src/shader.frag +++ b/src/shader.frag @@ -1,13 +1,16 @@ #version 450 layout(location=0) in vec2 v_tex_coords; -layout(location=1) in vec3 v_normal; -layout(location=2) in vec3 v_position; +layout(location=1) in vec3 v_position; +layout(location=2) in vec3 v_light_position; +layout(location=3) in vec3 v_view_position; layout(location=0) out vec4 f_color; layout(set = 0, binding = 0) uniform texture2D t_diffuse; layout(set = 0, binding = 1) uniform sampler s_diffuse; +layout(set = 0, binding = 2) uniform texture2D t_normal; +layout(set = 0, binding = 3) uniform sampler s_normal; layout(set=1, binding=0) uniform Uniforms { @@ -22,11 +25,13 @@ layout(set=2, binding=0) uniform Light { void main() { vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords); - vec3 normal = normalize(v_normal); - vec3 light_dir = normalize(light_position - v_position); - vec3 view_dir = normalize(u_view_position - v_position); + vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords); + vec3 normal = normalize(object_normal.rgb); + vec3 light_dir = normalize(v_light_position - v_position); + vec3 view_dir = normalize(v_view_position - v_position); vec3 half_dir = normalize(view_dir + light_dir); + float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32); vec3 specular_color = specular_strength * light_color; diff --git a/src/shader.vert b/src/shader.vert index f30bd9b..7cff54e 100644 --- a/src/shader.vert +++ b/src/shader.vert @@ -3,10 +3,13 @@ layout(location=0) in vec3 a_position; layout(location=1) in vec2 a_tex_coords; layout(location=2) in vec3 a_normal; +layout(location=3) in vec3 a_tangent; +layout(location=4) in vec3 a_bitangent; layout(location=0) out vec2 v_tex_coords; -layout(location=1) out vec3 v_normal; -layout(location=2) out vec3 v_position; +layout(location=1) out vec3 v_position; +layout(location=2) out vec3 v_light_position; +layout(location=3) out vec3 v_view_position; layout(set=1, binding=0) uniform Uniforms { vec3 u_view_position; @@ -17,12 +20,30 @@ layout(set=1, binding=1) buffer Instances { mat4 s_models[]; }; +layout(set=2, binding=0) uniform Light { + vec3 light_position; + vec3 light_color; +}; + void main() { v_tex_coords = a_tex_coords; mat4 model_matrix = s_models[gl_InstanceIndex]; mat3 normal_matrix = mat3(transpose(inverse(model_matrix))); // TODO: calculate this on CPU and pass in as an uniform + + vec3 normal = normalize(normal_matrix * a_normal); + vec3 tangent = normalize(normal_matrix * a_tangent); + vec3 bitangent = normalize(normal_matrix * a_bitangent); + + mat3 tangent_matrix = transpose(mat3( + tangent, + bitangent, + normal + )); + vec4 model_space = model_matrix * vec4(a_position, 1.0); - v_normal = normal_matrix * a_normal; - v_position = model_space.xyz; + + v_position = tangent_matrix * model_space.xyz; + v_light_position = tangent_matrix * light_position; + v_view_position = tangent_matrix * u_view_position; gl_Position = u_view_proj * model_space; } diff --git a/src/snubben.png b/src/snubben.png deleted file mode 100644 index 7a62366..0000000 Binary files a/src/snubben.png and /dev/null differ diff --git a/src/texture.rs b/src/texture.rs index cb1c21a..86d629f 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -14,16 +14,18 @@ impl Texture { device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8], + is_normal_map: bool, label: &str, ) -> Result { let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label)) + Self::from_image(device, queue, &img, is_normal_map, Some(label)) } pub fn from_image( device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, + is_normal_map: bool, label: Option<&str>, ) -> Result { let rgba = img.to_rgba(); @@ -41,7 +43,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::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, }); @@ -147,12 +153,13 @@ impl Texture { device: &wgpu::Device, queue: &wgpu::Queue, path: P, + is_normal_map: bool, ) -> Result { // Needed to appease the borrow checker let path_copy = path.as_ref().to_path_buf(); let label = path_copy.to_str(); let img = image::open(path)?; - Self::from_image(device, queue, &img, label) + Self::from_image(device, queue, &img, is_normal_map, label) } }