Added Post Processing, Improved UI a lot, Made File Explorer look nicer, Fixed up Raycast Selection, Added Placeholder Playmode Button, Added cameras, Organized Create menu in Inspector, Added outlines to selected objects, added view output for viewing cameras while in Playmode area.

This commit is contained in:
Anemunt
2025-12-10 15:13:05 -05:00
parent acebe7d7c0
commit 7831bea4e2
25 changed files with 2131 additions and 223 deletions

View File

@@ -188,71 +188,87 @@ void applyModernTheme() {
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.12f, 0.12f, 0.14f, 0.98f);
ImVec4 slate = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
ImVec4 panel = ImVec4(0.11f, 0.12f, 0.14f, 1.00f);
ImVec4 overlay = ImVec4(0.07f, 0.08f, 0.10f, 0.98f);
ImVec4 accent = ImVec4(0.33f, 0.63f, 0.98f, 1.00f);
ImVec4 accentMuted = ImVec4(0.25f, 0.46f, 0.78f, 1.00f);
ImVec4 highlight = ImVec4(0.18f, 0.23f, 0.30f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.28f, 0.32f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_Text] = ImVec4(0.87f, 0.89f, 0.92f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.52f, 0.56f, 0.62f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_WindowBg] = panel;
colors[ImGuiCol_ChildBg] = ImVec4(0.10f, 0.11f, 0.13f, 1.00f);
colors[ImGuiCol_PopupBg] = overlay;
colors[ImGuiCol_Border] = ImVec4(0.21f, 0.24f, 0.28f, 0.60f);
colors[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.06f, 0.07f, 0.08f, 1.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.18f, 0.18f, 0.22f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.22f, 0.22f, 0.26f, 1.00f);
colors[ImGuiCol_Header] = highlight;
colors[ImGuiCol_HeaderHovered] = ImVec4(0.22f, 0.28f, 0.36f, 1.00f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.44f, 0.72f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.12f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.06f, 0.06f, 0.08f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.17f, 0.19f, 0.22f, 1.00f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.23f, 0.27f, 0.33f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.36f, 0.46f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.10f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.16f, 1.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.14f, 0.15f, 0.18f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.22f, 0.27f, 1.00f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.32f, 0.41f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.40f, 0.48f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.06f, 0.07f, 0.08f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.10f, 0.12f, 0.14f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.04f, 0.05f, 0.06f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.24f, 0.24f, 0.28f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.30f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.36f, 0.42f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.12f, 0.13f, 0.15f, 1.00f);
colors[ImGuiCol_TabHovered] = ImVec4(0.20f, 0.28f, 0.38f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.16f, 0.19f, 0.23f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.09f, 0.10f, 0.12f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.13f, 0.15f, 0.18f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.55f, 0.78f, 1.00f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.18f, 0.20f, 0.24f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.24f, 0.32f, 0.42f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.30f, 0.44f, 0.60f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.26f, 0.30f, 1.00f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.45f, 0.72f, 0.95f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.45f, 0.72f, 0.95f, 0.95f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.07f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.21f, 0.23f, 0.27f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.26f, 0.30f, 0.36f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.30f, 0.36f, 0.44f, 1.00f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.45f, 0.72f, 0.95f, 0.70f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.08f, 0.08f, 0.10f, 1.00f);
colors[ImGuiCol_CheckMark] = accent;
colors[ImGuiCol_SliderGrab] = accent;
colors[ImGuiCol_SliderGrabActive] = accentMuted;
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.45f, 0.72f, 0.95f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.45f, 0.72f, 0.95f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.22f, 0.26f, 0.33f, 1.00f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.32f, 0.46f, 0.62f, 0.80f);
colors[ImGuiCol_ResizeGripActive] = accent;
style.WindowRounding = 4.0f;
style.ChildRounding = 4.0f;
style.FrameRounding = 4.0f;
style.PopupRounding = 4.0f;
style.ScrollbarRounding = 4.0f;
style.GrabRounding = 4.0f;
style.TabRounding = 4.0f;
colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.63f, 0.98f, 0.60f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.05f, 0.06f, 0.07f, 1.00f);
style.WindowPadding = ImVec2(8.0f, 8.0f);
style.FramePadding = ImVec2(6.0f, 4.0f);
style.ItemSpacing = ImVec2(8.0f, 4.0f);
style.ItemInnerSpacing = ImVec2(4.0f, 4.0f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.33f, 0.63f, 0.98f, 0.25f);
colors[ImGuiCol_NavHighlight] = accent;
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.15f, 0.17f, 0.20f, 1.00f);
style.WindowRounding = 5.0f;
style.ChildRounding = 5.0f;
style.FrameRounding = 6.0f;
style.PopupRounding = 6.0f;
style.ScrollbarRounding = 10.0f;
style.GrabRounding = 6.0f;
style.TabRounding = 6.0f;
style.WindowPadding = ImVec2(10.0f, 10.0f);
style.FramePadding = ImVec2(9.0f, 5.0f);
style.ItemSpacing = ImVec2(10.0f, 6.0f);
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);
style.IndentSpacing = 18.0f;
style.WindowBorderSize = 1.0f;
style.FrameBorderSize = 0.0f;
style.FrameBorderSize = 1.0f;
style.PopupBorderSize = 1.0f;
style.TabBorderSize = 1.0f;
}
void setupDockspace() {

View File

@@ -87,6 +87,22 @@ SceneObject* Engine::getSelectedObject() {
return (it != sceneObjects.end()) ? &(*it) : nullptr;
}
Camera Engine::makeCameraFromObject(const SceneObject& obj) const {
Camera cam;
cam.position = obj.position;
glm::quat q = glm::quat(glm::radians(obj.rotation));
glm::mat3 rot = glm::mat3_cast(q);
cam.front = glm::normalize(rot * glm::vec3(0.0f, 0.0f, -1.0f));
cam.up = glm::normalize(rot * glm::vec3(0.0f, 1.0f, 0.0f));
if (!std::isfinite(cam.front.x) || glm::length(cam.front) < 1e-3f) {
cam.front = glm::vec3(0.0f, 0.0f, -1.0f);
}
if (!std::isfinite(cam.up.x) || glm::length(cam.up) < 1e-3f) {
cam.up = glm::vec3(0.0f, 1.0f, 0.0f);
}
return cam;
}
void Engine::DecomposeMatrix(const glm::mat4& matrix, glm::vec3& pos, glm::vec3& rot, glm::vec3& scale) {
pos = glm::vec3(matrix[3]);
scale.x = glm::length(glm::vec3(matrix[0]));
@@ -204,6 +220,17 @@ void Engine::run() {
continue;
}
// Enforce cursor lock state every frame to avoid backends restoring it.
int desiredMode = cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL;
if (glfwGetInputMode(editorWindow, GLFW_CURSOR) != desiredMode) {
glfwSetInputMode(editorWindow, GLFW_CURSOR, desiredMode);
if (cursorLocked && glfwRawMouseMotionSupported()) {
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
} else if (!cursorLocked && glfwRawMouseMotionSupported()) {
glfwSetInputMode(editorWindow, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
}
}
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
@@ -223,6 +250,17 @@ void Engine::run() {
camera.firstMouse = true;
}
// Scroll-wheel speed adjustment while freelook is active
if (viewportController.isViewportFocused() && cursorLocked) {
float wheel = ImGui::GetIO().MouseWheel;
if (std::abs(wheel) > 0.0001f) {
float factor = std::pow(1.12f, wheel);
float ratio = (camera.moveSpeed > 0.001f) ? (camera.sprintSpeed / camera.moveSpeed) : 2.0f;
camera.moveSpeed = std::clamp(camera.moveSpeed * factor, 0.5f, 100.0f);
camera.sprintSpeed = std::clamp(camera.moveSpeed * ratio, 0.5f, 200.0f);
}
}
if (viewportController.isViewportFocused() && cursorLocked) {
camera.processKeyboard(deltaTime, editorWindow);
}
@@ -234,7 +272,7 @@ void Engine::run() {
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
renderer.beginRender(view, proj, camera.position);
renderer.renderScene(camera, sceneObjects);
renderer.renderScene(camera, sceneObjects, selectedObjectId);
renderer.endRender();
}
@@ -504,23 +542,39 @@ void Engine::handleKeyboardShortcuts() {
ctrlNPressed = false;
}
if (!cursorLocked) {
bool cameraActive = cursorLocked || viewportController.isViewportFocused() && cursorLocked;
if (!cameraActive) {
if (ImGui::IsKeyPressed(ImGuiKey_Q)) mCurrentGizmoOperation = ImGuizmo::TRANSLATE;
if (ImGui::IsKeyPressed(ImGuiKey_W)) mCurrentGizmoOperation = ImGuizmo::ROTATE;
if (ImGui::IsKeyPressed(ImGuiKey_E)) mCurrentGizmoOperation = ImGuizmo::SCALE;
if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
if (ImGui::IsKeyPressed(ImGuiKey_R)) mCurrentGizmoOperation = ImGuizmo::BOUNDS;
if (ImGui::IsKeyPressed(ImGuiKey_T)) mCurrentGizmoOperation = ImGuizmo::UNIVERSAL;
if (ImGui::IsKeyPressed(ImGuiKey_Z)) {
if (ImGui::IsKeyPressed(ImGuiKey_U)) {
mCurrentGizmoMode = (mCurrentGizmoMode == ImGuizmo::LOCAL) ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
}
}
static bool snapPressed = false;
if (ImGui::IsKeyPressed(ImGuiKey_X) && !snapPressed) {
useSnap = !useSnap;
snapPressed = true;
static bool snapHeldByCtrl = false;
static bool snapStateBeforeCtrl = false;
if (!snapHeldByCtrl && ctrlDown) {
snapStateBeforeCtrl = useSnap;
snapHeldByCtrl = true;
useSnap = true;
} else if (snapHeldByCtrl && !ctrlDown) {
useSnap = snapStateBeforeCtrl;
snapHeldByCtrl = false;
}
if (ImGui::IsKeyReleased(ImGuiKey_X)) {
if (!cameraActive) {
if (ImGui::IsKeyPressed(ImGuiKey_Y) && !snapPressed) {
useSnap = !useSnap;
snapPressed = true;
}
}
if (ImGui::IsKeyReleased(ImGuiKey_Y)) {
snapPressed = false;
}
@@ -544,16 +598,37 @@ void Engine::handleKeyboardShortcuts() {
}
void Engine::OpenProjectPath(const std::string& path) {
if (projectManager.loadProject(path)) {
if (!initRenderer()) {
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
} else {
showLauncher = false;
try {
if (projectManager.loadProject(path)) {
// Make sure project folders exist even for older/minimal projects
if (!fs::exists(projectManager.currentProject.assetsPath)) {
fs::create_directories(projectManager.currentProject.assetsPath);
}
if (!fs::exists(projectManager.currentProject.scenesPath)) {
fs::create_directories(projectManager.currentProject.scenesPath);
}
if (!initRenderer()) {
addConsoleMessage("Error: Failed to initialize renderer!", ConsoleMessageType::Error);
showLauncher = true;
return;
}
loadRecentScenes();
fileBrowser.setProjectRoot(projectManager.currentProject.assetsPath);
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true;
showLauncher = false;
addConsoleMessage("Opened project: " + projectManager.currentProject.name, ConsoleMessageType::Info);
} else {
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
}
} else {
addConsoleMessage("Error opening project: " + projectManager.errorMessage, ConsoleMessageType::Error);
} catch (const std::exception& e) {
addConsoleMessage(std::string("Exception opening project: ") + e.what(), ConsoleMessageType::Error);
showLauncher = true;
} catch (...) {
addConsoleMessage("Unknown exception opening project", ConsoleMessageType::Error);
showLauncher = true;
}
}
@@ -578,6 +653,7 @@ void Engine::createNewProject(const char* name, const char* location) {
addObject(ObjectType::Cube, "Cube");
fileBrowser.setProjectRoot(projectManager.currentProject.assetsPath);
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true;
@@ -614,6 +690,7 @@ void Engine::loadRecentScenes() {
}
recordState("sceneLoaded");
fileBrowser.setProjectRoot(projectManager.currentProject.assetsPath);
fileBrowser.currentPath = projectManager.currentProject.assetsPath;
fileBrowser.needsRefresh = true;
}
@@ -702,6 +779,13 @@ void Engine::addObject(ObjectType type, const std::string& baseName) {
sceneObjects.back().light.range = 10.0f;
sceneObjects.back().light.intensity = 3.0f;
sceneObjects.back().light.size = glm::vec2(2.0f, 2.0f);
} else if (type == ObjectType::PostFXNode) {
sceneObjects.back().postFx.enabled = true;
sceneObjects.back().postFx.bloomEnabled = true;
sceneObjects.back().postFx.colorAdjustEnabled = true;
} else if (type == ObjectType::Camera) {
sceneObjects.back().camera.type = SceneCameraType::Player;
sceneObjects.back().camera.fov = 60.0f;
}
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
@@ -732,6 +816,8 @@ void Engine::duplicateSelected() {
newObj.fragmentShaderPath = it->fragmentShaderPath;
newObj.useOverlay = it->useOverlay;
newObj.light = it->light;
newObj.camera = it->camera;
newObj.postFx = it->postFx;
sceneObjects.push_back(newObj);
selectedObjectId = id;

View File

@@ -82,6 +82,10 @@ private:
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
bool showEnvironmentWindow = true;
bool showCameraWindow = true;
bool isPlaying = false;
bool isPaused = false;
bool showViewOutput = true;
int previewCameraId = -1;
// Private methods
SceneObject* getSelectedObject();
@@ -107,6 +111,7 @@ private:
void renderViewport();
void renderDialogs();
void renderProjectBrowserPanel();
Camera makeCameraFromObject(const SceneObject& obj) const;
void renderFileBrowserToolbar();
void renderFileBrowserBreadcrumb();

File diff suppressed because it is too large Load Diff

View File

@@ -119,6 +119,7 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
glm::vec3 boundsMin(FLT_MAX);
glm::vec3 boundsMax(-FLT_MAX);
std::vector<glm::vec3> triPositions;
// Process all meshes in the scene
std::vector<float> vertices;
@@ -128,7 +129,7 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
result.hasTangents = false;
// Process the root node recursively
processNode(scene->mRootNode, scene, vertices, boundsMin, boundsMax);
processNode(scene->mRootNode, scene, aiMatrix4x4(), vertices, triPositions, boundsMin, boundsMax);
// Check mesh properties
for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
@@ -157,6 +158,7 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
loaded.boundsMin = boundsMin;
loaded.boundsMax = boundsMax;
loaded.triangleVertices = std::move(triPositions);
loadedMeshes.push_back(std::move(loaded));
@@ -171,20 +173,33 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
return result;
}
void ModelLoader::processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax) {
static glm::mat4 aiToGlm(const aiMatrix4x4& m) {
return glm::mat4(
m.a1, m.b1, m.c1, m.d1,
m.a2, m.b2, m.c2, m.d2,
m.a3, m.b3, m.c3, m.d3,
m.a4, m.b4, m.c4, m.d4
);
}
void ModelLoader::processNode(aiNode* node, const aiScene* scene, const aiMatrix4x4& parentTransform, std::vector<float>& vertices, std::vector<glm::vec3>& triPositions, glm::vec3& boundsMin, glm::vec3& boundsMax) {
aiMatrix4x4 currentTransform = parentTransform * node->mTransformation;
// Process all meshes in this node
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
processMesh(mesh, scene, vertices, boundsMin, boundsMax);
processMesh(mesh, currentTransform, vertices, triPositions, boundsMin, boundsMax);
}
// Process children nodes
for (unsigned int i = 0; i < node->mNumChildren; i++) {
processNode(node->mChildren[i], scene, vertices, boundsMin, boundsMax);
processNode(node->mChildren[i], scene, currentTransform, vertices, triPositions, boundsMin, boundsMax);
}
}
void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax) {
void ModelLoader::processMesh(aiMesh* mesh, const aiMatrix4x4& transform, std::vector<float>& vertices, std::vector<glm::vec3>& triPositions, glm::vec3& boundsMin, glm::vec3& boundsMax) {
glm::mat4 gTransform = aiToGlm(transform);
glm::mat3 normalMat = glm::transpose(glm::inverse(glm::mat3(gTransform)));
// Process each face
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
aiFace face = mesh->mFaces[i];
@@ -193,23 +208,34 @@ void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<fl
for (unsigned int j = 0; j < face.mNumIndices; j++) {
unsigned int index = face.mIndices[j];
// Position
vertices.push_back(mesh->mVertices[index].x);
vertices.push_back(mesh->mVertices[index].y);
vertices.push_back(mesh->mVertices[index].z);
glm::vec3 pos(mesh->mVertices[index].x,
mesh->mVertices[index].y,
mesh->mVertices[index].z);
glm::vec4 transformed = gTransform * glm::vec4(pos, 1.0f);
glm::vec3 finalPos = glm::vec3(transformed) / (transformed.w == 0.0f ? 1.0f : transformed.w);
boundsMin.x = std::min(boundsMin.x, mesh->mVertices[index].x);
boundsMin.y = std::min(boundsMin.y, mesh->mVertices[index].y);
boundsMin.z = std::min(boundsMin.z, mesh->mVertices[index].z);
boundsMax.x = std::max(boundsMax.x, mesh->mVertices[index].x);
boundsMax.y = std::max(boundsMax.y, mesh->mVertices[index].y);
boundsMax.z = std::max(boundsMax.z, mesh->mVertices[index].z);
vertices.push_back(finalPos.x);
vertices.push_back(finalPos.y);
vertices.push_back(finalPos.z);
triPositions.push_back(finalPos);
boundsMin.x = std::min(boundsMin.x, finalPos.x);
boundsMin.y = std::min(boundsMin.y, finalPos.y);
boundsMin.z = std::min(boundsMin.z, finalPos.z);
boundsMax.x = std::max(boundsMax.x, finalPos.x);
boundsMax.y = std::max(boundsMax.y, finalPos.y);
boundsMax.z = std::max(boundsMax.z, finalPos.z);
// Normal
if (mesh->mNormals) {
vertices.push_back(mesh->mNormals[index].x);
vertices.push_back(mesh->mNormals[index].y);
vertices.push_back(mesh->mNormals[index].z);
glm::vec3 n(mesh->mNormals[index].x,
mesh->mNormals[index].y,
mesh->mNormals[index].z);
n = glm::normalize(normalMat * n);
vertices.push_back(n.x);
vertices.push_back(n.y);
vertices.push_back(n.z);
} else {
vertices.push_back(0.0f);
vertices.push_back(1.0f);

View File

@@ -64,8 +64,8 @@ private:
ModelLoader& operator=(const ModelLoader&) = delete;
// Process Assimp scene
void processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax);
void processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax);
void processNode(aiNode* node, const aiScene* scene, const aiMatrix4x4& parentTransform, std::vector<float>& vertices, std::vector<glm::vec3>& triPositions, glm::vec3& boundsMin, glm::vec3& boundsMax);
void processMesh(aiMesh* mesh, const aiMatrix4x4& transform, std::vector<float>& vertices, std::vector<glm::vec3>& triPositions, glm::vec3& boundsMin, glm::vec3& boundsMax);
// Storage for loaded meshes (reusing OBJLoader::LoadedMesh structure)
std::vector<OBJLoader::LoadedMesh> loadedMeshes;

View File

@@ -236,7 +236,7 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
if (!file.is_open()) return false;
file << "# Scene File\n";
file << "version=2\n";
file << "version=4\n";
file << "nextId=" << nextId << "\n";
file << "objectCount=" << objects.size() << "\n";
file << "\n";
@@ -269,6 +269,24 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
file << "lightOuter=" << obj.light.outerAngle << "\n";
file << "lightSize=" << obj.light.size.x << "," << obj.light.size.y << "\n";
file << "lightEnabled=" << (obj.light.enabled ? 1 : 0) << "\n";
file << "cameraType=" << static_cast<int>(obj.camera.type) << "\n";
file << "cameraFov=" << obj.camera.fov << "\n";
file << "cameraNear=" << obj.camera.nearClip << "\n";
file << "cameraFar=" << obj.camera.farClip << "\n";
if (obj.type == ObjectType::PostFXNode) {
file << "postEnabled=" << (obj.postFx.enabled ? 1 : 0) << "\n";
file << "postBloomEnabled=" << (obj.postFx.bloomEnabled ? 1 : 0) << "\n";
file << "postBloomThreshold=" << obj.postFx.bloomThreshold << "\n";
file << "postBloomIntensity=" << obj.postFx.bloomIntensity << "\n";
file << "postBloomRadius=" << obj.postFx.bloomRadius << "\n";
file << "postColorAdjustEnabled=" << (obj.postFx.colorAdjustEnabled ? 1 : 0) << "\n";
file << "postExposure=" << obj.postFx.exposure << "\n";
file << "postContrast=" << obj.postFx.contrast << "\n";
file << "postSaturation=" << obj.postFx.saturation << "\n";
file << "postColorFilter=" << obj.postFx.colorFilter.r << "," << obj.postFx.colorFilter.g << "," << obj.postFx.colorFilter.b << "\n";
file << "postMotionBlurEnabled=" << (obj.postFx.motionBlurEnabled ? 1 : 0) << "\n";
file << "postMotionBlurStrength=" << obj.postFx.motionBlurStrength << "\n";
}
if ((obj.type == ObjectType::OBJMesh || obj.type == ObjectType::Model) && !obj.meshPath.empty()) {
file << "meshPath=" << obj.meshPath << "\n";
@@ -332,6 +350,9 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
else if (currentObj->type == ObjectType::PointLight) currentObj->light.type = LightType::Point;
else if (currentObj->type == ObjectType::SpotLight) currentObj->light.type = LightType::Spot;
else if (currentObj->type == ObjectType::AreaLight) currentObj->light.type = LightType::Area;
else if (currentObj->type == ObjectType::Camera) {
currentObj->camera.type = SceneCameraType::Scene;
}
} else if (key == "parentId") {
currentObj->parentId = std::stoi(value);
} else if (key == "position") {
@@ -395,6 +416,41 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
&currentObj->light.size.y);
} else if (key == "lightEnabled") {
currentObj->light.enabled = (std::stoi(value) != 0);
} else if (key == "cameraType") {
currentObj->camera.type = static_cast<SceneCameraType>(std::stoi(value));
} else if (key == "cameraFov") {
currentObj->camera.fov = std::stof(value);
} else if (key == "cameraNear") {
currentObj->camera.nearClip = std::stof(value);
} else if (key == "cameraFar") {
currentObj->camera.farClip = std::stof(value);
} else if (key == "postEnabled") {
currentObj->postFx.enabled = (std::stoi(value) != 0);
} else if (key == "postBloomEnabled") {
currentObj->postFx.bloomEnabled = (std::stoi(value) != 0);
} else if (key == "postBloomThreshold") {
currentObj->postFx.bloomThreshold = std::stof(value);
} else if (key == "postBloomIntensity") {
currentObj->postFx.bloomIntensity = std::stof(value);
} else if (key == "postBloomRadius") {
currentObj->postFx.bloomRadius = std::stof(value);
} else if (key == "postColorAdjustEnabled") {
currentObj->postFx.colorAdjustEnabled = (std::stoi(value) != 0);
} else if (key == "postExposure") {
currentObj->postFx.exposure = std::stof(value);
} else if (key == "postContrast") {
currentObj->postFx.contrast = std::stof(value);
} else if (key == "postSaturation") {
currentObj->postFx.saturation = std::stof(value);
} else if (key == "postColorFilter") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->postFx.colorFilter.r,
&currentObj->postFx.colorFilter.g,
&currentObj->postFx.colorFilter.b);
} else if (key == "postMotionBlurEnabled") {
currentObj->postFx.motionBlurEnabled = (std::stoi(value) != 0);
} else if (key == "postMotionBlurStrength") {
currentObj->postFx.motionBlurStrength = std::stof(value);
} else if (key == "meshPath") {
currentObj->meshPath = value;
if (!value.empty() && currentObj->type == ObjectType::OBJMesh) {

View File

@@ -298,6 +298,7 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
glm::vec3 boundsMin(FLT_MAX);
glm::vec3 boundsMax(-FLT_MAX);
std::vector<glm::vec3> triPositions;
for (const auto& shape : shapes) {
size_t indexOffset = 0;
@@ -360,6 +361,7 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
const TempVertex* tri[3] = { &faceVerts[0], &faceVerts[v], &faceVerts[v+1] };
for (int i = 0; i < 3; i++) {
triPositions.push_back(tri[i]->pos);
vertices.push_back(tri[i]->pos.x);
vertices.push_back(tri[i]->pos.y);
vertices.push_back(tri[i]->pos.z);
@@ -390,6 +392,7 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
loaded.hasTexCoords = !attrib.texcoords.empty();
loaded.boundsMin = boundsMin;
loaded.boundsMax = boundsMax;
loaded.triangleVertices = std::move(triPositions);
loadedMeshes.push_back(std::move(loaded));
return static_cast<int>(loadedMeshes.size() - 1);
@@ -420,9 +423,29 @@ Renderer::~Renderer() {
delete sphereMesh;
delete capsuleMesh;
delete skybox;
delete postShader;
delete brightShader;
delete blurShader;
if (previewTarget.fbo) glDeleteFramebuffers(1, &previewTarget.fbo);
if (previewTarget.texture) glDeleteTextures(1, &previewTarget.texture);
if (previewTarget.rbo) glDeleteRenderbuffers(1, &previewTarget.rbo);
if (postTarget.fbo) glDeleteFramebuffers(1, &postTarget.fbo);
if (postTarget.texture) glDeleteTextures(1, &postTarget.texture);
if (postTarget.rbo) glDeleteRenderbuffers(1, &postTarget.rbo);
if (historyTarget.fbo) glDeleteFramebuffers(1, &historyTarget.fbo);
if (historyTarget.texture) glDeleteTextures(1, &historyTarget.texture);
if (historyTarget.rbo) glDeleteRenderbuffers(1, &historyTarget.rbo);
if (bloomTargetA.fbo) glDeleteFramebuffers(1, &bloomTargetA.fbo);
if (bloomTargetA.texture) glDeleteTextures(1, &bloomTargetA.texture);
if (bloomTargetA.rbo) glDeleteRenderbuffers(1, &bloomTargetA.rbo);
if (bloomTargetB.fbo) glDeleteFramebuffers(1, &bloomTargetB.fbo);
if (bloomTargetB.texture) glDeleteTextures(1, &bloomTargetB.texture);
if (bloomTargetB.rbo) glDeleteRenderbuffers(1, &bloomTargetB.rbo);
if (framebuffer) glDeleteFramebuffers(1, &framebuffer);
if (viewportTexture) glDeleteTextures(1, &viewportTexture);
if (rbo) glDeleteRenderbuffers(1, &rbo);
if (quadVBO) glDeleteBuffers(1, &quadVBO);
if (quadVAO) glDeleteVertexArrays(1, &quadVAO);
}
Texture* Renderer::getTexture(const std::string& path) {
@@ -448,6 +471,36 @@ void Renderer::initialize() {
shader = nullptr;
throw std::runtime_error("Shader error");
}
postShader = new Shader(postVertPath.c_str(), postFragPath.c_str());
if (!postShader || postShader->ID == 0) {
std::cerr << "PostFX shader compilation failed!\n";
delete postShader;
postShader = nullptr;
} else {
postShader->use();
postShader->setInt("sceneTex", 0);
postShader->setInt("bloomTex", 1);
postShader->setInt("historyTex", 2);
}
brightShader = new Shader(postVertPath.c_str(), postBrightFragPath.c_str());
if (!brightShader || brightShader->ID == 0) {
std::cerr << "Bright-pass shader compilation failed!\n";
delete brightShader;
brightShader = nullptr;
} else {
brightShader->use();
brightShader->setInt("sceneTex", 0);
}
blurShader = new Shader(postVertPath.c_str(), postBlurFragPath.c_str());
if (!blurShader || blurShader->ID == 0) {
std::cerr << "Blur shader compilation failed!\n";
delete blurShader;
blurShader = nullptr;
} else {
blurShader->use();
blurShader->setInt("image", 0);
}
ShaderEntry entry;
entry.shader.reset(defaultShader);
entry.vertPath = defaultVertPath;
@@ -470,6 +523,12 @@ void Renderer::initialize() {
skybox = new Skybox();
setupFBO();
ensureRenderTarget(postTarget, currentWidth, currentHeight);
ensureRenderTarget(historyTarget, currentWidth, currentHeight);
ensureRenderTarget(bloomTargetA, currentWidth, currentHeight);
ensureRenderTarget(bloomTargetB, currentWidth, currentHeight);
ensureQuad();
clearHistory();
glEnable(GL_DEPTH_TEST);
}
@@ -573,6 +632,93 @@ void Renderer::setupFBO() {
std::cerr << "Framebuffer setup failed!\n";
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
displayTexture = viewportTexture;
}
void Renderer::ensureRenderTarget(RenderTarget& target, int w, int h) {
if (w <= 0 || h <= 0) return;
if (target.fbo == 0) {
glGenFramebuffers(1, &target.fbo);
glGenTextures(1, &target.texture);
glGenRenderbuffers(1, &target.rbo);
}
if (target.width == w && target.height == h) return;
target.width = w;
target.height = h;
glBindFramebuffer(GL_FRAMEBUFFER, target.fbo);
glBindTexture(GL_TEXTURE_2D, target.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, target.width, target.height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.texture, 0);
glBindRenderbuffer(GL_RENDERBUFFER, target.rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, target.width, target.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, target.rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Preview framebuffer setup failed!\n";
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Renderer::ensureQuad() {
if (quadVAO != 0) return;
float quadVertices[] = {
// positions // texcoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);
}
void Renderer::drawFullscreenQuad() {
if (quadVAO == 0) ensureQuad();
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
void Renderer::clearHistory() {
historyValid = false;
if (historyTarget.fbo != 0 && historyTarget.width > 0 && historyTarget.height > 0) {
glBindFramebuffer(GL_FRAMEBUFFER, historyTarget.fbo);
glViewport(0, 0, historyTarget.width, historyTarget.height);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
void Renderer::clearTarget(RenderTarget& target) {
if (target.fbo == 0 || target.width <= 0 || target.height <= 0) return;
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);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void Renderer::resize(int w, int h) {
@@ -591,6 +737,13 @@ void Renderer::resize(int w, int h) {
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
std::cerr << "Framebuffer incomplete after resize!\n";
}
ensureRenderTarget(postTarget, currentWidth, currentHeight);
ensureRenderTarget(historyTarget, currentWidth, currentHeight);
ensureRenderTarget(bloomTargetA, currentWidth, currentHeight);
ensureRenderTarget(bloomTargetB, currentWidth, currentHeight);
clearHistory();
displayTexture = viewportTexture;
}
void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj, const glm::vec3& cameraPos) {
@@ -598,6 +751,7 @@ void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj, const g
glViewport(0, 0, currentWidth, currentHeight);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
displayTexture = viewportTexture;
shader->use();
shader->setMat4("view", view);
@@ -698,11 +852,16 @@ void Renderer::renderObject(const SceneObject& obj) {
case ObjectType::DirectionalLight:
// Not rendered as geometry
break;
case ObjectType::Camera:
// Cameras are editor helpers only
break;
case ObjectType::PostFXNode:
break;
}
}
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) {
if (!defaultShader) return;
void Renderer::renderSceneInternal(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, bool unbindFramebuffer, float fovDeg, float nearPlane, float farPlane) {
if (!defaultShader || width <= 0 || height <= 0) return;
struct LightUniform {
int type = 0; // 0 dir,1 point,2 spot
@@ -774,8 +933,8 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
}
for (const auto& obj : sceneObjects) {
// Skip light gizmo-only types
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight) {
// 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;
}
@@ -785,7 +944,7 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
shader->use();
shader->setMat4("view", camera.getViewMatrix());
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
shader->setMat4("projection", glm::perspective(glm::radians(fovDeg), (float)width / (float)height, nearPlane, farPlane));
shader->setVec3("viewPos", camera.position);
shader->setVec3("ambientColor", ambientColor);
shader->setVec3("ambientColor", ambientColor);
@@ -862,14 +1021,153 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
if (skybox) {
glm::mat4 view = camera.getViewMatrix();
glm::mat4 proj = glm::perspective(glm::radians(FOV),
(float)currentWidth / currentHeight,
NEAR_PLANE, FAR_PLANE);
glm::mat4 proj = glm::perspective(glm::radians(fovDeg),
(float)width / height,
nearPlane, farPlane);
skybox->draw(glm::value_ptr(view), glm::value_ptr(proj));
}
if (unbindFramebuffer) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
PostFXSettings Renderer::gatherPostFX(const std::vector<SceneObject>& sceneObjects) const {
PostFXSettings combined;
combined.enabled = false;
for (const auto& obj : sceneObjects) {
if (obj.type != ObjectType::PostFXNode) continue;
if (!obj.postFx.enabled) continue;
combined = obj.postFx; // Last enabled node wins for now
combined.enabled = true;
}
return combined;
}
void Renderer::applyPostProcessing(const std::vector<SceneObject>& sceneObjects) {
PostFXSettings settings = gatherPostFX(sceneObjects);
bool wantsEffects = settings.enabled && (settings.bloomEnabled || settings.colorAdjustEnabled || settings.motionBlurEnabled);
if (!wantsEffects || !postShader || currentWidth <= 0 || currentHeight <= 0) {
displayTexture = viewportTexture;
clearHistory();
return;
}
ensureRenderTarget(postTarget, currentWidth, currentHeight);
ensureRenderTarget(historyTarget, currentWidth, currentHeight);
if (postTarget.fbo == 0 || postTarget.texture == 0) {
displayTexture = viewportTexture;
clearHistory();
return;
}
// --- Bloom using bright pass + separable blur (inspired by ProcessingPostFX) ---
unsigned int bloomTex = 0;
if (settings.bloomEnabled && brightShader && blurShader) {
// Bright pass
glDisable(GL_DEPTH_TEST);
brightShader->use();
brightShader->setFloat("threshold", settings.bloomThreshold);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, viewportTexture);
glBindFramebuffer(GL_FRAMEBUFFER, bloomTargetA.fbo);
glViewport(0, 0, currentWidth, currentHeight);
glClear(GL_COLOR_BUFFER_BIT);
drawFullscreenQuad();
// Blur ping-pong
blurShader->use();
float sigma = glm::max(settings.bloomRadius * 2.5f, 0.1f);
int radius = static_cast<int>(glm::clamp(settings.bloomRadius * 4.0f, 2.0f, 12.0f));
blurShader->setFloat("sigma", sigma);
blurShader->setInt("radius", radius);
bool horizontal = true;
unsigned int pingTex = bloomTargetA.texture;
RenderTarget* writeTarget = &bloomTargetB;
for (int i = 0; i < 4; ++i) {
blurShader->setBool("horizontal", horizontal);
blurShader->setVec2("texelSize", glm::vec2(1.0f / currentWidth, 1.0f / currentHeight));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, pingTex);
glBindFramebuffer(GL_FRAMEBUFFER, writeTarget->fbo);
glViewport(0, 0, currentWidth, currentHeight);
glClear(GL_COLOR_BUFFER_BIT);
drawFullscreenQuad();
// swap
pingTex = writeTarget->texture;
writeTarget = (writeTarget == &bloomTargetA) ? &bloomTargetB : &bloomTargetA;
horizontal = !horizontal;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_DEPTH_TEST);
bloomTex = pingTex;
} else {
bloomTex = 0;
clearTarget(bloomTargetA);
clearTarget(bloomTargetB);
}
glDisable(GL_DEPTH_TEST);
postShader->use();
postShader->setBool("enableBloom", settings.bloomEnabled && bloomTex != 0);
postShader->setFloat("bloomIntensity", settings.bloomIntensity);
postShader->setBool("enableColorAdjust", settings.colorAdjustEnabled);
postShader->setFloat("exposure", settings.exposure);
postShader->setFloat("contrast", settings.contrast);
postShader->setFloat("saturation", settings.saturation);
postShader->setVec3("colorFilter", settings.colorFilter);
postShader->setBool("enableMotionBlur", settings.motionBlurEnabled);
postShader->setFloat("motionBlurStrength", settings.motionBlurStrength);
postShader->setBool("hasHistory", historyValid);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, viewportTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, bloomTex ? bloomTex : viewportTexture);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, historyTarget.texture);
glBindFramebuffer(GL_FRAMEBUFFER, postTarget.fbo);
glViewport(0, 0, currentWidth, currentHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
drawFullscreenQuad();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_DEPTH_TEST);
displayTexture = postTarget.texture;
if (settings.motionBlurEnabled && historyTarget.fbo != 0) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, postTarget.fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, historyTarget.fbo);
glBlitFramebuffer(0, 0, currentWidth, currentHeight, 0, 0, currentWidth, currentHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
historyValid = true;
} else {
clearHistory();
}
}
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);
applyPostProcessing(sceneObjects);
}
unsigned int Renderer::renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane) {
ensureRenderTarget(previewTarget, width, height);
if (previewTarget.fbo == 0) return 0;
glBindFramebuffer(GL_FRAMEBUFFER, previewTarget.fbo);
glViewport(0, 0, width, height);
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);
return previewTarget.texture;
}
void Renderer::endRender() {

View File

@@ -39,6 +39,7 @@ public:
bool hasTexCoords = false;
glm::vec3 boundsMin = glm::vec3(FLT_MAX);
glm::vec3 boundsMax = glm::vec3(-FLT_MAX);
std::vector<glm::vec3> triangleVertices; // positions duplicated per-triangle for picking
};
private:
@@ -59,8 +60,23 @@ class Renderer {
private:
unsigned int framebuffer = 0, viewportTexture = 0, rbo = 0;
int currentWidth = 800, currentHeight = 600;
struct RenderTarget {
unsigned int fbo = 0;
unsigned int texture = 0;
unsigned int rbo = 0;
int width = 0;
int height = 0;
};
RenderTarget previewTarget;
RenderTarget postTarget;
RenderTarget historyTarget;
RenderTarget bloomTargetA;
RenderTarget bloomTargetB;
Shader* shader = nullptr;
Shader* defaultShader = nullptr;
Shader* postShader = nullptr;
Shader* brightShader = nullptr;
Shader* blurShader = nullptr;
Texture* texture1 = nullptr;
Texture* texture2 = nullptr;
std::unordered_map<std::string, std::unique_ptr<Texture>> textureCache;
@@ -74,14 +90,30 @@ private:
std::unordered_map<std::string, ShaderEntry> shaderCache;
std::string defaultVertPath = "Resources/Shaders/vert.glsl";
std::string defaultFragPath = "Resources/Shaders/frag.glsl";
std::string postVertPath = "Resources/Shaders/postfx_vert.glsl";
std::string postFragPath = "Resources/Shaders/postfx_frag.glsl";
std::string postBrightFragPath = "Resources/Shaders/postfx_bright_frag.glsl";
std::string postBlurFragPath = "Resources/Shaders/postfx_blur_frag.glsl";
bool autoReloadShaders = true;
glm::vec3 ambientColor = glm::vec3(0.2f, 0.2f, 0.2f);
Mesh* cubeMesh = nullptr;
Mesh* sphereMesh = nullptr;
Mesh* capsuleMesh = nullptr;
Skybox* skybox = nullptr;
unsigned int quadVAO = 0;
unsigned int quadVBO = 0;
unsigned int displayTexture = 0;
bool historyValid = false;
void setupFBO();
void ensureRenderTarget(RenderTarget& target, int w, int h);
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 applyPostProcessing(const std::vector<SceneObject>& sceneObjects);
PostFXSettings gatherPostFX(const std::vector<SceneObject>& sceneObjects) const;
public:
Renderer() = default;
@@ -100,9 +132,10 @@ public:
void beginRender(const glm::mat4& view, const glm::mat4& proj, const glm::vec3& cameraPos);
void renderSkybox(const glm::mat4& view, const glm::mat4& proj);
void renderObject(const SceneObject& obj);
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects);
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int selectedId = -1, float fovDeg = FOV, float nearPlane = NEAR_PLANE, float farPlane = FAR_PLANE);
unsigned int renderScenePreview(const Camera& camera, const std::vector<SceneObject>& sceneObjects, int width, int height, float fovDeg, float nearPlane, float farPlane);
void endRender();
Skybox* getSkybox() { return skybox; }
unsigned int getViewportTexture() const { return viewportTexture; }
unsigned int getViewportTexture() const { return displayTexture ? displayTexture : viewportTexture; }
};

View File

@@ -11,7 +11,9 @@ enum class ObjectType {
DirectionalLight,
PointLight,
SpotLight,
AreaLight
AreaLight,
Camera,
PostFXNode
};
struct MaterialProperties {
@@ -42,6 +44,33 @@ struct LightComponent {
bool enabled = true;
};
enum class SceneCameraType {
Scene = 0,
Player = 1
};
struct CameraComponent {
SceneCameraType type = SceneCameraType::Scene;
float fov = FOV;
float nearClip = NEAR_PLANE;
float farClip = FAR_PLANE;
};
struct PostFXSettings {
bool enabled = true;
bool bloomEnabled = true;
float bloomThreshold = 1.1f;
float bloomIntensity = 0.8f;
float bloomRadius = 1.5f;
bool colorAdjustEnabled = false;
float exposure = 0.0f; // in EV stops
float contrast = 1.0f;
float saturation = 1.0f;
glm::vec3 colorFilter = glm::vec3(1.0f);
bool motionBlurEnabled = false;
float motionBlurStrength = 0.15f; // 0..1 blend with previous frame
};
enum class ConsoleMessageType {
Info,
Warning,
@@ -71,6 +100,8 @@ public:
std::string fragmentShaderPath;
bool useOverlay = false;
LightComponent light; // Only used when type is a light
CameraComponent camera; // Only used when type is camera
PostFXSettings postFx; // Only used when type is PostFXNode
SceneObject(const std::string& name, ObjectType type, int id)
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}