diff --git a/.gitmodules b/.gitmodules index 4df89e7..3ed8382 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,3 +15,6 @@ [submodule "CWindow/vendor/stb"] path = CWindow/vendor/stb url = https://github.com/nothings/stb.git +[submodule "CWindow/vendor/assimp"] + path = CWindow/vendor/assimp + url = https://github.com/assimp/assimp diff --git a/CWindow/CMakeLists.txt b/CWindow/CMakeLists.txt index 3b29333..74e0ad7 100644 --- a/CWindow/CMakeLists.txt +++ b/CWindow/CMakeLists.txt @@ -42,13 +42,16 @@ project(CWindow LANGUAGES CXX C) ### Vendor Libs add_subdirectory(vendor/glfw) add_subdirectory(vendor/glm) +add_subdirectory(vendor/assimp) ### Subfolders add_subdirectory(Renderer) add_subdirectory(Gui) -set(src "CWindow.cpp") +set(src + "CWindow.cpp" +) add_library(CWindow STATIC ${src}) -target_link_libraries(CWindow Renderer Gui) \ No newline at end of file +target_link_libraries(CWindow Renderer Gui) \ No newline at end of file diff --git a/CWindow/Gui/OpenGL/CMakeLists.txt b/CWindow/Gui/OpenGL/CMakeLists.txt index 111dcb8..ead0e4e 100644 --- a/CWindow/Gui/OpenGL/CMakeLists.txt +++ b/CWindow/Gui/OpenGL/CMakeLists.txt @@ -19,6 +19,7 @@ set(headers ${ROOT_DIR}/vendor/glfw/include/ ${ROOT_DIR}/vendor/glm/glm/ ${ROOT_DIR}/vendor/stb/ + ${ROOT_DIR}/vendor/assimp/include/ ${ROOT_DIR}/vendor/imgui/ ${ROOT_DIR}/vendor/imgui/backends/ ) @@ -29,6 +30,7 @@ if(PLATFORM STREQUAL "UNIX") set(libs glfw glm + assimp X11 Xrandr Xcursor @@ -40,6 +42,7 @@ elseif(PLATFORM STREQUAL "WIN32") set(libs glfw glm + assimp winmm imm32 version diff --git a/CWindow/Material/PBRMaterial.h b/CWindow/Material/PBRMaterial.h new file mode 100644 index 0000000..b33dbbe --- /dev/null +++ b/CWindow/Material/PBRMaterial.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include + + + + + + + +namespace CW{ +struct PBRMaterial { + glm::vec3 albedo = glm::vec3(1.0f); + float metallic = 0.0f; + float roughness = 1.0f; + glm::vec3 emission_color = glm::vec3(0.0f); + float emission_strength = 0.0f; + float ambient_occlusion = 1.0f; +}; + + + +inline const std::string PBRShaderBRDFUniforms = R"( + +uniform vec3 albedo; +uniform float roughness; +uniform float metallic; +uniform float emission_strength; +uniform vec3 emission_color; +uniform float ambient_occlusion; + +)"; + + + +inline const std::string PBRShaderBRDFInclude = R"( + +const float PI = 3.14159265359; + +// ---------------- BRDF FUNCTIONS ---------------- + +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 denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return a2 / max(denom, 0.0001); +} + +float GeometrySchlickGGX(float NdotV, float roughness) +{ + float r = roughness + 1.0; + float k = (r * r) / 8.0; + + return NdotV / (NdotV * (1.0 - k) + k); +} + +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); + + return GeometrySchlickGGX(NdotV, roughness) * + GeometrySchlickGGX(NdotL, roughness); +} + +vec3 FresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} + +vec3 BRDF_PBR( + vec3 N, + vec3 V, + vec3 L, + vec3 lightColor +) +{ + vec3 H = normalize(V + L); + + float NdotL = max(dot(N, L), 0.0); + float NdotV = max(dot(N, V), 0.0); + float NdotH = max(dot(N, H), 0.0); + + // ---------------- Fresnel ---------------- + vec3 F0 = vec3(0.04); + F0 = mix(F0, albedo, metallic); + + vec3 F = FresnelSchlick(max(dot(V, H), 0.0), F0); + + // ---------------- GGX ---------------- + float a = roughness * roughness; + float a2 = a * a; + + float denom = NdotH * NdotH * (a2 - 1.0) + 1.0; + float D = a2 / (PI * denom * denom + 0.0001); + + float k = (roughness + 1.0); + k = (k * k) / 8.0; + + float G1 = NdotL / (NdotL * (1.0 - k) + k); + float G2 = NdotV / (NdotV * (1.0 - k) + k); + float G = G1 * G2; + + // ---------------- Specular ---------------- + vec3 spec = (D * G * F) / max(4.0 * NdotL * NdotV, 0.001); + + // ---------------- Diffuse (Blender-style energy conservation) ---------------- + vec3 kS = F; + vec3 kD = (1.0 - kS) * (1.0 - metallic); + + vec3 diffuse = kD * albedo / PI; + + return (diffuse + spec) * lightColor * NdotL; +} + +vec3 BRDF( + vec3 Normal, + vec3 FragPos, + vec3 cameraPos, + vec3 lightPos, + vec3 lightColor +) +{ + vec3 N = normalize(Normal); + vec3 V = normalize(cameraPos - FragPos); + vec3 L = normalize(lightPos - FragPos); + + vec3 lighting = BRDF_PBR(N, V, L, lightColor); + + // // AO ONLY affects indirect approximation (simple hack) + // lighting *= ambient_occlusion; + + vec3 ambient = 0.03 * albedo * ambient_occlusion; + + vec3 emissiveColor = emission_color * emission_strength; + + return lighting + ambient + emissiveColor; +} +)"; +}; \ No newline at end of file diff --git a/CWindow/MeshLoader/MeshLoader.h b/CWindow/MeshLoader/MeshLoader.h new file mode 100644 index 0000000..2e50549 --- /dev/null +++ b/CWindow/MeshLoader/MeshLoader.h @@ -0,0 +1,226 @@ +#pragma once +#include "glad/glad.h" +#include "GLFW/glfw3.h" +#include "glm/glm.hpp" + +#include "assimp/Importer.hpp" +#include "assimp/scene.h" +#include "assimp/postprocess.h" +#include "assimp/material.h" + +#include "Material/PBRMaterial.h" + +#include + + + + + + + +namespace CW{ +class MeshLoader{ + public: + std::vector vertices; + std::vector indices; + std::vector normals; + std::vector uvs; + std::vector tangents; + std::vector bitangents; + std::vector colors; + CW::PBRMaterial material; + + Assimp::Importer importer; + + void LoadModel(const std::string& path){ + + const aiScene* scene = importer.ReadFile( + path, + aiProcess_Triangulate | + aiProcess_FlipUVs | + aiProcess_CalcTangentSpace + ); + + if (!scene || !scene->mRootNode){ + printf("Can't open file %s\b", path); + return; + }; + + aiMesh* mesh = scene->mMeshes[0]; + + extractVertices(mesh); + extractIndices(mesh); + extractNormals(mesh); + extractUVs(mesh); + extractTangents(mesh); + extractColors(mesh); + extractMaterial(mesh, scene); + }; + + void extractVertices(aiMesh* mesh){ + vertices.clear(); + vertices.reserve(mesh->mNumVertices * 4); + for (unsigned int i = 0; i < mesh->mNumVertices; i++){ + const aiVector3D& v = mesh->mVertices[i]; + vertices.push_back(v.x); + vertices.push_back(v.y); + vertices.push_back(v.z); + vertices.push_back(1.0); + } + } + + void extractIndices(aiMesh* mesh){ + indices.clear(); + indices.reserve(mesh->mNumFaces * 3); + for (unsigned int i = 0; i < mesh->mNumFaces; i++){ + const aiFace& face = mesh->mFaces[i]; + + for (unsigned int j = 0; j < face.mNumIndices; j++) + indices.push_back(face.mIndices[j]); + } + } + + void extractNormals(aiMesh* mesh){ + normals.clear(); + normals.reserve(mesh->mNumVertices * 3); + + if (mesh->HasNormals()){ + for (unsigned int i = 0; i < mesh->mNumVertices; i++){ + const aiVector3D& n = mesh->mNormals[i]; + + normals.push_back(n.x); + normals.push_back(n.y); + normals.push_back(n.z); + } + } + else{ + for (unsigned int i = 0; i < mesh->mNumVertices; i++){ + normals.push_back(0.0f); + normals.push_back(1.0f); + normals.push_back(0.0f); + } + } + } + + void extractUVs(aiMesh* mesh){ + uvs.clear(); + + if (mesh->HasTextureCoords(0)){ + uvs.reserve(mesh->mNumVertices * 2); + + for (unsigned int i = 0; i < mesh->mNumVertices; i++){ + const aiVector3D& uv = mesh->mTextureCoords[0][i]; + + uvs.push_back(uv.x); + uvs.push_back(uv.y); + } + } + } + + void extractTangents(aiMesh* mesh){ + tangents.clear(); + bitangents.clear(); + + if (mesh->HasTangentsAndBitangents()){ + tangents.reserve(mesh->mNumVertices * 3); + bitangents.reserve(mesh->mNumVertices * 3); + + for (unsigned int i = 0; i < mesh->mNumVertices; i++) + { + const aiVector3D& t = mesh->mTangents[i]; + const aiVector3D& b = mesh->mBitangents[i]; + + tangents.insert(tangents.end(), {t.x, t.y, t.z}); + bitangents.insert(bitangents.end(), {b.x, b.y, b.z}); + } + } + } + + void extractColors(aiMesh* mesh){ + colors.clear(); + + if (mesh->HasVertexColors(0)){ + colors.reserve(mesh->mNumVertices * 3); + + for (unsigned int i = 0; i < mesh->mNumVertices; i++){ + const aiColor4D& c = mesh->mColors[0][i]; + + colors.push_back(c.r); + colors.push_back(c.g); + colors.push_back(c.b); + } + } + } + + void extractMaterial(aiMesh* mesh, const aiScene* scene) + { + if (!scene || !mesh) return; + + aiMaterial* mat = scene->mMaterials[mesh->mMaterialIndex]; + + // ---------------- BASE COLOR ---------------- + aiColor3D baseColor(1.0f, 1.0f, 1.0f); + material.albedo = glm::vec3(1.0f); + if (mat->Get(AI_MATKEY_BASE_COLOR, baseColor) == AI_SUCCESS || + mat->Get(AI_MATKEY_COLOR_DIFFUSE, baseColor) == AI_SUCCESS) + { + material.albedo = glm::vec3(baseColor.r, baseColor.g, baseColor.b); + } + + // ---------------- ROUGHNESS ---------------- + float roughness = 1.0f; + material.roughness = roughness; + if (mat->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness) == AI_SUCCESS) + { + material.roughness = roughness; + } + + // ---------------- METALLIC ---------------- + float metallic = 0.0f; + material.metallic = metallic; + if (mat->Get(AI_MATKEY_METALLIC_FACTOR, metallic) == AI_SUCCESS) + { + material.metallic = metallic; + } + + // ---------------- EMISSIVE (REAL ONE) ---------------- + aiColor3D emi_color(0.0f, 0.0f, 0.0f); + material.emission_color = glm::vec3(0.0f); + if (mat->Get(AI_MATKEY_COLOR_EMISSIVE, emi_color) == AI_SUCCESS) + { + material.emission_color = glm::vec3(emi_color.r, emi_color.g, emi_color.b); + } + + float emi_strength = 1.0f; + material.emission_strength = emi_strength; + if (mat->Get(AI_MATKEY_EMISSIVE_INTENSITY, emi_strength) == AI_SUCCESS) + { + material.emission_strength = emi_strength; + } + + // ---------------- AO (fallback) ---------------- + material.ambient_occlusion = 1.0f; + } + + void bindMaterialToUniform(CW::Renderer::Uniform& uniform){ + uniform["albedo"]->set(material.albedo); + uniform["roughness"]->set(material.roughness); + uniform["metallic"]->set(material.metallic); + uniform["emission_color"]->set(material.emission_color); + uniform["emission_strength"]->set(material.emission_strength); + uniform["ambient_occlusion"]->set(material.ambient_occlusion); + }; + + void clear(){ + vertices.clear(); + indices.clear(); + normals.clear(); + uvs.clear(); + tangents.clear(); + bitangents.clear(); + colors.clear(); + material = CW::PBRMaterial(); + } +}; + +}; \ No newline at end of file diff --git a/CWindow/Renderer/OpenGL/CMakeLists.txt b/CWindow/Renderer/OpenGL/CMakeLists.txt index ccfef72..690d938 100644 --- a/CWindow/Renderer/OpenGL/CMakeLists.txt +++ b/CWindow/Renderer/OpenGL/CMakeLists.txt @@ -34,6 +34,7 @@ set(headers ${ROOT_DIR}/vendor/glfw/include/ ${ROOT_DIR}/vendor/glm/glm/ ${ROOT_DIR}/vendor/stb/ + ${ROOT_DIR}/vendor/assimp/include/ ) ############# [Set Files] ############# @@ -42,6 +43,7 @@ if(PLATFORM STREQUAL "UNIX") set(libs glfw glm + assimp X11 Xrandr Xcursor @@ -53,6 +55,7 @@ elseif(PLATFORM STREQUAL "WIN32") set(libs glfw glm + assimp winmm imm32 version diff --git a/CWindow/Renderer/OpenGL/Renderer.h b/CWindow/Renderer/OpenGL/Renderer.h index 98f6f2c..f43d5f4 100644 --- a/CWindow/Renderer/OpenGL/Renderer.h +++ b/CWindow/Renderer/OpenGL/Renderer.h @@ -8,6 +8,7 @@ #include "Shader/Compute/Shader.h" #include "Uniform/Uniform.h" + #include "Texture/Texture.h" #include "Texture/TextureLoader.h" @@ -17,6 +18,9 @@ #include "glad/glad.h" +#include "../../Material/PBRMaterial.h" +#include "../../MeshLoader/MeshLoader.h" + #include #include #include diff --git a/CWindow/vendor/assimp b/CWindow/vendor/assimp new file mode 160000 index 0000000..0404c87 --- /dev/null +++ b/CWindow/vendor/assimp @@ -0,0 +1 @@ +Subproject commit 0404c8758cf4a85825c241283804d764616af623 diff --git a/Example/CMakeLists.txt b/Example/CMakeLists.txt index c04d83b..7bc37f3 100644 --- a/Example/CMakeLists.txt +++ b/Example/CMakeLists.txt @@ -6,4 +6,5 @@ add_subdirectory(ComputeShader) add_subdirectory(Texture) add_subdirectory(Data) add_subdirectory(RotatitingSquare) -add_subdirectory(Mesh) \ No newline at end of file +add_subdirectory(Mesh) +add_subdirectory(MeshLoader) \ No newline at end of file diff --git a/Example/MeshLoader/CMakeLists.txt b/Example/MeshLoader/CMakeLists.txt new file mode 100644 index 0000000..fbc6f15 --- /dev/null +++ b/Example/MeshLoader/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.15) + +project(MeshLoaderExample LANGUAGES CXX C) + +set(src "Main.cpp") +set(headers + "Shaders.h" +) + +add_executable(MeshLoaderExample ${src}) +target_link_libraries(MeshLoaderExample CWindow) \ No newline at end of file diff --git a/Example/MeshLoader/Main.cpp b/Example/MeshLoader/Main.cpp new file mode 100644 index 0000000..4fde61f --- /dev/null +++ b/Example/MeshLoader/Main.cpp @@ -0,0 +1,79 @@ +#include "Renderer.h" +#include "Shaders.h" + +#include "FreeCamera/FreeCamera3D.h" +#define GLM_ENABLE_EXPERIMENTAL +#include "../vendor/glm/glm/gtx/euler_angles.hpp" + + + + +int main(){ + CW::Renderer::Renderer window; + window.setWindowTitle("Mesh Creation and Loading"); + CW::Renderer::FreeCamera3D camera(&window); + window.setCursorVisibility(false); + + + + CW::Renderer::Shader shader(Shader::vertex, Shader::fragment); + CW::Renderer::Uniform uniform; + + shader.getUniforms().emplace_back(&uniform); + + CW::MeshLoader data = CW::MeshLoader(); + data.LoadModel("../Example/MeshLoader/asset.stl"); + + CW::Renderer::Mesh asset; + asset.addVertices(data.vertices, 4, 0); + asset.addIndices(data.indices); + asset.setData(data.normals, 3, 1, GL_FLOAT); + // asset.setData(data.colors, 3, 2, GL_FLOAT); + + data.bindMaterialToUniform(uniform); + + uniform["lightPos"]->set({50.0f, 100.0f, 20.0f}); + uniform["lightColor"]->set({1.0f, 1.0f, 1.0f}); + + float time = 0.0f; + float cursor_visible_lock = 0.0f; + bool cursor_lock = true; + + while(!window.getWindowData()->should_close){ + window.beginFrame(); + + // time += window.getWindowData()->delta_time; + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(0.0f, 0.0f, 5.0f)); + model = model * glm::eulerAngleXYZ(-3.14f / 2.0f, 0.0f, 3.14f); + model = glm::scale(model, glm::vec3(0.1f)); + + glm::mat4 mvp = camera.transformation(&window) * model; + + uniform["transformation"]->set(mvp); + uniform["model"]->set(model); + + + if(cursor_lock) window.setCursorOn(true); + else window.setCursorOn(false); + + if(window.getInputData()->is_key_down("ESC") && cursor_visible_lock <= 0.0f) { + cursor_lock = !cursor_lock; + cursor_visible_lock = 0.5f; + camera.resetMouse(); + } + else if(cursor_visible_lock > 0.0f) cursor_visible_lock -= window.getWindowData()->delta_time; + + if(!cursor_lock) camera.event(&window); + + + shader.bind(); + asset.render(); + shader.unbind(); + + window.windowEvents(); + window.swapBuffer(); + }; + + return 0; +} \ No newline at end of file diff --git a/Example/MeshLoader/Shaders.h b/Example/MeshLoader/Shaders.h new file mode 100644 index 0000000..e2c32a4 --- /dev/null +++ b/Example/MeshLoader/Shaders.h @@ -0,0 +1,65 @@ +#pragma once +#include +#include "Material/PBRMaterial.h" + + + + +namespace Shader { +const std::string vertex = R"(#version 330 core + +layout(location = 0) in vec4 aPos; +layout(location = 1) in vec3 aNormal; + +uniform mat4 transformation; +uniform mat4 model; + +out vec3 FragPos; +out vec3 Normal; + +void main() +{ + FragPos = vec3(model * aPos); + Normal = mat3(transpose(inverse(model))) * aNormal; + + gl_Position = transformation * aPos; +})"; + + +const std::string fragment = +R"(#version 330 core + +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; + +)" ++ CW::PBRShaderBRDFUniforms + +R"( + +uniform vec3 lightPos; +uniform vec3 lightColor; +uniform vec3 cameraPos; + +)" ++ CW::PBRShaderBRDFInclude + +R"( + +// ---------------- MAIN ---------------- + +void main() +{ + vec3 color = BRDF( + Normal, + FragPos, + cameraPos, + lightPos, + lightColor + ); + + FragColor = vec4(color, 1.0); +} +)"; + +}; \ No newline at end of file diff --git a/Example/MeshLoader/asset.stl b/Example/MeshLoader/asset.stl new file mode 100644 index 0000000..9cddbfb Binary files /dev/null and b/Example/MeshLoader/asset.stl differ