Added Mirror, it kinda works..?
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Camera.h"
|
||||
#include "ModelLoader.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#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<float> generateSphere(int segments, int rings) {
|
||||
std::vector<float> 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<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) {
|
||||
if (sceneObjects.empty() || width <= 0 || height <= 0) return;
|
||||
|
||||
std::unordered_set<int> 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<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane) {
|
||||
void Renderer::renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& 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::vector<Scene
|
||||
|
||||
for (const auto& obj : sceneObjects) {
|
||||
if (!obj.enabled) continue;
|
||||
if (!drawMirrorObjects && obj.type == ObjectType::Mirror) continue;
|
||||
// Skip light gizmo-only types and camera helpers
|
||||
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight || obj.type == ObjectType::Camera || obj.type == ObjectType::PostFXNode) {
|
||||
continue;
|
||||
@@ -971,6 +1102,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
shader->setMat4("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::vector<Scene
|
||||
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;
|
||||
@@ -1035,6 +1175,7 @@ void Renderer::renderSceneInternal(const Camera& camera, const std::vector<Scene
|
||||
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
|
||||
else if (obj.type == ObjectType::Sphere) meshToDraw = sphereMesh;
|
||||
else if (obj.type == ObjectType::Capsule) meshToDraw = capsuleMesh;
|
||||
else if (obj.type == ObjectType::Mirror) meshToDraw = planeMesh;
|
||||
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
|
||||
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||
} else if (obj.type == ObjectType::Model && obj.meshId != -1) {
|
||||
@@ -1211,7 +1352,8 @@ unsigned int Renderer::applyPostProcessing(const std::vector<SceneObject>& scene
|
||||
}
|
||||
|
||||
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& 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;
|
||||
}
|
||||
|
||||
@@ -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<int, MirrorSmoothing> 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<int, RenderTarget> mirrorTargets;
|
||||
|
||||
void setupFBO();
|
||||
void ensureRenderTarget(RenderTarget& target, int w, int h);
|
||||
void releaseRenderTarget(RenderTarget& target);
|
||||
void updateMirrorTargets(const Camera& camera, const std::vector<SceneObject>& 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<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane);
|
||||
void renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane, bool drawMirrorObjects);
|
||||
unsigned int applyPostProcessing(const std::vector<SceneObject>& sceneObjects, unsigned int sourceTexture, int width, int height, bool allowHistory);
|
||||
PostFXSettings gatherPostFX(const std::vector<SceneObject>& sceneObjects) const;
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ enum class ObjectType {
|
||||
SpotLight,
|
||||
AreaLight,
|
||||
Camera,
|
||||
PostFXNode
|
||||
PostFXNode,
|
||||
Mirror
|
||||
};
|
||||
|
||||
struct MaterialProperties {
|
||||
|
||||
Reference in New Issue
Block a user