diff --git a/43-normal-maps/config.json b/43-normal-maps/config.json index 080b5f1..a0455a0 100644 --- a/43-normal-maps/config.json +++ b/43-normal-maps/config.json @@ -1,8 +1,8 @@ { "scene": { "shader": { - "vertex_path": "shaders/33-framebuffers.vert.glsl", - "frag_path": "shaders/33-framebuffers.frag.glsl", + "vertex_path": "shaders/43-framebuffers.vert.glsl", + "frag_path": "shaders/43-framebuffers.frag.glsl", "geometry_path": "" }, "reflect_shader": { @@ -66,6 +66,10 @@ "human": { "directory": "../assets", "model_path": "human.glb" + }, + "brick_cube": { + "directory": "../assets", + "model_path": "brick_cube_normal.obj" } }, "things": [ @@ -73,10 +77,10 @@ "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]}, "scale": 1.0, "material": "shiny"}, - {"model": "marble_cube", "position": [1.5, 0.0, -1.0], + {"model": "brick_cube", "position": [-2, 0.21, -1.0], "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]}, - "scale": 1.0, - "material": "shiny"}, + "scale": 0.5, + "material": "default"}, {"model": "human", "position": [1.28, 0.0, 1.33], "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]}, "scale": 1.0, diff --git a/43-normal-maps/shaders/43-framebuffers.frag.glsl b/43-normal-maps/shaders/43-framebuffers.frag.glsl new file mode 100644 index 0000000..fbd8ba1 --- /dev/null +++ b/43-normal-maps/shaders/43-framebuffers.frag.glsl @@ -0,0 +1,82 @@ +#version 330 core +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoords; +in vec4 FragPosLightSpace; +in mat3 TBN; + +uniform vec3 lightPos; +uniform vec3 viewPos; +uniform sampler2D shadowMap; +uniform sampler2D texture_diffuse1; +uniform sampler2D texture_specular1; +uniform sampler2D texture_height1; // bump/normal map +uniform bool hasHeight; + +float ShadowCalculation(vec4 fragPosLightSpace, float bias) +{ + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + // avoid the frustrum + if(projCoords.z > 1.0) return 0.0; + + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) + float closestDepth = texture(shadowMap, projCoords.xy).r; + + // get depth of current fragment from light's perspective + float currentDepth = projCoords.z; + + // check whether current frag pos is in shadow + // PCF + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(shadowMap, 0); + for(int x = -1; x <= 1; ++x) { + for(int y = -1; y <= 1; ++y) { + float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; + } + } + + return shadow / 9.0; +} + +void main() +{ + vec3 color = texture(texture_diffuse1, TexCoords).rgb; + vec3 normal = normalize(Normal); + + if(hasHeight) { + normal = texture(texture_height1, TexCoords).rgb; + normal = normal * 2.0 - 1.0; + normal = normalize(TBN * normal); + } + + vec3 lightColor = vec3(0.7); + // ambient + vec3 ambient = 0.3 * lightColor; + // diffuse + vec3 lightDir = normalize(lightPos - FragPos); + float diff = max(dot(lightDir, normal), 0.0); + vec3 diffuse = diff * lightColor; + // specular + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDir, normal); + float spec = 0.0; + vec3 halfwayDir = normalize(lightDir + viewDir); + spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); + vec3 specular = spec * lightColor; + // calculate shadow + + float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); + float shadow = ShadowCalculation(FragPosLightSpace, bias); + + vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; + + FragColor = vec4(lighting, 1.0); +} diff --git a/43-normal-maps/shaders/43-framebuffers.vert.glsl b/43-normal-maps/shaders/43-framebuffers.vert.glsl new file mode 100644 index 0000000..a6d137f --- /dev/null +++ b/43-normal-maps/shaders/43-framebuffers.vert.glsl @@ -0,0 +1,31 @@ +#version 330 core +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 FragPos; +out vec3 Normal; +out vec2 TexCoords; +out vec4 FragPosLightSpace; +out mat3 TBN; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 lightSpaceMatrix; + +void main() +{ + FragPos = vec3(model * vec4(aPos, 1.0)); + Normal = transpose(inverse(mat3(model))) * aNormal; + TexCoords = aTexCoords; + FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); + gl_Position = projection * view * model * vec4(aPos, 1.0); + + 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))); + TBN = mat3(T, B, N); +} diff --git a/43-normal-maps/src/main.cpp b/43-normal-maps/src/main.cpp index df98f1b..ef325f8 100644 --- a/43-normal-maps/src/main.cpp +++ b/43-normal-maps/src/main.cpp @@ -112,7 +112,7 @@ void process_input(GLFWwindow *window, std::shared_ptr scene) { std::shared_ptr setup() { glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); - glDisable(GL_CULL_FACE); + glEnable(GL_CULL_FACE); glEnable(GL_MULTISAMPLE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/43-normal-maps/src/mesh.cpp b/43-normal-maps/src/mesh.cpp index c95277b..4f676d4 100644 --- a/43-normal-maps/src/mesh.cpp +++ b/43-normal-maps/src/mesh.cpp @@ -14,17 +14,25 @@ void Mesh::draw(Shader &shader, bool with_textures) { void Mesh::apply_textures(const Shader& shader) { int texture_i = 1; + bool has_height = false; for(auto& texture : textures) { texture.uniform_id = glGetUniformLocation(shader.ID, texture.target.c_str()); if(texture.uniform_id == -1) continue; + if(texture.target.starts_with("texture_height")) { + has_height = true; + } + glActiveTexture(GL_TEXTURE0 + texture_i); glBindTexture(GL_TEXTURE_2D, texture.id); glUniform1i(texture.uniform_id, texture_i); texture_i++; } + + // quick hack to avoid applying height/normal to things without one + shader.setBool("hasHeight", has_height); } void Mesh::setup_mesh() { @@ -48,4 +56,10 @@ void Mesh::setup_mesh() { glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords)); + + glEnableVertexAttribArray(3); + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tangent)); + + glEnableVertexAttribArray(4); + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, bitangent)); } diff --git a/43-normal-maps/src/mesh.hpp b/43-normal-maps/src/mesh.hpp index d33095e..2b8b94d 100644 --- a/43-normal-maps/src/mesh.hpp +++ b/43-normal-maps/src/mesh.hpp @@ -13,6 +13,8 @@ struct Vertex { glm::vec3 position; glm::vec3 normal; glm::vec2 tex_coords; + glm::vec3 tangent; + glm::vec3 bitangent; }; struct TextureCounts { diff --git a/43-normal-maps/src/model.cpp b/43-normal-maps/src/model.cpp index 8c296bb..097a595 100644 --- a/43-normal-maps/src/model.cpp +++ b/43-normal-maps/src/model.cpp @@ -92,6 +92,8 @@ void Model::draw(Shader &shader, bool with_textures) { void Model::load_model() { std::string path = directory + "/" + model_path; + fmt::println("Loading model {}", path); + Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, @@ -131,6 +133,8 @@ Mesh Model::process_mesh(const aiScene *scene, aiMesh *mesh) { glm::vec3 normal{}; glm::vec2 tex_coords{0.0f, 0.0f}; + glm::vec3 tangent; + glm::vec3 bitangent; if(mesh->HasNormals()) { normal = glm::vec3( @@ -146,7 +150,22 @@ Mesh Model::process_mesh(const aiScene *scene, aiMesh *mesh) { ); } - vertices.emplace_back(position, normal, tex_coords); + // if has height/normal map + if(mesh->HasTangentsAndBitangents()) { + tangent = glm::vec3( + mesh->mTangents[i].x, + mesh->mTangents[i].y, + mesh->mTangents[i].z + ); + + bitangent = glm::vec3( + mesh->mBitangents[i].x, + mesh->mBitangents[i].y, + mesh->mBitangents[i].z + ); + } + + vertices.emplace_back(position, normal, tex_coords, tangent, bitangent); } for(unsigned int i = 0; i < mesh->mNumFaces; i++) { @@ -172,17 +191,19 @@ Mesh Model::process_mesh(const aiScene *scene, aiMesh *mesh) { // 2. specular maps load_material_textures(scene, textures, material, aiTextureType_SPECULAR, "texture_specular"); - // 3. normal maps - load_material_textures(scene, textures, material, aiTextureType_HEIGHT, "texture_normal"); + // 3. normal maps, possibly wrong? + load_material_textures(scene, textures, material, aiTextureType_NORMALS, "texture_normal"); // 4. height maps - load_material_textures(scene, textures, material, aiTextureType_AMBIENT, "texture_height"); + load_material_textures(scene, textures, material, aiTextureType_HEIGHT, "texture_height"); return Mesh(vertices, indices, textures); } void Model::load_material_textures(const aiScene *scene, std::vector& textures, aiMaterial *mat, aiTextureType type, std::string type_name) { + fmt::println(">>> loading texture type {}", type_name); + for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) { aiString str; @@ -190,6 +211,8 @@ void Model::load_material_textures(const aiScene *scene, std::vector& t std::string tx_path{str.C_Str()}; dbc::check(!tx_path.empty(), "Texture has empty path, should be impossible?"); + fmt::println("texture {}", tx_path); + if(textures_loaded.contains(tx_path)) { textures.push_back(textures_loaded.at(tx_path)); } else { @@ -210,4 +233,6 @@ void Model::load_material_textures(const aiScene *scene, std::vector& t textures_loaded.try_emplace(tx_path, result); } } + + fmt::println("<<< loading texture type {}", type_name); } diff --git a/43-normal-maps/src/model.hpp b/43-normal-maps/src/model.hpp index 2d85b76..07ab6cb 100644 --- a/43-normal-maps/src/model.hpp +++ b/43-normal-maps/src/model.hpp @@ -15,13 +15,13 @@ #include #include #include -#include +#include #include struct Model { std::string directory; std::string model_path; - std::map textures_loaded; + std::unordered_map textures_loaded; std::vector meshes; TextureCounts texture_counts; bool gammaCorrection;