From 8b14fc0ff51ff12bc4b165a5a8cd06b5c7fff9ef Mon Sep 17 00:00:00 2001 From: ProtectedVariable Date: Wed, 17 Dec 2025 16:21:37 +0100 Subject: [PATCH 1/6] first crack at pbr shader --- Assets/Shaders/normal.fs | 4 +- Assets/Shaders/normal.vs | 15 ----- Assets/Shaders/pbr.fs | 83 ++++++++++++++++++++++++ Assets/Shaders/picking.vs | 12 ---- Assets/Shaders/{phong.vs => skinning.vs} | 0 Assets/Shaders/solid.vs | 13 ---- ICE/IO/src/ModelLoader.cpp | 70 +++++++++++--------- ICE/IO/src/Project.cpp | 9 +-- 8 files changed, 129 insertions(+), 77 deletions(-) delete mode 100644 Assets/Shaders/normal.vs create mode 100644 Assets/Shaders/pbr.fs delete mode 100644 Assets/Shaders/picking.vs rename Assets/Shaders/{phong.vs => skinning.vs} (100%) delete mode 100644 Assets/Shaders/solid.vs diff --git a/Assets/Shaders/normal.fs b/Assets/Shaders/normal.fs index 770248b4..4aee37d0 100644 --- a/Assets/Shaders/normal.fs +++ b/Assets/Shaders/normal.fs @@ -2,8 +2,8 @@ out vec4 frag_color; -in vec3 f_normal; +in vec3 fnormal; void main() { - frag_color = vec4(f_normal, 1.0); + frag_color = vec4((fnormal + 1.0) / 2.0, 1.0); } \ No newline at end of file diff --git a/Assets/Shaders/normal.vs b/Assets/Shaders/normal.vs deleted file mode 100644 index f45c8194..00000000 --- a/Assets/Shaders/normal.vs +++ /dev/null @@ -1,15 +0,0 @@ -#version 420 core - -#include "vert_uniforms.glsl" - -layout (location = 0) in vec3 vertex; -layout (location = 1) in vec3 normal; - -uniform mat4 model; - -out vec3 f_normal; - -void main() { - f_normal = abs(normal); - gl_Position = uProjection * uView * model * vec4(vertex, 1.0); -} \ No newline at end of file diff --git a/Assets/Shaders/pbr.fs b/Assets/Shaders/pbr.fs new file mode 100644 index 00000000..aa3302cd --- /dev/null +++ b/Assets/Shaders/pbr.fs @@ -0,0 +1,83 @@ +#version 420 core + +#include "frag_uniforms.glsl" + +struct Material { + vec3 baseColor; + float metallic; + float roughness; + float ao; + + bool hasBaseColorMap; + sampler2D baseColorMap; + + bool hasMetallicRoughnessMap; + sampler2D metallicRoughnessMap; + + bool hasNormalMap; + sampler2D normalMap; + + bool hasAoMap; + sampler2D aoMap; +}; + +uniform Material material; + +in vec3 fnormal; +in vec3 ftangent; +in vec3 fbitangent; +in vec3 fposition; +in vec3 fview; +in vec2 ftex_coords; + +out vec4 frag_color; + +void main() { + vec3 N = fnormal; + vec3 V = normalize(fview - fposition); + + // Normal map + if (material.hasNormalMap) { + vec3 normalTex = texture(material.normalMap, ftex_coords).xyz * 2.0 - 1.0; + mat3 TBN = mat3(ftangent, fbitangent, fnormal); + N = normalize(TBN * normalTex); + } + + vec3 albedo = material.baseColor; + if (material.hasBaseColorMap) + albedo *= texture(material.baseColorMap, ftex_coords).rgb; + + float metallic = material.metallic; + float roughness = material.roughness; + float ao = material.ao; + if (material.hasAoMap) + ao *= texture(material.aoMap, ftex_coords).r; + + vec3 Lo = vec3(0.0); + for (int i = 0; i < light_count; i++) { + vec3 L = normalize(lights[i].position - fposition); + float distance = length(lights[i].position - fposition); + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0); + float NdotV = max(dot(N, V), 0.001); + float NdotH = max(dot(N, H), 0.0); + float VdotH = max(dot(V, H), 0.0); + + // Cook-Torrance specular + float alpha = roughness * roughness; + float D = alpha / (3.141592 * pow(NdotH*NdotH*(alpha-1)+1,2)); + float k = (alpha + 1)*(alpha +1)/8; + float G = NdotL/(NdotL*(1-k)+k) * NdotV/(NdotV*(1-k)+k); + vec3 F0 = mix(vec3(0.04), albedo, metallic); + vec3 F = F0 + (1.0 - F0)*pow(1.0 - VdotH, 5.0); + + vec3 spec = (D * G * F)/(4*NdotV*NdotL + 0.001); + vec3 diffuse = albedo / 3.141592 * (1.0 - metallic); + + vec3 radiance = lights[i].color / (distance*distance); + Lo += NdotL * radiance * (diffuse + spec); + } + + frag_color = vec4(Lo * ao, 1.0); +} diff --git a/Assets/Shaders/picking.vs b/Assets/Shaders/picking.vs deleted file mode 100644 index e309f5a0..00000000 --- a/Assets/Shaders/picking.vs +++ /dev/null @@ -1,12 +0,0 @@ -#version 420 core - -#include "vert_uniforms.glsl" - -layout (location = 0) in vec3 vertex; -layout (location = 1) in vec3 normal; - -uniform mat4 model; - -void main() { - gl_Position = uProjection * uView * model * vec4(vertex, 1.0); -} \ No newline at end of file diff --git a/Assets/Shaders/phong.vs b/Assets/Shaders/skinning.vs similarity index 100% rename from Assets/Shaders/phong.vs rename to Assets/Shaders/skinning.vs diff --git a/Assets/Shaders/solid.vs b/Assets/Shaders/solid.vs deleted file mode 100644 index b3b2f510..00000000 --- a/Assets/Shaders/solid.vs +++ /dev/null @@ -1,13 +0,0 @@ -#version 420 core - -#include "vert_uniforms.glsl" - -layout (location = 0) in vec3 vertex; -layout (location = 1) in vec3 normal; -layout (location = 2) in vec2 tex_coords; - -uniform mat4 model; - -void main() { - gl_Position = uProjection * uView * model * vec4(vertex, 1.0); -} \ No newline at end of file diff --git a/ICE/IO/src/ModelLoader.cpp b/ICE/IO/src/ModelLoader.cpp index 6557ca80..7452c960 100644 --- a/ICE/IO/src/ModelLoader.cpp +++ b/ICE/IO/src/ModelLoader.cpp @@ -161,47 +161,55 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str if (mtl_name.length == 0) { mtl_name = "DefaultMat"; } + //If material already exists, return its UID auto bank_name = model_name + "/" + mtl_name.C_Str(); + if (ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)) != 0) { + return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); + } + auto mtl = std::make_shared(); - mtl->setUniform("material.use_diffuse_map", false); - mtl->setUniform("material.use_ambient_map", false); - mtl->setUniform("material.use_specular_map", false); - mtl->setUniform("material.use_normal_map", false); + mtl->setUniform("material.hasAoMap", 0); + mtl->setUniform("material.hasBaseColorMap", 0); + mtl->setUniform("material.hasMetallicRoughnessMap", 0); + mtl->setUniform("material.hasNormalMap", 0); + mtl->setUniform("material.ao", 1.0f); + mtl->setUniform("material.metallic", 0.0f); + mtl->setUniform("material.roughness", 1.0f); + mtl->setShader(ref_bank.getUID(AssetPath::WithTypePrefix("pbr"))); + // Base color + aiColor4D diffuse; + if (aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse) == aiReturn_SUCCESS) + mtl->setUniform("material.baseColor", colorToVec(&diffuse)); + + ai_real shininess = 1.0f; + if (aiGetMaterialFloat(material, AI_MATKEY_SHININESS, &shininess) == aiReturn_SUCCESS) + mtl->setUniform("material.roughness", sqrt(2.0f / (shininess + 2.0f))); + + aiColor4D specular; + if (aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, &specular) == aiReturn_SUCCESS) { + float maxSpec = std::max({specular.r, specular.g, specular.b}); + mtl->setUniform("material.metallic", maxSpec); + } + if (auto ambient_map = extractTexture(material, bank_name + "/ambient_map", scene, aiTextureType_AMBIENT); ambient_map != 0) { - mtl->setUniform("material.ambient_map", ambient_map); - mtl->setUniform("material.use_ambient_map", true); + mtl->setUniform("material.hasAoMap", 1); + mtl->setUniform("material.aoMap", ambient_map); } + if (auto diffuse_tex = extractTexture(material, bank_name + "/diffuse_map", scene, aiTextureType_DIFFUSE); diffuse_tex != 0) { - mtl->setUniform("material.diffuse_map", diffuse_tex); - mtl->setUniform("material.use_diffuse_map", true); + mtl->setUniform("material.hasBaseColorMap", 1); + mtl->setUniform("material.baseColorMap", diffuse_tex); } + if (auto specular_tex = extractTexture(material, bank_name + "/specular_map", scene, aiTextureType_SPECULAR); specular_tex != 0) { - mtl->setUniform("material.specular_map", specular_tex); - mtl->setUniform("material.use_specular_map", true); - } - if (auto normal_tex = extractTexture(material, bank_name + "/normal_map", scene, aiTextureType_NORMALS); normal_tex != 0) { - mtl->setUniform("material.normal_map", normal_tex); - mtl->setUniform("material.use_normal_map", true); + mtl->setUniform("material.hasMetallicRoughnessMap", 1); + mtl->setUniform("material.metallicRoughnessMap", specular_tex); } - if (ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)) != 0) { - return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); + if (auto normal_tex = extractTexture(material, bank_name + "/normal_map", scene, aiTextureType_NORMALS); normal_tex != 0) { + mtl->setUniform("material.hasNormalMap", 1); + mtl->setUniform("material.normalMap", normal_tex); } - mtl->setShader(ref_bank.getUID(AssetPath::WithTypePrefix("phong"))); - - aiColor4D diffuse; - aiColor4D specular; - aiColor4D ambient; - ai_real alpha = 1.0; - - if (aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse) == aiReturn_SUCCESS) - mtl->setUniform("material.albedo", colorToVec(&diffuse)); - if (aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, &specular) == aiReturn_SUCCESS) - mtl->setUniform("material.specular", colorToVec(&specular)); - if (aiGetMaterialColor(material, AI_MATKEY_COLOR_AMBIENT, &ambient) == aiReturn_SUCCESS) - mtl->setUniform("material.ambient", colorToVec(&ambient)); - if (aiGetMaterialFloat(material, AI_MATKEY_SHININESS, &alpha) == aiReturn_SUCCESS) - mtl->setUniform("material.alpha", std::max(alpha, 1.0f)); ref_bank.addAsset(bank_name, mtl); return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); diff --git a/ICE/IO/src/Project.cpp b/ICE/IO/src/Project.cpp index e88c2034..5e96be8e 100644 --- a/ICE/IO/src/Project.cpp +++ b/ICE/IO/src/Project.cpp @@ -41,11 +41,12 @@ bool Project::CreateDirectories() { } catch (std::filesystem::filesystem_error &e) { Logger::Log(Logger::FATAL, "IO", "Could not copy default assets: %s", e.what()); } - assetBank->addAsset("solid", {m_shaders_directory / "solid.vs", m_shaders_directory / "solid.fs"}); - assetBank->addAsset("phong", {m_shaders_directory / "phong.vs", m_shaders_directory / "phong.fs"}); - assetBank->addAsset("normal", {m_shaders_directory / "normal.vs", m_shaders_directory / "normal.fs"}); + assetBank->addAsset("solid", {m_shaders_directory / "skinning.vs", m_shaders_directory / "solid.fs"}); + assetBank->addAsset("phong", {m_shaders_directory / "skinning.vs", m_shaders_directory / "phong.fs"}); + assetBank->addAsset("normal", {m_shaders_directory / "skinning.vs", m_shaders_directory / "normal.fs"}); + assetBank->addAsset("pbr", {m_shaders_directory / "skinning.vs", m_shaders_directory / "pbr.fs"}); assetBank->addAsset("lastpass", {m_shaders_directory / "lastpass.vs", m_shaders_directory / "lastpass.fs"}); - assetBank->addAsset("__ice__picking_shader", {m_shaders_directory / "picking.vs", m_shaders_directory / "picking.fs"}); + assetBank->addAsset("__ice__picking_shader", {m_shaders_directory / "skinning.vs", m_shaders_directory / "picking.fs"}); assetBank->addAsset("base_mat", {m_materials_directory / "base_mat.icm"}); From b42978ef145613223b7c154ce49d49e357fd5881 Mon Sep 17 00:00:00 2001 From: ProtectedVariable Date: Thu, 18 Dec 2025 11:12:51 +0100 Subject: [PATCH 2/6] first pbr test ok --- Assets/Shaders/pbr.fs | 155 +++++++++++++++++++++++++++---------- ICE/IO/src/ModelLoader.cpp | 37 +++++---- 2 files changed, 132 insertions(+), 60 deletions(-) diff --git a/Assets/Shaders/pbr.fs b/Assets/Shaders/pbr.fs index aa3302cd..796b5336 100644 --- a/Assets/Shaders/pbr.fs +++ b/Assets/Shaders/pbr.fs @@ -11,8 +11,11 @@ struct Material { bool hasBaseColorMap; sampler2D baseColorMap; - bool hasMetallicRoughnessMap; - sampler2D metallicRoughnessMap; + bool hasMetallicMap; + sampler2D metallicMap; + + bool hasRoughnessMap; + sampler2D roughnessMap; bool hasNormalMap; sampler2D normalMap; @@ -31,53 +34,119 @@ in vec3 fview; in vec2 ftex_coords; out vec4 frag_color; +const float PI = 3.14159265359; -void main() { - vec3 N = fnormal; - vec3 V = normalize(fview - fposition); +vec3 getNormalFromMap() { + if(material.hasNormalMap) { + vec3 tangentNormal = texture(material.normalMap, ftex_coords).xyz * 2.0 - 1.0; - // Normal map - if (material.hasNormalMap) { - vec3 normalTex = texture(material.normalMap, ftex_coords).xyz * 2.0 - 1.0; - mat3 TBN = mat3(ftangent, fbitangent, fnormal); - N = normalize(TBN * normalTex); + vec3 N = normalize(fnormal); + vec3 T = normalize(ftangent); + vec3 B = normalize(fbitangent); + mat3 TBN = mat3(T, B, N); + + return normalize(TBN * tangentNormal); + } else { + return fnormal; } +} +// ---------------------------------------------------------------------------- +float DistributionGGX(vec3 N, vec3 H, float roughness) { + float a = roughness*roughness; + float a2 = a*a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH*NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return nom / denom; +} +// ---------------------------------------------------------------------------- +float GeometrySchlickGGX(float NdotV, float roughness) { + float r = (roughness + 1.0); + float k = (r*r) / 8.0; - vec3 albedo = material.baseColor; - if (material.hasBaseColorMap) - albedo *= texture(material.baseColorMap, ftex_coords).rgb; + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; - float metallic = material.metallic; - float roughness = material.roughness; - float ao = material.ao; - if (material.hasAoMap) - ao *= texture(material.aoMap, ftex_coords).r; + return nom / denom; +} +// ---------------------------------------------------------------------------- +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} +// ---------------------------------------------------------------------------- +vec3 fresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} +// ---------------------------------------------------------------------------- +void main() { + vec3 albedo = pow(material.hasBaseColorMap ? texture(material.baseColorMap, ftex_coords).rgb : material.baseColor, vec3(2.2)); + float metallic = material.hasMetallicMap ? texture(material.metallicMap, ftex_coords).r : material.metallic; + float roughness = material.hasRoughnessMap ? texture(material.roughnessMap, ftex_coords).r : material.roughness; + float ao = material.hasAoMap ? texture(material.aoMap, ftex_coords).r : material.ao; + + vec3 N = getNormalFromMap(); + vec3 V = normalize(fview - fposition); + + // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 + // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow) + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metallic); + // reflectance equation vec3 Lo = vec3(0.0); - for (int i = 0; i < light_count; i++) { + for(int i = 0; i < 4; ++i) { + // calculate per-light radiance vec3 L = normalize(lights[i].position - fposition); + vec3 H = normalize(V + L); float distance = length(lights[i].position - fposition); - vec3 H = normalize(L + V); - - float NdotL = max(dot(N, L), 0.0); - float NdotV = max(dot(N, V), 0.001); - float NdotH = max(dot(N, H), 0.0); - float VdotH = max(dot(V, H), 0.0); - - // Cook-Torrance specular - float alpha = roughness * roughness; - float D = alpha / (3.141592 * pow(NdotH*NdotH*(alpha-1)+1,2)); - float k = (alpha + 1)*(alpha +1)/8; - float G = NdotL/(NdotL*(1-k)+k) * NdotV/(NdotV*(1-k)+k); - vec3 F0 = mix(vec3(0.04), albedo, metallic); - vec3 F = F0 + (1.0 - F0)*pow(1.0 - VdotH, 5.0); - - vec3 spec = (D * G * F)/(4*NdotV*NdotL + 0.001); - vec3 diffuse = albedo / 3.141592 * (1.0 - metallic); - - vec3 radiance = lights[i].color / (distance*distance); - Lo += NdotL * radiance * (diffuse + spec); - } - - frag_color = vec4(Lo * ao, 1.0); -} + float attenuation = 1.0 / (1 + lights[i].distance_dropoff * distance * distance); + vec3 radiance = lights[i].color * attenuation; + + // Cook-Torrance BRDF + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; // + 0.0001 to prevent divide by zero + vec3 specular = numerator / denominator; + + // kS is equal to Fresnel + vec3 kS = F; + // for energy conservation, the diffuse and specular light can't + // be above 1.0 (unless the surface emits light); to preserve this + // relationship the diffuse component (kD) should equal 1.0 - kS. + vec3 kD = vec3(1.0) - kS; + // multiply kD by the inverse metalness such that only non-metals + // have diffuse lighting, or a linear blend if partly metal (pure metals + // have no diffuse light). + kD *= 1.0 - metallic; + + // scale light by NdotL + float NdotL = max(dot(N, L), 0.0); + + // add to outgoing radiance Lo + Lo += (kD * albedo / PI + specular) * radiance * NdotL; // note that we already multiplied the BRDF by the Fresnel (kS) so we won't multiply by kS again + } + + //TODO IBL + vec3 ambient = vec3(0.03) * albedo * ao; + + vec3 color = ambient + Lo; + + // HDR tonemapping + color = color / (color + vec3(1.0)); + // gamma correct + color = pow(color, vec3(1.0/2.2)); + + frag_color = vec4(color, 1.0); +} \ No newline at end of file diff --git a/ICE/IO/src/ModelLoader.cpp b/ICE/IO/src/ModelLoader.cpp index 7452c960..196dea73 100644 --- a/ICE/IO/src/ModelLoader.cpp +++ b/ICE/IO/src/ModelLoader.cpp @@ -177,21 +177,19 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str mtl->setUniform("material.roughness", 1.0f); mtl->setShader(ref_bank.getUID(AssetPath::WithTypePrefix("pbr"))); // Base color - aiColor4D diffuse; - if (aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse) == aiReturn_SUCCESS) - mtl->setUniform("material.baseColor", colorToVec(&diffuse)); - - ai_real shininess = 1.0f; - if (aiGetMaterialFloat(material, AI_MATKEY_SHININESS, &shininess) == aiReturn_SUCCESS) - mtl->setUniform("material.roughness", sqrt(2.0f / (shininess + 2.0f))); - - aiColor4D specular; - if (aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, &specular) == aiReturn_SUCCESS) { - float maxSpec = std::max({specular.r, specular.g, specular.b}); - mtl->setUniform("material.metallic", maxSpec); - } + aiColor4D diffuse = aiColor4D(1, 1, 1, 1); + aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse); + mtl->setUniform("material.baseColor", Eigen::Vector3f(colorToVec(&diffuse).head<3>())); + + ai_real roughness = 1.0f; + aiGetMaterialFloat(material, AI_MATKEY_ROUGHNESS_FACTOR, &roughness); + mtl->setUniform("material.roughness", (float) roughness); + + ai_real metallic = 0.0f; + aiGetMaterialFloat(material, AI_MATKEY_METALLIC_FACTOR, &metallic); + mtl->setUniform("material.metallic", (float) metallic); - if (auto ambient_map = extractTexture(material, bank_name + "/ambient_map", scene, aiTextureType_AMBIENT); ambient_map != 0) { + if (auto ambient_map = extractTexture(material, bank_name + "/ao_map", scene, aiTextureType_AMBIENT); ambient_map != 0) { mtl->setUniform("material.hasAoMap", 1); mtl->setUniform("material.aoMap", ambient_map); } @@ -201,9 +199,14 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str mtl->setUniform("material.baseColorMap", diffuse_tex); } - if (auto specular_tex = extractTexture(material, bank_name + "/specular_map", scene, aiTextureType_SPECULAR); specular_tex != 0) { - mtl->setUniform("material.hasMetallicRoughnessMap", 1); - mtl->setUniform("material.metallicRoughnessMap", specular_tex); + if (auto metallic_tex = extractTexture(material, bank_name + "/metallic_map", scene, aiTextureType_SPECULAR); metallic_tex != 0) { + mtl->setUniform("material.hasMetallicMap", 1); + mtl->setUniform("material.metallicMap", metallic_tex); + } + + if (auto roughness_tex = extractTexture(material, bank_name + "/roughness_map", scene, aiTextureType_SPECULAR); roughness_tex != 0) { + mtl->setUniform("material.hasRoughnessMap", 1); + mtl->setUniform("material.roughnessMap", roughness_tex); } if (auto normal_tex = extractTexture(material, bank_name + "/normal_map", scene, aiTextureType_NORMALS); normal_tex != 0) { From a137116bf768500b1fd79d187448b2abffbbfde0 Mon Sep 17 00:00:00 2001 From: ProtectedVariable Date: Thu, 18 Dec 2025 11:40:56 +0100 Subject: [PATCH 3/6] couple fixes --- Assets/Shaders/pbr.fs | 2 +- ICE/IO/src/ModelLoader.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Assets/Shaders/pbr.fs b/Assets/Shaders/pbr.fs index 796b5336..f3392726 100644 --- a/Assets/Shaders/pbr.fs +++ b/Assets/Shaders/pbr.fs @@ -103,7 +103,7 @@ void main() { // reflectance equation vec3 Lo = vec3(0.0); - for(int i = 0; i < 4; ++i) { + for(int i = 0; i < light_count; ++i) { // calculate per-light radiance vec3 L = normalize(lights[i].position - fposition); vec3 H = normalize(V + L); diff --git a/ICE/IO/src/ModelLoader.cpp b/ICE/IO/src/ModelLoader.cpp index 196dea73..4b4e12f5 100644 --- a/ICE/IO/src/ModelLoader.cpp +++ b/ICE/IO/src/ModelLoader.cpp @@ -170,7 +170,8 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str auto mtl = std::make_shared(); mtl->setUniform("material.hasAoMap", 0); mtl->setUniform("material.hasBaseColorMap", 0); - mtl->setUniform("material.hasMetallicRoughnessMap", 0); + mtl->setUniform("material.hasMetallicMap", 0); + mtl->setUniform("material.hasRoughnessMap", 0); mtl->setUniform("material.hasNormalMap", 0); mtl->setUniform("material.ao", 1.0f); mtl->setUniform("material.metallic", 0.0f); @@ -189,22 +190,22 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str aiGetMaterialFloat(material, AI_MATKEY_METALLIC_FACTOR, &metallic); mtl->setUniform("material.metallic", (float) metallic); - if (auto ambient_map = extractTexture(material, bank_name + "/ao_map", scene, aiTextureType_AMBIENT); ambient_map != 0) { + if (auto ambient_map = extractTexture(material, bank_name + "/ao_map", scene, aiTextureType_AMBIENT_OCCLUSION); ambient_map != 0) { mtl->setUniform("material.hasAoMap", 1); mtl->setUniform("material.aoMap", ambient_map); } - if (auto diffuse_tex = extractTexture(material, bank_name + "/diffuse_map", scene, aiTextureType_DIFFUSE); diffuse_tex != 0) { + if (auto diffuse_tex = extractTexture(material, bank_name + "/diffuse_map", scene, aiTextureType_BASE_COLOR); diffuse_tex != 0) { mtl->setUniform("material.hasBaseColorMap", 1); mtl->setUniform("material.baseColorMap", diffuse_tex); } - if (auto metallic_tex = extractTexture(material, bank_name + "/metallic_map", scene, aiTextureType_SPECULAR); metallic_tex != 0) { + if (auto metallic_tex = extractTexture(material, bank_name + "/metallic_map", scene, aiTextureType_METALNESS); metallic_tex != 0) { mtl->setUniform("material.hasMetallicMap", 1); mtl->setUniform("material.metallicMap", metallic_tex); } - if (auto roughness_tex = extractTexture(material, bank_name + "/roughness_map", scene, aiTextureType_SPECULAR); roughness_tex != 0) { + if (auto roughness_tex = extractTexture(material, bank_name + "/roughness_map", scene, aiTextureType_DIFFUSE_ROUGHNESS); roughness_tex != 0) { mtl->setUniform("material.hasRoughnessMap", 1); mtl->setUniform("material.roughnessMap", roughness_tex); } From 7fdf2c493d128bf2eae0918fd765494be1eb2fc8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 18 Dec 2025 15:28:58 +0100 Subject: [PATCH 4/6] fix for normal map --- Assets/Shaders/pbr.fs | 10 ++++++---- Assets/Shaders/skinning.vs | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Assets/Shaders/pbr.fs b/Assets/Shaders/pbr.fs index f3392726..bfd1ca41 100644 --- a/Assets/Shaders/pbr.fs +++ b/Assets/Shaders/pbr.fs @@ -40,11 +40,13 @@ vec3 getNormalFromMap() { if(material.hasNormalMap) { vec3 tangentNormal = texture(material.normalMap, ftex_coords).xyz * 2.0 - 1.0; - vec3 N = normalize(fnormal); - vec3 T = normalize(ftangent); - vec3 B = normalize(fbitangent); - mat3 TBN = mat3(T, B, N); + vec3 N = normalize(fnormal); + vec3 T = normalize(ftangent); + // Gram-Schmidt process to re-orthogonalize T + T = normalize(T - dot(T, N) * N); + vec3 B = cross(N, T); // Reconstruct Bitangent + mat3 TBN = mat3(T, B, N); return normalize(TBN * tangentNormal); } else { return fnormal; diff --git a/Assets/Shaders/skinning.vs b/Assets/Shaders/skinning.vs index a49b8419..085b09cb 100644 --- a/Assets/Shaders/skinning.vs +++ b/Assets/Shaders/skinning.vs @@ -32,6 +32,8 @@ void main() { if(bone_ids == ivec4(-1)) { totalPosition = vec4(vertex, 1.0f); totalNormal = normal; + totalTangent = tangent; + totalBitangent = bitangent; } else { for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) { if(bone_ids[i] == -1) continue; @@ -39,6 +41,8 @@ void main() { if(bone_ids[i] >= MAX_BONES) { totalPosition = vec4(vertex, 1.0f); totalNormal = normal; + totalTangent = tangent; + totalBitangent = bitangent; break; } From 5c2f2b06337cbf33680edbce108ee5001f2665dd Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 18 Dec 2025 19:28:39 +0100 Subject: [PATCH 5/6] PBR rendering, loading of modern textures --- Assets/Shaders/pbr.fs | 12 +++-- ICE/Graphics/include/Texture.h | 2 +- .../OpenGL/include/OpenGLTexture.h | 52 +++++++++++++++++++ .../OpenGL/src/OpenGLTexture2D.cpp | 23 ++------ .../OpenGL/src/OpenGLTextureCube.cpp | 2 +- ICE/IO/include/ModelLoader.h | 2 + ICE/IO/src/ModelLoader.cpp | 30 +++++++++-- 7 files changed, 95 insertions(+), 28 deletions(-) diff --git a/Assets/Shaders/pbr.fs b/Assets/Shaders/pbr.fs index bfd1ca41..6bbaeb73 100644 --- a/Assets/Shaders/pbr.fs +++ b/Assets/Shaders/pbr.fs @@ -22,6 +22,9 @@ struct Material { bool hasAoMap; sampler2D aoMap; + + bool hasEmissiveMap; + sampler2D emissiveMap; }; uniform Material material; @@ -91,9 +94,9 @@ vec3 fresnelSchlick(float cosTheta, vec3 F0) { // ---------------------------------------------------------------------------- void main() { vec3 albedo = pow(material.hasBaseColorMap ? texture(material.baseColorMap, ftex_coords).rgb : material.baseColor, vec3(2.2)); - float metallic = material.hasMetallicMap ? texture(material.metallicMap, ftex_coords).r : material.metallic; - float roughness = material.hasRoughnessMap ? texture(material.roughnessMap, ftex_coords).r : material.roughness; - float ao = material.hasAoMap ? texture(material.aoMap, ftex_coords).r : material.ao; + float metallic = material.hasMetallicMap ? texture(material.metallicMap, ftex_coords).b : material.metallic; + float roughness = material.hasRoughnessMap ? texture(material.roughnessMap, ftex_coords).g : material.roughness; + float ao = material.hasAoMap ? texture(material.aoMap, ftex_coords).length() : material.ao; vec3 N = getNormalFromMap(); vec3 V = normalize(fview - fposition); @@ -143,7 +146,8 @@ void main() { //TODO IBL vec3 ambient = vec3(0.03) * albedo * ao; - vec3 color = ambient + Lo; + vec3 emissive = material.hasEmissiveMap ? texture(material.emissiveMap, ftex_coords).rgb : vec3(0.0); + vec3 color = ambient + Lo + emissive; // HDR tonemapping color = color / (color + vec3(1.0)); diff --git a/ICE/Graphics/include/Texture.h b/ICE/Graphics/include/Texture.h index 76f6ea12..6de9653d 100644 --- a/ICE/Graphics/include/Texture.h +++ b/ICE/Graphics/include/Texture.h @@ -13,7 +13,7 @@ #include namespace ICE { -enum class TextureFormat { None = 0, RGB = 1, RGBA = 2, Float16 = 3, MONO8 = 4 }; +enum class TextureFormat { None = 0, SRGB8, SRGBA8, RGB8, RGBA8, Float16, MONO8 }; enum class TextureWrap { None = 0, Clamp = 1, Repeat = 2 }; diff --git a/ICE/GraphicsAPI/OpenGL/include/OpenGLTexture.h b/ICE/GraphicsAPI/OpenGL/include/OpenGLTexture.h index 8753aef4..a7063c71 100644 --- a/ICE/GraphicsAPI/OpenGL/include/OpenGLTexture.h +++ b/ICE/GraphicsAPI/OpenGL/include/OpenGLTexture.h @@ -11,6 +11,58 @@ #include namespace ICE { + +constexpr GLenum textureFormatToGLInternalFormat(TextureFormat format) { + switch (format) { + case TextureFormat::SRGB8: + return GL_SRGB8; + case TextureFormat::SRGBA8: + return GL_SRGB8_ALPHA8; + case TextureFormat::RGB8: + return GL_RGB8; + case TextureFormat::RGBA8: + return GL_RGBA8; + case TextureFormat::Float16: + return GL_RGBA16F; + case TextureFormat::MONO8: + return GL_R8; + default: + return GL_RGBA8; + } +} + +constexpr int textureFormatToAlignment(TextureFormat format) { + switch (format) { + case TextureFormat::SRGB8: + case TextureFormat::RGB8: + return 1; + case TextureFormat::SRGBA8: + case TextureFormat::RGBA8: + case TextureFormat::Float16: + return 4; + case TextureFormat::MONO8: + return 1; + default: + return 4; + } +} + +constexpr int textureFormatToChannels(TextureFormat format) { + switch (format) { + case TextureFormat::SRGB8: + case TextureFormat::RGB8: + return 3; + case TextureFormat::SRGBA8: + case TextureFormat::RGBA8: + case TextureFormat::Float16: + return 4; + case TextureFormat::MONO8: + return 1; + default: + return 4; + } +} + class OpenGLTexture2D : public Texture2D { public: OpenGLTexture2D(const std::string &file); diff --git a/ICE/GraphicsAPI/OpenGL/src/OpenGLTexture2D.cpp b/ICE/GraphicsAPI/OpenGL/src/OpenGLTexture2D.cpp index dc5fbed6..cb5be8e4 100644 --- a/ICE/GraphicsAPI/OpenGL/src/OpenGLTexture2D.cpp +++ b/ICE/GraphicsAPI/OpenGL/src/OpenGLTexture2D.cpp @@ -12,7 +12,7 @@ namespace ICE { OpenGLTexture2D::OpenGLTexture2D(const std::string &file) : file(file) { int channels, w, h; void *data = Texture::getDataFromFile(file, &w, &h, &channels); - loadData(data, w, h, channels == 4 ? TextureFormat::RGBA : TextureFormat::RGB); + loadData(data, w, h, channels == 4 ? TextureFormat::RGBA8 : TextureFormat::RGB8); stbi_image_free(data); } @@ -23,24 +23,13 @@ OpenGLTexture2D::OpenGLTexture2D(const void *data, size_t w, size_t h, TextureFo void OpenGLTexture2D::loadData(const void *data, size_t w, size_t h, TextureFormat fmt) { width = w; height = h; - storageFormat = GL_RGBA; - dataFormat = GL_RGBA; glGenTextures(1, &id); glBindTexture(GL_TEXTURE_2D, id); - if (fmt == TextureFormat::RGBA) { - format = TextureFormat::RGBA; - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - } else if (fmt == TextureFormat::RGB) { - storageFormat = dataFormat = GL_RGB; - format = TextureFormat::RGB; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - } else if (fmt == TextureFormat::MONO8) { - storageFormat = dataFormat = GL_RED; - format = TextureFormat::MONO8; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - } + storageFormat = textureFormatToGLInternalFormat(fmt); + dataFormat = (textureFormatToChannels(fmt) == 4) ? GL_RGBA : (textureFormatToChannels(fmt) == 3) ? GL_RGB : GL_RED; + glPixelStorei(GL_UNPACK_ALIGNMENT, textureFormatToAlignment(fmt)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -53,10 +42,6 @@ void OpenGLTexture2D::loadData(const void *data, size_t w, size_t h, TextureForm } void OpenGLTexture2D::setData(void *data, uint32_t size) { - uint32_t bpp = (format == TextureFormat::RGBA) ? 4 : 3; - if (size != bpp * width * height) { - throw ICEException("Texture size corrupted"); - } glTextureSubImage2D(id, 0, 0, 0, width, height, dataFormat, GL_UNSIGNED_BYTE, data); } diff --git a/ICE/GraphicsAPI/OpenGL/src/OpenGLTextureCube.cpp b/ICE/GraphicsAPI/OpenGL/src/OpenGLTextureCube.cpp index faeba6c4..b7ef4a13 100644 --- a/ICE/GraphicsAPI/OpenGL/src/OpenGLTextureCube.cpp +++ b/ICE/GraphicsAPI/OpenGL/src/OpenGLTextureCube.cpp @@ -38,7 +38,7 @@ void OpenGLTextureCube::bind(uint32_t slot) const { } TextureFormat OpenGLTextureCube::getFormat() const { - return TextureFormat::RGB; + return TextureFormat::RGB8; } uint32_t OpenGLTextureCube::getWidth() const { diff --git a/ICE/IO/include/ModelLoader.h b/ICE/IO/include/ModelLoader.h index 453bdb77..0b92c85c 100644 --- a/ICE/IO/include/ModelLoader.h +++ b/ICE/IO/include/ModelLoader.h @@ -39,6 +39,8 @@ class ModelLoader : public IAssetLoader { Eigen::Vector3f aiVec3ToEigen(const aiVector3D &vec); Eigen::Quaternionf aiQuatToEigen(const aiQuaternion &q); + constexpr TextureFormat getTextureFormat(aiTextureType type, int channels); + AssetBank &ref_bank; std::shared_ptr m_graphics_factory; }; diff --git a/ICE/IO/src/ModelLoader.cpp b/ICE/IO/src/ModelLoader.cpp index 4b4e12f5..3b892ed6 100644 --- a/ICE/IO/src/ModelLoader.cpp +++ b/ICE/IO/src/ModelLoader.cpp @@ -173,6 +173,7 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str mtl->setUniform("material.hasMetallicMap", 0); mtl->setUniform("material.hasRoughnessMap", 0); mtl->setUniform("material.hasNormalMap", 0); + mtl->setUniform("material.hasEmissiveMap", 0); mtl->setUniform("material.ao", 1.0f); mtl->setUniform("material.metallic", 0.0f); mtl->setUniform("material.roughness", 1.0f); @@ -190,7 +191,7 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str aiGetMaterialFloat(material, AI_MATKEY_METALLIC_FACTOR, &metallic); mtl->setUniform("material.metallic", (float) metallic); - if (auto ambient_map = extractTexture(material, bank_name + "/ao_map", scene, aiTextureType_AMBIENT_OCCLUSION); ambient_map != 0) { + if (auto ambient_map = extractTexture(material, bank_name + "/ao_map", scene, aiTextureType_LIGHTMAP); ambient_map != 0) { mtl->setUniform("material.hasAoMap", 1); mtl->setUniform("material.aoMap", ambient_map); } @@ -215,6 +216,11 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str mtl->setUniform("material.normalMap", normal_tex); } + if (auto emissive_tex = extractTexture(material, bank_name + "/emissive_map", scene, aiTextureType_EMISSIVE); emissive_tex != 0) { + mtl->setUniform("material.hasEmissiveMap", 1); + mtl->setUniform("material.emissiveMap", emissive_tex); + } + ref_bank.addAsset(bank_name, mtl); return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); } @@ -228,14 +234,15 @@ AssetUID ModelLoader::extractTexture(const aiMaterial *material, const std::stri void *data2 = nullptr; int width = texture->mWidth; int height = texture->mHeight; - int channels = 0; + int channels = 3; if (height == 0) { //Compressed memory, use stbi to load data2 = stbi_load_from_memory(data, texture->mWidth, &width, &height, &channels, 4); + channels = 4; } else { data2 = data; } - auto texture_ice = m_graphics_factory->createTexture2D(data2, width, height, TextureFormat::RGBA); + auto texture_ice = m_graphics_factory->createTexture2D(data2, width, height, getTextureFormat(type, channels)); if (tex_id = ref_bank.getUID(AssetPath::WithTypePrefix(tex_path)); tex_id != 0) { ref_bank.removeAsset(AssetPath::WithTypePrefix(tex_path)); ref_bank.addAssetWithSpecificUID(AssetPath::WithTypePrefix(tex_path), texture_ice, tex_id); @@ -370,4 +377,21 @@ Eigen::Quaternionf ModelLoader::aiQuatToEigen(const aiQuaternion &q) { return quat; } +constexpr TextureFormat ModelLoader::getTextureFormat(aiTextureType type, int channels) { + switch (type) { + case aiTextureType_METALNESS: + case aiTextureType_AMBIENT_OCCLUSION: + case aiTextureType_LIGHTMAP: + case aiTextureType_DIFFUSE_ROUGHNESS: + case aiTextureType_NORMALS: + return channels == 3 ? TextureFormat::RGB8 : TextureFormat::RGBA8; + + case aiTextureType_BASE_COLOR: + case aiTextureType_EMISSIVE: + return channels == 3 ? TextureFormat::SRGB8 : TextureFormat::SRGBA8; + default: + return channels == 3 ? TextureFormat::RGB8 : TextureFormat::RGBA8; + } +} + } // namespace ICE From 879a32810712ff18901597bd36fac3f845332eab Mon Sep 17 00:00:00 2001 From: ProtectedVariable Date: Fri, 19 Dec 2025 10:24:54 +0100 Subject: [PATCH 6/6] fix material loading --- ICE/IO/src/ModelLoader.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ICE/IO/src/ModelLoader.cpp b/ICE/IO/src/ModelLoader.cpp index 3b892ed6..04ee55f2 100644 --- a/ICE/IO/src/ModelLoader.cpp +++ b/ICE/IO/src/ModelLoader.cpp @@ -161,12 +161,7 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str if (mtl_name.length == 0) { mtl_name = "DefaultMat"; } - //If material already exists, return its UID auto bank_name = model_name + "/" + mtl_name.C_Str(); - if (ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)) != 0) { - return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); - } - auto mtl = std::make_shared(); mtl->setUniform("material.hasAoMap", 0); mtl->setUniform("material.hasBaseColorMap", 0); @@ -221,6 +216,10 @@ AssetUID ModelLoader::extractMaterial(const aiMaterial *material, const std::str mtl->setUniform("material.emissiveMap", emissive_tex); } + if (ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)) != 0) { + return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); + } + ref_bank.addAsset(bank_name, mtl); return ref_bank.getUID(AssetPath::WithTypePrefix(bank_name)); }