From 43c9111a8c3aa16254a216448d77a5d77d5681c0 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Sat, 4 Nov 2023 21:40:01 +0200 Subject: [PATCH] WIP: normal maps --- .gitignore | 1 + TODO.md | 4 + shaders/frag.glsl | 30 +++++--- shaders/vertex.glsl | 10 +++ src/aabb.rs | 64 ++++++++++++++-- src/importer/mod.rs | 5 +- src/importer/obj.rs | 182 ++++++++++++++++++++++++++++++++++++++++++-- src/importer/stl.rs | 4 + src/main.rs | 4 +- src/mesh.rs | 43 ++++++++--- src/model.rs | 4 +- src/ui/ui.rs | 12 ++- 12 files changed, 320 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 4c2e105..a10ec09 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ 3rdparty/*/target models/**/* 3dobs.log +capture-* diff --git a/TODO.md b/TODO.md index 5301928..8d32313 100644 --- a/TODO.md +++ b/TODO.md @@ -35,5 +35,9 @@ - [x] Shortcuts/keybinds window under the 'Help' menu item - [x] Texture support (basic, but it's there) - [ ] Make model loading async and show a loading indicator, instead of blocking the main thread + - [ ] Multi-thread the texture loading and then join all threads before finishing the model loading func + - [ ] (BUG) Loading the Kairi model creates a weird red specular on it, and a weird blue specular on the nier model. I imagine other models are also affected + - [ ] Add support for .fbx (if it's an easy enough format, otherwise we'll look into something else) + - [ ] Draw a gizmo thingy for the light sources in the viewport - [ ] Bring back the regular camera movement (can maybe be a switch between them) (for later) diff --git a/shaders/frag.glsl b/shaders/frag.glsl index 4af4737..28883bf 100644 --- a/shaders/frag.glsl +++ b/shaders/frag.glsl @@ -2,6 +2,7 @@ in vec3 fragNormals; in vec3 fragPos; in vec2 texCoords; +in mat3 fragTBN; out vec4 FragColor; @@ -10,6 +11,7 @@ struct Material { sampler2D texture_specular; sampler2D texture_ambient; sampler2D texture_emission; + sampler2D texture_normal; vec3 ambient; vec3 diffuse; @@ -63,6 +65,7 @@ uniform PointLight pointLights[NR_POINT_LIGHTS]; uniform SpotLight spotLight; uniform bool useTextures; uniform bool hasEmissionTexture; +uniform bool hasNormalTexture; vec3 CalculateDirLight(DirLight light, vec3 normal, vec3 viewDir) { vec3 lightDir = normalize(-light.direction); @@ -83,9 +86,9 @@ vec3 CalculateDirLight(DirLight light, vec3 normal, vec3 viewDir) { vec3 specular = vec3(0.0, 0.0, 0.0); if (useTextures) { - ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)) * material.ambient; - diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)) * material.diffuse; - specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)) * material.specular; + ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)); + diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)); + specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)); } else { ambient = light.ambient * material.ambient; diffuse = light.diffuse * (diff * material.diffuse); @@ -118,9 +121,9 @@ vec3 CalculatePointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewD vec3 specular = vec3(0.0, 0.0, 0.0); if (useTextures) { - ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)) * material.ambient; - diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)) * material.diffuse; - specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)) * material.specular; + ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)); + diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)); + specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)); } else { ambient = light.ambient * material.ambient; diffuse = light.diffuse * (diff * material.diffuse); @@ -162,9 +165,9 @@ vec3 CalculateSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir vec3 specular = vec3(0.0, 0.0, 0.0); if (useTextures) { - ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)) * material.ambient; - diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)) * material.diffuse; - specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)) * material.specular; + ambient = light.ambient * vec3(texture(material.texture_ambient, texCoords)); + diffuse = light.diffuse * diff * vec3(texture(material.texture_diffuse, texCoords)); + specular = light.specular * spec * vec3(texture(material.texture_specular, texCoords)); } else { ambient = light.ambient * material.ambient; diffuse = light.diffuse * (diff * material.diffuse); @@ -190,6 +193,14 @@ void main() } vec3 norm = normalize(fragNormals); + + // use normal map if available + if (useTextures && hasNormalTexture) { + vec3 normal = texture(material.texture_normal, texCoords).rgb; + normal = normal * 2.0 - 1.0; + norm = normalize(fragTBN * normal); + } + vec3 viewDir = normalize(viewPos - fragPos); // direction light @@ -202,6 +213,7 @@ void main() result += CalculateSpotLight(spotLight, norm, fragPos, viewDir); + // emission if (useTextures && hasEmissionTexture) { result += texture(material.texture_emission, texCoords).rgb; } diff --git a/shaders/vertex.glsl b/shaders/vertex.glsl index 32c34f7..913f975 100644 --- a/shaders/vertex.glsl +++ b/shaders/vertex.glsl @@ -2,9 +2,12 @@ layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; +layout (location = 3) in vec3 aTangent; +layout (location = 4) in vec3 aBitangent; out vec3 fragNormals; out vec3 fragPos; out vec2 texCoords; +out mat3 fragTBN; uniform mat4 model; uniform mat4 view; @@ -19,7 +22,14 @@ void main() fragNormals = normalMatrix * aNormal; else fragNormals = mat3(transpose(inverse(model))) * aNormal; + + vec3 T = normalize(vec3(model * vec4(aTangent, 0.0))); + vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0))); + vec3 N = normalize(vec3(model * vec4(aNormal, 0.0))); + mat3 TBN = mat3(T, B, N); + fragPos = vec3(model * vec4(aPos, 1.0)); texCoords = aTexCoords; + fragTBN = TBN; } diff --git a/src/aabb.rs b/src/aabb.rs index 077e4cc..acb71d5 100644 --- a/src/aabb.rs +++ b/src/aabb.rs @@ -19,14 +19,62 @@ impl AABB { let mut ebo = 0; let vertices = vec![ - Vertex { position: glm::vec3(min.x, min.y, min.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(max.x, min.y, min.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(max.x, max.y, min.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(min.x, max.y, min.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(min.x, min.y, max.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(max.x, min.y, max.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(max.x, max.y, max.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) }, - Vertex { position: glm::vec3(min.x, max.y, max.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) }, + Vertex { + position: glm::vec3(min.x, min.y, min.z), + tex_coords: glm::vec2(0.0, 0.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(max.x, min.y, min.z), + tex_coords: glm::vec2(1.0, 0.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(max.x, max.y, min.z), + tex_coords: glm::vec2(1.0, 1.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(min.x, max.y, min.z), + tex_coords: glm::vec2(0.0, 1.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(min.x, min.y, max.z), + tex_coords: glm::vec2(0.0, 0.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(max.x, min.y, max.z), + tex_coords: glm::vec2(1.0, 0.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(max.x, max.y, max.z), + tex_coords: glm::vec2(1.0, 1.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, + Vertex { + position: glm::vec3(min.x, max.y, max.z), + tex_coords: glm::vec2(0.0, 1.0), + normal: glm::vec3(0.0, 0.0, 0.0), + tangent: glm::vec3(0.0, 0.0, 0.0), + bitangent: glm::vec3(0.0, 0.0, 0.0), + }, ]; let indices = vec![ diff --git a/src/importer/mod.rs b/src/importer/mod.rs index a11fce2..04ccd8a 100644 --- a/src/importer/mod.rs +++ b/src/importer/mod.rs @@ -25,7 +25,10 @@ impl TextureType { "map_Kd" => Some(TextureType::Diffuse), "map_Ks" => Some(TextureType::Specular), "map_Ns" => Some(TextureType::SpecularHighlight), - "map_bump" => Some(TextureType::Bump), + "map_Bump" => Some(TextureType::Bump), + "bump" => Some(TextureType::Bump), + "norm" => Some(TextureType::Bump), + "map_Kn" => Some(TextureType::Bump), "map_d" => Some(TextureType::Displacement), "decal" => Some(TextureType::Decal), "refl" => Some(TextureType::Reflection), diff --git a/src/importer/obj.rs b/src/importer/obj.rs index 42271fc..d7c359d 100644 --- a/src/importer/obj.rs +++ b/src/importer/obj.rs @@ -1,6 +1,6 @@ use std::{io::{BufReader, BufRead}, collections::HashMap, path::PathBuf}; -use log::{error, warn, trace}; +use log::{error, warn, trace, info}; use crate::{mesh::Vertex, aabb::AABB, importer::{ObjMesh, Object, Material, Texture, TextureType}}; @@ -77,7 +77,11 @@ impl MtlToken { "map_Ks" => Some(MtlToken::SpecularTexture), "map_Ns" => Some(MtlToken::SpecularHighlightTexture), "map_Ke" => Some(MtlToken::EmissiveTexture), - "map_bump" => Some(MtlToken::BumpTexture), + "map_Bump" => Some(MtlToken::BumpTexture), + "bump" => Some(MtlToken::BumpTexture), + // "map_Kn" => Some(MtlToken::BumpTexture), // non-standard, we don't + // yet know if it specifies a bump parameter so we let it log a warning + "norm" => Some(MtlToken::BumpTexture), "map_d" => Some(MtlToken::DisplacementTexture), "decal" => Some(MtlToken::DecalTexture), "refl" => Some(MtlToken::ReflectionTexture), @@ -174,7 +178,49 @@ fn parse_mtl(path: &PathBuf, obj_textures: &mut HashMap) -> Res tex }; - mat_textures.push(mat); + mat_textures.push(tex); + } + Some(MtlToken::BumpTexture) => { + // norm doesn't specify a bump parameter + // map_bump does + + let mut bm = 1.0; + let mut name = String::new(); + + let next = iter.next().unwrap(); + if next == "-bm" { + bm = iter.next().unwrap().parse::().unwrap(); + name = iter.next().unwrap().to_string(); + } else { + name = next.to_string(); + + if let Some(possible_bump) = iter.next() { + if possible_bump == "-bm" { + bm = iter.next().unwrap().parse::().unwrap(); + } + } + } + + let tex = if obj_textures.contains_key(&name) { + let mut tex = obj_textures.get(&name).unwrap().clone(); + tex.typ = TextureType::Bump; + tex + } else { + let path = path.parent().unwrap().join(&name); + let tex = match Texture::new(path, TextureType::Bump) { + Ok(v) => v, + Err(e) => { + error!("Failed to load texture: {}", e); + continue; + } + }; + + obj_textures.insert(name, tex.clone()); + tex + }; + + // TODO: use bm somewhere + mat_textures.push(tex); } _ => { warn!("Unhandled material token: {}", token) }, } @@ -335,7 +381,9 @@ pub fn load_obj(obj_path: &PathBuf, file: std::fs::File) -> Result Result Result().unwrap(); @@ -392,10 +444,12 @@ pub fn load_obj(obj_path: &PathBuf, file: std::fs::File) -> Result Result { @@ -457,7 +589,7 @@ pub fn load_obj(obj_path: &PathBuf, file: std::fs::File) -> Result Result Result Result anyhow::Result<(), Box> { mesh_shader.set_3fv("dirLight.direction", glm::vec3(-0.2, -1.0, -0.3)); mesh_shader.set_3fv("dirLight.ambient", glm::vec3(0.1, 0.1, 0.1)); - mesh_shader.set_3fv("dirLight.diffuse", glm::vec3(1.0, 1.0, 1.0)); + mesh_shader.set_3fv("dirLight.diffuse", glm::vec3(0.5, 0.5, 0.5)); mesh_shader.set_3fv("dirLight.specular", glm::vec3(1.0, 1.0, 1.0)); let scene_fb = create_scene_framebuffer(); @@ -264,7 +264,7 @@ fn main() -> anyhow::Result<(), Box> { } else { gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL); } - if Some(obj.id) == state.active_model {obj.draw(&mesh_shader, state.draw_aabb, state.show_textures);} + if Some(obj.id) == state.active_model {obj.draw(&mesh_shader, state.draw_aabb, state.show_textures, state.show_normal, state.show_emission);} } gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL); diff --git a/src/mesh.rs b/src/mesh.rs index 906b718..fce77c6 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -1,5 +1,4 @@ use glad_gl::gl; -use log::warn; use crate::{shader::Shader, utils, importer::{Material, TextureType}}; @@ -75,6 +74,14 @@ impl Mesh { gl::EnableVertexAttribArray(2); gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, std::mem::size_of::() as i32, (6 * std::mem::size_of::()) as *const std::ffi::c_void); + // vertex tangent + gl::EnableVertexAttribArray(3); + gl::VertexAttribPointer(3, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::() as i32, (8 * std::mem::size_of::()) as *const std::ffi::c_void); + + // vertex bitangent + gl::EnableVertexAttribArray(4); + gl::VertexAttribPointer(4, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::() as i32, (11 * std::mem::size_of::()) as *const std::ffi::c_void); + gl::BindVertexArray(0); } @@ -93,7 +100,7 @@ impl Mesh { } } - pub fn draw(&self, shader: &Shader, scale: f32, pivot: glm::Vec3, show_textures: bool) { + pub fn draw(&self, shader: &Shader, scale: f32, pivot: glm::Vec3, show_textures: bool, use_normal: bool, use_emissive: bool) { shader.use_shader(); let model_mat = glm::ext::scale(&utils::mat_ident(), glm::vec3(scale, scale, scale)); @@ -135,11 +142,13 @@ impl Mesh { if show_textures { shader.set_bool("useTextures", self.material.textures.len() > 0); - for (i, tex) in self.material.textures.iter().enumerate() { - shader.set_bool("hasEmissionTexture", false); + shader.set_bool("hasNormalTexture", false); + shader.set_bool("hasEmissionTexture", false); + for i in 0..self.material.textures.len() { unsafe { gl::ActiveTexture(gl::TEXTURE0 + i as u32); - match tex.typ { + gl::BindTexture(gl::TEXTURE_2D, self.material.textures[i].id); + match self.material.textures[i].typ { TextureType::Ambient => { shader.set_int("material.texture_ambient", i as i32); }, @@ -150,13 +159,20 @@ impl Mesh { shader.set_int("material.texture_specular", i as i32); }, TextureType::Emissive => { + if use_emissive { + shader.set_bool("hasEmissionTexture", true); + } shader.set_int("material.texture_emission", i as i32); - shader.set_bool("hasEmissionTexture", true); }, - _ => {}, + TextureType::Bump => { + if use_normal { + shader.set_bool("hasNormalTexture", true); + } + shader.set_int("material.texture_normal", i as i32); + }, + _ => { continue; }, } - gl::BindTexture(gl::TEXTURE_2D, tex.id); } } } else { @@ -185,12 +201,13 @@ impl Mesh { impl Drop for Mesh { fn drop(&mut self) { - // TODO: should we impl a Drop on material to delete the textures from gpu?? unsafe { + gl::ActiveTexture(gl::TEXTURE0); gl::BindVertexArray(0); gl::DeleteBuffers(1, &self.vbo); gl::DeleteBuffers(1, &self.ebo); gl::DeleteVertexArrays(1, &self.vao); + gl::DeleteTextures(self.material.textures.len() as i32, self.material.textures.iter().map(|t| t.id).collect::>().as_ptr()); } } } @@ -201,14 +218,18 @@ pub struct Vertex { pub position: glm::Vec3, pub normal: glm::Vec3, pub tex_coords: glm::Vec2, + pub tangent: glm::Vec3, + pub bitangent: glm::Vec3, } impl Vertex { - pub fn new(position: glm::Vec3, normal: glm::Vec3, tex_coords: glm::Vec2) -> Self { + pub fn new(position: glm::Vec3, normal: glm::Vec3, tex_coords: glm::Vec2, tangent: glm::Vec3, bitangent: glm::Vec3) -> Self { Vertex { position, normal, - tex_coords + tex_coords, + tangent, + bitangent, } } } diff --git a/src/model.rs b/src/model.rs index 4ce673f..9c2b2a3 100644 --- a/src/model.rs +++ b/src/model.rs @@ -41,7 +41,7 @@ impl Model { return model; } - pub fn draw(&self, shader: &Shader, draw_aabb: bool, show_textures: bool) { + pub fn draw(&self, shader: &Shader, draw_aabb: bool, show_textures: bool, use_normal: bool, use_emissive: bool) { let center_x = ((self.aabb.max.x / 2.0) + (self.aabb.min.x / 2.0)) * self.scaling_factor; let center_y = ((self.aabb.max.y / 2.0) + (self.aabb.min.y / 2.0)) * self.scaling_factor; let center_z = ((self.aabb.max.z / 2.0) + (self.aabb.min.z / 2.0)) * self.scaling_factor; @@ -53,7 +53,7 @@ impl Model { for mesh in &self.meshes { - mesh.draw(shader, self.scaling_factor, pivot, show_textures); + mesh.draw(shader, self.scaling_factor, pivot, show_textures, use_normal, use_emissive); } if draw_aabb { diff --git a/src/ui/ui.rs b/src/ui/ui.rs index 3e582b9..38978d4 100644 --- a/src/ui/ui.rs +++ b/src/ui/ui.rs @@ -31,7 +31,9 @@ pub struct State { pub logger: logger::WritableLog, pub settings: Settings, pub fps: f32, - pub show_textures: bool + pub show_textures: bool, + pub show_normal: bool, + pub show_emission: bool, } impl Default for State { @@ -56,7 +58,9 @@ impl Default for State { logger: logger::WritableLog::default(), settings: Settings::default(), fps: 0.0, - show_textures: true + show_textures: true, + show_normal: true, + show_emission: true, } } } @@ -491,6 +495,10 @@ fn draw_viewport(ui: &imgui::Ui, state: &mut State, texture: u32) { ui.same_line(); ui.checkbox("Show Textures", &mut state.show_textures); ui.same_line(); + ui.checkbox("Use Normal", &mut state.show_normal); + ui.same_line(); + ui.checkbox("Use Emissive", &mut state.show_emission); + ui.same_line(); ui.set_next_item_width(150.0); imgui::Drag::new("Camera Speed") .range(1.0, 10000.0)