Normal/Height maps now working from models, and are only applied to models that have them.

This commit is contained in:
Zed A. Shaw 2026-09-30 17:36:48 -04:00
parent 4efe72b8d8
commit 8b41591360
8 changed files with 170 additions and 12 deletions

View file

@ -1,8 +1,8 @@
{ {
"scene": { "scene": {
"shader": { "shader": {
"vertex_path": "shaders/33-framebuffers.vert.glsl", "vertex_path": "shaders/43-framebuffers.vert.glsl",
"frag_path": "shaders/33-framebuffers.frag.glsl", "frag_path": "shaders/43-framebuffers.frag.glsl",
"geometry_path": "" "geometry_path": ""
}, },
"reflect_shader": { "reflect_shader": {
@ -66,6 +66,10 @@
"human": { "human": {
"directory": "../assets", "directory": "../assets",
"model_path": "human.glb" "model_path": "human.glb"
},
"brick_cube": {
"directory": "../assets",
"model_path": "brick_cube_normal.obj"
} }
}, },
"things": [ "things": [
@ -73,10 +77,10 @@
"rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]}, "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]},
"scale": 1.0, "scale": 1.0,
"material": "shiny"}, "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]}, "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]},
"scale": 1.0, "scale": 0.5,
"material": "shiny"}, "material": "default"},
{"model": "human", "position": [1.28, 0.0, 1.33], {"model": "human", "position": [1.28, 0.0, 1.33],
"rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]}, "rotation": {"angle": 0.0, "axes": [1.0, 1.0, 1.0]},
"scale": 1.0, "scale": 1.0,

View file

@ -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);
}

View file

@ -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);
}

View file

@ -112,7 +112,7 @@ void process_input(GLFWwindow *window, std::shared_ptr<Scene> scene) {
std::shared_ptr<Scene> setup() { std::shared_ptr<Scene> setup() {
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glDisable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glEnable(GL_MULTISAMPLE); glEnable(GL_MULTISAMPLE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

View file

@ -14,17 +14,25 @@ void Mesh::draw(Shader &shader, bool with_textures) {
void Mesh::apply_textures(const Shader& shader) { void Mesh::apply_textures(const Shader& shader) {
int texture_i = 1; int texture_i = 1;
bool has_height = false;
for(auto& texture : textures) { for(auto& texture : textures) {
texture.uniform_id = glGetUniformLocation(shader.ID, texture.target.c_str()); texture.uniform_id = glGetUniformLocation(shader.ID, texture.target.c_str());
if(texture.uniform_id == -1) continue; if(texture.uniform_id == -1) continue;
if(texture.target.starts_with("texture_height")) {
has_height = true;
}
glActiveTexture(GL_TEXTURE0 + texture_i); glActiveTexture(GL_TEXTURE0 + texture_i);
glBindTexture(GL_TEXTURE_2D, texture.id); glBindTexture(GL_TEXTURE_2D, texture.id);
glUniform1i(texture.uniform_id, texture_i); glUniform1i(texture.uniform_id, texture_i);
texture_i++; texture_i++;
} }
// quick hack to avoid applying height/normal to things without one
shader.setBool("hasHeight", has_height);
} }
void Mesh::setup_mesh() { void Mesh::setup_mesh() {
@ -48,4 +56,10 @@ void Mesh::setup_mesh() {
glEnableVertexAttribArray(2); glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords)); 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));
} }

View file

@ -13,6 +13,8 @@ struct Vertex {
glm::vec3 position; glm::vec3 position;
glm::vec3 normal; glm::vec3 normal;
glm::vec2 tex_coords; glm::vec2 tex_coords;
glm::vec3 tangent;
glm::vec3 bitangent;
}; };
struct TextureCounts { struct TextureCounts {

View file

@ -92,6 +92,8 @@ void Model::draw(Shader &shader, bool with_textures) {
void Model::load_model() { void Model::load_model() {
std::string path = directory + "/" + model_path; std::string path = directory + "/" + model_path;
fmt::println("Loading model {}", path);
Assimp::Importer importer; Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, const aiScene* scene = importer.ReadFile(path,
@ -131,6 +133,8 @@ Mesh Model::process_mesh(const aiScene *scene, aiMesh *mesh) {
glm::vec3 normal{}; glm::vec3 normal{};
glm::vec2 tex_coords{0.0f, 0.0f}; glm::vec2 tex_coords{0.0f, 0.0f};
glm::vec3 tangent;
glm::vec3 bitangent;
if(mesh->HasNormals()) { if(mesh->HasNormals()) {
normal = glm::vec3( 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++) { 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 // 2. specular maps
load_material_textures(scene, textures, material, aiTextureType_SPECULAR, "texture_specular"); load_material_textures(scene, textures, material, aiTextureType_SPECULAR, "texture_specular");
// 3. normal maps // 3. normal maps, possibly wrong?
load_material_textures(scene, textures, material, aiTextureType_HEIGHT, "texture_normal"); load_material_textures(scene, textures, material, aiTextureType_NORMALS, "texture_normal");
// 4. height maps // 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); return Mesh(vertices, indices, textures);
} }
void Model::load_material_textures(const aiScene *scene, std::vector<Texture>& textures, aiMaterial *mat, aiTextureType type, std::string type_name) void Model::load_material_textures(const aiScene *scene, std::vector<Texture>& 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++) { for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
aiString str; aiString str;
@ -190,6 +211,8 @@ void Model::load_material_textures(const aiScene *scene, std::vector<Texture>& t
std::string tx_path{str.C_Str()}; std::string tx_path{str.C_Str()};
dbc::check(!tx_path.empty(), "Texture has empty path, should be impossible?"); dbc::check(!tx_path.empty(), "Texture has empty path, should be impossible?");
fmt::println("texture {}", tx_path);
if(textures_loaded.contains(tx_path)) { if(textures_loaded.contains(tx_path)) {
textures.push_back(textures_loaded.at(tx_path)); textures.push_back(textures_loaded.at(tx_path));
} else { } else {
@ -210,4 +233,6 @@ void Model::load_material_textures(const aiScene *scene, std::vector<Texture>& t
textures_loaded.try_emplace(tx_path, result); textures_loaded.try_emplace(tx_path, result);
} }
} }
fmt::println("<<< loading texture type {}", type_name);
} }

View file

@ -15,13 +15,13 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <map> #include <unordered_map>
#include <vector> #include <vector>
struct Model { struct Model {
std::string directory; std::string directory;
std::string model_path; std::string model_path;
std::map<std::string, Texture> textures_loaded; std::unordered_map<std::string, Texture> textures_loaded;
std::vector<Mesh> meshes; std::vector<Mesh> meshes;
TextureCounts texture_counts; TextureCounts texture_counts;
bool gammaCorrection; bool gammaCorrection;