From 55ae8406786712c616acf4c6ebb84c13dcf4df8d Mon Sep 17 00:00:00 2001 From: antopilo Date: Sun, 26 Sep 2021 22:56:25 -0400 Subject: [PATCH] UI editor work --- Editor/resources/Fonts/OpenSans-Bold.ttf | Bin 0 -> 129752 bytes Editor/resources/Shaders/deferred.shader | 88 +--- Editor/resources/Shaders/volumetric.shader | 120 +++++ Editor/src/EditorInterface.cpp | 490 +++++++++++++------ Nuake/src/Core/Logger.cpp | 6 +- Nuake/src/Rendering/Camera.cpp | 27 +- Nuake/src/Rendering/Camera.h | 16 +- Nuake/src/Rendering/PostFX/Bloom.cpp | 21 - Nuake/src/Rendering/PostFX/Bloom.h | 2 +- Nuake/src/Rendering/PostFX/Volumetric.cpp | 66 +++ Nuake/src/Rendering/PostFX/Volumetric.h | 43 ++ Nuake/src/Rendering/Renderer.cpp | 5 +- Nuake/src/Rendering/SceneRenderer.cpp | 20 +- Nuake/src/Rendering/SceneRenderer.h | 3 + Nuake/src/Rendering/Shaders/Shader.cpp | 44 +- Nuake/src/Rendering/Shaders/Shader.h | 4 +- Nuake/src/Scene/Components/MeshComponent.cpp | 129 +++-- Nuake/src/Scene/EditorCamera.cpp | 22 +- Nuake/src/Scene/Entities/ImGuiHelper.h | 36 +- Nuake/src/Scripting/Modules/SceneModule.h | 2 +- Nuake/src/Vendors/imgui/imgui.cpp | 2 +- Nuake/src/Window.cpp | 100 ++-- 22 files changed, 835 insertions(+), 411 deletions(-) create mode 100644 Editor/resources/Fonts/OpenSans-Bold.ttf create mode 100644 Editor/resources/Shaders/volumetric.shader create mode 100644 Nuake/src/Rendering/PostFX/Volumetric.cpp create mode 100644 Nuake/src/Rendering/PostFX/Volumetric.h diff --git a/Editor/resources/Fonts/OpenSans-Bold.ttf b/Editor/resources/Fonts/OpenSans-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b379424ec7bd9a0ea103832a460f88d0a8d471d4 GIT binary patch literal 129752 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 pcfDepth ? 1.0 : 0.0; } -// Mie scaterring approximated with Henyey-Greenstein phase function. -float ComputeScattering(float lightDotView) -{ - float result = 1.0f - u_FogAmount * u_FogAmount; - result /= (4.0f * PI * pow(1.0f + u_FogAmount * u_FogAmount - (2.0f * u_FogAmount) * lightDotView, 1.5f)); - return result; -} - - -vec3 ComputeVolumetric(vec3 FragPos, Light light) -{ - // world space frag position. - vec3 startPosition = u_EyePosition; // Camera Position - vec3 rayVector = FragPos - startPosition; // Ray Direction - - float rayLength = length(rayVector); // Length of the raymarched - - float stepLength = rayLength / u_FogStepCount; // Step length - vec3 rayDirection = rayVector / rayLength; - vec3 step = rayDirection * stepLength; // Normalized to step length direction - - vec3 currentPosition = startPosition; // First step position - vec3 accumFog = vec3(0.0f, 0.0f, 0.0f); // accumulative color - - // Raymarching - for (int i = 0; i < u_FogStepCount; i++) - { - vec4 fragPosLightSpace = light.LightTransforms[0] * vec4(currentPosition, 1.0f); - // perform perspective divide - vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; - // transform to [0,1] range - projCoords = projCoords * 0.5 + 0.5; - - float currentDepth = projCoords.z; - - // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) - vec2 texelSize = 1.0 / textureSize(ShadowMaps[0], 0); - - float closestDepth = texture(ShadowMaps[0], projCoords.xy).r; - - if (closestDepth > currentDepth) - accumFog += (ComputeScattering(dot(rayDirection, light.Direction)).xxx * light.Color); - - //float ditherPattern[4][4] = { - // { 0.0f, 0.5f, 0.125f, 0.625f}, - // { 0.75f, 0.22f, 0.875f, 0.375f}, - // { 0.1875f, 0.6875f, 0.0625f, 0.5625}, - // { 0.9375f, 0.4375f, 0.8125f, 0.3125} - //}; - - currentPosition += step; //* ditherPattern[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4]; - } - accumFog /= u_FogStepCount; - - return accumFog; -} - void main() { vec3 worldPos = WorldPosFromDepth(texture(m_Depth, UV).r); @@ -246,12 +181,11 @@ void main() F0 = mix(F0, albedo, metallic); // reflectance equation + vec3 eyeDirection = normalize(u_EyePosition - worldPos); + vec3 Lo = vec3(0.0); vec3 fog = vec3(0.0); float shadow = 0.0f; - - vec3 eyeDirection = normalize(u_EyePosition - worldPos); - for (int i = 0; i < LightCount; i++) { vec3 L = normalize(Lights[i].Position - worldPos); @@ -262,10 +196,6 @@ void main() if (Lights[i].Type == 0) { L = normalize(Lights[i].Direction); attenuation = 1.0f; - - if (Lights[i].Volumetric == 1) - fog += ComputeVolumetric(worldPos, Lights[i]); - shadow += ShadowCalculation(Lights[i], worldPos, N); } @@ -304,14 +234,6 @@ void main() vec3 ambient = (kD * albedo) * ao; vec3 color = ambient + Lo; - color += fog; - //color = color / (color + vec3(1.0)); - //const float gamma = 2.2; - //// HDR tonemapping - //color = vec3(1.0) - exp(-color * 1.0); - //// gamma correct - //color = pow(color, vec3(1.0 / gamma)); - - FragColor = mix(vec4(color, 1.0), vec4(albedo, 1.0), 0); + FragColor = vec4(color, 1.0); } \ No newline at end of file diff --git a/Editor/resources/Shaders/volumetric.shader b/Editor/resources/Shaders/volumetric.shader new file mode 100644 index 00000000..6d6fde46 --- /dev/null +++ b/Editor/resources/Shaders/volumetric.shader @@ -0,0 +1,120 @@ +#shader vertex +#version 460 core + +layout(location = 0) in vec3 VertexPosition; +layout(location = 1) in vec2 UVPosition; + +out flat vec2 UV; +out mat4 InvView; +out mat4 InvProjection; + +uniform mat4 u_View; +uniform mat4 u_Projection; + +void main() +{ + UV = UVPosition; + InvView = inverse(u_View); + InvProjection = inverse(u_Projection); + gl_Position = vec4(VertexPosition, 1.0f); +} + +#shader fragment +#version 460 core + +in mat4 InvView; +in mat4 InvProjection; + +uniform sampler2D u_Depth; + +uniform vec3 u_CamPosition; +uniform int u_StepCount; +uniform float u_FogAmount; + +const int MAX_LIGHT = 25; +uniform int u_LightCount; +struct Light { + mat4 transform; + vec3 color; + vec3 direction; + sampler2D shadowmap; +}; + +uniform Light u_Lights[MAX_LIGHT]; + +in vec2 UV; + +out vec4 FragColor; +const float PI = 3.141592653589793f; + +// Mie scaterring approximated with Henyey-Greenstein phase function. +float ComputeScattering(float lightDotView) +{ + float result = 1.0f - u_FogAmount * u_FogAmount; + result /= (4.0f * PI * pow(1.0f + u_FogAmount * u_FogAmount - (2.0f * u_FogAmount) * lightDotView, 1.5f)); + return result; +} + +vec3 ComputeVolumetric(vec3 FragPos, Light light) +{ + vec3 rayVector = FragPos - u_CamPosition; // Ray Direction + + float rayLength = length(rayVector); // Length of the raymarched + + float stepLength = rayLength / u_StepCount; // Step length + vec3 rayDirection = rayVector / rayLength; + vec3 step = rayDirection * stepLength; // Normalized to step length direction + + vec3 accumFog = vec3(0.0f, 0.0f, 0.0f); // accumulative color + + // Raymarching + vec3 currentPosition = u_CamPosition; + for (int i = 0; i < u_StepCount; i++) + { + vec4 fragPosLightSpace = light.transform * vec4(currentPosition, 1.0f); + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + float currentDepth = projCoords.z; + float closestDepth = texture(light.shadowmap, projCoords.xy).r; + if (closestDepth > currentDepth) + accumFog += (ComputeScattering(dot(rayDirection, normalize(light.direction))).xxx * light.color); + + currentPosition += step; //* ditherPattern[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4]; + } + accumFog /= u_StepCount; + + return accumFog; +} + +// Converts depth to World space coords. +vec3 WorldPosFromDepth(float depth) { + float z = depth * 2.0 - 1.0; + + vec4 clipSpacePosition = vec4(UV * 2.0 - 1.0, z, 1.0); + vec4 viewSpacePosition = InvProjection * clipSpacePosition; + + // Perspective division + viewSpacePosition /= viewSpacePosition.w; + + vec4 worldSpacePosition = InvView * viewSpacePosition; + + return worldSpacePosition.xyz; +} + +void main() +{ + float depth = texture(u_Depth, UV).r; + vec3 globalFragmentPosition = WorldPosFromDepth(depth); + + vec3 fog = vec3(0, 0, 0); + + for (int i = 0; i < u_LightCount; i++) + { + fog += ComputeVolumetric(globalFragmentPosition, u_Lights[i]); + } + + FragColor = vec4(mix(fog, vec3(depth), 0.01), 1.0); +} \ No newline at end of file diff --git a/Editor/src/EditorInterface.cpp b/Editor/src/EditorInterface.cpp index 3c70b705..27c02cfd 100644 --- a/Editor/src/EditorInterface.cpp +++ b/Editor/src/EditorInterface.cpp @@ -40,6 +40,7 @@ namespace Nuake { Ref userInterface; ImFont* normalFont; + ImFont* boldFont; ImFont* EditorInterface::bigIconFont; void EditorInterface::Init() { @@ -63,6 +64,8 @@ namespace Nuake { std::string name = ICON_FA_GAMEPAD + std::string(" Scene"); if (ImGui::Begin(name.c_str())) { + + ImGui::PopStyleVar(); Overlay(); ImGuizmo::BeginFrame(); @@ -146,68 +149,6 @@ namespace Nuake { ImGui::PopStyleVar(); } ImGui::End(); - - if (m_IsEntitySelected) - { - //if (ImGui::Begin("DEFERRED")) - //{ - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = Engine::GetCurrentWindow()->GetDeferredBuffer()->GetTexture(GL_COLOR_ATTACHMENT0); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // - //} - //ImGui::End(); - // - //if (ImGui::Begin("GBUFFER")) - //{ - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = Engine::GetCurrentWindow()->GetGBuffer()->GetTexture(GL_DEPTH_ATTACHMENT); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // - //} - //ImGui::End(); - //if (ImGui::Begin("GBUFFER2")) - //{ - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = Engine::GetCurrentWindow()->GetGBuffer()->GetTexture(GL_COLOR_ATTACHMENT0); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // - //} - //ImGui::End(); - //if (ImGui::Begin("GBUFFER3")) - //{ - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = Engine::GetCurrentWindow()->GetGBuffer()->GetTexture(GL_COLOR_ATTACHMENT1); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // - //} - //ImGui::End(); - //if (ImGui::Begin("GBUFFER4")) - //{ - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = Engine::GetCurrentWindow()->GetGBuffer()->GetTexture(GL_COLOR_ATTACHMENT2); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // - //} - //ImGui::End(); - // - //if (ImGui::Begin("CSM 2")) - //{ - // if (m_SelectedEntity.HasComponent()) - // { - // ImVec2 regionAvail = ImGui::GetContentRegionAvail(); - // glm::vec2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); - // Ref texture = m_SelectedEntity.GetComponent().m_Framebuffers[1]->GetTexture(GL_DEPTH_ATTACHMENT); - // ImGui::Image((void*)texture->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); - // } - //} - //ImGui::End(); - } } static int selected = 0; @@ -223,9 +164,13 @@ namespace Nuake { if (m_SelectedEntity == e) base_flags |= ImGuiTreeNodeFlags_Selected; + ImGui::TableNextColumn(); + + + ImGui::TableNextColumn(); + // Write in normal font. ImGui::PushFont(normalFont); - // If has no childrens draw tree node leaf if (parent.Children.size() <= 0) base_flags |= ImGuiTreeNodeFlags_Leaf; @@ -278,45 +223,49 @@ namespace Nuake { m_IsEntitySelected = true; } - if (ImGui::BeginPopupContextItem()) + if (m_IsEntitySelected) { - if (m_SelectedEntity.HasComponent()) + if (ImGui::BeginPopupContextItem()) { - if (ImGui::Selectable("Focus camera")) + if (m_SelectedEntity.HasComponent()) { - Engine::GetCurrentScene()->m_EditorCamera->Translation = m_SelectedEntity.GetComponent().Translation; - Engine::GetCurrentScene()->m_EditorCamera->SetDirection(m_SelectedEntity.GetComponent().CameraInstance->GetDirection()); + if (ImGui::Selectable("Focus camera")) + { + Engine::GetCurrentScene()->m_EditorCamera->Translation = m_SelectedEntity.GetComponent().Translation; + Engine::GetCurrentScene()->m_EditorCamera->SetDirection(m_SelectedEntity.GetComponent().CameraInstance->GetDirection()); + } + ImGui::Separator(); } - ImGui::Separator(); - } - if (ImGui::Selectable("Remove")) - { - QueueDeletion = e; - } - - - if (ImGui::Selectable("Move to root")) - { - auto& p = m_SelectedEntity.GetComponent(); - if (p.HasParent) + if (ImGui::Selectable("Remove")) { - auto& pp = p.Parent.GetComponent(); - pp.RemoveChildren(m_SelectedEntity); - p.HasParent = false; + QueueDeletion = e; } + + + if (ImGui::Selectable("Move to root")) + { + auto& p = m_SelectedEntity.GetComponent(); + if (p.HasParent) + { + auto& pp = p.Parent.GetComponent(); + pp.RemoveChildren(m_SelectedEntity); + p.HasParent = false; + } + } + if (ImGui::Selectable("Save as new prefab")) + { + Ref newPrefab = Prefab::CreatePrefabFromEntity(m_SelectedEntity); + std::string savePath = FileDialog::SaveFile("*.prefab"); + newPrefab->SaveAs(savePath); + m_SelectedEntity.AddComponent().PrefabInstance = newPrefab; + } + ImGui::EndPopup(); } - if (ImGui::Selectable("Save as new prefab")) - { - Ref newPrefab = Prefab::CreatePrefabFromEntity(m_SelectedEntity); - std::string savePath = FileDialog::SaveFile("*.prefab"); - newPrefab->SaveAs(savePath); - m_SelectedEntity.AddComponent().PrefabInstance = newPrefab; - } - ImGui::EndPopup(); } if (open) { + // Caching list to prevent deletion while iterating. std::vector childrens = parent.Children; for (auto c : childrens) @@ -324,12 +273,10 @@ namespace Nuake { ImGui::TreePop(); } + ImGui::PopFont(); } - - - void EditorInterface::DrawSceneTree() { Ref scene = Engine::GetCurrentScene(); @@ -375,64 +322,78 @@ namespace Nuake { ImGui::End(); std::string title = ICON_FA_TREE + std::string(" Hierarchy"); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); if (ImGui::Begin(title.c_str())) { // Buttons to add and remove entity. - if(ImGui::BeginChild("Buttons", ImVec2(300, 20), false)) + if(ImGui::BeginChild("Buttons", ImVec2(300, 30), false)) { // Add entity. - if (ImGui::Button("Add")) + if (ImGui::Button(ICON_FA_PLUS)) Engine::GetCurrentScene()->CreateEntity("Entity"); - ImGui::SameLine(); - - // Remove Entity - if (ImGui::Button("Remove")) - { - scene->DestroyEntity(m_SelectedEntity); - - // Unselect delted entity. - m_SelectedEntity = scene->GetAllEntities().at(0); - } + //// Remove Entity + //if (ImGui::Button("Remove")) + //{ + // scene->DestroyEntity(m_SelectedEntity); + // + // // Unselect delted entity. + // m_SelectedEntity = scene->GetAllEntities().at(0); + //} } ImGui::EndChild(); ImGui::Separator(); // Draw a tree of entities. - if (ImGui::BeginChild("Scene tree", ImGui::GetContentRegionAvail(), true)) + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(26.f / 255.0f, 26.f / 255.0f, 26.f / 255.0f, 1)); + if (ImGui::BeginChild("Scene tree", ImGui::GetContentRegionAvail(), false)) { - std::vector entities = scene->GetAllEntities(); - for (Entity e : entities) + if (ImGui::BeginTable("entity_table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_NoBordersInBody)) { - ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + std::string icon = ICON_FA_EYE; + ImGui::TableSetupColumn((" " + icon).c_str(), ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed, 16); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentEnable); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + + std::vector entities = scene->GetAllEntities(); + for (Entity e : entities) + { + ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; std::string name = e.GetComponent().Name; // If selected add selected flag. if (m_SelectedEntity == e) base_flags |= ImGuiTreeNodeFlags_Selected; // Write in normal font. - ImGui::PushFont(normalFont); + ImGui::PushFont(normalFont); - // Small icons + name. - std::string label = ICON_FA_CIRCLE + std::string(" ") + name; + // Small icons + name. + std::string label = ICON_FA_CIRCLE + std::string(" ") + name; - // Draw all entity without parents. - if (!e.GetComponent().HasParent) - { - // Recursively draw childrens. - DrawEntityTree(e); + // Draw all entity without parents. + if (!e.GetComponent().HasParent) + { + // Recursively draw childrens. + DrawEntityTree(e); + } + + // Pop font. + ImGui::PopFont(); + + // Right click menu + //if (ImGui::BeginPopupContextItem()) + // ImGui::EndPopup(); } - - // Pop font. - ImGui::PopFont(); - - // Right click menu - //if (ImGui::BeginPopupContextItem()) - // ImGui::EndPopup(); } + + + + ImGui::EndTable(); } ImGui::EndChild(); + ImGui::PopStyleColor(); if (ImGui::BeginDragDropTarget()) // Drag n drop new prefab file into scene tree { @@ -461,6 +422,7 @@ namespace Nuake { } } ImGui::End(); + ImGui::PopStyleVar(); } @@ -542,23 +504,84 @@ namespace Nuake { ImGui::Separator(); } - ImGui::PushFont(normalFont); + std::string iconTransform = ICON_FA_MAP_MARKER; - if (ImGui::CollapsingHeader((iconTransform + " Transform").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) + + ImGui::PushFont(boldFont); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 8)); + bool headerOpened = ImGui::CollapsingHeader(" TRANSFORM", ImGuiTreeNodeFlags_DefaultOpen); + if (headerOpened) { - ImGui::TextColored(ImGui::GetStyleColorVec4(1), "Transform properties"); + ImGui::PopFont(); + ImGui::PopStyleVar(); + if (ImGui::BeginTable("TransformTable", 3, ImGuiTableFlags_BordersInner)) + { + TransformComponent& component = m_SelectedEntity.GetComponent(); + + ImGui::TableSetupColumn("name", 0, 0.3); + ImGui::TableSetupColumn("set", 0, 0.6); + ImGui::TableSetupColumn("reset", 0, 0.1); + + ImGui::TableNextColumn(); + ImGui::Text("Translation"); + ImGui::TableNextColumn(); + ImGuiHelper::DrawVec3("Translation", &component.Translation); + ImGui::TableNextColumn(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetTranslation = ICON_FA_UNDO + std::string("##ResetTranslation"); + if (ImGui::Button(resetTranslation.c_str())) component.Translation = Vector3(0, 0, 0); + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Rotation"); + + ImGui::TableNextColumn(); + ImGuiHelper::DrawVec3("Rotation", &component.Rotation); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetRotation = ICON_FA_UNDO + std::string("##ResetRotation"); + if (ImGui::Button(resetRotation.c_str())) component.Rotation = Vector3(0, 0, 0); + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Scale"); + + ImGui::TableNextColumn(); + ImGuiHelper::DrawVec3("Scale", &component.Scale); + + ImGui::TableNextColumn(); + std::string resetScale = ICON_FA_UNDO + std::string("##ResetScale"); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + if (ImGui::Button(resetScale.c_str())) component.Scale = Vector3(1, 1, 1); + ImGui::PopStyleColor(); + + ImGui::EndTable(); + } //ImGui::InputText("Name:", selectedEntity->m_Name.data(), 12); - TransformComponent& component = m_SelectedEntity.GetComponent(); - ImGuiHelper::DrawVec3("Translation", &component.Translation); - ImGuiHelper::DrawVec3("Rotation", &component.Rotation); - ImGuiHelper::DrawVec3("Scale", &component.Scale); - ImGui::Separator(); + } + else + { + ImGui::PopStyleVar(); + ImGui::PopFont(); + } + + ImGui::PopStyleVar(); if (m_SelectedEntity.HasComponent()) { - if (ImGui::CollapsingHeader("Interface", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::PushFont(boldFont); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 8)); + + headerOpened = ImGui::CollapsingHeader("Interface", ImGuiTreeNodeFlags_DefaultOpen); + if (headerOpened) { + ImGui::PopFont(); + ImGui::PopStyleVar(); InterfaceComponent& component = m_SelectedEntity.GetComponent(); std::string& path = component.Path; char pathBuffer[256]; @@ -583,7 +606,16 @@ namespace Nuake { ImGui::EndDragDropTarget(); } } + else + { + ImGui::PopStyleVar(); + ImGui::PopFont(); + } + + + ImGui::PopStyleVar(); } + if (m_SelectedEntity.HasComponent()) { @@ -674,14 +706,137 @@ namespace Nuake { } - if (m_SelectedEntity.HasComponent()) { - std::string icon = ICON_FA_LIGHTBULB; - if (ImGui::CollapsingHeader((icon + " " + "Light").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - { - m_SelectedEntity.GetComponent().DrawEditor(); - ImGui::Separator(); - } + if (m_SelectedEntity.HasComponent()) + { + ImGui::PushFont(boldFont); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 8)); + bool headerOpened = ImGui::CollapsingHeader(" LIGHT", ImGuiTreeNodeFlags_DefaultOpen); + if (headerOpened) + { + ImGui::PopFont(); + ImGui::PopStyleVar(); + if (ImGui::BeginTable("TransformTable", 3, ImGuiTableFlags_BordersInner)) + { + LightComponent& component = m_SelectedEntity.GetComponent(); + + ImGui::TableSetupColumn("name", 0, 0.3); + ImGui::TableSetupColumn("set", 0, 0.6); + ImGui::TableSetupColumn("reset", 0, 0.1); + + ImGui::TableNextColumn(); + ImGui::Text("Color"); + ImGui::TableNextColumn(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth()); + ImGui::ColorEdit3("##lightcolor", &component.Color.r, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); + ImGui::PopItemWidth(); + ImGui::TableNextColumn(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetColor = ICON_FA_UNDO + std::string("##ResetTranslation"); + if (ImGui::Button(resetColor.c_str())) component.Color = Vector3(1, 1, 1); + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Strength"); + + ImGui::TableNextColumn(); + ImGui::SliderFloat("Strength", &component.Strength, 0.0f, 100.0f); + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetStrength = ICON_FA_UNDO + std::string("##resetStrength"); + if (ImGui::Button(resetStrength.c_str())) component.Strength = 10.0f; + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Cast Shadows"); + + ImGui::TableNextColumn(); + ImGui::Checkbox("##CastShadows", &component.CastShadows); + + ImGui::TableNextColumn(); + std::string resetShadow = ICON_FA_UNDO + std::string("##resetShadow"); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + if (ImGui::Button(resetShadow.c_str())) component.CastShadows = false; + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + const char* types[] = { "Directional", "Point", "Spot" }; + static const char* current_item = types[component.Type]; + + if (ImGui::BeginCombo("Type", current_item)) // The second parameter is the label previewed before opening the combo. + { + for (int n = 0; n < IM_ARRAYSIZE(types); n++) + { + bool is_selected = (current_item == types[n]); // You can store your selection however you want, outside or inside your objects + if (ImGui::Selectable(types[n], is_selected)) + { + current_item = types[n]; + component.Type = (LightType)n; + } + if (is_selected) + ImGui::SetItemDefaultFocus(); // You may set the initial focus when opening the combo (scrolling + for keyboard navigation support) + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + std::string resetType = ICON_FA_UNDO + std::string("##resetType"); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + if (ImGui::Button(resetShadow.c_str())) component.Type = Point; + ImGui::PopStyleColor(); + + + if (component.Type == Directional) + { + ImGui::TableNextColumn(); + ImGui::Text("Is Volumetric"); + + ImGui::TableNextColumn(); + ImGui::Checkbox("##Volumetric", &component.IsVolumetric); + + ImGui::TableNextColumn(); + std::string resetIsVolumetric = ICON_FA_UNDO + std::string("##IsVolumetric"); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + if (ImGui::Button(resetIsVolumetric.c_str())) component.IsVolumetric = false; + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Sync Sky Direction"); + + ImGui::TableNextColumn(); + ImGui::Checkbox("##SyncSky", &component.SyncDirectionWithSky); + + ImGui::TableNextColumn(); + std::string resetSyncDirectionWithSky = ICON_FA_UNDO + std::string("##resetSyncDirectionWithSky"); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + if (ImGui::Button(resetSyncDirectionWithSky.c_str())) component.SyncDirectionWithSky = false; + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::Text("Direction"); + ImGui::TableNextColumn(); + ImGuiHelper::DrawVec3("Direction", &component.Direction); + ImGui::TableNextColumn(); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetDirection = ICON_FA_UNDO + std::string("##resetDirection"); + if (ImGui::Button(resetDirection.c_str())) component.Direction = Vector3(0, -1, 0); + ImGui::PopStyleColor(); + } + ImGui::EndTable(); + } + } + else + { + ImGui::PopStyleVar(); + ImGui::PopFont(); + } + ImGui::PopStyleVar(); } if (m_SelectedEntity.HasComponent()) { @@ -841,7 +996,6 @@ namespace Nuake { } } - ImGui::PopFont(); } ImGui::End(); } @@ -1078,9 +1232,9 @@ namespace Nuake { { // FIXME-VIEWPORT: Select a default viewport const float DISTANCE = 10.0f; - static int corner = 3; + int corner = 0; ImGuiIO& io = ImGui::GetIO(); - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; if (corner != -1) { window_flags |= ImGuiWindowFlags_NoMove; @@ -1093,24 +1247,42 @@ namespace Nuake { ImGui::SetNextWindowViewport(viewport->ID); } ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 32.0f); if (ImGui::Begin("Example: Simple overlay", &m_ShowOverlay, window_flags)) { - ImGui::Text("Developpement build 0.1"); - ImGui::Separator(); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - if (ImGui::BeginPopupContextWindow()) - { - if (ImGui::MenuItem("Custom", NULL, corner == -1)) corner = -1; - if (ImGui::MenuItem("Top-left", NULL, corner == 0)) corner = 0; - if (ImGui::MenuItem("Top-right", NULL, corner == 1)) corner = 1; - if (ImGui::MenuItem("Bottom-left", NULL, corner == 2)) corner = 2; - if (ImGui::MenuItem("Bottom-right", NULL, corner == 3)) corner = 3; - if (m_ShowOverlay && ImGui::MenuItem("Close")) m_ShowOverlay = false; - ImGui::EndPopup(); - } - + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 100); + if (ImGui::Button(ICON_FA_HAND_POINTER)) CurrentOperation = ImGuizmo::OPERATION::TRANSLATE; + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROWS_ALT)) CurrentOperation = ImGuizmo::OPERATION::TRANSLATE; + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_SYNC_ALT)) CurrentOperation = ImGuizmo::OPERATION::ROTATE; + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_EXPAND_ALT)) CurrentOperation = ImGuizmo::OPERATION::SCALE; + ImGui::PopStyleVar(); } + ImGui::PopStyleVar(); + ImGui::End(); + int corner2 = 1; + + window_flags |= ImGuiWindowFlags_NoMove; + ImGuiViewport* viewport = ImGui::GetWindowViewport(); + ImVec2 work_area_pos = ImGui::GetCurrentWindow()->Pos; // Instead of using viewport->Pos we use GetWorkPos() to avoid menu bars, if any! + ImVec2 work_area_size = ImGui::GetCurrentWindow()->Size; + ImVec2 window_pos = ImVec2((corner2 & 1) ? (work_area_pos.x + work_area_size.x - DISTANCE) : (work_area_pos.x + DISTANCE), (corner2 & 2) ? (work_area_pos.y + work_area_size.y - DISTANCE) : (work_area_pos.y + DISTANCE)); + ImVec2 window_pos_pivot = ImVec2((corner2 & 1) ? 1.0f : 0.0f, (corner2 & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 32.0f); + if (ImGui::Begin("Controls", &m_ShowOverlay, window_flags)) + { + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 100); + if (ImGui::Button("...")) {}; + ImGui::PopStyleVar(); + } + + ImGui::PopStyleVar(); ImGui::End(); } @@ -1642,7 +1814,7 @@ namespace Nuake { static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true; normalFont = io.Fonts->AddFontFromFileTTF("resources/Fonts/fa-solid-900.ttf", 11.0f, &icons_config, icons_ranges); - + boldFont = io.Fonts->AddFontFromFileTTF("resources/Fonts/OpenSans-Bold.ttf", 16.0); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.5f)); ImGui::GetIO().Fonts->AddFontDefault(); icons_config.MergeMode = true; diff --git a/Nuake/src/Core/Logger.cpp b/Nuake/src/Core/Logger.cpp index fb8e7fee..bbb6870d 100644 --- a/Nuake/src/Core/Logger.cpp +++ b/Nuake/src/Core/Logger.cpp @@ -12,7 +12,7 @@ namespace Nuake { char buff[100]; time_t now = time(0); - strftime(buff, 100, "%Y-%m-%d %H:%M:%S.000", localtime(&now)); + strftime(buff, 100, "%H:%M:%S", localtime(&now)); LogEntry newLog = { type, @@ -20,8 +20,8 @@ namespace Nuake log }; - std::string msg = "[" + std::string(buff) + "]" + std::string(" - ") + log; - printf((msg + "\n").c_str()); + std::string msg = "[" + std::string(buff) + "]" + std::string(" - ") + log + "\n"; + printf((msg).c_str()); if (m_Logs.size() >= MAX_LOG) m_Logs.erase(m_Logs.begin()); diff --git a/Nuake/src/Rendering/Camera.cpp b/Nuake/src/Rendering/Camera.cpp index 45c8b3e3..340c570e 100644 --- a/Nuake/src/Rendering/Camera.cpp +++ b/Nuake/src/Rendering/Camera.cpp @@ -14,11 +14,10 @@ namespace Nuake { m_Type = PERSPECTIVE; Translation = position; - cameraDirection = glm::vec3(0, 0, 1); - up = glm::vec3(0.0f, 1.0f, 0.0f); - cameraRight = glm::normalize(glm::cross(up, cameraDirection)); - cameraUp = glm::cross(cameraDirection, cameraRight); - cameraFront = cameraDirection; + + Direction = Vector3(0, 0, 1); + Right = glm::normalize(glm::cross(Vector3(0, 1, 0), Direction)); + Up = glm::cross(Direction, Right); m_Frustum = Frustum(GetTransform()); } @@ -27,10 +26,9 @@ namespace Nuake { m_Type = PERSPECTIVE; Translation = glm::vec3(0, 0, 0); - cameraDirection = glm::vec3(0, 0, 1); - up = glm::vec3(0.0f, 1.0f, 0.0f); - cameraRight = glm::normalize(glm::cross(up, cameraDirection)); - cameraUp = glm::cross(cameraDirection, cameraRight); + Direction = Vector3(0, 0, 1); + Right = glm::normalize(glm::cross(Vector3(0, 1, 0), Direction)); + Up = glm::cross(Direction, Right); } void Camera::SetType(CAMERA_TYPE type) @@ -52,9 +50,8 @@ namespace Nuake //cam->cameraDirection.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); //cam->cameraFront = glm::normalize(cam->cameraDirection); //cam->cameraRight = glm::normalize(glm::cross(cam->up, cam->cameraFront)); - cameraDirection = glm::normalize(direction); - cameraFront = cameraDirection; - cameraRight = glm::normalize(glm::cross(up, cameraFront)); + Direction = glm::normalize(direction); + Right = glm::normalize(glm::cross(Vector3(0, 1, 0), Direction)); } Vector3 Camera::GetTranslation() { @@ -62,7 +59,7 @@ namespace Nuake } Vector3 Camera::GetDirection() { - return cameraDirection; + return Direction; } Matrix4 Camera::GetPerspective() @@ -74,13 +71,13 @@ namespace Nuake Matrix4 Camera::GetTransform() { - glm::mat4 tr = lookAt(Translation, Translation + cameraFront, cameraUp); + glm::mat4 tr = lookAt(Translation, Translation + Direction, Up); return tr; } Matrix4 Camera::GetTransformRotation() { - return lookAt(glm::vec3(), cameraFront, cameraUp); + return lookAt(glm::vec3(), Direction, Up); } bool Camera::BoxFrustumCheck(const AABB& aabb) diff --git a/Nuake/src/Rendering/Camera.h b/Nuake/src/Rendering/Camera.h index 3251e606..e94144af 100644 --- a/Nuake/src/Rendering/Camera.h +++ b/Nuake/src/Rendering/Camera.h @@ -23,20 +23,16 @@ namespace Nuake Vector3 Rotation = { 0.0f, 0.0f, 0.0f }; Vector3 Scale = { 1.0f, 1.0f, 1.0f }; + Vector3 Up = Vector3(0, 1, 0); + Vector3 Right = Vector3(1, 0, 0); Matrix4 m_Perspective; + public: float AspectRatio = 16.0f / 9.0f; - // TODO: remove duplicate direction and have a proper api. - Vector3 up = Vector3(0.0f, 1.0f, 0.0f); - Vector3 cameraFront = Vector3(0.0f, 0.0f, 1.0f); - - Vector3 cameraTarget; - Vector3 cameraDirection; - + + Vector3 Direction = Vector3(0, 0, 1); Vector3 Translation = { 0.0f, 0.0f, 0.0f }; - Vector3 cameraRight; - Vector3 cameraUp; float Fov = 88.0f; float Exposure = 1.0f; float Speed = 1.0f; @@ -54,7 +50,7 @@ namespace Nuake Matrix4 GetPerspective(); Matrix4 GetTransform(); Matrix4 GetTransformRotation(); - + inline Vector3 GetRight() const { return Right; } bool BoxFrustumCheck(const AABB& aabb); json Serialize() override; diff --git a/Nuake/src/Rendering/PostFX/Bloom.cpp b/Nuake/src/Rendering/PostFX/Bloom.cpp index 9b87ce39..9030286a 100644 --- a/Nuake/src/Rendering/PostFX/Bloom.cpp +++ b/Nuake/src/Rendering/PostFX/Bloom.cpp @@ -102,14 +102,6 @@ namespace Nuake m_DownSampleFB[i]->Unbind(); } - if (ImGui::Begin("Downsample")) - { - ImGui::DragInt("MIP", &downsampleMip, 1.0f, 0, m_Iteration - 1); - ImGui::Image((void*)m_DownSampleFB[downsampleMip]->GetTexture()->GetID(), ImGui::GetContentRegionAvail(), ImVec2(0, 1), ImVec2(1, 0)); - } - ImGui::End(); - - for (int i = 0; i < m_Iteration; i++) { // Horizontal blur @@ -165,14 +157,6 @@ namespace Nuake Renderer::DrawQuad(Matrix4()); m_UpSampleFB[i]->Unbind(); } - - if (ImGui::Begin("UpSample")) - { - ImGui::DragInt("MIP", &upSampleMip, 1.0f, 0, m_Iteration - 1); - ImGui::Image((void*)m_UpSampleFB[upSampleMip]->GetTexture()->GetID(), ImGui::GetContentRegionAvail(), ImVec2(0, 1), ImVec2(1, 0)); - } - ImGui::End(); - m_FinalFB->Bind(); m_FinalFB->Clear(); { @@ -186,11 +170,6 @@ namespace Nuake } m_FinalFB->Unbind(); - if (ImGui::Begin("Final")) - { - ImGui::Image((void*)m_FinalFB->GetTexture()->GetID(), ImGui::GetContentRegionAvail(), ImVec2(0, 1), ImVec2(1, 0)); - } - ImGui::End(); } void Bloom::Resize(Vector2 size) diff --git a/Nuake/src/Rendering/PostFX/Bloom.h b/Nuake/src/Rendering/PostFX/Bloom.h index cedfbc79..bb53493e 100644 --- a/Nuake/src/Rendering/PostFX/Bloom.h +++ b/Nuake/src/Rendering/PostFX/Bloom.h @@ -18,7 +18,7 @@ namespace Nuake { const unsigned int MIN_ITERATION = 1; private: - float m_Threshold = 0.3f; + float m_Threshold = 2.3f; float m_BlurAmount = 12.0f; unsigned int m_Iteration; Vector2 m_Size; diff --git a/Nuake/src/Rendering/PostFX/Volumetric.cpp b/Nuake/src/Rendering/PostFX/Volumetric.cpp new file mode 100644 index 00000000..9d099aed --- /dev/null +++ b/Nuake/src/Rendering/PostFX/Volumetric.cpp @@ -0,0 +1,66 @@ +#include "Volumetric.h" +#include "src/Rendering/Shaders/ShaderManager.h" +#include "src/Rendering/Renderer.h" +#include +#include +namespace Nuake { + Volumetric::Volumetric() + { + mSize = Vector2(1920, 1080); + Init(); + } + + void Volumetric::SetDepth(Texture* depth) + { + Resize(depth->GetSize()); + mDepth = depth; + } + + void Volumetric::Init() + { + mFinalFramebuffer = CreateScope(false, mSize); + mFinalFramebuffer->SetTexture(CreateRef(mSize, GL_RGBA, GL_RGBA16F, GL_FLOAT)); + } + + void Volumetric::Resize(Vector2 size) + { + if (mSize == size) + return; + + mSize = size * mRenderRatio; + Init(); + } + + void Volumetric::Draw(Matrix4 projection, Matrix4 view, std::vector& lights) + { + mFinalFramebuffer->Bind(); + mFinalFramebuffer->Clear(); + RenderCommand::Disable(RendererEnum::DEPTH_TEST); + RenderCommand::Disable(RendererEnum::FACE_CULL); + + Shader* volumetricShader = ShaderManager::GetShader("resources/Shaders/volumetric.shader"); + volumetricShader->Bind(); + volumetricShader->SetUniformMat4f("u_Projection", projection); + volumetricShader->SetUniformMat4f("u_View", view); + volumetricShader->SetUniformTex("u_Depth", mDepth, 0); + volumetricShader->SetUniformVec3("u_CamPosition", view[3]); + volumetricShader->SetUniform1i("u_StepCount", mStepCount); + volumetricShader->SetUniform1f("u_FogAmount", mFogAmount); + volumetricShader->SetUniform1i("u_LightCount", lights.size()); + + for (uint16_t i = 0; i < lights.size(); i++) + { + LightComponent& light = lights[i]; + + std::string u_light = "u_Lights[" + std::to_string(i) + "]."; + volumetricShader->SetUniformMat4f(u_light + "transform", light.mViewProjections[0]); + volumetricShader->SetUniformVec3(u_light + "color", light.Color); + volumetricShader->SetUniformVec3(u_light + "direction", light.GetDirection()); + + volumetricShader->SetUniformTex(u_light + "shadowmap", light.m_Framebuffers[0]->GetTexture(GL_DEPTH_ATTACHMENT).get(), 1 + i); + } + + Renderer::DrawQuad(); + mFinalFramebuffer->Unbind(); + } +} \ No newline at end of file diff --git a/Nuake/src/Rendering/PostFX/Volumetric.h b/Nuake/src/Rendering/PostFX/Volumetric.h new file mode 100644 index 00000000..d607f2bb --- /dev/null +++ b/Nuake/src/Rendering/PostFX/Volumetric.h @@ -0,0 +1,43 @@ +#pragma once +#include "src/Core/Core.h" +#include "src/Core/Maths.h" +#include "src/Rendering/Buffers/FrameBuffer.h" +#include "src/Scene/Components/LightComponent.h" +#include + +namespace Nuake { + class Volumetric + { + private: + uint32_t mStepCount = 50; + float mRenderRatio = 1.0f; + float mFogAmount = 0.9f; + + Vector2 mSize; + Texture* mDepth; + Scope mFinalFramebuffer; + + public: + Volumetric(); + + void SetDepth(Texture* depth); + void Init(); + void Resize(Vector2 size); + void Draw(Matrix4 projection, Matrix4 view, std::vector& lights); + + inline int GetStepCount() const { return mStepCount;} + + inline void SetStepCount(unsigned int amount) { mStepCount = amount; } + + inline float GetFogAmount() const { return mFogAmount; } + void SetFogAmount(float amount) + { + if (amount > 0) mFogAmount = amount; + } + + Texture* GetFinalOutput() + { + return mFinalFramebuffer->GetTexture().get(); + } + }; +} \ No newline at end of file diff --git a/Nuake/src/Rendering/Renderer.cpp b/Nuake/src/Rendering/Renderer.cpp index 365b96d9..c4d28759 100644 --- a/Nuake/src/Rendering/Renderer.cpp +++ b/Nuake/src/Rendering/Renderer.cpp @@ -131,7 +131,7 @@ namespace Nuake int idx = m_Lights.size(); Vector3 direction = light.GetDirection(); - Vector3 pos = transform.GetGlobalTransform()[3]; + Vector3 pos = transform.GlobalTranslation; Matrix4 lightView = glm::lookAt(pos, pos - direction, glm::vec3(0.0f, 1.0f, 0.0f)); //light.m_Framebuffer->GetTexture(GL_DEPTH_ATTACHMENT)->Bind(17); @@ -152,8 +152,7 @@ namespace Nuake deferredShader->SetUniform1i("LightCount", idx); deferredShader->SetUniform1i("Lights[" + std::to_string(idx - 1) + "].Type", light.Type); - deferredShader->SetUniformMat4f("Lights[" + std::to_string(idx - 1) + "].LightTransform", light.GetProjection() * lightView); - deferredShader->SetUniform3f("Lights[" + std::to_string(idx - 1) + "].Position",pos.x, pos.y, pos.z); + deferredShader->SetUniform3f("Lights[" + std::to_string(idx - 1) + "].Position", pos.x, pos.y, pos.z); deferredShader->SetUniform3f("Lights[" + std::to_string(idx - 1) + "].Direction", direction.x, direction.y, direction.z); deferredShader->SetUniform3f("Lights[" + std::to_string(idx - 1) + "].Color", light.Color.r * light.Strength, light.Color.g * light.Strength, light.Color.b * light.Strength); deferredShader->SetUniform1i("Lights[" + std::to_string(idx - 1) + "].Volumetric", light.IsVolumetric); diff --git a/Nuake/src/Rendering/SceneRenderer.cpp b/Nuake/src/Rendering/SceneRenderer.cpp index fa9eb6a3..2df48371 100644 --- a/Nuake/src/Rendering/SceneRenderer.cpp +++ b/Nuake/src/Rendering/SceneRenderer.cpp @@ -3,6 +3,7 @@ #include #include + namespace Nuake { void SceneRenderer::Init() { @@ -19,6 +20,8 @@ namespace Nuake { mBloom = CreateScope(4); mBloom->SetSource(shadedTexture); + + mVolumetric = CreateScope(); } void SceneRenderer::Cleanup() @@ -46,12 +49,25 @@ namespace Nuake { scene.GetEnvironment()->mBloom->Resize(framebuffer.GetSize()); scene.GetEnvironment()->mBloom->Draw(); + auto view = scene.m_Registry.view(); + std::vector lightList = std::vector(); + for (auto l : view) + { + auto& lc = view.get(l); + if (lc.Type == Directional && lc.IsVolumetric) + lightList.push_back(lc); + } + + + mVolumetric->Resize(framebuffer.GetSize()); + mVolumetric->SetDepth(mGBuffer->GetTexture(GL_DEPTH_ATTACHMENT).get()); + mVolumetric->Draw(mProjection, mView, lightList); + // Copy final output to target framebuffer framebuffer.Bind(); { Shader* shader = ShaderManager::GetShader("resources/Shaders/copy.shader"); shader->Bind(); - shader->SetUniformTex("u_Source", scene.GetEnvironment()->mBloom->GetOutput().get()); Renderer::DrawQuad(); } @@ -171,8 +187,6 @@ namespace Nuake { shadingShader->SetUniformVec3("u_EyePosition", mView[3]); Ref env = scene.GetEnvironment(); - shadingShader->SetUniform1f("u_FogAmount", env->VolumetricFog); - shadingShader->SetUniform1f("u_FogStepCount", env->VolumetricStepCount); auto view = scene.m_Registry.view(); for (auto l : view) diff --git a/Nuake/src/Rendering/SceneRenderer.h b/Nuake/src/Rendering/SceneRenderer.h index 96cf1a2e..467bacd5 100644 --- a/Nuake/src/Rendering/SceneRenderer.h +++ b/Nuake/src/Rendering/SceneRenderer.h @@ -5,6 +5,8 @@ #include "src/Scene/Scene.h" #include "src/Rendering/Buffers/Framebuffer.h" #include "src/Rendering/PostFX/Bloom.h" +#include "src/Rendering/PostFX/Volumetric.h" + namespace Nuake { class SceneRenderer { public: @@ -19,6 +21,7 @@ namespace Nuake { Scope mGBuffer; Scope mShadingBuffer; Scope mBloom; + Scope mVolumetric; void ShadowPass(Scene& scene); void GBufferPass(Scene& scene); diff --git a/Nuake/src/Rendering/Shaders/Shader.cpp b/Nuake/src/Rendering/Shaders/Shader.cpp index 30414689..85158916 100644 --- a/Nuake/src/Rendering/Shaders/Shader.cpp +++ b/Nuake/src/Rendering/Shaders/Shader.cpp @@ -34,13 +34,13 @@ namespace Nuake enum class ShaderType { - NONE = -1, VERTEX = 0, FRAGMENT = 1 + NONE = -1, VERTEX = 0, FRAGMENT = 1, GEOMETRY = 2, COMPUTE = 3 }; ShaderType type = ShaderType::NONE; std::string line; - std::stringstream ss[2]; + std::stringstream ss[4]; while (getline(stream, line)) { @@ -58,6 +58,14 @@ namespace Nuake // set mode to fragment type = ShaderType::FRAGMENT; } + else if (line.find("geometry") != std::string::npos) + { + type = ShaderType::GEOMETRY; + } + else if (line.find("compute") != std::string::npos) + { + type = ShaderType::COMPUTE; + } } else { @@ -65,7 +73,7 @@ namespace Nuake } } - return { ss[0].str(), ss[1].str() }; + return { ss[0].str(), ss[1].str(), ss[2].str(), ss[3].str() }; } // Parse both source and creates a shader program. @@ -73,8 +81,23 @@ namespace Nuake unsigned int Shader::CreateProgram() { unsigned int program = glCreateProgram(); + unsigned int vs = Compile(GL_VERTEX_SHADER); unsigned int fs = Compile(GL_FRAGMENT_SHADER); + unsigned int gs = 0; + unsigned int cs = 0; + + if (Source.GeometryShader != "") + { + gs = Compile(GL_GEOMETRY_SHADER); + glAttachShader(program, gs); + } + + if (Source.ComputeShader != "") + { + cs = Compile(GL_COMPUTE_SHADER); + glAttachShader(program, cs); + } glAttachShader(program, vs); glAttachShader(program, fs); @@ -84,6 +107,11 @@ namespace Nuake glDeleteShader(vs); glDeleteShader(fs); + if (gs != 0) + glDeleteShader(gs); + if (cs != 0) + glDeleteShader(cs); + return program; } @@ -95,6 +123,8 @@ namespace Nuake const char* src; if (type == GL_FRAGMENT_SHADER) src = Source.FragmentShader.c_str(); if (type == GL_VERTEX_SHADER) src = Source.VertexShader.c_str(); + if (type == GL_GEOMETRY_SHADER) src = Source.GeometryShader.c_str(); + if (type == GL_COMPUTE_SHADER) src = Source.ComputeShader.c_str(); glShaderSource(id, 1, &src, nullptr); glCompileShader(id); @@ -115,10 +145,16 @@ namespace Nuake glGetShaderInfoLog(id, length, &length, message); + std::string stype; + if (type == GL_FRAGMENT_SHADER) stype = "Fragment"; + if (type == GL_VERTEX_SHADER) stype = "Vertex"; + if (type == GL_GEOMETRY_SHADER) stype = "Geometry"; + if (type == GL_COMPUTE_SHADER) stype = "Compute"; + std::cout << "Failed to compile " << (type == GL_VERTEX_SHADER ? "vertex" : "Fragment") << " shader!" << std::endl; - std::cout << message << std::endl; + std::cout << message << std::endl; // Delete invalid shader glDeleteShader(id); return 0; diff --git a/Nuake/src/Rendering/Shaders/Shader.h b/Nuake/src/Rendering/Shaders/Shader.h index e167b2d3..fdf45238 100644 --- a/Nuake/src/Rendering/Shaders/Shader.h +++ b/Nuake/src/Rendering/Shaders/Shader.h @@ -13,6 +13,8 @@ namespace Nuake { std::string VertexShader; std::string FragmentShader; + std::string GeometryShader; + std::string ComputeShader; }; class Shader @@ -28,8 +30,6 @@ namespace Nuake void Bind() const; void Unbind() const; - - // Todo: Other uniform types. void SetUniformVec4(const std::string& name, Vector4 vec); void SetUniformVec3(const std::string& name, Vector3 vec); void SetUniformVec2(const std::string& name, Vector2 vec); diff --git a/Nuake/src/Scene/Components/MeshComponent.cpp b/Nuake/src/Scene/Components/MeshComponent.cpp index a7b85bce..45171530 100644 --- a/Nuake/src/Scene/Components/MeshComponent.cpp +++ b/Nuake/src/Scene/Components/MeshComponent.cpp @@ -11,6 +11,14 @@ namespace Nuake { Assimp::Importer import; import.SetPropertyFloat("PP_GSN_MAX_SMOOTHING_ANGLE", 90); const aiScene* scene = import.ReadFile(FileSystem::Root + ModelPath, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FixInfacingNormals | aiProcess_CalcTangentSpace); + + + directory = ""; + std::vector path = String::Split(ModelPath, '/'); + for (int i = 0; i < path.size(); i++) + if (i < path.size() - 1) + directory += path[i] + '/'; + if (scene->HasTextures()) { int textureCount = scene->mNumTextures; @@ -96,44 +104,95 @@ namespace Nuake { } // process material - if (mesh->mMaterialIndex >= 0) + if (mesh->mMaterialIndex < 0) + return nullptr; + + aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + Ref newMaterial = MaterialManager::Get()->GetMaterial(directory + std::to_string(mesh->mMaterialIndex) + material->GetName().C_Str()); + + aiString str; + + unsigned int textureCount = material->GetTextureCount(aiTextureType_DIFFUSE); + material->GetTexture(aiTextureType_DIFFUSE, 0, &str); + + // Embedded texture + if (String::BeginsWith(str.C_Str(), "*")) { - aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; - Ref newMaterial = MaterialManager::Get()->GetMaterial(material->GetName().C_Str()); + int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); + const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); - aiString str; - - unsigned int textureCount = material->GetTextureCount(aiTextureType_DIFFUSE); - material->GetTexture(aiTextureType_DIFFUSE, 0, &str); - - // Embedded texture - if (String::BeginsWith(str.C_Str(), "*")) - { - int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); - const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); - - Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); - newMaterial->SetAlbedo(newTexture); - } - - - //material->GetTexture(aiTextureType_NORMALS, 0, &str); - //newMaterial->SetNormal(TextureManager::Get()->GetTexture(directory + str.C_Str())); - - //material->GetTexture(aiTextureType_METALNESS, 0, &str); - //newMaterial->SetMetalness(TextureManager::Get()->GetTexture(directory + str.C_Str())); - - //material->GetTexture(aiTextureType_DIFFUSE_ROUGHNESS, 0, &str); - //newMaterial->SetRoughness(TextureManager::Get()->GetTexture(directory + str.C_Str())); - - //material->GetTexture(aiTextureType_DISPLACEMENT, 0, &str); - //newMaterial->SetDisplacement(TextureManager::Get()->GetTexture(directory + str.C_Str())); - - //material->GetTexture(aiTextureType_AMBIENT_OCCLUSION, 0, &str); - //newMaterial->SetAO(TextureManager::Get()->GetTexture(directory + str.C_Str())); - - return CreateRef(vertices, indices, newMaterial); + Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); + newMaterial->SetAlbedo(newTexture); } + else + { + newMaterial->SetAlbedo(TextureManager::Get()->GetTexture(FileSystem::Root + directory + str.C_Str())); + } + + material->GetTexture(aiTextureType_NORMALS, 0, &str); + if (String::BeginsWith(str.C_Str(), "*")) + { + int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); + const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); + + Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); + newMaterial->SetNormal(newTexture); + } + else + { + newMaterial->SetNormal(TextureManager::Get()->GetTexture(FileSystem::Root + directory + str.C_Str())); + } + //newMaterial->SetNormal(TextureManager::Get()->GetTexture(directory + str.C_Str())); + + material->GetTexture(aiTextureType_METALNESS, 0, &str); + if (String::BeginsWith(str.C_Str(), "*")) + { + int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); + const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); + + Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); + newMaterial->SetMetalness(newTexture); + } + else + { + newMaterial->SetMetalness(TextureManager::Get()->GetTexture(FileSystem::Root + directory + str.C_Str())); + } + //newMaterial->SetMetalness(TextureManager::Get()->GetTexture(directory + str.C_Str())); + + material->GetTexture(aiTextureType_DIFFUSE_ROUGHNESS, 0, &str); + if (String::BeginsWith(str.C_Str(), "*")) + { + int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); + const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); + + Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); + newMaterial->SetRoughness(newTexture); + } + else + { + newMaterial->SetRoughness(TextureManager::Get()->GetTexture(FileSystem::Root + directory + str.C_Str())); + } + //newMaterial->SetRoughness(TextureManager::Get()->GetTexture(directory + str.C_Str())); + + //material->GetTexture(aiTextureType_DISPLACEMENT, 0, &str); + //newMaterial->SetDisplacement(TextureManager::Get()->GetTexture(directory + str.C_Str())); + + material->GetTexture(aiTextureType_AMBIENT_OCCLUSION, 0, &str); + if (String::BeginsWith(str.C_Str(), "*")) + { + int textureIndex = std::atoi(String::Split(str.C_Str(), '*')[1].c_str()); + const aiTexture* texture = scene->GetEmbeddedTexture(str.C_Str()); + + Ref newTexture = CreateRef(Vector2(texture->mWidth, texture->mHeight), (unsigned char*)texture->pcData, texture->mWidth); + newMaterial->SetAO(newTexture); + } + else + { + newMaterial->SetAO(TextureManager::Get()->GetTexture(FileSystem::Root + directory + str.C_Str())); + } + //newMaterial->SetAO(TextureManager::Get()->GetTexture(directory + str.C_Str())); + + return CreateRef(vertices, indices, newMaterial); } std::vector MeshComponent::LoadMaterialTextures(aiMaterial* mat, aiTextureType type) diff --git a/Nuake/src/Scene/EditorCamera.cpp b/Nuake/src/Scene/EditorCamera.cpp index f7b0970a..c69c3101 100644 --- a/Nuake/src/Scene/EditorCamera.cpp +++ b/Nuake/src/Scene/EditorCamera.cpp @@ -52,18 +52,18 @@ namespace Nuake glm::vec3 movement = glm::vec3(0, 0, 0); if (Input::IsKeyDown(GLFW_KEY_D)) - movement -= cameraRight * (Speed * ts); + movement -= Right * (Speed * ts); if (Input::IsKeyDown(GLFW_KEY_A)) - movement += cameraRight * (Speed * ts); + movement += Right * (Speed * ts); if (Input::IsKeyDown(GLFW_KEY_W)) - movement += cameraDirection * (Speed * ts); + movement += Direction * (Speed * ts); if (Input::IsKeyDown(GLFW_KEY_S)) - movement -= cameraDirection * (Speed * ts); + movement -= Direction * (Speed * ts); if (Input::IsKeyDown(GLFW_KEY_LEFT_SHIFT)) - movement -= up * (Speed * ts); + movement -= Up * (Speed * ts); if (Input::IsKeyDown(GLFW_KEY_SPACE)) - movement += up * (Speed * ts); + movement += Up * (Speed * ts); Translation += Vector3(movement); } @@ -93,10 +93,10 @@ namespace Nuake if (Pitch < -89.0f) Pitch = -89.0f; - cameraDirection.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - cameraDirection.y = sin(glm::radians(Pitch)); - cameraDirection.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); - cameraFront = glm::normalize(cameraDirection); - cameraRight = glm::normalize(glm::cross(up, cameraFront)); + Direction.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + Direction.y = sin(glm::radians(Pitch)); + Direction.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch)); + Direction = glm::normalize(Direction); + Right = glm::normalize(glm::cross(Up, Direction)); } } diff --git a/Nuake/src/Scene/Entities/ImGuiHelper.h b/Nuake/src/Scene/Entities/ImGuiHelper.h index eb6649b8..93f3720b 100644 --- a/Nuake/src/Scene/Entities/ImGuiHelper.h +++ b/Nuake/src/Scene/Entities/ImGuiHelper.h @@ -12,32 +12,28 @@ public: auto boldFont = io.Fonts->Fonts[0]; ImGui::PushID(label.c_str()); - - ImGui::Columns(2); - ImGui::SetColumnWidth(0, columnWidth); - ImGui::Text(label.c_str()); - ImGui::NextColumn(); - - ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + + float availWidth = (ImGui::GetContentRegionAvailWidth() / 3.0) - 9.0; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{ 0, 0 }); - + float lineHeight = GImGui->Font->FontSize + GImGui->Style.FramePadding.y * 2.0f; - ImVec2 buttonSize = { lineHeight + 3.0f, lineHeight }; - + ImVec2 buttonSize = { 3.0f, lineHeight }; + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.9f, 0.2f, 0.2f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.8f, 0.1f, 0.15f, 1.0f }); ImGui::PushFont(boldFont); - if (ImGui::Button("X", buttonSize)) + if (ImGui::Button("###X", buttonSize)) values->x = resetValue; ImGui::PopFont(); ImGui::PopStyleColor(3); - + ImGui::SameLine(); + ImGui::PushItemWidth(availWidth); ImGui::DragFloat("##X", &values->x, 0.1f, 0.0f, 0.0f, "%.2f"); ImGui::PopItemWidth(); ImGui::SameLine(); - + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.2f, 0.7f, 0.2f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.3f, 0.8f, 0.3f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.2f, 0.7f, 0.2f, 1.0f }); @@ -46,12 +42,12 @@ public: values->y = resetValue; ImGui::PopFont(); ImGui::PopStyleColor(3); - ImGui::SameLine(); + ImGui::PushItemWidth(availWidth); ImGui::DragFloat("##Y", &values->y, 0.1f, 0.0f, 0.0f, "%.2f"); ImGui::PopItemWidth(); ImGui::SameLine(); - + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{ 0.1f, 0.25f, 0.8f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{ 0.2f, 0.35f, 0.9f, 1.0f }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{ 0.1f, 0.25f, 0.8f, 1.0f }); @@ -60,15 +56,15 @@ public: values->z = resetValue; ImGui::PopFont(); ImGui::PopStyleColor(3); - ImGui::SameLine(); + + ImGui::PushItemWidth(availWidth); ImGui::DragFloat("##Z", &values->z, 0.1f, 0.0f, 0.0f, "%.2f"); ImGui::PopItemWidth(); - + ImGui::PopStyleVar(); - - ImGui::Columns(1); - + ImGui::PopID(); + //ImGui::Text("Hello"); } }; \ No newline at end of file diff --git a/Nuake/src/Scripting/Modules/SceneModule.h b/Nuake/src/Scripting/Modules/SceneModule.h index ad18e161..dd982a84 100644 --- a/Nuake/src/Scripting/Modules/SceneModule.h +++ b/Nuake/src/Scripting/Modules/SceneModule.h @@ -184,7 +184,7 @@ namespace Nuake { auto& cam = ent.GetComponent(); - Vector3 right = cam.CameraInstance->cameraRight; + Vector3 right = cam.CameraInstance->GetRight(); // set the slots wrenSetSlotDouble(vm, 1, right.x); diff --git a/Nuake/src/Vendors/imgui/imgui.cpp b/Nuake/src/Vendors/imgui/imgui.cpp index bbe9a7fa..ad184216 100644 --- a/Nuake/src/Vendors/imgui/imgui.cpp +++ b/Nuake/src/Vendors/imgui/imgui.cpp @@ -14561,7 +14561,7 @@ ImGuiID ImGui::DockSpaceOverViewport(ImGuiViewport* viewport, ImGuiDockNodeFlags PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 8.0f)); Begin(label, NULL, host_window_flags); PopStyleVar(3); diff --git a/Nuake/src/Window.cpp b/Nuake/src/Window.cpp index ba708582..746e7c42 100644 --- a/Nuake/src/Window.cpp +++ b/Nuake/src/Window.cpp @@ -145,62 +145,84 @@ namespace Nuake { ImGuiIO& io = ImGui::GetIO(); (void)io; //io.Fonts->AddFontDefault(); io.Fonts->AddFontFromFileTTF("resources/Fonts/OpenSans-Regular.ttf", 16.0); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGui::StyleColorsDark(); - ImGui::GetStyle().FrameRounding = 2.0f; - ImGui::GetStyle().GrabRounding = 2.0f; + ImGuiStyle& s = ImGui::GetStyle(); + s.FrameRounding = 2.0f; + s.GrabRounding = 2.0f; + s.CellPadding = ImVec2(8, 8); + s.WindowPadding = ImVec2(8, 8); + s.ScrollbarRounding = 9.0f; + s.TabRounding = 0; + s.WindowRounding = 0; + s.ChildRounding = 0; + s.FrameRounding = 0; + s.GrabRounding = 0; + s.FramePadding = ImVec2(8, 4); + s.ItemSpacing = ImVec2(8, 4); + s.ItemInnerSpacing = ImVec2(4, 4); ImVec4* colors = ImGui::GetStyle().Colors; - colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); - colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); - colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 0.00f); colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f); - colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.09f, 0.09f, 0.09f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.13f, 0.13f, 0.13f, 1.00f); + colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); - colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f); - colors[ImGuiCol_CheckMark] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); - colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); - colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f); - colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f); - colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); - colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); - colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); - colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); - colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); - colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f); - colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); - colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); - colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.55f, 0.76f, 0.29f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.55f, 0.76f, 0.29f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.55f, 0.76f, 0.29f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.10f, 0.10f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.00f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.00f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.00f); + colors[ImGuiCol_Tab] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + colors[ImGuiCol_DockingPreview] = ImVec4(0.55f, 0.76f, 0.29f, 1.00f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); - colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.11f, 0.11f, 0.11f, 1.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.93f, 0.27f, 0.27f, 0.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.08f, 0.49f, 0.97f, 0.28f); + colors[ImGuiCol_DragDropTarget] = ImVec4(0.55f, 0.76f, 0.29f, 1.00f); colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); + + ImGui_ImplGlfw_InitForOpenGL(m_Window, true); ImGui_ImplOpenGL3_Init("#version 330"); }