diff --git a/Resources/Shaders/frag.glsl b/Resources/Shaders/frag.glsl index 6cde661..2e554c3 100644 --- a/Resources/Shaders/frag.glsl +++ b/Resources/Shaders/frag.glsl @@ -11,6 +11,7 @@ uniform sampler2D normalMap; uniform float mixAmount = 0.2; uniform bool hasOverlay = false; uniform bool hasNormalMap = false; +uniform bool unlit = false; uniform vec3 viewPos; uniform vec3 materialColor = vec3(1.0); @@ -52,6 +53,11 @@ void main() } vec3 baseColor = texColor * materialColor; + if (unlit) { + FragColor = vec4(baseColor, tex1.a); + return; + } + // Normal map (tangent-space) if (hasNormalMap) { vec3 mapN = texture(normalMap, TexCoord).xyz * 2.0 - 1.0; diff --git a/src/EditorWindows/SceneWindows.cpp b/src/EditorWindows/SceneWindows.cpp index e96a8dd..f90f709 100644 --- a/src/EditorWindows/SceneWindows.cpp +++ b/src/EditorWindows/SceneWindows.cpp @@ -77,6 +77,7 @@ void Engine::renderHierarchyPanel() { if (ImGui::MenuItem("Cube")) addObject(ObjectType::Cube, "Cube"); if (ImGui::MenuItem("Sphere")) addObject(ObjectType::Sphere, "Sphere"); if (ImGui::MenuItem("Capsule")) addObject(ObjectType::Capsule, "Capsule"); + if (ImGui::MenuItem("Mirror")) addObject(ObjectType::Mirror, "Mirror"); ImGui::EndMenu(); } @@ -138,6 +139,7 @@ void Engine::renderObjectNode(SceneObject& obj, const std::string& filter) { case ObjectType::SpotLight: icon = "(S)"; break; case ObjectType::AreaLight: icon = "(L)"; break; case ObjectType::PostFXNode: icon = "(FX)"; break; + case ObjectType::Mirror: icon = "[R]"; break; } bool nodeOpen = ImGui::TreeNodeEx((void*)(intptr_t)obj.id, flags, "%s %s", icon, obj.name.c_str()); @@ -613,6 +615,7 @@ void Engine::renderInspectorPanel() { case ObjectType::SpotLight: typeLabel = "Spot Light"; break; case ObjectType::AreaLight: typeLabel = "Area Light"; break; case ObjectType::PostFXNode: typeLabel = "Post FX Node"; break; + case ObjectType::Mirror: typeLabel = "Mirror"; break; } ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "%s", typeLabel); diff --git a/src/EditorWindows/ViewportWindows.cpp b/src/EditorWindows/ViewportWindows.cpp index 3da76ff..a3ace54 100644 --- a/src/EditorWindows/ViewportWindows.cpp +++ b/src/EditorWindows/ViewportWindows.cpp @@ -1576,6 +1576,9 @@ void Engine::renderViewport() { case ObjectType::Capsule: hit = rayAabb(localOrigin, localDir, glm::vec3(-0.35f, -0.9f, -0.35f), glm::vec3(0.35f, 0.9f, 0.35f), hitT); break; + case ObjectType::Mirror: + hit = rayAabb(localOrigin, localDir, glm::vec3(-0.5f, -0.5f, -0.02f), glm::vec3(0.5f, 0.5f, 0.02f), hitT); + break; case ObjectType::OBJMesh: { const auto* info = g_objLoader.getMeshInfo(obj.meshId); if (info && info->boundsMin.x < info->boundsMax.x) { diff --git a/src/Engine.cpp b/src/Engine.cpp index 80bd044..99960b8 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1107,6 +1107,11 @@ void Engine::addObject(ObjectType type, const std::string& baseName) { } else if (type == ObjectType::Camera) { sceneObjects.back().camera.type = SceneCameraType::Player; sceneObjects.back().camera.fov = 60.0f; + } else if (type == ObjectType::Mirror) { + sceneObjects.back().useOverlay = true; + sceneObjects.back().material.textureMix = 1.0f; + sceneObjects.back().material.color = glm::vec3(1.0f); + sceneObjects.back().scale = glm::vec3(2.0f, 2.0f, 0.05f); } setPrimarySelection(id); if (projectManager.currentProject.isLoaded) { diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index cca3606..dd1b138 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -258,7 +258,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath, if (!file.is_open()) return false; file << "# Scene File\n"; - file << "version=8\n"; + file << "version=9\n"; file << "nextId=" << nextId << "\n"; file << "objectCount=" << objects.size() << "\n"; file << "\n"; diff --git a/src/Rendering.cpp b/src/Rendering.cpp index dda9d02..b27da00 100644 --- a/src/Rendering.cpp +++ b/src/Rendering.cpp @@ -2,6 +2,7 @@ #include "Camera.h" #include "ModelLoader.h" #include +#include #define TINYOBJLOADER_IMPLEMENTATION #include "../include/ThirdParty/tiny_obj_loader.h" @@ -60,6 +61,17 @@ float vertices[] = { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }; +float mirrorPlaneVertices[] = { + // positions // normals // texcoords + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f +}; + std::vector generateSphere(int segments, int rings) { std::vector vertices; @@ -422,6 +434,7 @@ Renderer::~Renderer() { delete cubeMesh; delete sphereMesh; delete capsuleMesh; + delete planeMesh; delete skybox; delete postShader; delete brightShader; @@ -441,6 +454,11 @@ Renderer::~Renderer() { if (bloomTargetB.fbo) glDeleteFramebuffers(1, &bloomTargetB.fbo); if (bloomTargetB.texture) glDeleteTextures(1, &bloomTargetB.texture); if (bloomTargetB.rbo) glDeleteRenderbuffers(1, &bloomTargetB.rbo); + for (auto& entry : mirrorTargets) { + releaseRenderTarget(entry.second); + } + mirrorTargets.clear(); + mirrorSmooth.clear(); if (framebuffer) glDeleteFramebuffers(1, &framebuffer); if (viewportTexture) glDeleteTextures(1, &viewportTexture); if (rbo) glDeleteRenderbuffers(1, &rbo); @@ -519,6 +537,7 @@ void Renderer::initialize() { auto capsuleVerts = generateCapsule(); capsuleMesh = new Mesh(capsuleVerts.data(), capsuleVerts.size() * sizeof(float)); + planeMesh = new Mesh(mirrorPlaneVertices, sizeof(mirrorPlaneVertices)); skybox = new Skybox(); @@ -668,6 +687,105 @@ void Renderer::ensureRenderTarget(RenderTarget& target, int w, int h) { glBindFramebuffer(GL_FRAMEBUFFER, 0); } +void Renderer::releaseRenderTarget(RenderTarget& target) { + if (target.texture) { + glDeleteTextures(1, &target.texture); + } + if (target.rbo) { + glDeleteRenderbuffers(1, &target.rbo); + } + if (target.fbo) { + glDeleteFramebuffers(1, &target.fbo); + } + target = {}; +} + +void Renderer::updateMirrorTargets(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) { + if (sceneObjects.empty() || width <= 0 || height <= 0) return; + + std::unordered_set active; + GLint prevFBO = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFBO); + + auto planeNormal = [](const SceneObject& obj) { + glm::quat q = glm::quat(glm::radians(obj.rotation)); + glm::vec3 n = q * glm::vec3(0.0f, 0.0f, 1.0f); + if (!std::isfinite(n.x) || glm::length(n) < 1e-3f) { + n = glm::vec3(0.0f, 0.0f, 1.0f); + } + return glm::normalize(n); + }; + auto planeUp = [](const SceneObject& obj) { + glm::quat q = glm::quat(glm::radians(obj.rotation)); + glm::vec3 u = q * glm::vec3(0.0f, 1.0f, 0.0f); + if (!std::isfinite(u.x) || glm::length(u) < 1e-3f) { + u = glm::vec3(0.0f, 1.0f, 0.0f); + } + return glm::normalize(u); + }; + + for (const auto& obj : sceneObjects) { + if (!obj.enabled || obj.type != ObjectType::Mirror) continue; + active.insert(obj.id); + + RenderTarget& target = mirrorTargets[obj.id]; + ensureRenderTarget(target, width, height); + if (target.fbo == 0 || target.texture == 0) continue; + + glBindFramebuffer(GL_FRAMEBUFFER, target.fbo); + glViewport(0, 0, target.width, target.height); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glm::vec3 n = planeNormal(obj); + glm::vec3 planePoint = obj.position; + glm::vec3 upVec = planeUp(obj); + glm::vec3 tangent = glm::normalize(glm::cross(upVec, n)); + if (!std::isfinite(tangent.x) || glm::length(tangent) < 1e-3f) { + tangent = glm::vec3(1.0f, 0.0f, 0.0f); + } + glm::vec3 bitangent = glm::cross(n, tangent); + + Camera mirrorCam = camera; + glm::vec3 relToPlane = camera.position - planePoint; + float alongT = glm::dot(relToPlane, tangent); + float alongB = glm::dot(relToPlane, bitangent); + MirrorSmoothing& sm = mirrorSmooth[obj.id]; + if (!sm.initialized) { + sm.planar = glm::vec2(alongT, alongB); + sm.initialized = true; + } else { + float lerp = 0.2f; // slow the planar tracking slightly + sm.planar = glm::mix(sm.planar, glm::vec2(alongT, alongB), lerp); + } + + float fixedDepth = 0.05f; // keep a small offset off the plane; ignore viewer local Z movement + mirrorCam.position = planePoint + tangent * sm.planar.x + bitangent * sm.planar.y + n * fixedDepth; + mirrorCam.front = n; // Look straight out from the mirror face + mirrorCam.up = upVec; + if (!std::isfinite(mirrorCam.front.x) || glm::length(mirrorCam.front) < 1e-3f) { + mirrorCam.front = glm::vec3(0.0f, 0.0f, -1.0f); + } + if (!std::isfinite(mirrorCam.up.x) || glm::length(mirrorCam.up) < 1e-3f) { + mirrorCam.up = glm::vec3(0.0f, 1.0f, 0.0f); + } + + renderSceneInternal(mirrorCam, sceneObjects, target.width, target.height, false, fovDeg, nearPlane, farPlane, false); + } + + glBindFramebuffer(GL_FRAMEBUFFER, prevFBO); + + for (auto it = mirrorTargets.begin(); it != mirrorTargets.end(); ) { + if (active.find(it->first) == active.end()) { + releaseRenderTarget(it->second); + mirrorSmooth.erase(it->first); + it = mirrorTargets.erase(it); + } else { + ++it; + } + } +} + void Renderer::ensureQuad() { if (quadVAO != 0) return; @@ -790,6 +908,7 @@ void Renderer::renderObject(const SceneObject& obj) { shader->setFloat("specularStrength", obj.material.specularStrength); shader->setFloat("shininess", obj.material.shininess); shader->setFloat("mixAmount", obj.material.textureMix); + shader->setBool("unlit", obj.type == ObjectType::Mirror); Texture* baseTex = texture1; if (!obj.albedoTexturePath.empty()) { @@ -798,7 +917,15 @@ void Renderer::renderObject(const SceneObject& obj) { if (baseTex) baseTex->Bind(GL_TEXTURE0); bool overlayUsed = false; - if (obj.useOverlay && !obj.overlayTexturePath.empty()) { + if (obj.type == ObjectType::Mirror) { + auto it = mirrorTargets.find(obj.id); + if (it != mirrorTargets.end() && it->second.texture != 0) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, it->second.texture); + overlayUsed = true; + } + } + if (!overlayUsed && obj.useOverlay && !obj.overlayTexturePath.empty()) { if (auto* t = getTexture(obj.overlayTexturePath)) { t->Bind(GL_TEXTURE1); overlayUsed = true; @@ -828,6 +955,9 @@ void Renderer::renderObject(const SceneObject& obj) { case ObjectType::Capsule: capsuleMesh->draw(); break; + case ObjectType::Mirror: + if (planeMesh) planeMesh->draw(); + break; case ObjectType::OBJMesh: if (obj.meshId >= 0) { Mesh* objMesh = g_objLoader.getMesh(obj.meshId); @@ -860,7 +990,7 @@ void Renderer::renderObject(const SceneObject& obj) { } } -void Renderer::renderSceneInternal(const Camera& camera, const std::vector& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane) { +void Renderer::renderSceneInternal(const Camera& camera, const std::vector& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane, bool drawMirrorObjects) { if (!defaultShader || width <= 0 || height <= 0) return; struct LightUniform { @@ -958,6 +1088,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vectorsetMat4("view", camera.getViewMatrix()); shader->setMat4("projection", glm::perspective(glm::radians(fovDeg), (float)width / (float)height, nearPlane, farPlane)); shader->setVec3("viewPos", camera.position); + shader->setBool("unlit", obj.type == ObjectType::Mirror); shader->setVec3("ambientColor", ambientColor); shader->setVec3("ambientColor", ambientColor); @@ -1011,7 +1143,15 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vectorBind(GL_TEXTURE0); bool overlayUsed = false; - if (obj.useOverlay && !obj.overlayTexturePath.empty()) { + if (obj.type == ObjectType::Mirror) { + auto it = mirrorTargets.find(obj.id); + if (it != mirrorTargets.end() && it->second.texture != 0) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, it->second.texture); + overlayUsed = true; + } + } + if (!overlayUsed && obj.useOverlay && !obj.overlayTexturePath.empty()) { if (auto* t = getTexture(obj.overlayTexturePath)) { t->Bind(GL_TEXTURE1); overlayUsed = true; @@ -1035,6 +1175,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector& scene } void Renderer::renderScene(const Camera& camera, const std::vector& sceneObjects, int /*selectedId*/, float fovDeg, float nearPlane, float farPlane) { - renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane); + updateMirrorTargets(camera, sceneObjects, currentWidth, currentHeight, fovDeg, nearPlane, farPlane); + renderSceneInternal(camera, sceneObjects, currentWidth, currentHeight, true, fovDeg, nearPlane, farPlane, true); unsigned int result = applyPostProcessing(sceneObjects, viewportTexture, currentWidth, currentHeight, true); displayTexture = result ? result : viewportTexture; } @@ -1225,7 +1367,8 @@ unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vecto glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane); + updateMirrorTargets(camera, sceneObjects, width, height, fovDeg, nearPlane, farPlane); + renderSceneInternal(camera, sceneObjects, width, height, true, fovDeg, nearPlane, farPlane, true); if (!applyPostFX) { return previewTarget.texture; } diff --git a/src/Rendering.h b/src/Rendering.h index 0262080..58678f8 100644 --- a/src/Rendering.h +++ b/src/Rendering.h @@ -73,6 +73,11 @@ private: RenderTarget historyTarget; RenderTarget bloomTargetA; RenderTarget bloomTargetB; + struct MirrorSmoothing { + glm::vec2 planar = glm::vec2(0.0f); + bool initialized = false; + }; + std::unordered_map mirrorSmooth; Shader* shader = nullptr; Shader* defaultShader = nullptr; Shader* postShader = nullptr; @@ -100,19 +105,23 @@ private: Mesh* cubeMesh = nullptr; Mesh* sphereMesh = nullptr; Mesh* capsuleMesh = nullptr; + Mesh* planeMesh = nullptr; Skybox* skybox = nullptr; unsigned int quadVAO = 0; unsigned int quadVBO = 0; unsigned int displayTexture = 0; bool historyValid = false; + std::unordered_map mirrorTargets; void setupFBO(); void ensureRenderTarget(RenderTarget& target, int w, int h); + void releaseRenderTarget(RenderTarget& target); + void updateMirrorTargets(const Camera& camera, const std::vector& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane); void ensureQuad(); void drawFullscreenQuad(); void clearHistory(); void clearTarget(RenderTarget& target); - void renderSceneInternal(const Camera& camera, const std::vector& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane); + void renderSceneInternal(const Camera& camera, const std::vector& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane, bool drawMirrorObjects); unsigned int applyPostProcessing(const std::vector& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory); PostFXSettings gatherPostFX(const std::vector& sceneObjects) const; diff --git a/src/SceneObject.h b/src/SceneObject.h index 6d7d9d0..ea36f27 100644 --- a/src/SceneObject.h +++ b/src/SceneObject.h @@ -13,7 +13,8 @@ enum class ObjectType { SpotLight, AreaLight, Camera, - PostFXNode + PostFXNode, + Mirror }; struct MaterialProperties {