diff --git a/Engine/Components/ModelComponent.h b/Engine/Components/ModelComponent.h index da6f35f..0d34871 100644 --- a/Engine/Components/ModelComponent.h +++ b/Engine/Components/ModelComponent.h @@ -24,7 +24,7 @@ struct SubMesh { float shininess; GLuint diffuseTexture; // (Optional) If you add a normal texture per submesh. - // GLuint normalTexture; + GLuint normalTexture; GLuint VAO, VBO, EBO; SubMesh() diff --git a/Engine/Engine.h b/Engine/Engine.h index f769a1b..314b07c 100644 --- a/Engine/Engine.h +++ b/Engine/Engine.h @@ -52,12 +52,19 @@ private: static GLuint depthRenderbuffer; // Shader program used for rendering the scene. static GLuint shaderProgram; + + static GLuint skyboxShaderProgram; + static GLuint skyboxVAO, skyboxVBO, skyboxEBO; + static GLuint skyboxCubemap; + // Rotation angle (if needed for animated models). static float rotationAngle; // Current offscreen framebuffer dimensions. static int fbWidth; static int fbHeight; + + // Helper function to compile and link shaders. static GLuint CompileShader(const char* vertexSrc, const char* fragmentSrc); // Sets up common scene resources (e.g. loading the scene shader). diff --git a/Engine/engine.cpp b/Engine/engine.cpp index ba12f4f..1623f0b 100644 --- a/Engine/engine.cpp +++ b/Engine/engine.cpp @@ -11,6 +11,13 @@ GLuint Engine::framebuffer = 0; GLuint Engine::colorTexture = 0; GLuint Engine::depthRenderbuffer = 0; GLuint Engine::shaderProgram = 0; + +GLuint Engine::skyboxShaderProgram = 0; +GLuint Engine::skyboxVAO = 0; +GLuint Engine::skyboxVBO = 0; +GLuint Engine::skyboxEBO = 0; +GLuint Engine::skyboxCubemap = 0; + float Engine::rotationAngle = 0.0f; int Engine::fbWidth = 640; int Engine::fbHeight = 400; @@ -18,6 +25,64 @@ int Engine::fbHeight = 400; // Global normal map texture (if needed for legacy models; otherwise each model handles its own) GLuint normalMapTexture = 0; + + + + + +unsigned int loadCubemap(const std::vector& faces) +{ + unsigned int textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + + // Disable vertical flipping for cubemaps. + stbi_set_flip_vertically_on_load(false); + + int width, height, nrChannels; + for (unsigned int i = 0; i < faces.size(); i++) + { + + std::cout << "Loading Cubemap: " << faces[i] << std::endl; + unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); + if (data) + { + GLenum format = GL_RGB; + if(nrChannels == 1) + format = GL_RED; + else if(nrChannels == 3) + format = GL_RGB; + else if(nrChannels == 4) + format = GL_RGBA; + + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, + 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + stbi_image_free(data); + } + else + { + std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl; + stbi_image_free(data); + } + } + + // Set texture parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + return textureID; +} + + + + + + + + bool Engine::Init() { if (!glfwInit()) @@ -172,12 +237,9 @@ GLuint Engine::CompileShader(const char *vertexSrc, const char *fragmentSrc) return program; } -// SetupScene now creates the shader program used for all models. -// It no longer creates cube-specific VAOs, since ModelComponent will store mesh data. bool Engine::SetupScene() { - - // Vertex Shader remains largely unchanged. + // --- Scene Shader Program --- const char *vertexShaderSrc = R"( #version 330 core layout(location = 0) in vec3 aPos; @@ -201,10 +263,9 @@ bool Engine::SetupScene() Tangent = mat3(model) * aTangent; gl_Position = projection * view * vec4(FragPos, 1.0); } -)"; + )"; - // Fragment Shader with support for optional diffuse and normal maps and using Blinn-Phong lighting. -const char* fragmentShaderSrc = R"( + const char *fragmentShaderSrc = R"( #version 330 core out vec4 FragColor; @@ -222,7 +283,10 @@ const char* fragmentShaderSrc = R"( uniform sampler2D diffuseTexture; uniform sampler2D normalMap; uniform bool useDiffuseTexture; - uniform bool useNormalMap; // Control flag for normal mapping + uniform bool useNormalMap; + + // Skybox cubemap for ambient lighting. + uniform samplerCube skybox; // Material properties. uniform vec3 materialDiffuse; @@ -233,35 +297,31 @@ const char* fragmentShaderSrc = R"( // Optionally sample the diffuse texture. vec3 diffuseTex = useDiffuseTexture ? texture(diffuseTexture, TexCoords).rgb : vec3(1.0); - // Determine the normal. + // Determine the final normal. vec3 finalNormal; if(useNormalMap) { - // Sample the normal map and convert from [0,1] to [-1,1]. vec3 normMap = texture(normalMap, TexCoords).rgb; normMap = normalize(normMap * 2.0 - 1.0); - // (The z-flip here depends on your texture convention.) normMap.z = -normMap.z; vec3 T = normalize(Tangent); vec3 B = normalize(cross(Normal, T)); mat3 TBN = mat3(T, B, normalize(Normal)); finalNormal = normalize(TBN * normMap); } else { - // Use the interpolated vertex normal if no normal map is provided. finalNormal = normalize(Normal); } - // Start with an ambient term. - vec3 ambient = 0.1 * materialDiffuse * diffuseTex; + // Sample ambient from the skybox cubemap. + vec3 ambient = texture(skybox, finalNormal).rgb * 0.1 * materialDiffuse; + vec3 lighting = ambient; - // Loop through each light. + // Loop through lights. for(int i = 0; i < numLights; i++) { vec3 lightDir = normalize(lightPositions[i] - FragPos); - // Diffuse term. float diff = max(dot(finalNormal, lightDir), 0.0); vec3 diffuse = diff * materialDiffuse * diffuseTex * lightColors[i]; - // Blinn-Phong Specular term. vec3 viewDir = normalize(viewPos - FragPos); vec3 halfDir = normalize(lightDir + viewDir); float spec = pow(max(dot(finalNormal, halfDir), 0.0), materialShininess); @@ -272,8 +332,7 @@ const char* fragmentShaderSrc = R"( FragColor = vec4(lighting, 1.0); } -)"; - + )"; shaderProgram = CompileShader(vertexShaderSrc, fragmentShaderSrc); if (shaderProgram == 0) @@ -281,74 +340,189 @@ const char* fragmentShaderSrc = R"( return false; } + // --- Skybox Shader Program --- + const char *skyboxVertexShaderSrc = R"( + #version 330 core + layout(location = 0) in vec3 aPos; + out vec3 TexCoords; + uniform mat4 view; + uniform mat4 projection; + void main() + { + TexCoords = aPos; + // Remove translation by setting w equal to z component. + vec4 pos = projection * view * vec4(aPos, 1.0); + gl_Position = pos.xyww; + } + )"; + + const char *skyboxFragmentShaderSrc = R"( + #version 330 core + in vec3 TexCoords; + out vec4 FragColor; + uniform samplerCube skybox; + void main() + { + FragColor = texture(skybox, TexCoords); + } + )"; + + skyboxShaderProgram = CompileShader(skyboxVertexShaderSrc, skyboxFragmentShaderSrc); + if (skyboxShaderProgram == 0) + { + return false; + } + + // Define the full cube (36 vertices) for the skybox. + float skyboxVertices[] = { + // positions + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f}; + + unsigned int skyboxVAO, skyboxVBO; + glGenVertexArrays(1, &skyboxVAO); + glGenBuffers(1, &skyboxVBO); + glBindVertexArray(skyboxVAO); + glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), skyboxVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); + glBindVertexArray(0); + + std::vector faces{ + "assets/skybox/right.jpg", + "assets/skybox/left.jpg", + "assets/skybox/top.jpg", + "assets/skybox/bottom.jpg", + "assets/skybox/front.jpg", + "assets/skybox/back.jpg" + }; + skyboxCubemap = loadCubemap(faces); + + return true; } -ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projection, const glm::vec3 &viewPos, const std::vector& entities) { +ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projection, + const glm::vec3 &viewPos, const std::vector &entities) +{ glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); glViewport(0, 0, fbWidth, fbHeight); glEnable(GL_DEPTH_TEST); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // --- Draw Scene Objects --- glUseProgram(shaderProgram); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(view)); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniform3f(glGetUniformLocation(shaderProgram, "viewPos"), viewPos.x, viewPos.y, viewPos.z); - // Gather up to 2 lights. - glm::vec3 lightPositions[2] = { glm::vec3(0.0f), glm::vec3(0.0f) }; - glm::vec3 lightColors[2] = { glm::vec3(1.0f), glm::vec3(1.0f) }; + // Bind skybox cubemap for ambient sampling in the scene shader. + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap); + glUniform1i(glGetUniformLocation(shaderProgram, "skybox"), 2); + + // Set up lights (up to 2). + glm::vec3 lightPositions[2] = {glm::vec3(0.0f), glm::vec3(0.0f)}; + glm::vec3 lightColors[2] = {glm::vec3(1.0f), glm::vec3(1.0f)}; int lightCount = 0; - for (auto e : entities) { - if (e->GetType() == EntityType::LIGHT && lightCount < 2) { + for (auto e : entities) + { + if (e->GetType() == EntityType::LIGHT && lightCount < 2) + { lightPositions[lightCount] = e->transform.position; - if (e->lightComponent) { + if (e->lightComponent) + { lightColors[lightCount] = e->lightComponent->color * e->lightComponent->intensity; } lightCount++; } } glUniform1i(glGetUniformLocation(shaderProgram, "numLights"), lightCount); - if (lightCount > 0) { + if (lightCount > 0) + { glUniform3fv(glGetUniformLocation(shaderProgram, "lightPositions"), lightCount, glm::value_ptr(lightPositions[0])); glUniform3fv(glGetUniformLocation(shaderProgram, "lightColors"), lightCount, glm::value_ptr(lightColors[0])); } // Render each cube entity using its ModelComponent. - for (auto e : entities) { - if (e->GetType() == EntityType::CUBE && e->modelComponent) { + for (auto e : entities) + { + if (e->GetType() == EntityType::CUBE && e->modelComponent) + { glm::mat4 modelMatrix = e->transform.GetMatrix(); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix)); - // Loop through all submeshes in the model component. - for (const auto &mesh : e->modelComponent->meshes) { - // Set material properties. + for (const auto &mesh : e->modelComponent->meshes) + { glUniform3fv(glGetUniformLocation(shaderProgram, "materialDiffuse"), 1, glm::value_ptr(mesh.diffuseColor)); glUniform3fv(glGetUniformLocation(shaderProgram, "materialSpecular"), 1, glm::value_ptr(mesh.specularColor)); glUniform1f(glGetUniformLocation(shaderProgram, "materialShininess"), mesh.shininess); - // Bind diffuse texture if available. - if (mesh.diffuseTexture != 0) { + if (mesh.diffuseTexture != 0) + { glUniform1i(glGetUniformLocation(shaderProgram, "useDiffuseTexture"), 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture); glUniform1i(glGetUniformLocation(shaderProgram, "diffuseTexture"), 0); - } else { + } + else + { glUniform1i(glGetUniformLocation(shaderProgram, "useDiffuseTexture"), 0); } - // Bind normal map if available. - if (mesh.normalTexture != 0) { + if (mesh.normalTexture != 0) + { glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), 1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, mesh.normalTexture); glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 1); - } else { + } + else + { glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), 0); } - // Bind the submesh's VAO and draw its elements. glBindVertexArray(mesh.VAO); glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_INT, 0); glBindVertexArray(0); @@ -356,11 +530,34 @@ ImTextureID Engine::RenderScene(const glm::mat4 &view, const glm::mat4 &projecti } } + + // Draw Skybox + glDepthFunc(GL_LEQUAL); // Allow skybox fragments to pass depth test. + glUseProgram(skyboxShaderProgram); + + glm::mat4 viewNoTrans = glm::mat4(glm::mat3(view)); + glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(viewNoTrans)); + glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + + // Bind skybox VAO and cubemap texture. + glBindVertexArray(skyboxVAO); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap); + glUniform1i(glGetUniformLocation(skyboxShaderProgram, "skybox"), 0); + + // Draw the cube (36 vertices) + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + glDepthFunc(GL_LESS); // Restore default depth function. + + + + + glBindFramebuffer(GL_FRAMEBUFFER, 0); return (ImTextureID)(intptr_t)colorTexture; } - ImTextureID Engine::GetFinalRenderingTexture() { return (ImTextureID)(intptr_t)colorTexture; diff --git a/Three-Labs.exe b/Three-Labs.exe index 2bc89a5..73f9872 100644 Binary files a/Three-Labs.exe and b/Three-Labs.exe differ diff --git a/assets/scenes/new_scene.yaml b/assets/scenes/new_scene.yaml index 1430ebc..4482c94 100644 --- a/assets/scenes/new_scene.yaml +++ b/assets/scenes/new_scene.yaml @@ -2,12 +2,12 @@ entities: - type: cube transform: position: + - -2 + - 0 - 0 - - -1.29999995 - - 0.600000024 rotation: - 0 - - 90 + - 0 - 0 scale: - 0.00999999978 @@ -16,9 +16,9 @@ entities: model: modelPath: ./assets/models/sponza.obj globalDiffuseColor: - - 1 - - 1 - - 1 + - 0.800000012 + - 0.200000003 + - 0.200000003 globalSpecularColor: - 1 - 1 @@ -40,10 +40,10 @@ entities: - 1 light: color: - - 0.995098054 - - 0.878314674 - - 0.814614594 - intensity: 2.29999995 + - 1 + - 1 + - 1 + intensity: 1.5 - type: light transform: position: @@ -60,7 +60,7 @@ entities: - 1 light: color: - - 0 - - 2.38418579e-07 + - 0.200000003 + - 0.200000003 - 1 - intensity: 1.79999995 \ No newline at end of file + intensity: 0 \ No newline at end of file diff --git a/assets/skybox.png b/assets/skybox.png new file mode 100644 index 0000000..2783364 Binary files /dev/null and b/assets/skybox.png differ diff --git a/assets/skybox.zip b/assets/skybox.zip new file mode 100644 index 0000000..eb9e7a5 Binary files /dev/null and b/assets/skybox.zip differ diff --git a/assets/skybox/back.jpg b/assets/skybox/back.jpg new file mode 100644 index 0000000..470a679 Binary files /dev/null and b/assets/skybox/back.jpg differ diff --git a/assets/skybox/bottom.jpg b/assets/skybox/bottom.jpg new file mode 100644 index 0000000..893f394 Binary files /dev/null and b/assets/skybox/bottom.jpg differ diff --git a/assets/skybox/front.jpg b/assets/skybox/front.jpg new file mode 100644 index 0000000..4e17b77 Binary files /dev/null and b/assets/skybox/front.jpg differ diff --git a/assets/skybox/left.jpg b/assets/skybox/left.jpg new file mode 100644 index 0000000..5750b91 Binary files /dev/null and b/assets/skybox/left.jpg differ diff --git a/assets/skybox/right.jpg b/assets/skybox/right.jpg new file mode 100644 index 0000000..8963037 Binary files /dev/null and b/assets/skybox/right.jpg differ diff --git a/assets/skybox/top.jpg b/assets/skybox/top.jpg new file mode 100644 index 0000000..4db3c2a Binary files /dev/null and b/assets/skybox/top.jpg differ diff --git a/build/Engine/engine.o b/build/Engine/engine.o index c03ad28..49220bc 100644 Binary files a/build/Engine/engine.o and b/build/Engine/engine.o differ diff --git a/imgui.ini b/imgui.ini index fa3c9fe..322fbaa 100644 --- a/imgui.ini +++ b/imgui.ini @@ -15,9 +15,9 @@ DockId=0x00000001,0 [Window][Rendered Output] Pos=247,0 -Size=772,800 +Size=772,601 Collapsed=0 -DockId=0x00000002,0 +DockId=0x00000007,0 [Window][Editor] Pos=176,231 @@ -31,30 +31,30 @@ Collapsed=0 DockId=0x00000004,0 [Window][Entity List] -Pos=0,78 -Size=245,722 +Pos=0,0 +Size=245,800 +Collapsed=0 +DockId=0x00000005,0 + +[Window][Scene File] +Pos=247,603 +Size=772,197 Collapsed=0 DockId=0x00000008,0 -[Window][Scene File] -Pos=0,0 -Size=245,76 -Collapsed=0 -DockId=0x00000007,0 - [Window][Edit Model] -Pos=246,-1 -Size=886,794 +Pos=284,3 +Size=886,1171 Collapsed=0 [Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4 - DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Split=Y Selected=0x5A1EAB5B - DockNode ID=0x00000007 Parent=0x00000005 SizeRef=245,76 Selected=0xE1A4FD08 - DockNode ID=0x00000008 Parent=0x00000005 SizeRef=245,722 Selected=0x5A1EAB5B - DockNode ID=0x00000006 Parent=0x08BD597D SizeRef=1033,800 Split=X - DockNode ID=0x00000003 Parent=0x00000006 SizeRef=772,800 Split=X - DockNode ID=0x00000001 Parent=0x00000003 SizeRef=272,800 Selected=0x5098C5B2 - DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1006,800 CentralNode=1 Selected=0xB6999AB4 - DockNode ID=0x00000004 Parent=0x00000006 SizeRef=259,800 Selected=0x82A01C92 +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,0 Size=1280,800 Split=X Selected=0xB6999AB4 + DockNode ID=0x00000005 Parent=0x08BD597D SizeRef=245,800 Selected=0x5A1EAB5B + DockNode ID=0x00000006 Parent=0x08BD597D SizeRef=1033,800 Split=X + DockNode ID=0x00000003 Parent=0x00000006 SizeRef=772,800 Split=X + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=272,800 Selected=0x5098C5B2 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1006,800 Split=Y Selected=0xB6999AB4 + DockNode ID=0x00000007 Parent=0x00000002 SizeRef=1412,978 CentralNode=1 Selected=0xB6999AB4 + DockNode ID=0x00000008 Parent=0x00000002 SizeRef=1412,197 Selected=0xE1A4FD08 + DockNode ID=0x00000004 Parent=0x00000006 SizeRef=259,800 Selected=0x82A01C92