diff --git a/README.md b/README.md index 839baf3..ee3e0e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SlimeProject SlimeProject is a game development project consisting of two main components: SlimeOdyssey (a game engine library) and SlimeGame (a game using the SlimeOdyssey engine). +![image](https://github.com/user-attachments/assets/4f4498c2-1deb-4c52-8ac2-a8515645774d) ![image](https://github.com/user-attachments/assets/229d0dc7-eb05-4651-8d49-2df8f8564e9a) ## Project Structure diff --git a/SlimeGame/src/Application.cpp b/SlimeGame/src/Application.cpp index 8c127f3..e3800ea 100644 --- a/SlimeGame/src/Application.cpp +++ b/SlimeGame/src/Application.cpp @@ -2,6 +2,87 @@ #include #include +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include + +#ifdef _WIN32 +# include +#else +# include +#endif + +template +class breakpoint_on_error_sink : public spdlog::sinks::base_sink +{ +protected: + const char* get_color_code(spdlog::level::level_enum level) const + { + switch (level) + { + case spdlog::level::trace: + return "\033[90m"; // Dark gray + case spdlog::level::debug: + return "\033[36m"; // Cyan + case spdlog::level::info: + return "\033[32m"; // Green + case spdlog::level::warn: + return "\033[33m"; // Yellow + case spdlog::level::err: + return "\033[31m"; // Red + case spdlog::level::critical: + return "\033[1m\033[31m"; // Bold red + default: + return "\033[0m"; // Reset color + } + } + + void sink_it_(const spdlog::details::log_msg& msg) override + { + // Get current time + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + // Format time + std::tm tm_time{}; +#ifdef _WIN32 + localtime_s(&tm_time, &time); +#else + localtime_r(&time, &tm_time); +#endif + + // Print timestamp + std::cout << '[' << std::put_time(&tm_time, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << ms.count() << "] "; + + // Print colored log level + std::cout << '[' << get_color_code(msg.level) << spdlog::level::to_string_view(msg.level).data() << "\033[0m] "; // Reset color after log level + + // Print message + std::cout.write(msg.payload.data(), static_cast(msg.payload.size())); + + // End line + std::cout << std::endl; + + // Check if the log level is error + if (msg.level == spdlog::level::err || msg.level == spdlog::level::critical) + { + // Trigger a breakpoint +#ifdef _WIN32 + __debugbreak(); +#else + std::raise(SIGTRAP); +#endif + } + } + + void flush_() override + { + } +}; + + Application::Application() : m_window({ .title = "Slime Odyssey", .width = 1920, .height = 1080, .resizable = true, .decorated = true, .fullscreen = false }), m_scene(&m_window) { @@ -31,8 +112,9 @@ void Application::Cleanup() void Application::InitializeLogging() { - spdlog::set_level(spdlog::level::trace); - spdlog::stdout_color_mt("console"); + auto breakpoint_sink = std::make_shared>(); + spdlog::set_default_logger(std::make_shared("console", breakpoint_sink)); + spdlog::set_level(spdlog::level::trace); } void Application::InitializeWindow() diff --git a/SlimeGame/src/Application.h b/SlimeGame/src/Application.h index cff6905..78ecad3 100644 --- a/SlimeGame/src/Application.h +++ b/SlimeGame/src/Application.h @@ -4,7 +4,7 @@ #include #include -#define DEBUG_SCENE +// #define DEBUG_SCENE #ifdef DEBUG_SCENE diff --git a/SlimeGame/src/DebugScene.cpp b/SlimeGame/src/DebugScene.cpp index 2e8c194..d906269 100644 --- a/SlimeGame/src/DebugScene.cpp +++ b/SlimeGame/src/DebugScene.cpp @@ -14,7 +14,7 @@ DebugScene::DebugScene(SlimeWindow* window) : Scene(), m_window(window) { Entity mainCamera = Entity("MainCamera"); - mainCamera.AddComponent(90.0f, 1920.0f / 1080.0f, 0.001f, 1000.0f); + mainCamera.AddComponent(90.0f, 1920.0f / 1080.0f, 0.01f, 1000.0f); m_entityManager.AddEntity(mainCamera); } @@ -28,6 +28,9 @@ int DebugScene::Enter(VulkanContext& vulkanContext, ModelManager& modelManager, pbrMaterialResource = descriptorManager.CreatePBRMaterial(vulkanContext, modelManager, "PBR Planet Material", "planet_surface/albedo.png", "planet_surface/normal.png", "planet_surface/metallic.png", "planet_surface/roughness.png", "planet_surface/ao.png"); m_pbrMaterials.push_back(pbrMaterialResource); + pbrMaterialResource = descriptorManager.CreatePBRMaterial(vulkanContext, modelManager, "Grass Material", "grass/albedo.png", "grass/normal.png", "planet_surface/metallic.png", "grass/roughness.png", "grass/ao.png"); + m_pbrMaterials.push_back(pbrMaterialResource); + InitializeDebugObjects(vulkanContext, modelManager); return 0; @@ -62,7 +65,8 @@ void DebugScene::InitializeDebugObjects(VulkanContext& vulkanContext, ModelManag { // Light auto lightEntity = std::make_shared("Light"); - DirectionalLight& light = lightEntity->AddComponent().light; + DirectionalLight& light = lightEntity->AddComponent(); + light.SetColor(glm::vec3(0.98f, 0.506f, 0.365f)); m_entityManager.AddEntity(lightEntity); VmaAllocator allocator = vulkanContext.GetAllocator(); @@ -73,12 +77,23 @@ void DebugScene::InitializeDebugObjects(VulkanContext& vulkanContext, ModelManag auto bunnyMesh = modelManager.LoadModel("stanford-bunny.obj", "pbr"); modelManager.CreateBuffersForMesh(allocator, *bunnyMesh); + auto groundPlane = modelManager.CreatePlane(allocator, 50.0f, 25); + modelManager.CreateBuffersForMesh(allocator, *groundPlane); + + // Create the groundPlane + Entity ground = Entity("Ground"); + ground.AddComponent(groundPlane); + ground.AddComponent(m_pbrMaterials[2]); + auto& groundTransform = ground.AddComponent(); + groundTransform.position = glm::vec3(0.0f, 0.2f, 0.0f); // Slightly above the grid + m_entityManager.AddEntity(ground); + // Create a bunny Entity bunny = Entity("Bunny"); bunny.AddComponent(bunnyMesh); bunny.AddComponent(m_pbrMaterials[0]); auto& bunnyTransform = bunny.AddComponent(); - bunnyTransform.position = glm::vec3(10.0f, 4.0f, -10.0f); + bunnyTransform.position = glm::vec3(10.0f, 3.0f, -10.0f); bunnyTransform.scale = glm::vec3(20.0f); m_entityManager.AddEntity(bunny); @@ -136,7 +151,7 @@ void DebugScene::Update(float dt, VulkanContext& vulkanContext, const InputManag } } -void DebugScene::Render(VulkanContext& vulkanContext, ModelManager& modelManager) +void DebugScene::Render() { // scene Camera info ImGui::Begin("Camera Info"); @@ -164,17 +179,17 @@ void DebugScene::Exit(VulkanContext& vulkanContext, ModelManager& modelManager) } // Clean up lights - std::vector> lightEntities = m_entityManager.GetEntitiesWithComponents(); + std::vector> lightEntities = m_entityManager.GetEntitiesWithComponents(); for (const auto& entity: lightEntities) { - PointLightObject& light = entity->GetComponent(); + PointLight& light = entity->GetComponent(); vmaDestroyBuffer(vulkanContext.GetAllocator(), light.buffer, light.allocation); } - lightEntities = m_entityManager.GetEntitiesWithComponents(); + lightEntities = m_entityManager.GetEntitiesWithComponents(); for (const auto& entity: lightEntities) { - DirectionalLightObject& light = entity->GetComponent(); + DirectionalLight& light = entity->GetComponent(); vmaDestroyBuffer(vulkanContext.GetAllocator(), light.buffer, light.allocation); } diff --git a/SlimeGame/src/DebugScene.h b/SlimeGame/src/DebugScene.h index b7d4e06..78c420a 100644 --- a/SlimeGame/src/DebugScene.h +++ b/SlimeGame/src/DebugScene.h @@ -16,7 +16,7 @@ class DebugScene : public Scene DebugScene(SlimeWindow* window); int Enter(VulkanContext& vulkanContext, ModelManager& modelManager, ShaderManager& shaderManager, DescriptorManager& descriptorManager) override; void Update(float dt, VulkanContext& vulkanContext, const InputManager* inputManager) override; - void Render(VulkanContext& vulkanContext, ModelManager& modelManager) override; + void Render() override; void Exit(VulkanContext& vulkanContext, ModelManager& modelManager) override; private: diff --git a/SlimeGame/src/PlatformerGame.cpp b/SlimeGame/src/PlatformerGame.cpp index eadb864..141cedf 100644 --- a/SlimeGame/src/PlatformerGame.cpp +++ b/SlimeGame/src/PlatformerGame.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "glm/gtc/random.hpp" struct Velocity : public Component { @@ -44,7 +45,7 @@ PlatformerGame::PlatformerGame(SlimeWindow* window) // Light auto lightEntity = std::make_shared("Light"); - DirectionalLight& light = lightEntity->AddComponent().light; + DirectionalLight& light = lightEntity->AddComponent(); m_entityManager.AddEntity(lightEntity); } @@ -52,7 +53,6 @@ int PlatformerGame::Enter(VulkanContext& vulkanContext, ModelManager& modelManag { SetupShaders(vulkanContext, modelManager, shaderManager, descriptorManager); - std::shared_ptr pbrMaterialResource = descriptorManager.CreatePBRMaterial(vulkanContext, modelManager, "PBR Material", "albedo.png", "normal.png", "metallic.png", "roughness.png", "ao.png"); m_pbrMaterials.push_back(pbrMaterialResource); @@ -68,7 +68,7 @@ void PlatformerGame::SetupShaders(VulkanContext& vulkanContext, ModelManager& mo // Set up a basic pipeline std::vector> pbrShaderPaths = { - {ResourcePathManager::GetShaderPath("basic.vert.spv"), VK_SHADER_STAGE_VERTEX_BIT}, + {ResourcePathManager::GetShaderPath("basic.vert.spv"), VK_SHADER_STAGE_VERTEX_BIT}, {ResourcePathManager::GetShaderPath("basic.frag.spv"), VK_SHADER_STAGE_FRAGMENT_BIT} }; modelManager.CreatePipeline("pbr", vulkanContext, shaderManager, descriptorManager, pbrShaderPaths, true); @@ -118,6 +118,9 @@ void PlatformerGame::InitializeGameObjects(VulkanContext& vulkanContext, ModelMa m_entityManager.AddEntity(m_player); m_entityManager.AddEntity(m_obstacle); m_entityManager.AddEntity(m_lightCube); + + SpawnCollectibles(vulkanContext, modelManager); + SpawnMovingPlatforms(vulkanContext, modelManager); } void PlatformerGame::Update(float dt, VulkanContext& vulkanContext, const InputManager* inputManager) @@ -143,11 +146,24 @@ void PlatformerGame::Update(float dt, VulkanContext& vulkanContext, const InputM { m_window->Close(); } + + UpdateCollectibles(dt); + UpdateMovingPlatforms(dt); + CheckCollectibleCollisions(); + UpdatePowerUp(dt); } -void PlatformerGame::Render(VulkanContext& vulkanContext, ModelManager& modelManager) +void PlatformerGame::Render() { - // m_entityManager.ImGuiDebug(); + m_entityManager.ImGuiDebug(); + + ImGui::Begin("Game Info"); + ImGui::Text("Score: %d", m_score); + if (m_hasPowerUp) + { + ImGui::Text("Power-up active: %.1f seconds remaining", m_powerUpTimer); + } + ImGui::End(); } void PlatformerGame::Exit(VulkanContext& vulkanContext, ModelManager& modelManager) @@ -158,10 +174,10 @@ void PlatformerGame::Exit(VulkanContext& vulkanContext, ModelManager& modelManag vmaDestroyBuffer(vulkanContext.GetAllocator(), material->configBuffer, material->configAllocation); } - std::vector> lightEntities = m_entityManager.GetEntitiesWithComponents(); + std::vector> lightEntities = m_entityManager.GetEntitiesWithComponents(); for (const auto& entity: lightEntities) { - DirectionalLightObject& light = entity->GetComponent(); + DirectionalLight& light = entity->GetComponent(); vmaDestroyBuffer(vulkanContext.GetAllocator(), light.buffer, light.allocation); } @@ -172,7 +188,7 @@ void PlatformerGame::Exit(VulkanContext& vulkanContext, ModelManager& modelManag Camera& camera = entity->GetComponent(); camera.DestroyCameraUBOBuffer(vulkanContext.GetAllocator()); } - + modelManager.CleanUpAllPipelines(vulkanContext.GetDispatchTable()); } @@ -282,6 +298,18 @@ void PlatformerGame::UpdatePlayer(float dt, const InputManager* inputManager) // Update camera target camera.SetTarget(playerTransform.position + glm::vec3(0.0f, 1.0f, 0.0f)); + + // Apply power-up effect + if (m_hasPowerUp) + { + m_gameParams.moveSpeed = 4.0f; // Increased speed + m_gameParams.jumpForce = 7.5f; // Higher jump + } + else + { + m_gameParams.moveSpeed = 2.0f; // Normal speed + m_gameParams.jumpForce = 5.0f; // Normal jump + } } void PlatformerGame::UpdateCamera(float dt, const InputManager* inputManager) @@ -360,6 +388,102 @@ void PlatformerGame::CheckCollisions() } } +void PlatformerGame::SpawnCollectibles(VulkanContext& vulkanContext, ModelManager& modelManager) +{ + // Create a simple collectible model (e.g., a sphere) + auto collectibleMesh = modelManager.CreateSphere(vulkanContext.GetAllocator(), 0.5f); + modelManager.CreateBuffersForMesh(vulkanContext.GetAllocator(), *collectibleMesh); + + // Spawn collectibles at random positions + for (int i = 0; i < 10; ++i) + { + auto collectible = std::make_shared("Collectible" + std::to_string(i)); + collectible->AddComponent(collectibleMesh); + collectible->AddComponent(m_pbrMaterials[0]); + auto& transform = collectible->AddComponent(); + transform.position = glm::vec3(glm::linearRand(-10.0f, 10.0f), glm::linearRand(1.0f, 5.0f), glm::linearRand(-10.0f, 10.0f)); + transform.scale = glm::vec3(0.5f); + m_collectibles.push_back(collectible); + m_entityManager.AddEntity(collectible); + } +} + +void PlatformerGame::UpdateCollectibles(float dt) +{ + for (auto& collectible: m_collectibles) + { + auto& transform = collectible->GetComponent(); + transform.rotation.y += dt * 2.0f; // Rotate collectibles + transform.position.y += std::sin(glfwGetTime() * 2.0f) * 0.01f; // Make collectibles float up and down + } +} + +void PlatformerGame::SpawnMovingPlatforms(VulkanContext& vulkanContext, ModelManager& modelManager) +{ + auto platformMesh = modelManager.CreateCube(vulkanContext.GetAllocator(), 2.0f); + modelManager.CreateBuffersForMesh(vulkanContext.GetAllocator(), *platformMesh); + + for (int i = 0; i < 3; ++i) + { + auto platform = std::make_shared("MovingPlatform" + std::to_string(i)); + platform->AddComponent(platformMesh); + platform->AddComponent(m_pbrMaterials[0]); + auto& transform = platform->AddComponent(); + transform.position = glm::vec3(glm::linearRand(-8.0f, 8.0f), glm::linearRand(2.0f, 6.0f), glm::linearRand(-8.0f, 8.0f)); + transform.scale = glm::vec3(2.0f, 0.5f, 2.0f); + m_movingPlatforms.push_back(platform); + m_entityManager.AddEntity(platform); + } +} + +void PlatformerGame::UpdateMovingPlatforms(float dt) +{ + for (size_t i = 0; i < m_movingPlatforms.size(); ++i) + { + auto& transform = m_movingPlatforms[i]->GetComponent(); + float t = glfwGetTime() * 0.5f + i * 2.0f * glm::pi() / m_movingPlatforms.size(); + transform.position.x += std::sin(t) * dt * 2.0f; + transform.position.z += std::cos(t) * dt * 2.0f; + } +} + +void PlatformerGame::CheckCollectibleCollisions() +{ + auto& playerTransform = m_player->GetComponent(); + auto playerPos = playerTransform.position; + + for (auto it = m_collectibles.begin(); it != m_collectibles.end();) + { + auto& collectibleTransform = (*it)->GetComponent(); + if (glm::distance(playerPos, collectibleTransform.position) < 1.0f) + { + m_score += 10; + m_hasPowerUp = true; + m_powerUpTimer = 5.0f; // 5 seconds power-up duration + spdlog::info("Collected! Score: {}", m_score); + m_entityManager.RemoveEntity((*it)); + it = m_collectibles.erase(it); + } + else + { + ++it; + } + } +} + +void PlatformerGame::UpdatePowerUp(float dt) +{ + if (m_hasPowerUp) + { + m_powerUpTimer -= dt; + if (m_powerUpTimer <= 0.0f) + { + m_hasPowerUp = false; + spdlog::info("Power-up ended!"); + } + } +} + void PlatformerGame::CheckWinCondition() { auto& playerTransform = m_player->GetComponent(); diff --git a/SlimeGame/src/PlatformerGame.h b/SlimeGame/src/PlatformerGame.h index e3b529e..00f8ce6 100644 --- a/SlimeGame/src/PlatformerGame.h +++ b/SlimeGame/src/PlatformerGame.h @@ -1,5 +1,4 @@ #pragma once -#define GLM_ENABLE_EXPERIMENTAL #include #include #include @@ -20,7 +19,7 @@ class PlatformerGame : public Scene PlatformerGame(SlimeWindow* window); int Enter(VulkanContext& vulkanContext, ModelManager& modelManager, ShaderManager& shaderManager, DescriptorManager& descriptorManager) override; void Update(float dt, VulkanContext& vulkanContext, const InputManager* inputManager) override; - void Render(VulkanContext& vulkanContext, ModelManager& modelManager) override; + void Render() override; void Exit(VulkanContext& vulkanContext, ModelManager& modelManager) override; private: @@ -74,4 +73,18 @@ class PlatformerGame : public Scene GameParameters m_gameParams; SlimeWindow* m_window; + + std::vector> m_collectibles; + std::vector> m_movingPlatforms; + int m_score = 0; + float m_powerUpTimer = 0.0f; + bool m_hasPowerUp = false; + + // New methods + void SpawnCollectibles(VulkanContext& vulkanContext, ModelManager& modelManager); + void UpdateCollectibles(float dt); + void SpawnMovingPlatforms(VulkanContext& vulkanContext, ModelManager& modelManager); + void UpdateMovingPlatforms(float dt); + void CheckCollectibleCollisions(); + void UpdatePowerUp(float dt); }; diff --git a/SlimeOdyssey/CMakeLists.txt b/SlimeOdyssey/CMakeLists.txt index 60db804..fda714e 100644 --- a/SlimeOdyssey/CMakeLists.txt +++ b/SlimeOdyssey/CMakeLists.txt @@ -40,6 +40,7 @@ target_compile_definitions(${PROJECT_NAME} PUBLIC IMGUI_IMPL_VULKAN_DYNAMIC_LOADER=glfwGetInstanceProcAddress IMGUI_IMPL_VULKAN_NO_PROTOTYPES IMGUI_DEFINE_MATH_OPERATORS + GLM_ENABLE_EXPERIMENTAL ) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/SlimeOdyssey/include/Camera.h b/SlimeOdyssey/include/Camera.h index 336e9e9..094f0fe 100644 --- a/SlimeOdyssey/include/Camera.h +++ b/SlimeOdyssey/include/Camera.h @@ -51,6 +51,9 @@ class Camera : public Component void ImGuiDebug(); + glm::vec3 GetForward() const; + float GetFOV() const; + float GetAspectRatio() const; private: // Helper method void UpdateCameraVectors(); @@ -72,4 +75,13 @@ class Camera : public Component CameraUBO m_cameraUBO; VkBuffer m_cameraUBOBBuffer = VK_NULL_HANDLE; VmaAllocation m_cameraUBOAllocation = VK_NULL_HANDLE; + +public: + float GetNearZ() const; + + float GetFarZ() const; + + glm::vec3 GetUp() const; + + glm::vec3 GetRight() const; }; diff --git a/SlimeOdyssey/include/Entity.h b/SlimeOdyssey/include/Entity.h index 3b21ecf..7dd3a5f 100644 --- a/SlimeOdyssey/include/Entity.h +++ b/SlimeOdyssey/include/Entity.h @@ -1,13 +1,13 @@ #pragma once #include #include +#include #include #include #include -#include "Component.h" +#include "Component.h" #include "EntityManager.h" -#include class Entity { @@ -24,7 +24,7 @@ class Entity void SetActive(bool isActive); - template + template T& AddComponent(Args&&... args) { static_assert(std::is_base_of_v, "T must inherit from Component"); @@ -76,6 +76,17 @@ class Entity return nullptr; } + template + std::shared_ptr GetComponentShrPtr() const + { + auto it = m_components.find(std::type_index(typeid(T))); + if (it != m_components.end()) + { + return std::static_pointer_cast(it->second); + } + return nullptr; + } + template bool HasComponents() const { diff --git a/SlimeOdyssey/include/EntityManager.h b/SlimeOdyssey/include/EntityManager.h index c30661d..9b5f3bd 100644 --- a/SlimeOdyssey/include/EntityManager.h +++ b/SlimeOdyssey/include/EntityManager.h @@ -1,12 +1,12 @@ #pragma once #include -#include -#include #include #include #include #include +#include +#include #include class Entity; @@ -28,6 +28,7 @@ class EntityManager // Remove an entity from the manager void RemoveEntity(const Entity& entity); + void RemoveEntity(std::shared_ptr entity); // Get all entities const std::vector>& GetEntities() const; @@ -44,8 +45,8 @@ class EntityManager // Get entity by name std::shared_ptr GetEntityByName(const std::string& name) const; - // Get entities with specific components - template + // Get entities with specific components + template std::vector> GetEntitiesWithComponents() { ComponentMask mask = GetComponentMask(); @@ -60,7 +61,7 @@ class EntityManager return result; } - // Get entity count with specific components + // Get entity count with specific components template size_t GetEntityCountWithComponents() const { @@ -76,7 +77,7 @@ class EntityManager return count; } - // Execute a function for each entity with specific components + // Execute a function for each entity with specific components template void ForEachEntityWith(Func func) { @@ -93,8 +94,8 @@ class EntityManager // Update component masks when an entity's components change void OnEntityComponentChanged(const Entity& entity); - // Shows a window of all entities and their components - void ImGuiDebug(); + // Shows a window of all entities and their components + void ImGuiDebug(); private: // Helper function to get or create component type index @@ -123,13 +124,13 @@ class EntityManager // Get entity index size_t GetEntityIndex(const Entity& entity) const; - // Imgui debug - Entity* m_selectedEntity = nullptr; - float m_splitRatio = 0.5f; - void RenderComponentDetails(); - void RenderEntityTree(const std::string& searchStr); - - std::vector> m_entities; + // Imgui debug + Entity* m_selectedEntity = nullptr; + float m_splitRatio = 0.5f; + void RenderComponentDetails(); + void RenderEntityTree(const std::string& searchStr); + + std::vector> m_entities; std::vector m_entityMasks; mutable std::unordered_map m_componentTypeIndices; diff --git a/SlimeOdyssey/include/Light.h b/SlimeOdyssey/include/Light.h index ca4e404..f096082 100644 --- a/SlimeOdyssey/include/Light.h +++ b/SlimeOdyssey/include/Light.h @@ -1,44 +1,131 @@ #pragma once -#include -#include "Component.h" + +#include #include +#include + +#include "Component.h" + +constexpr float PADDING = 420.0f; + +enum class LightType +{ + Undefined, + Directional, + Point +}; -struct PointLight +// Base Light struct +struct LightData { - glm::vec3 pos = glm::vec3(-6.0f, 6.0f, 6.0f); + // Split by 16 byte chunks + glm::vec3 color = glm::vec3(1.0f); + float padding1 = PADDING; + float ambientStrength = 0.0f; - glm::vec3 colour = glm::vec3(1.0f, 0.8f, 0.95f); float specularStrength = 0.0f; - glm::vec3 direction = glm::vec3(0.0f); - float shininess = 0.0f; + float padding2[2] = { PADDING, PADDING }; + glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); }; -struct PointLightObject : public Component +// Base Light class +class Light : public Component { - PointLight light; +public: + virtual ~Light() = default; + + LightType GetType() const + { + return m_lightType; + } + + void SetLightSpaceMatrix(glm::mat4 lightSpaceMatric) + { + m_data.lightSpaceMatrix = lightSpaceMatric; + } + + glm::mat4 GetLightSpaceMatrix() const + { + return m_data.lightSpaceMatrix; + } + + glm::vec3 GetColor() const + { + return m_data.color; + } + + void SetColor(glm::vec3 color) + { + m_data.color = color; + } + + virtual void ImGuiDebug() = 0; VkBuffer buffer = VK_NULL_HANDLE; VmaAllocation allocation = VK_NULL_HANDLE; - void ImGuiDebug(); +protected: + LightType m_lightType = LightType::Undefined; + LightData m_data; }; -struct DirectionalLight +// Directional Light +class DirectionalLight : public Light { - glm::vec3 direction = glm::vec3(-20.0f, -25.0f, 20.0f); - float ambientStrength = 0.01f; - glm::vec3 color = glm::vec3(1.0f); - float padding; - glm::mat4 lightSpaceMatrix = glm::mat4(); +public: + DirectionalLight(); + + glm::vec3 GetDirection() const; + void SetDirection(const glm::vec3& direction); + + const LightData& GetData() const; + void SetData(const LightData& data); + + // Get the binding data + struct BindingData + { + LightData data; + glm::vec3 direction; + float padding1 = PADDING; + }; + BindingData GetBindingData(); + size_t GetBindingDataSize() const; + + void ImGuiDebug() override; + +private: + glm::vec3 m_direction = glm::normalize(glm::vec3(-20.0f, 15.0f, 20.0f)); + float m_padding3 = PADDING; }; -struct DirectionalLightObject : public Component +// Point Light +class PointLight : public Light { - DirectionalLight light; +public: + PointLight(); - VkBuffer buffer = VK_NULL_HANDLE; - VmaAllocation allocation = VK_NULL_HANDLE; + glm::vec3 GetPosition() const; + void SetPosition(const glm::vec3& position); + + float GetRadius() const; + void SetRadius(float radius); - void ImGuiDebug(); -}; \ No newline at end of file + const LightData& GetData() const; + void SetData(const LightData& data); + + // Get the binding data + struct BindingData + { + LightData data; + glm::vec3 position; + float radius; + }; + BindingData GetBindingData(); + size_t GetBindingDataSize() const; + + void ImGuiDebug() override; +private: + glm::vec3 m_position = glm::vec3(-6.0f, 6.0f, 6.0f); + float m_radius = 50.0f; // Light's influence radius +}; diff --git a/SlimeOdyssey/include/Model.h b/SlimeOdyssey/include/Model.h index dcbe935..3d77b76 100644 --- a/SlimeOdyssey/include/Model.h +++ b/SlimeOdyssey/include/Model.h @@ -7,7 +7,6 @@ #include "Component.h" #include "vk_mem_alloc.h" -#define GLM_ENABLE_EXPERIMENTAL #include #include @@ -32,22 +31,22 @@ struct ModelResource std::vector vertices; std::vector indices; - VkBuffer vertexBuffer; - VmaAllocation vertexAllocation; + VkBuffer vertexBuffer = VK_NULL_HANDLE; + VmaAllocation vertexAllocation = VK_NULL_HANDLE; - VkBuffer indexBuffer; - VmaAllocation indexAllocation; + VkBuffer indexBuffer = VK_NULL_HANDLE; + VmaAllocation indexAllocation = VK_NULL_HANDLE; std::string pipelineName; }; struct TextureResource { - VmaAllocation allocation; + VmaAllocation allocation = VK_NULL_HANDLE; - VkImage image; - VkImageView imageView; - VkSampler sampler; + VkImage image = VK_NULL_HANDLE; + VkImageView imageView = VK_NULL_HANDLE; + VkSampler sampler = VK_NULL_HANDLE; uint32_t width; uint32_t height; diff --git a/SlimeOdyssey/include/Renderer.h b/SlimeOdyssey/include/Renderer.h index 61a6d86..2035bfa 100644 --- a/SlimeOdyssey/include/Renderer.h +++ b/SlimeOdyssey/include/Renderer.h @@ -1,12 +1,19 @@ #pragma once +#include +#include +#include +#include +#include #include +#include #include -#include +#include #include + #include "Model.h" #include "PipelineGenerator.h" -#include +#include "ShadowSystem.h" // Forward declarations class Camera; @@ -55,9 +62,9 @@ namespace std { size_t operator()(const DirectionalLight& light) const { - size_t h1 = hash()(light.direction); - size_t h2 = hash()(light.color); - size_t h3 = hash()(light.ambientStrength); + size_t h1 = hash()(light.GetDirection()); + size_t h2 = hash()(light.GetData().color); + size_t h3 = hash()(light.GetData().ambientStrength); return h1 ^ (h2 << 1) ^ (h3 << 2); } }; @@ -69,12 +76,32 @@ class Renderer Renderer() = default; ~Renderer() = default; + void SetUp(vkb::DispatchTable& disp, VmaAllocator allocator, vkb::Swapchain swapchain, VulkanDebugUtils& debugUtils); + void CleanUp(vkb::DispatchTable& disp, VmaAllocator allocator); + + int Draw(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + DescriptorManager& descriptorManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + vkb::Swapchain swapchain, + std::vector& swapchainImages, + std::vector& swapchainImageViews, + uint32_t imageIndex, + Scene* scene); + void SetupViewportAndScissor(vkb::Swapchain swapchain, vkb::DispatchTable disp, VkCommandBuffer& cmd); void DrawModelsForShadowMap(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, VkCommandBuffer& cmd, ModelManager& modelManager, Scene* scene); - void DrawModels(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, VmaAllocator allocator, VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene, TextureResource* shadowMap); + void DrawModels(vkb::DispatchTable& disp, VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, VulkanDebugUtils& debugUtils, Scene* scene); -private: + void DrawImguiDebugger(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, ModelManager& modelManager, VulkanDebugUtils& debugUtils); + + void CreateDepthImage(vkb::DispatchTable& disp, VmaAllocator allocator, vkb::Swapchain swapchain, VulkanDebugUtils& debugUtils); +private: // Push Constant struct MVP { @@ -82,6 +109,8 @@ class Renderer glm::mat3 normalMatrix; } m_mvp; + bool m_forceInvalidateDecriptorSets = false; + glm::vec4 m_clearColour = glm::vec4(0.98f, 0.506f, 0.365f, 1.0f); void UpdateCommonBuffers(VulkanDebugUtils& debugUtils, VmaAllocator allocator, VkCommandBuffer& cmd, Scene* scene); void UpdateLightBuffer(EntityManager& entityManager, VmaAllocator allocator); @@ -91,7 +120,7 @@ class Renderer void DrawInfiniteGrid(vkb::DispatchTable& disp, VkCommandBuffer commandBuffer, const Camera& camera, VkPipeline gridPipeline, VkPipelineLayout gridPipelineLayout); void UpdateSharedDescriptors(DescriptorManager& descriptorManager, VkDescriptorSet sharedSet, VkDescriptorSetLayout setLayout, EntityManager& entityManager, VmaAllocator allocator); - + // /// MATERIALS /////////////////////////////////// // @@ -105,9 +134,22 @@ class Renderer std::unordered_map::iterator> m_materialDescriptorCache; const size_t MAX_CACHE_SIZE = 75; - VkDescriptorSet GetOrUpdateDescriptorSet(EntityManager& entityManager, Entity* entity, PipelineConfig* pipelineConfig, DescriptorManager& descriptorManager, VmaAllocator allocator, VulkanDebugUtils& debugUtils, TextureResource* shadowMap, int setIndex); + VkDescriptorSet GetOrUpdateDescriptorSet(EntityManager& entityManager, Entity* entity, PipelineConfig* pipelineConfig, DescriptorManager& descriptorManager, VmaAllocator allocator, VulkanDebugUtils& debugUtils, int setIndex); void UpdateBasicMaterialDescriptors(EntityManager& entityManager, DescriptorManager& descriptorManager, VkDescriptorSet materialSet, Entity* entity, VmaAllocator allocator, int setIndex); - void UpdatePBRMaterialDescriptors(EntityManager& entityManager, DescriptorManager& descriptorManager, VkDescriptorSet descSet, Entity* entity, VmaAllocator allocator, TextureResource* shadowMap, int setIndex); + void UpdatePBRMaterialDescriptors(EntityManager& entityManager, DescriptorManager& descriptorManager, VkDescriptorSet descSet, Entity* entity, VmaAllocator allocator, int setIndex); + + // + /// SHADOWS /////////////////////////////////// + // + ShadowSystem m_shadowSystem; + + // + /// DEPTH TESTING /////////////////////////////////// + // + void CleanupDepthImage(vkb::DispatchTable& disp, VmaAllocator allocator); + VkImage m_depthImage = VK_NULL_HANDLE; + VkImageView m_depthImageView = VK_NULL_HANDLE; + VmaAllocation m_depthImageAllocation; size_t GenerateDescriptorHash(const Entity* entity, int setIndex); }; diff --git a/SlimeOdyssey/include/Scene.h b/SlimeOdyssey/include/Scene.h index c262c1c..e5c7060 100644 --- a/SlimeOdyssey/include/Scene.h +++ b/SlimeOdyssey/include/Scene.h @@ -15,7 +15,7 @@ class Scene virtual int Enter(VulkanContext& vulkanContext, ModelManager& modelManager , ShaderManager& shaderManager, DescriptorManager& descriptorManager) = 0; virtual void Update(float dt, VulkanContext& vulkanContext, const InputManager* inputManager) = 0; - virtual void Render(VulkanContext& vulkanContext, ModelManager& modelManager) = 0; + virtual void Render() = 0; virtual void Exit(VulkanContext& vulkanContext, ModelManager& modelManager) = 0; diff --git a/SlimeOdyssey/include/ShadowSystem.h b/SlimeOdyssey/include/ShadowSystem.h new file mode 100644 index 0000000..601a99a --- /dev/null +++ b/SlimeOdyssey/include/ShadowSystem.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Camera.h" +#include "Light.h" +#include "Model.h" +#include "ModelManager.h" + +class VulkanDebugUtils; +class ModelManager; +class Scene; + +namespace vkb +{ + struct DispatchTable; + struct Swapchain; +} // namespace vkb + +class ShadowSystem +{ +public: + ShadowSystem() = default; + ~ShadowSystem() = default; + + void Initialize(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils); + void Cleanup(vkb::DispatchTable& disp, VmaAllocator allocator); + + bool UpdateShadowMaps(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + Scene* scene, + std::function drawModels, + const std::vector>& lights, + std::shared_ptr camera); + + TextureResource GetShadowMap(const std::shared_ptr light) const; + glm::mat4 GetLightSpaceMatrix(const std::shared_ptr light) const; + + void SetShadowMapResolution(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils, uint32_t width, uint32_t height, bool reconstructImmediately = false); + void ReconstructShadowMaps(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils); + + void SetShadowNearPlane(float near); + void SetShadowFarPlane(float far); + + void SetDirectionalLightDistance(float distance); + float GetDirectionalLightDistance() const; + + float GetShadowMapPixelValue(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, const std::shared_ptr light, int x, int y) const; + + void RenderShadowMapInspector(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, ModelManager& modelManager, VulkanDebugUtils& debugUtils); + +private: + struct ShadowData + { + ImTextureID textureId = 0; + TextureResource shadowMap; + glm::mat4 lightSpaceMatrix; + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VmaAllocation stagingBufferAllocation = VK_NULL_HANDLE; + VkDeviceSize stagingBufferSize = 0; + + // For optimizing the recalculations + glm::vec3 lastCameraPosition; + float frustumRadius; + }; + + std::unordered_map, ShadowData> m_shadowData; + + float m_directionalLightDistance = 100.0f; + + uint32_t m_pendingShadowMapWidth = 0; + uint32_t m_pendingShadowMapHeight = 0; + bool m_shadowMapNeedsReconstruction = false; + + uint32_t m_shadowMapWidth = 4096; + uint32_t m_shadowMapHeight = 4096; + float m_shadowNear = 0.1f; + float m_shadowFar = 120.0f; + + void CreateShadowMap(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils, const std::shared_ptr light); + void CleanupShadowMap(vkb::DispatchTable& disp, VmaAllocator allocator, const std::shared_ptr light); + void GenerateShadowMap(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + Scene* scene, + std::function drawModels, + const std::shared_ptr light, + const std::shared_ptr camera); + void CalculateLightSpaceMatrix(const std::shared_ptr light, const std::shared_ptr camera); + + std::vector CalculateFrustumCorners(float fov, float aspect, float near, float far, const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up, const glm::vec3& right) const; + void CalculateFrustumSphere(const std::vector& frustumCorners, glm::vec3& center, float& radius) const; +}; diff --git a/SlimeOdyssey/include/VulkanContext.h b/SlimeOdyssey/include/VulkanContext.h index 7007664..28f7a0e 100644 --- a/SlimeOdyssey/include/VulkanContext.h +++ b/SlimeOdyssey/include/VulkanContext.h @@ -50,10 +50,6 @@ class VulkanContext int CreateRenderCommandBuffers(); int InitSyncObjects(); int InitImGui(SlimeWindow* window); - int GenerateShadowMap(VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene); - - // Rendering methods - int Draw(VkCommandBuffer& cmd, int imageIndex, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene); // Vulkan core vkb::Instance m_instance; @@ -78,24 +74,6 @@ class VulkanContext std::vector m_imageInFlight; size_t m_currentFrame = 0; - // Shadows - void CreateShadowMapStagingBuffer(); - void DestroyShadowMapStagingBuffer(); - - float GetShadowMapPixelValue(ModelManager& modelManager, int x, int y); - void RenderShadowMapInspector(ModelManager& modelManager); - TextureResource m_shadowMap; - ImTextureID m_shadowMapId; - float m_shadowMapZoom = 1.0f; - VkBuffer m_shadowMapStagingBuffer = VK_NULL_HANDLE; - VmaAllocation m_shadowMapStagingBufferAllocation = VK_NULL_HANDLE; - VkDeviceSize m_shadowMapStagingBufferSize = 0; - - // Depth image might want this elsewhere? - VkImage m_depthImage = VK_NULL_HANDLE; - VkImageView m_depthImageView = VK_NULL_HANDLE; - VmaAllocation m_depthImageAllocation; - Renderer m_renderer; VulkanDebugUtils m_debugUtils; diff --git a/SlimeOdyssey/resources/shaders/basic.frag b/SlimeOdyssey/resources/shaders/basic.frag index bdfb9e7..72f0246 100644 --- a/SlimeOdyssey/resources/shaders/basic.frag +++ b/SlimeOdyssey/resources/shaders/basic.frag @@ -22,11 +22,14 @@ layout(set = 0, binding = 0, scalar) uniform CameraUBO { // Light uniforms (set = 1) layout(set = 1, binding = 0, scalar) uniform LightUBO { - vec3 direction; - float ambientStrength; vec3 color; - float padding1; // Add padding to ensure 16-byte alignment + float padding1; + float ambientStrength; + float specularStrength; + vec2 padding2; mat4 lightSpaceMatrix; + vec3 direction; + float padding3; } light; // Material uniforms (set = 2) @@ -71,6 +74,10 @@ void main() vec3 normalMap = texture(normalMap, TexCoords).rgb * 2.0 - 1.0; vec3 N = normalize(TBN * normalMap); + // Debug normals + // FragColor = vec4(N * 0.5 + 0.5, 1.0); + // return; + // Correct view and light vectors vec3 V = normalize(camera.viewPos - FragPos); vec3 L = normalize(-light.direction); @@ -99,7 +106,7 @@ void main() float shadow = ShadowCalculation(FragPosLightSpace); // Combine lighting - vec3 Lo = (kD * albedo / PI + specular) * light.color * NdotL * (1 - shadow); + vec3 Lo = (kD * albedo / PI + specular) * light.color * NdotL * (1.0 - shadow) * ao; vec3 ambient = light.ambientStrength * albedo * ao; vec3 color = ambient + Lo; @@ -146,9 +153,8 @@ float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) return ggx1 * ggx2; } -vec3 fresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +vec3 fresnelSchlick(float cosTheta, vec3 F0) { + return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0); } float ShadowCalculation(vec4 fragPosLightSpace) diff --git a/SlimeOdyssey/resources/textures/grass/albedo.png b/SlimeOdyssey/resources/textures/grass/albedo.png new file mode 100644 index 0000000..0305255 Binary files /dev/null and b/SlimeOdyssey/resources/textures/grass/albedo.png differ diff --git a/SlimeOdyssey/resources/textures/grass/ao.png b/SlimeOdyssey/resources/textures/grass/ao.png new file mode 100644 index 0000000..1b34177 Binary files /dev/null and b/SlimeOdyssey/resources/textures/grass/ao.png differ diff --git a/SlimeOdyssey/resources/textures/grass/height.png b/SlimeOdyssey/resources/textures/grass/height.png new file mode 100644 index 0000000..147dee9 Binary files /dev/null and b/SlimeOdyssey/resources/textures/grass/height.png differ diff --git a/SlimeOdyssey/resources/textures/grass/normal.png b/SlimeOdyssey/resources/textures/grass/normal.png new file mode 100644 index 0000000..220710a Binary files /dev/null and b/SlimeOdyssey/resources/textures/grass/normal.png differ diff --git a/SlimeOdyssey/resources/textures/grass/roughness.png b/SlimeOdyssey/resources/textures/grass/roughness.png new file mode 100644 index 0000000..3889b0a Binary files /dev/null and b/SlimeOdyssey/resources/textures/grass/roughness.png differ diff --git a/SlimeOdyssey/src/Camera.cpp b/SlimeOdyssey/src/Camera.cpp index d362bb0..69019ea 100644 --- a/SlimeOdyssey/src/Camera.cpp +++ b/SlimeOdyssey/src/Camera.cpp @@ -126,6 +126,21 @@ void Camera::ImGuiDebug() ImGui::DragFloat("Pitch", &m_pitch, 0.1f); } +glm::vec3 Camera::GetForward() const +{ + return m_front; +} + +float Camera::GetFOV() const +{ + return m_fov; +} + +float Camera::GetAspectRatio() const +{ + return m_aspect; +} + void Camera::UpdateCameraVectors() { glm::vec3 front; @@ -134,3 +149,23 @@ void Camera::UpdateCameraVectors() front.z = std::sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch)); m_front = glm::normalize(front); } + +float Camera::GetNearZ() const +{ + return m_nearZ; +} + +float Camera::GetFarZ() const +{ + return m_farZ; +} + +glm::vec3 Camera::GetUp() const +{ + return m_up; +} + +glm::vec3 Camera::GetRight() const +{ + return glm::normalize(glm::cross(m_front, m_up)); +} diff --git a/SlimeOdyssey/src/EntityManager.cpp b/SlimeOdyssey/src/EntityManager.cpp index b4ecfee..c0ba079 100644 --- a/SlimeOdyssey/src/EntityManager.cpp +++ b/SlimeOdyssey/src/EntityManager.cpp @@ -1,19 +1,18 @@ #include "EntityManager.h" -#include -#include -#include - -#include "Entity.h" +#include #include +#include +#include #include +#include #include -#include #include -#include +#include +#include +#include "Entity.h" #include "imgui.h" -#include // Update entity's component mask void EntityManager::UpdateEntityMask(const Entity& entity) @@ -68,6 +67,11 @@ void EntityManager::RemoveEntity(const Entity& entity) } } +void EntityManager::RemoveEntity(std::shared_ptr entity) +{ + RemoveEntity(*entity); +} + // Get all entities const std::vector>& EntityManager::GetEntities() const { @@ -111,45 +115,45 @@ void EntityManager::OnEntityComponentChanged(const Entity& entity) // Shows a window of all entities and their components void EntityManager::ImGuiDebug() { - ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::Begin("Entity Manager", nullptr, ImGuiWindowFlags_NoScrollbar); - - // Search bar and controls - static char searchBuffer[256] = ""; - ImGui::InputText("Search Entities", searchBuffer, IM_ARRAYSIZE(searchBuffer)); - std::string searchStr = searchBuffer; - ImGui::Text("Total Entities: %zu", m_entities.size()); - ImGui::Separator(); - - // Calculate available height for split view - float availableHeight = ImGui::GetContentRegionAvail().y; - - // Splitter - static float splitHeight = availableHeight * 0.5f; // Initial split at 50% - - ImGui::BeginChild("TopRegion", ImVec2(0, splitHeight), true); - RenderEntityTree(searchStr); - ImGui::EndChild(); - - // Splitter bar - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); - - ImGui::Button("##splitter", ImVec2(-1, 5)); - if (ImGui::IsItemActive()) - { - splitHeight += ImGui::GetIO().MouseDelta.y; - splitHeight = glm::clamp(splitHeight, availableHeight * 0.1f, availableHeight * 0.9f); - } - - ImGui::PopStyleColor(3); - - ImGui::BeginChild("BottomRegion", ImVec2(0, 0), true); - RenderComponentDetails(); - ImGui::EndChild(); - - ImGui::End(); + ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(FLT_MAX, FLT_MAX)); + ImGui::Begin("Entity Manager", nullptr, ImGuiWindowFlags_NoScrollbar); + + // Search bar and controls + static char searchBuffer[256] = ""; + ImGui::InputText("Search Entities", searchBuffer, IM_ARRAYSIZE(searchBuffer)); + std::string searchStr = searchBuffer; + ImGui::Text("Total Entities: %zu", m_entities.size()); + ImGui::Separator(); + + // Calculate available height for split view + float availableHeight = ImGui::GetContentRegionAvail().y; + + // Splitter + static float splitHeight = availableHeight * 0.5f; // Initial split at 50% + + ImGui::BeginChild("TopRegion", ImVec2(0, splitHeight), true); + RenderEntityTree(searchStr); + ImGui::EndChild(); + + // Splitter bar + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.9f, 0.9f, 0.9f, 1.0f)); + + ImGui::Button("##splitter", ImVec2(-1, 5)); + if (ImGui::IsItemActive()) + { + splitHeight += ImGui::GetIO().MouseDelta.y; + splitHeight = glm::clamp(splitHeight, availableHeight * 0.1f, availableHeight * 0.9f); + } + + ImGui::PopStyleColor(3); + + ImGui::BeginChild("BottomRegion", ImVec2(0, 0), true); + RenderComponentDetails(); + ImGui::EndChild(); + + ImGui::End(); } void EntityManager::RenderEntityTree(const std::string& searchStr) @@ -195,25 +199,25 @@ void EntityManager::RenderEntityTree(const std::string& searchStr) void EntityManager::RenderComponentDetails() { - if (m_selectedEntity) - { - // Component Count - ImGui::Text("Component Count: %zu", m_selectedEntity->GetComponentCount()); - ImGui::Separator(); - for (const auto& [type, component] : m_selectedEntity->GetComponents()) - { - ImGui::PushID(type.name()); - if (ImGui::CollapsingHeader(type.name(), ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Indent(); - component->ImGuiDebug(); - ImGui::Unindent(); - } - ImGui::PopID(); - } - } - else - { - ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Select an entity to view its components"); - } -} \ No newline at end of file + if (m_selectedEntity) + { + // Component Count + ImGui::Text("Component Count: %zu", m_selectedEntity->GetComponentCount()); + ImGui::Separator(); + for (const auto& [type, component]: m_selectedEntity->GetComponents()) + { + ImGui::PushID(type.name()); + if (ImGui::CollapsingHeader(type.name(), ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(); + component->ImGuiDebug(); + ImGui::Unindent(); + } + ImGui::PopID(); + } + } + else + { + ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Select an entity to view its components"); + } +} diff --git a/SlimeOdyssey/src/Light.cpp b/SlimeOdyssey/src/Light.cpp index b6085d4..0599bd9 100644 --- a/SlimeOdyssey/src/Light.cpp +++ b/SlimeOdyssey/src/Light.cpp @@ -1,36 +1,155 @@ #include "Light.h" + #include "imgui.h" -void PointLightObject::ImGuiDebug() +void PointLight::ImGuiDebug() { - ImGui::Text("Point Light"); - ImGui::DragFloat3("Position", &light.pos.x, 0.1f); - ImGui::ColorEdit3("Colour", &light.colour.x); - ImGui::DragFloat3("View", &light.direction.x, 0.1f); - ImGui::DragFloat("Ambient Strength", &light.ambientStrength, 0.1f); - ImGui::DragFloat("Specular Strength", &light.specularStrength, 0.1f); - ImGui::DragFloat("Shininess", &light.shininess, 0.1f); + ImGui::Text("Point Light"); + + glm::vec3 position = GetPosition(); + if (ImGui::DragFloat3("Position", &position.x, 0.1f)) + { + SetPosition(position); + } + + LightData data = GetData(); + + if (ImGui::ColorEdit3("Colour", &data.color.x)) + { + SetData(data); + } - ImGui::Spacing(); - ImGui::Text("Light Space Matrix"); - for (int i = 0; i < 4; i++) - { - ImGui::Text("%f %f %f %f", light.lightSpaceMatrix[i][0], light.lightSpaceMatrix[i][1], light.lightSpaceMatrix[i][2], light.lightSpaceMatrix[i][3]); - } + if (ImGui::DragFloat("Ambient Strength", &data.ambientStrength, 0.01f, 0.0f, 1.0f)) + { + SetData(data); + } + + if (ImGui::DragFloat("Specular Strength", &data.specularStrength, 0.01f, 0.0f, 1.0f)) + { + SetData(data); + } + + float radius = GetRadius(); + if (ImGui::DragFloat("Radius", &radius, 0.1f, 0.1f, 1000.0f)) + { + SetRadius(radius); + } + + ImGui::Spacing(); + ImGui::Text("Light Space Matrix"); + for (int i = 0; i < 4; i++) + { + ImGui::Text("%f %f %f %f", data.lightSpaceMatrix[i][0], data.lightSpaceMatrix[i][1], data.lightSpaceMatrix[i][2], data.lightSpaceMatrix[i][3]); + } } -void DirectionalLightObject::ImGuiDebug() +void DirectionalLight::ImGuiDebug() { - ImGui::Text("Directional Light"); + ImGui::Text("Directional Light"); - ImGui::DragFloat3("Direction", &light.direction.x, 0.1f); - ImGui::ColorEdit3("Colour", &light.color.x); - ImGui::DragFloat("Ambient Strength", &light.ambientStrength, 0.1f); + glm::vec3 direction = GetDirection(); + if (ImGui::DragFloat3("Direction", &direction.x, 0.01f)) + { + SetDirection(glm::normalize(direction)); + } + + LightData data = GetData(); + + if (ImGui::ColorEdit3("Colour", &data.color.x)) + { + SetData(data); + } + + if (ImGui::DragFloat("Ambient Strength", &data.ambientStrength, 0.01f, 0.0f, 1.0f)) + { + SetData(data); + } ImGui::Spacing(); - ImGui::Text("Light Space Matrix"); - for (int i = 0; i < 4; i++) - { - ImGui::Text("%f %f %f %f", light.lightSpaceMatrix[i][0], light.lightSpaceMatrix[i][1], light.lightSpaceMatrix[i][2], light.lightSpaceMatrix[i][3]); - } + ImGui::Text("Light Space Matrix"); + for (int i = 0; i < 4; i++) + { + ImGui::Text("%f %f %f %f", data.lightSpaceMatrix[i][0], data.lightSpaceMatrix[i][1], data.lightSpaceMatrix[i][2], data.lightSpaceMatrix[i][3]); + } +} + +DirectionalLight::DirectionalLight() +{ + m_lightType = LightType::Directional; + m_data.ambientStrength = 0.075f; +} + +glm::vec3 DirectionalLight::GetDirection() const +{ + return m_direction; +} + +void DirectionalLight::SetDirection(const glm::vec3& direction) +{ + m_direction = direction; +} + +const LightData& DirectionalLight::GetData() const +{ + return m_data; +} + +void DirectionalLight::SetData(const LightData& data) +{ + m_data = data; +} + +DirectionalLight::BindingData DirectionalLight::GetBindingData() +{ + return { m_data, m_direction, m_padding3 }; +} + +size_t DirectionalLight::GetBindingDataSize() const +{ + return sizeof(BindingData); +} + +PointLight::PointLight() +{ + m_lightType = LightType::Point; +} + +glm::vec3 PointLight::GetPosition() const +{ + return m_position; +} + +void PointLight::SetPosition(const glm::vec3& position) +{ + m_position = position; +} + +float PointLight::GetRadius() const +{ + return m_radius; +} + +void PointLight::SetRadius(float radius) +{ + m_radius = radius; +} + +const LightData& PointLight::GetData() const +{ + return m_data; +} + +void PointLight::SetData(const LightData& data) +{ + m_data = data; +} + +PointLight::BindingData PointLight::GetBindingData() +{ + return { m_data, m_position, m_radius }; +} + +size_t PointLight::GetBindingDataSize() const +{ + return sizeof(BindingData); } diff --git a/SlimeOdyssey/src/ModelManager.cpp b/SlimeOdyssey/src/ModelManager.cpp index bad70cf..0ec2b25 100644 --- a/SlimeOdyssey/src/ModelManager.cpp +++ b/SlimeOdyssey/src/ModelManager.cpp @@ -953,10 +953,11 @@ ModelResource* ModelManager::CreatePlane(VmaAllocator allocator, float size, int float z = -size / 2 + j * step; Vertex vertex; vertex.pos = glm::vec3(x, 0.0f, z); - vertex.normal = glm::vec3(0.0f, -1.0f, 0.0f); // Inverted normal - vertex.texCoord = glm::vec2(static_cast(i) / divisions, static_cast(j) / divisions); + vertex.normal = glm::vec3(0.0f, 1.0f, -1.0f); + // Calculate UV coordinates to be 0-1 for each quad, but uniform direction + vertex.texCoord = glm::vec2(static_cast(i % 2), static_cast(1 - (j % 2))); vertex.tangent = glm::vec3(1.0f, 0.0f, 0.0f); - vertex.bitangent = glm::vec3(0.0f, 0.0f, -1.0f); // Inverted bitangent + vertex.bitangent = glm::vec3(0.0f, 0.0f, -1.0f); model.vertices.push_back(vertex); } } @@ -969,11 +970,11 @@ ModelResource* ModelManager::CreatePlane(VmaAllocator allocator, float size, int int topRight = topLeft + 1; int bottomLeft = (i + 1) * (divisions + 1) + j; int bottomRight = bottomLeft + 1; - // First triangle (changed winding order) + // First triangle model.indices.push_back(topLeft); model.indices.push_back(topRight); model.indices.push_back(bottomLeft); - // Second triangle (changed winding order) + // Second triangle model.indices.push_back(topRight); model.indices.push_back(bottomRight); model.indices.push_back(bottomLeft); @@ -1078,7 +1079,6 @@ ModelResource* ModelManager::CreateCube(VmaAllocator allocator, float size) return &m_modelResources[name]; } - ModelResource* ModelManager::CreateSphere(VmaAllocator allocator, float radius, int segments, int rings) { std::string name = "debug_sphere" + std::to_string(radius) + "_" + std::to_string(segments) + "_" + std::to_string(rings); diff --git a/SlimeOdyssey/src/Renderer.cpp b/SlimeOdyssey/src/Renderer.cpp index d635a5a..92fdaa8 100644 --- a/SlimeOdyssey/src/Renderer.cpp +++ b/SlimeOdyssey/src/Renderer.cpp @@ -1,6 +1,9 @@ #include "Renderer.h" +#include +#include #include +#include #include #include "DescriptorManager.h" @@ -11,12 +14,157 @@ #include "VulkanContext.h" #include "VulkanUtil.h" +void Renderer::SetUp(vkb::DispatchTable& disp, VmaAllocator allocator, vkb::Swapchain swapchain, VulkanDebugUtils& debugUtils) +{ + CreateDepthImage(disp, allocator, swapchain, debugUtils); +} + +void Renderer::CleanUp(vkb::DispatchTable& disp, VmaAllocator allocator) +{ + m_shadowSystem.Cleanup(disp, allocator); + CleanupDepthImage(disp, allocator); +} + +int Renderer::Draw(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + DescriptorManager& descriptorManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + vkb::Swapchain swapchain, + std::vector& swapchainImages, + std::vector& swapchainImageViews, + uint32_t imageIndex, + Scene* scene) +{ + if (SlimeUtil::BeginCommandBuffer(disp, cmd) != 0) + return -1; + + // Generate shadow map + std::vector> lights; + scene->m_entityManager.ForEachEntityWith( + [&lights](Entity& entity) + { + if (auto light = entity.GetComponentShrPtr()) + { + lights.push_back(std::move(light)); + } + }); + + scene->m_entityManager.ForEachEntityWith( + [&lights](Entity& entity) + { + if (auto light = entity.GetComponentShrPtr()) + { + lights.push_back(std::move(light)); + } + }); + + std::shared_ptr camera = scene->m_entityManager.GetEntityByName("MainCamera")->GetComponentShrPtr(); + + std::function DrawModelsForShadowMap = std::bind(&Renderer::DrawModelsForShadowMap, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, scene); + + if (m_shadowSystem.UpdateShadowMaps(disp, cmd, modelManager, allocator, commandPool, graphicsQueue, debugUtils, scene, DrawModelsForShadowMap, lights, camera)) + { + m_forceInvalidateDecriptorSets = true; + } + + SetupViewportAndScissor(swapchain, disp, cmd); + SlimeUtil::SetupDepthTestingAndLineWidth(disp, cmd); + + // Transition color image to color attachment optimal + modelManager.TransitionImageLayout(disp, graphicsQueue, commandPool, swapchainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // Transition depth image to depth attachment optimal + modelManager.TransitionImageLayout(disp, graphicsQueue, commandPool, m_depthImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); + + VkRenderingAttachmentInfo colorAttachmentInfo = {}; + colorAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + colorAttachmentInfo.imageView = swapchainImageViews[imageIndex]; + colorAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + colorAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachmentInfo.clearValue = { + .color = {m_clearColour.r, m_clearColour.g, m_clearColour.b, m_clearColour.a} + }; + + VkRenderingAttachmentInfo depthAttachmentInfo = {}; + depthAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachmentInfo.imageView = m_depthImageView; + depthAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachmentInfo.clearValue.depthStencil.depth = 1.f; + + VkRenderingInfo renderingInfo = {}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { + .offset = {0, 0}, + .extent = swapchain.extent + }; + renderingInfo.layerCount = 1; + renderingInfo.colorAttachmentCount = 1; + renderingInfo.pColorAttachments = &colorAttachmentInfo; + renderingInfo.pDepthAttachment = &depthAttachmentInfo; + + disp.cmdBeginRendering(cmd, &renderingInfo); + + // Start the Dear ImGui frame + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Create a full screen docking space for ImGui + ImGuiDockNodeFlags dockFlags = ImGuiDockNodeFlags_PassthruCentralNode; + ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()->ID, ImGui::GetMainViewport(), dockFlags); + + if (scene) + { + DrawModels(disp, cmd, modelManager, descriptorManager, allocator, commandPool, graphicsQueue, debugUtils, scene); + + debugUtils.BeginDebugMarker(cmd, "Draw ImGui", debugUtil_BindDescriptorSetColour); + scene->Render(); + } + + DrawImguiDebugger(disp, allocator, commandPool, graphicsQueue, modelManager, debugUtils); + + ImGui::Render(); + ImDrawData* drawData = ImGui::GetDrawData(); + const bool isMinimized = (drawData->DisplaySize.x <= 0.0f || drawData->DisplaySize.y <= 0.0f); + if (!isMinimized) + { + // Record dear imgui primitives into command buffer + ImGui_ImplVulkan_RenderDrawData(drawData, cmd); + } + + debugUtils.EndDebugMarker(cmd); + + disp.cmdEndRendering(cmd); + + // Transition color image to present src layout + modelManager.TransitionImageLayout(disp, graphicsQueue, commandPool, swapchainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + if (SlimeUtil::EndCommandBuffer(disp, cmd) != 0) + return -1; + + // Handle multi-viewport rendering here, outside of the main command buffer + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } + + return 0; +} + void Renderer::SetupViewportAndScissor(vkb::Swapchain swapchain, vkb::DispatchTable disp, VkCommandBuffer& cmd) { VkViewport viewport = { .x = 0.0f, .y = 0.0f, .width = static_cast(swapchain.extent.width), .height = static_cast(swapchain.extent.height), .minDepth = 0.0f, .maxDepth = 1.0f }; VkRect2D scissor = { - .offset = { 0, 0 }, + .offset = {0, 0}, .extent = swapchain.extent }; @@ -24,33 +172,6 @@ void Renderer::SetupViewportAndScissor(vkb::Swapchain swapchain, vkb::DispatchTa disp.cmdSetScissor(cmd, 0, 1, &scissor); } -void calculateDirectionalLightMatrix(DirectionalLight& dirLight, Camera& camera) -{ - // Near and far planes - float nearPlane = 50.0f; - float farPlane = 1000.0f; - - // Calculate the scene center and radius - glm::vec3 sceneCenter = camera.GetPosition(); - float sceneRadius = 10.0f; - - // Calculate the light position - glm::vec3 lightPos = -dirLight.direction * sceneRadius * 2.0f; - - // Create the view matrix - glm::mat4 lightView = glm::lookAt(lightPos, sceneCenter, glm::vec3(0.0f, 1.0f, 0.0f)); - - // Calculate orthographic projection bounds - float aspect = 1; - float orthoSize = sceneRadius * 2.0f; - - // Create the orthographic projection matrix - glm::mat4 lightProjection = glm::ortho(-orthoSize * aspect, orthoSize * aspect, -orthoSize, orthoSize, nearPlane, farPlane + sceneRadius); - - // Combine view and projection matrices - dirLight.lightSpaceMatrix = lightProjection * lightView; -} - void Renderer::DrawModelsForShadowMap(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, VkCommandBuffer& cmd, ModelManager& modelManager, Scene* scene) { EntityManager& entityManager = scene->m_entityManager; @@ -65,9 +186,8 @@ void Renderer::DrawModelsForShadowMap(vkb::DispatchTable disp, VulkanDebugUtils& return; } - DirectionalLight& light = lightEntity->GetComponent().light; + DirectionalLight& light = lightEntity->GetComponent(); Camera& camera = entityManager.GetEntityByName("MainCamera")->GetComponent(); - calculateDirectionalLightMatrix(light, camera); // Bind the shadow map pipeline PipelineConfig* shadowMapPipeline = BindPipeline(disp, cmd, modelManager, "ShadowMap", debugUtils); @@ -100,7 +220,7 @@ void Renderer::DrawModelsForShadowMap(vkb::DispatchTable disp, VulkanDebugUtils& glm::mat4 modelMatrix; } pushConstants; - pushConstants.lightSpaceMatrix = light.lightSpaceMatrix; + pushConstants.lightSpaceMatrix = light.GetLightSpaceMatrix(); pushConstants.modelMatrix = transform.GetModelMatrix(); disp.cmdPushConstants(cmd, shadowMapPipeline->pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowMapPushConstants), &pushConstants); @@ -113,7 +233,7 @@ void Renderer::DrawModelsForShadowMap(vkb::DispatchTable disp, VulkanDebugUtils& } } -void Renderer::DrawModels(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, VmaAllocator allocator, VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene, TextureResource* shadowMap) +void Renderer::DrawModels(vkb::DispatchTable& disp, VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, VulkanDebugUtils& debugUtils, Scene* scene) { debugUtils.BeginDebugMarker(cmd, "Draw Models", debugUtil_BeginColour); @@ -177,7 +297,7 @@ void Renderer::DrawModels(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, // Bind descriptor sets for (size_t i = 1; i < pipelineConfig->descriptorSetLayouts.size(); i++) { - VkDescriptorSet currentDescriptorSet = GetOrUpdateDescriptorSet(entityManager, entity.get(), pipelineConfig, descriptorManager, allocator, debugUtils, shadowMap, i); + VkDescriptorSet currentDescriptorSet = GetOrUpdateDescriptorSet(entityManager, entity.get(), pipelineConfig, descriptorManager, allocator, debugUtils, i); disp.cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineConfig->pipelineLayout, i, 1, ¤tDescriptorSet, 0, nullptr); } @@ -200,6 +320,19 @@ void Renderer::DrawModels(vkb::DispatchTable disp, VulkanDebugUtils& debugUtils, debugUtils.EndDebugMarker(cmd); } +void Renderer::DrawImguiDebugger(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, ModelManager& modelManager, VulkanDebugUtils& debugUtils) +{ + m_shadowSystem.RenderShadowMapInspector(disp, allocator, commandPool, graphicsQueue, modelManager, debugUtils); + + // Render the ImGui debugger + ImGui::Begin("Renderer Debugger"); + + // clear colour + ImGui::ColorEdit4("Clear Colour", &m_clearColour.r); + + ImGui::End(); +} + void Renderer::UpdateCommonBuffers(VulkanDebugUtils& debugUtils, VmaAllocator allocator, VkCommandBuffer& cmd, Scene* scene) { debugUtils.BeginDebugMarker(cmd, "Update Common Buffers", debugUtil_UpdateLightBufferColour); @@ -224,12 +357,14 @@ void Renderer::UpdateLightBuffer(EntityManager& entityManager, VmaAllocator allo return; } - DirectionalLightObject& light = lightEntity->GetComponent(); + DirectionalLight& light = lightEntity->GetComponent(); if (light.buffer == VK_NULL_HANDLE) { - SlimeUtil::CreateBuffer("Light Buffer", allocator, sizeof(light.light), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, light.buffer, light.allocation); + SlimeUtil::CreateBuffer("Light Buffer", allocator, light.GetBindingDataSize(), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, light.buffer, light.allocation); } - SlimeUtil::CopyStructToBuffer(light.light, allocator, light.allocation); + + auto bindingData = light.GetBindingData(); + SlimeUtil::CopyStructToBuffer(bindingData, allocator, light.allocation); } void Renderer::UpdateCameraBuffer(EntityManager& entityManager, VmaAllocator allocator) @@ -293,10 +428,23 @@ void Renderer::UpdateSharedDescriptors(DescriptorManager& descriptorManager, VkD // /// MATERIALS /////////////////////////////////// // -VkDescriptorSet Renderer::GetOrUpdateDescriptorSet(EntityManager& entityManager, Entity* entity, PipelineConfig* pipelineConfig, DescriptorManager& descriptorManager, VmaAllocator allocator, VulkanDebugUtils& debugUtils, TextureResource* shadowMap, int setIndex) +VkDescriptorSet Renderer::GetOrUpdateDescriptorSet(EntityManager& entityManager, Entity* entity, PipelineConfig* pipelineConfig, DescriptorManager& descriptorManager, VmaAllocator allocator, VulkanDebugUtils& debugUtils, int setIndex) { size_t descriptorHash = GenerateDescriptorHash(entity, setIndex); + if (m_forceInvalidateDecriptorSets) + { + m_forceInvalidateDecriptorSets = false; + + for (auto& entry: m_materialDescriptorCache) + { + descriptorManager.FreeDescriptorSet(entry.second->descriptorSet); + } + + m_materialDescriptorCache.clear(); + m_lruList.clear(); + } + auto it = m_materialDescriptorCache.find(descriptorHash); if (it != m_materialDescriptorCache.end()) { @@ -316,7 +464,7 @@ VkDescriptorSet Renderer::GetOrUpdateDescriptorSet(EntityManager& entityManager, } else if (entity->HasComponent()) { - UpdatePBRMaterialDescriptors(entityManager, descriptorManager, newDescriptorSet, entity, allocator, shadowMap, setIndex); + UpdatePBRMaterialDescriptors(entityManager, descriptorManager, newDescriptorSet, entity, allocator, setIndex); } // If cache is full, remove the least recently used item @@ -345,19 +493,19 @@ void Renderer::UpdateBasicMaterialDescriptors(EntityManager& entityManager, Desc } } -void Renderer::UpdatePBRMaterialDescriptors(EntityManager& entityManager, DescriptorManager& descriptorManager, VkDescriptorSet descSet, Entity* entity, VmaAllocator allocator, TextureResource* shadowMap, int setIndex) +void Renderer::UpdatePBRMaterialDescriptors(EntityManager& entityManager, DescriptorManager& descriptorManager, VkDescriptorSet descSet, Entity* entity, VmaAllocator allocator, int setIndex) { - if (setIndex == 1) // Light + auto lightEntity = entityManager.GetEntityByName("Light"); + if (!lightEntity) { - auto lightEntity = entityManager.GetEntityByName("Light"); - if (!lightEntity) - { - spdlog::critical("Light entity not found in UpdatePBRMaterialDescriptors."); - return; - } + spdlog::critical("Light entity not found in UpdatePBRMaterialDescriptors."); + return; + } - DirectionalLightObject& light = lightEntity->GetComponent(); - descriptorManager.BindBuffer(descSet, 0, light.buffer, 0, sizeof(light.light)); + auto light = lightEntity->GetComponentShrPtr(); + if (setIndex == 1) // Light + { + descriptorManager.BindBuffer(descSet, 0, light->buffer, 0, light->GetBindingDataSize()); } else if (setIndex == 2) // Material { @@ -367,7 +515,10 @@ void Renderer::UpdatePBRMaterialDescriptors(EntityManager& entityManager, Descri SlimeUtil::CopyStructToBuffer(materialResource.config, allocator, materialResource.configAllocation); descriptorManager.BindBuffer(descSet, 0, materialResource.configBuffer, 0, sizeof(PBRMaterialResource::Config)); - descriptorManager.BindImage(descSet, 1, shadowMap->imageView, shadowMap->sampler); + // TODO add support for multiple shadow maps + auto shadowMap = m_shadowSystem.GetShadowMap(light); + + descriptorManager.BindImage(descSet, 1, shadowMap.imageView, shadowMap.sampler); if (materialResource.albedoTex) descriptorManager.BindImage(descSet, 2, materialResource.albedoTex->imageView, materialResource.albedoTex->sampler); @@ -382,6 +533,64 @@ void Renderer::UpdatePBRMaterialDescriptors(EntityManager& entityManager, Descri } } +// +/// DEPTH TESTING /////////////////////////////////// +// +void Renderer::CreateDepthImage(vkb::DispatchTable& disp, VmaAllocator allocator, vkb::Swapchain swapchain, VulkanDebugUtils& debugUtils) +{ + spdlog::debug("Creating depth image"); + + // Clean up old depth image and image view + if (m_depthImage) + { + CleanupDepthImage(disp, allocator); + } + + // Create the depth image + VkFormat depthFormat = VK_FORMAT_D32_SFLOAT; // Or VK_FORMAT_D32_SFLOAT_S8_UINT if you need stencil + VkImageCreateInfo depthImageInfo = {}; + depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + depthImageInfo.imageType = VK_IMAGE_TYPE_2D; + depthImageInfo.extent.width = swapchain.extent.width; + depthImageInfo.extent.height = swapchain.extent.height; + depthImageInfo.extent.depth = 1; + depthImageInfo.mipLevels = 1; + depthImageInfo.arrayLayers = 1; + depthImageInfo.format = depthFormat; + depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo depthAllocInfo = {}; + depthAllocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VK_CHECK(vmaCreateImage(allocator, &depthImageInfo, &depthAllocInfo, &m_depthImage, &m_depthImageAllocation, nullptr)); + debugUtils.SetObjectName(m_depthImage, "DepthImage"); + + // Create the depth image view + VkImageViewCreateInfo depthImageViewInfo = {}; + depthImageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + depthImageViewInfo.image = m_depthImage; + depthImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthImageViewInfo.format = depthFormat; + depthImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + depthImageViewInfo.subresourceRange.baseMipLevel = 0; + depthImageViewInfo.subresourceRange.levelCount = 1; + depthImageViewInfo.subresourceRange.baseArrayLayer = 0; + depthImageViewInfo.subresourceRange.layerCount = 1; + + VK_CHECK(disp.createImageView(&depthImageViewInfo, nullptr, &m_depthImageView)); + debugUtils.SetObjectName(m_depthImageView, "DepthImageView"); +} + +void Renderer::CleanupDepthImage(vkb::DispatchTable& disp, VmaAllocator allocator) +{ + vmaDestroyImage(allocator, m_depthImage, m_depthImageAllocation); + disp.destroyImageView(m_depthImageView, nullptr); +} + // Helper function to generate a hash for a material // Helper function to generate a hash for all descriptor types inline size_t Renderer::GenerateDescriptorHash(const Entity* entity, int setIndex) diff --git a/SlimeOdyssey/src/ShadowSystem.cpp b/SlimeOdyssey/src/ShadowSystem.cpp new file mode 100644 index 0000000..874009c --- /dev/null +++ b/SlimeOdyssey/src/ShadowSystem.cpp @@ -0,0 +1,664 @@ +#include "ShadowSystem.h" + +#include +#include +#include + +#include "ModelManager.h" +#include "VulkanDebugUtils.h" +#include "VulkanUtil.h" + +void ShadowSystem::Initialize(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils) +{ + // Initialization code (if needed) +} + +void ShadowSystem::Cleanup(vkb::DispatchTable& disp, VmaAllocator allocator) +{ + for (auto& [light, shadowData]: m_shadowData) + { + CleanupShadowMap(disp, allocator, light); + } + m_shadowData.clear(); +} + +bool ShadowSystem::UpdateShadowMaps(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + Scene* scene, + std::function drawModels, + const std::vector>& lights, + std::shared_ptr camera) +{ + bool invalidateDescriptors = false; + + if (m_shadowMapNeedsReconstruction) + { + invalidateDescriptors = true; + disp.deviceWaitIdle(); + ReconstructShadowMaps(disp, allocator, debugUtils); + } + + for (const auto light: lights) + { + if (m_shadowData.find(light) == m_shadowData.end()) + { + CreateShadowMap(disp, allocator, debugUtils, light); + } + CalculateLightSpaceMatrix(light, camera); + GenerateShadowMap(disp, cmd, modelManager, allocator, commandPool, graphicsQueue, debugUtils, scene, drawModels, light, camera); + } + + return invalidateDescriptors; +} + +TextureResource ShadowSystem::GetShadowMap(const std::shared_ptr light) const +{ + auto it = m_shadowData.find(light); + if (it != m_shadowData.end()) + { + return it->second.shadowMap; + } + return TextureResource(); // Return an empty TextureResource if not found +} + +glm::mat4 ShadowSystem::GetLightSpaceMatrix(const std::shared_ptr light) const +{ + auto it = m_shadowData.find(light); + if (it != m_shadowData.end()) + { + return it->second.lightSpaceMatrix; + } + return glm::mat4(1.0f); // Return identity matrix if not found +} + +void ShadowSystem::SetShadowMapResolution(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils, uint32_t width, uint32_t height, bool reconstructImmediately) +{ + if (width != m_shadowMapWidth || height != m_shadowMapHeight) + { + if (reconstructImmediately) + { + m_shadowMapWidth = width; + m_shadowMapHeight = height; + ReconstructShadowMaps(disp, allocator, debugUtils); + } + else + { + m_pendingShadowMapWidth = width; + m_pendingShadowMapHeight = height; + m_shadowMapNeedsReconstruction = true; + } + } +} + +void ShadowSystem::ReconstructShadowMaps(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils) +{ + if (m_shadowMapNeedsReconstruction) + { + m_shadowMapWidth = m_pendingShadowMapWidth; + m_shadowMapHeight = m_pendingShadowMapHeight; + m_shadowMapNeedsReconstruction = false; + } + + // Reconstruct all shadow maps + for (auto& [light, shadowData]: m_shadowData) + { + // Clean up existing shadow map + CleanupShadowMap(disp, allocator, light); + + // clear the imgui id + if (shadowData.textureId != 0) + { + ImGui_ImplVulkan_RemoveTexture((VkDescriptorSet) shadowData.textureId); + shadowData.textureId = 0; + } + + // Create new shadow map with updated size + CreateShadowMap(disp, allocator, debugUtils, light); + } +} + +void ShadowSystem::SetShadowNearPlane(float near) +{ + m_shadowNear = near; +} + +void ShadowSystem::SetShadowFarPlane(float far) +{ + m_shadowFar = far; +} + +void ShadowSystem::SetDirectionalLightDistance(float distance) +{ + m_directionalLightDistance = distance; + + for (auto& [light, shadowData]: m_shadowData) + { + if (light->GetType() == LightType::Directional) + { + shadowData.lastCameraPosition = glm::vec3(std::numeric_limits::max()); + } + } +} + +inline float ShadowSystem::GetDirectionalLightDistance() const +{ + return m_directionalLightDistance; +} + +float ShadowSystem::GetShadowMapPixelValue(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, const std::shared_ptr light, int x, int y) const +{ + auto it = m_shadowData.find(light); + if (it == m_shadowData.end()) + { + return 1.0f; // Return max depth if shadow map not found + } + + const auto& shadowData = it->second; + + // Ensure x and y are within bounds + x = std::clamp(x, 0, static_cast(m_shadowMapWidth) - 1); + y = std::clamp(y, 0, static_cast(m_shadowMapHeight) - 1); + + // Create command buffer for copy operation + VkCommandBuffer commandBuffer = SlimeUtil::BeginSingleTimeCommands(disp, commandPool); + + // Transition shadow map image layout for transfer + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = shadowData.shadowMap.image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + + disp.cmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + // Copy image to buffer + VkBufferImageCopy region{}; + region.bufferOffset = 0; + region.bufferRowLength = 0; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + region.imageSubresource.mipLevel = 0; + region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.layerCount = 1; + region.imageOffset = { x, y, 0 }; + region.imageExtent = { 1, 1, 1 }; + + disp.cmdCopyImageToBuffer(commandBuffer, shadowData.shadowMap.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, shadowData.stagingBuffer, 1, ®ion); + + // Transition shadow map image layout back + barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + + disp.cmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + SlimeUtil::EndSingleTimeCommands(disp, graphicsQueue, commandPool, commandBuffer); + + // Read data from staging buffer + float pixelValue; + void* data; + vmaMapMemory(allocator, shadowData.stagingBufferAllocation, &data); + memcpy(&pixelValue, data, sizeof(float)); + vmaUnmapMemory(allocator, shadowData.stagingBufferAllocation); + + return pixelValue; +} + +void ShadowSystem::RenderShadowMapInspector(vkb::DispatchTable& disp, VmaAllocator allocator, VkCommandPool commandPool, VkQueue graphicsQueue, ModelManager& modelManager, VulkanDebugUtils& debugUtils) +{ + ImGui::Begin("Shadow Map Inspector", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + + // Shadow Map Selection + static int currentItem = 0; + if (ImGui::BeginCombo("Select Shadow Map", std::to_string(currentItem).c_str())) + { + for (int i = 0; i < m_shadowData.size(); i++) + { + bool isSelected = (currentItem == i); + if (ImGui::Selectable(std::to_string(i).c_str(), isSelected)) + currentItem = i; + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + // Get the selected shadow map + auto it = m_shadowData.begin(); + std::advance(it, currentItem); + auto light = it->first; + auto& lightData = it->second; + + ImGui::Text("Light Type: %s", light->GetType() == LightType::Directional ? "Directional" : "Point"); + + ImGui::Separator(); + + // Shadow Map Settings + if (ImGui::CollapsingHeader("Shadow Map Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); + + // Shadow map size controls + ImGui::Text("Shadow Map Size:"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + + static uint32_t newWidth = m_shadowMapWidth; + static uint32_t newHeight = m_shadowMapHeight; + bool sizeChanged = false; + + sizeChanged |= ImGui::DragScalar("Width##ShadowMap", ImGuiDataType_U32, &newWidth, 1.0f, nullptr, nullptr, "%u"); + ImGui::SameLine(); + sizeChanged |= ImGui::DragScalar("Height##ShadowMap", ImGuiDataType_U32, &newHeight, 1.0f, nullptr, nullptr, "%u"); + + ImGui::PopItemWidth(); + + if (sizeChanged) + { + SetShadowMapResolution(disp, allocator, debugUtils, newWidth, newHeight, false); + } + + if (m_shadowMapNeedsReconstruction) + { + ImGui::SameLine(); + if (ImGui::Button("Apply Size Change")) + { + ReconstructShadowMaps(disp, allocator, debugUtils); + } + } + + // Shadow near and far plane controls + ImGui::Text("Shadow Planes:"); + ImGui::SameLine(); + ImGui::PushItemWidth(100); + ImGui::DragFloat("Near##ShadowPlane", &m_shadowNear, 0.1f, 0.1f, m_shadowFar - 0.1f, "%.2f"); + ImGui::SameLine(); + ImGui::DragFloat("Far##ShadowPlane", &m_shadowFar, 0.1f, m_shadowNear + 0.1f, 1000.0f, "%.2f"); + ImGui::PopItemWidth(); + + // Directional light distance control + float lightDistance = GetDirectionalLightDistance(); + if (ImGui::SliderFloat("Directional Light Distance", &lightDistance, 10.0f, 500.0f, "%.1f")) + { + SetDirectionalLightDistance(lightDistance); + } + + ImGui::PopStyleVar(); + } + + ImGui::Separator(); + + // Visualization Settings + static float zoom = 1.0f; + static float contrast = 1.0f; + + if (ImGui::CollapsingHeader("Visualization Settings", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(5, 5)); + + ImGui::SliderFloat("Zoom", &zoom, 0.1f, 10.0f); + ImGui::SliderFloat("Contrast", &contrast, 0.1f, 5.0f); + + // Display a depth scale + ImGui::Text("Depth Scale:"); + ImVec2 scaleSize(200, 20); + ImVec2 scalePos = ImGui::GetCursorScreenPos(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + for (int i = 0; i < 100; i++) + { + float t = i / 99.0f; + float enhancedT = std::pow((t - 0.5f) * contrast + 0.5f, 2.2f); + enhancedT = std::clamp(enhancedT, 0.0f, 1.0f); + ImU32 color = ImGui::ColorConvertFloat4ToU32(ImVec4(enhancedT, enhancedT, enhancedT, 1.0f)); + drawList->AddRectFilled(ImVec2(scalePos.x + t * scaleSize.x, scalePos.y), ImVec2(scalePos.x + (t + 0.01f) * scaleSize.x, scalePos.y + scaleSize.y), color); + } + drawList->AddRect(scalePos, ImVec2(scalePos.x + scaleSize.x, scalePos.y + scaleSize.y), IM_COL32_WHITE); + + ImGui::Dummy(scaleSize); + ImGui::Text("0.0"); + ImGui::SameLine(185); + ImGui::Text("1.0"); + + ImGui::PopStyleVar(); + } + + ImGui::Separator(); + + // Shadow Map Display + ImGui::Text("Shadow Map:"); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); + float shadowmapAspectRatio = static_cast(m_shadowMapWidth) / m_shadowMapHeight; + + // Calculate image size + ImVec2 imageSize; + if (windowSize.x / windowSize.y > shadowmapAspectRatio) + { + imageSize = ImVec2(windowSize.y * shadowmapAspectRatio, windowSize.y); + } + else + { + imageSize = ImVec2(windowSize.x, windowSize.x / shadowmapAspectRatio); + } + imageSize.x *= zoom; + imageSize.y *= zoom; + + ImGui::BeginChild("ShadowMapRegion", windowSize, true, ImGuiWindowFlags_HorizontalScrollbar); + if (lightData.textureId == 0) + { + lightData.textureId = ImGui_ImplVulkan_AddTexture(lightData.shadowMap.sampler, lightData.shadowMap.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + } + + ImGui::Image(lightData.textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(1, 1, 1, 1), ImVec4(0, 0, 0, 0)); + + // Pixel info on hover + if (ImGui::IsItemHovered()) + { + ImVec2 mousePos = ImGui::GetMousePos(); + ImVec2 imagePos = ImGui::GetItemRectMin(); + ImVec2 relativePos = ImVec2(mousePos.x - imagePos.x, mousePos.y - imagePos.y); + + int pixelX = static_cast((relativePos.x / imageSize.x) * m_shadowMapWidth); + int pixelY = static_cast((relativePos.y / imageSize.y) * m_shadowMapHeight); + + float pixelValue = GetShadowMapPixelValue(disp, allocator, commandPool, graphicsQueue, light, pixelX, pixelY); + + // Apply contrast enhancement + float enhancedValue = std::pow((pixelValue - 0.5f) * contrast + 0.5f, 2.2f); + enhancedValue = std::clamp(enhancedValue, 0.0f, 1.0f); + + ImGui::BeginTooltip(); + ImGui::Text("Pixel: (%d, %d)", pixelX, pixelY); + ImGui::Text("Depth: %.3f", pixelValue); + ImGui::Text("Enhanced: %.3f", enhancedValue); + + // Display a color swatch representing the depth + ImVec4 depthColor(enhancedValue, enhancedValue, enhancedValue, 1.0f); + ImGui::ColorButton("Depth Color", depthColor, 0, ImVec2(40, 20)); + + ImGui::EndTooltip(); + } + + ImGui::EndChild(); + ImGui::End(); +} + +void ShadowSystem::CreateShadowMap(vkb::DispatchTable& disp, VmaAllocator allocator, VulkanDebugUtils& debugUtils, const std::shared_ptr light) +{ + ShadowData& shadowData = m_shadowData[light]; + + // Create Shadow map image + VkImageCreateInfo shadowMapImageInfo = {}; + shadowMapImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + shadowMapImageInfo.imageType = VK_IMAGE_TYPE_2D; + shadowMapImageInfo.extent.width = m_shadowMapWidth; + shadowMapImageInfo.extent.height = m_shadowMapHeight; + shadowMapImageInfo.extent.depth = 1; + shadowMapImageInfo.mipLevels = 1; + shadowMapImageInfo.arrayLayers = 1; + shadowMapImageInfo.format = VK_FORMAT_D32_SFLOAT; + shadowMapImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + shadowMapImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + shadowMapImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + shadowMapImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; + shadowMapImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VmaAllocationCreateInfo shadowMapAllocInfo = {}; + shadowMapAllocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; + + VK_CHECK(vmaCreateImage(allocator, &shadowMapImageInfo, &shadowMapAllocInfo, &shadowData.shadowMap.image, &shadowData.shadowMap.allocation, nullptr)); + debugUtils.SetObjectName(shadowData.shadowMap.image, "ShadowMapImage"); + + // Create the shadow map image view + VkImageViewCreateInfo shadowMapImageViewInfo = {}; + shadowMapImageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + shadowMapImageViewInfo.image = shadowData.shadowMap.image; + shadowMapImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + shadowMapImageViewInfo.format = VK_FORMAT_D32_SFLOAT; + shadowMapImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + shadowMapImageViewInfo.subresourceRange.baseMipLevel = 0; + shadowMapImageViewInfo.subresourceRange.levelCount = 1; + shadowMapImageViewInfo.subresourceRange.baseArrayLayer = 0; + shadowMapImageViewInfo.subresourceRange.layerCount = 1; + + VK_CHECK(disp.createImageView(&shadowMapImageViewInfo, nullptr, &shadowData.shadowMap.imageView)); + debugUtils.SetObjectName(shadowData.shadowMap.imageView, "ShadowMapImageView"); + + // Create the shadow map sampler + shadowData.shadowMap.sampler = SlimeUtil::CreateSampler(disp); + debugUtils.SetObjectName(shadowData.shadowMap.sampler, "ShadowMapSampler"); + + shadowData.stagingBufferSize = sizeof(float); + SlimeUtil::CreateBuffer("ShadowMapPixelStagingBuffer", allocator, shadowData.stagingBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, shadowData.stagingBuffer, shadowData.stagingBufferAllocation); +} + +void ShadowSystem::CleanupShadowMap(vkb::DispatchTable& disp, VmaAllocator allocator, const std::shared_ptr light) +{ + auto it = m_shadowData.find(light); + if (it != m_shadowData.end()) + { + ShadowData& shadowData = it->second; + vmaDestroyImage(allocator, shadowData.shadowMap.image, shadowData.shadowMap.allocation); + disp.destroyImageView(shadowData.shadowMap.imageView, nullptr); + disp.destroySampler(shadowData.shadowMap.sampler, nullptr); + vmaDestroyBuffer(allocator, shadowData.stagingBuffer, shadowData.stagingBufferAllocation); + } +} + +void ShadowSystem::GenerateShadowMap(vkb::DispatchTable& disp, + VkCommandBuffer& cmd, + ModelManager& modelManager, + VmaAllocator allocator, + VkCommandPool commandPool, + VkQueue graphicsQueue, + VulkanDebugUtils& debugUtils, + Scene* scene, + std::function drawModels, + const std::shared_ptr light, + const std::shared_ptr camera) +{ + debugUtils.BeginDebugMarker(cmd, "Draw Models for Shadow Map", debugUtil_BeginColour); + + auto shadowMap = GetShadowMap(light); + + // Transition shadow map image to depth attachment optimal + modelManager.TransitionImageLayout(disp, graphicsQueue, commandPool, shadowMap.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); + + VkRenderingAttachmentInfo depthAttachmentInfo = {}; + depthAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAttachmentInfo.imageView = shadowMap.imageView; + depthAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + depthAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + depthAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + depthAttachmentInfo.clearValue.depthStencil.depth = 1.0f; + + VkRenderingInfo renderingInfo = {}; + renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + renderingInfo.renderArea = { + .offset = { 0, 0}, + .extent = {m_shadowMapWidth, m_shadowMapHeight} + }; + renderingInfo.layerCount = 1; + renderingInfo.pDepthAttachment = &depthAttachmentInfo; + + disp.cmdBeginRendering(cmd, &renderingInfo); + + // Set viewport and scissor for shadow map + VkViewport viewport = { 0, 0, (float) m_shadowMapWidth, (float) m_shadowMapHeight, 0.0f, 1.0f }; + VkRect2D scissor = { + { 0, 0}, + {m_shadowMapWidth, m_shadowMapHeight} + }; + disp.cmdSetViewport(cmd, 0, 1, &viewport); + disp.cmdSetScissor(cmd, 0, 1, &scissor); + + // Draw models for shadow map + drawModels(disp, debugUtils, cmd, modelManager, scene); + + disp.cmdEndRendering(cmd); + + // Transition shadow map image to shader read-only optimal + modelManager.TransitionImageLayout(disp, graphicsQueue, commandPool, shadowMap.image, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + debugUtils.EndDebugMarker(cmd); +} + +void ShadowSystem::CalculateLightSpaceMatrix(const std::shared_ptr light, const std::shared_ptr camera) +{ + auto it = m_shadowData.find(light); + if (it == m_shadowData.end()) + return; + ShadowData& shadowData = it->second; + + // Check if the camera has moved outside the light's frustum + float distanceSquared = glm::length2(camera->GetPosition() - shadowData.lastCameraPosition); + if (distanceSquared < shadowData.frustumRadius * shadowData.frustumRadius) + { + // Camera is still inside the light's frustum, no need to recalculate + return; + } + + // Update the last known camera position + shadowData.lastCameraPosition = camera->GetPosition(); + + float fov = camera->GetFOV(); + float aspect = camera->GetAspectRatio(); + + // Calculate the corners of the view frustum in world space + std::vector frustumCorners = CalculateFrustumCorners(fov, aspect, m_shadowNear, m_shadowFar, camera->GetPosition(), camera->GetForward(), camera->GetUp(), camera->GetRight()); + + // Calculate the bounding sphere of the frustum + glm::vec3 frustumCenter; + float frustumRadius; + CalculateFrustumSphere(frustumCorners, frustumCenter, frustumRadius); + + // Store the frustum radius for future checks + shadowData.frustumRadius = frustumRadius; + + glm::vec3 lightDir; + glm::vec3 lightPos; + + if (light->GetType() == LightType::Directional) + { + const DirectionalLight* dirLight = static_cast(light.get()); + lightDir = glm::normalize(-dirLight->GetDirection()); + lightPos = frustumCenter - lightDir * frustumRadius; // Use frustumRadius as a base distance + } + else if (light->GetType() == LightType::Point) + { + const PointLight* pointLight = static_cast(light.get()); + lightPos = pointLight->GetPosition(); + lightDir = glm::normalize(frustumCenter - lightPos); + } + else + { + spdlog::error("Unsupported light type"); + return; + } + + // Create the light's view matrix + glm::mat4 lightView = glm::lookAt(lightPos, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f)); + + // Transform frustum corners to light space + std::vector lightSpaceCorners; + for (const auto& corner: frustumCorners) + { + lightSpaceCorners.push_back(glm::vec3(lightView * glm::vec4(corner, 1.0f))); + } + + // Calculate the bounding box in light space + glm::vec3 minBounds(std::numeric_limits::max()); + glm::vec3 maxBounds(std::numeric_limits::lowest()); + for (const auto& corner: lightSpaceCorners) + { + minBounds = glm::min(minBounds, corner); + maxBounds = glm::max(maxBounds, corner); + } + + // Adjust the orthographic projection based on the directional light distance + if (light->GetType() == LightType::Directional) + { + float scaleFactor = m_directionalLightDistance / frustumRadius; + minBounds.x *= scaleFactor; + maxBounds.x *= scaleFactor; + minBounds.y *= scaleFactor; + maxBounds.y *= scaleFactor; + } + + // Add some padding to the bounding box + float padding = (maxBounds.z - minBounds.z) * 0.1f; // 10% of the depth range as padding + minBounds -= glm::vec3(padding); + maxBounds += glm::vec3(padding); + + // Adjust the Z range for Vulkan's depth range [0, 1] + float zNear = -maxBounds.z; + float zFar = -minBounds.z; + + // Create the light's orthographic projection matrix + glm::mat4 lightProjection = glm::ortho(minBounds.x, maxBounds.x, minBounds.y, maxBounds.y, zNear, zFar); + + glm::mat4 vulkanNdcAdjustment = glm::mat4(1.0f); + vulkanNdcAdjustment[1][1] = -1.0f; + vulkanNdcAdjustment[2][2] = 0.5f; + vulkanNdcAdjustment[3][2] = 0.5f; + + // Combine view and projection matrices + shadowData.lightSpaceMatrix = vulkanNdcAdjustment * lightProjection * lightView; + light->SetLightSpaceMatrix(shadowData.lightSpaceMatrix); +} + +std::vector ShadowSystem::CalculateFrustumCorners(float fov, float aspect, float near, float far, const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up, const glm::vec3& right) const +{ + float tanHalfFov = tan(glm::radians(fov * 0.5f)); + glm::vec3 nearCenter = position + forward * near; + glm::vec3 farCenter = position + forward * far; + + float nearHeight = 2.0f * tanHalfFov * near; + float nearWidth = nearHeight * aspect; + float farHeight = 2.0f * tanHalfFov * far; + float farWidth = farHeight * aspect; + + std::vector corners(8); + corners[0] = nearCenter + up * (nearHeight * 0.5f) - right * (nearWidth * 0.5f); + corners[1] = nearCenter + up * (nearHeight * 0.5f) + right * (nearWidth * 0.5f); + corners[2] = nearCenter - up * (nearHeight * 0.5f) - right * (nearWidth * 0.5f); + corners[3] = nearCenter - up * (nearHeight * 0.5f) + right * (nearWidth * 0.5f); + corners[4] = farCenter + up * (farHeight * 0.5f) - right * (farWidth * 0.5f); + corners[5] = farCenter + up * (farHeight * 0.5f) + right * (farWidth * 0.5f); + corners[6] = farCenter - up * (farHeight * 0.5f) - right * (farWidth * 0.5f); + corners[7] = farCenter - up * (farHeight * 0.5f) + right * (farWidth * 0.5f); + + return corners; +} + +void ShadowSystem::CalculateFrustumSphere(const std::vector& frustumCorners, glm::vec3& center, float& radius) const +{ + center = glm::vec3(0.0f); + for (const auto& corner: frustumCorners) + { + center += corner; + } + center /= frustumCorners.size(); + + radius = 0.0f; + for (const auto& corner: frustumCorners) + { + float distance = glm::length(corner - center); + radius = glm::max(radius, distance); + } +} diff --git a/SlimeOdyssey/src/VulkanContext.cpp b/SlimeOdyssey/src/VulkanContext.cpp index dc2445d..9684ba7 100644 --- a/SlimeOdyssey/src/VulkanContext.cpp +++ b/SlimeOdyssey/src/VulkanContext.cpp @@ -20,8 +20,6 @@ #include "VulkanUtil.h" #define MAX_FRAMES_IN_FLIGHT 2 -#define SHADOW_MAP_WIDTH 1920 -#define SHADOW_MAP_HEIGHT 1920 // IMGUI #include "backends/imgui_impl_glfw.h" @@ -52,6 +50,8 @@ int VulkanContext::CreateContext(SlimeWindow* window) if (InitImGui(window) != 0) // Add this line return -1; + m_renderer.SetUp(m_disp, m_allocator, m_swapchain, m_debugUtils); + return 0; } @@ -266,8 +266,6 @@ int VulkanContext::CreateSwapchain(SlimeWindow* window) spdlog::debug("Creating swapchain..."); m_disp.deviceWaitIdle(); - bool recreate = m_swapchain.swapchain != VK_NULL_HANDLE; - vkb::SwapchainBuilder swapchain_builder(m_device, m_surface); auto swap_ret = swapchain_builder.use_default_format_selection() @@ -302,97 +300,7 @@ int VulkanContext::CreateSwapchain(SlimeWindow* window) m_debugUtils.SetObjectName(m_swapchainImageViews[i], "SwapchainImageView_" + std::to_string(i)); } - // Clean up old depth image and image view - if (m_depthImage) - { - vmaDestroyImage(m_allocator, m_depthImage, m_depthImageAllocation); - m_disp.destroyImageView(m_depthImageView, nullptr); - } - - // Create the depth image - VkFormat depthFormat = VK_FORMAT_D32_SFLOAT; // Or VK_FORMAT_D32_SFLOAT_S8_UINT if you need stencil - VkImageCreateInfo depthImageInfo = {}; - depthImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - depthImageInfo.imageType = VK_IMAGE_TYPE_2D; - depthImageInfo.extent.width = m_swapchain.extent.width; - depthImageInfo.extent.height = m_swapchain.extent.height; - depthImageInfo.extent.depth = 1; - depthImageInfo.mipLevels = 1; - depthImageInfo.arrayLayers = 1; - depthImageInfo.format = depthFormat; - depthImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - depthImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - depthImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - depthImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - depthImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VmaAllocationCreateInfo depthAllocInfo = {}; - depthAllocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VK_CHECK(vmaCreateImage(m_allocator, &depthImageInfo, &depthAllocInfo, &m_depthImage, &m_depthImageAllocation, nullptr)); - m_debugUtils.SetObjectName(m_depthImage, "DepthImage"); - - // Create the depth image view - VkImageViewCreateInfo depthImageViewInfo = {}; - depthImageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - depthImageViewInfo.image = m_depthImage; - depthImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - depthImageViewInfo.format = depthFormat; - depthImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - depthImageViewInfo.subresourceRange.baseMipLevel = 0; - depthImageViewInfo.subresourceRange.levelCount = 1; - depthImageViewInfo.subresourceRange.baseArrayLayer = 0; - depthImageViewInfo.subresourceRange.layerCount = 1; - - VK_CHECK(m_disp.createImageView(&depthImageViewInfo, nullptr, &m_depthImageView)); - m_debugUtils.SetObjectName(m_depthImageView, "DepthImageView"); - - // If we are recreating the swapchain we dont need to recreate the shadow map - if (recreate) - { - return 0; - } - - // Create Shadow map image - VkImageCreateInfo shadowMapImageInfo = {}; - shadowMapImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - shadowMapImageInfo.imageType = VK_IMAGE_TYPE_2D; - shadowMapImageInfo.extent.width = SHADOW_MAP_WIDTH; - shadowMapImageInfo.extent.height = SHADOW_MAP_HEIGHT; - shadowMapImageInfo.extent.depth = 1; - shadowMapImageInfo.mipLevels = 1; - shadowMapImageInfo.arrayLayers = 1; - shadowMapImageInfo.format = VK_FORMAT_D32_SFLOAT; - shadowMapImageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - shadowMapImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - shadowMapImageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - shadowMapImageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - shadowMapImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - - VmaAllocationCreateInfo shadowMapAllocInfo = {}; - shadowMapAllocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; - - VK_CHECK(vmaCreateImage(m_allocator, &shadowMapImageInfo, &shadowMapAllocInfo, &m_shadowMap.image, &m_shadowMap.allocation, nullptr)); - m_debugUtils.SetObjectName(m_shadowMap.image, "ShadowMapImage"); - - // Create the shadow map image view - VkImageViewCreateInfo shadowMapImageViewInfo = {}; - shadowMapImageViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - shadowMapImageViewInfo.image = m_shadowMap.image; - shadowMapImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - shadowMapImageViewInfo.format = VK_FORMAT_D32_SFLOAT; - shadowMapImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - shadowMapImageViewInfo.subresourceRange.baseMipLevel = 0; - shadowMapImageViewInfo.subresourceRange.levelCount = 1; - shadowMapImageViewInfo.subresourceRange.baseArrayLayer = 0; - shadowMapImageViewInfo.subresourceRange.layerCount = 1; - - VK_CHECK(m_disp.createImageView(&shadowMapImageViewInfo, nullptr, &m_shadowMap.imageView)); - m_debugUtils.SetObjectName(m_shadowMap.imageView, "ShadowMapImageView"); - - // Create the shadow map sampler - m_shadowMap.sampler = SlimeUtil::CreateSampler(m_disp); - m_debugUtils.SetObjectName(m_shadowMap.sampler, "ShadowMapSampler"); + m_renderer.CreateDepthImage(m_disp, m_allocator, m_swapchain, m_debugUtils); return 0; } @@ -672,350 +580,6 @@ int VulkanContext::InitImGui(SlimeWindow* window) ImGui_ImplVulkan_CreateFontsTexture(); } - m_shadowMapId = ImGui_ImplVulkan_AddTexture(m_shadowMap.sampler, m_shadowMap.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - return 0; -} - -int VulkanContext::GenerateShadowMap(VkCommandBuffer& cmd, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene) -{ - m_debugUtils.BeginDebugMarker(cmd, "Draw Models for Shadow Map", debugUtil_BeginColour); - // Transition shadow map image to depth attachment optimal - modelManager.TransitionImageLayout(m_disp, m_graphicsQueue, m_commandPool, m_shadowMap.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); - - VkRenderingAttachmentInfo depthAttachmentInfo = {}; - depthAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - depthAttachmentInfo.imageView = m_shadowMap.imageView; - depthAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; - depthAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachmentInfo.clearValue.depthStencil.depth = 1.0f; - - VkRenderingInfo renderingInfo = {}; - renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; - renderingInfo.renderArea = { - .offset = { 0, 0}, - .extent = {SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT} - }; - renderingInfo.layerCount = 1; - renderingInfo.pDepthAttachment = &depthAttachmentInfo; - - m_disp.cmdBeginRendering(cmd, &renderingInfo); - - // Set viewport and scissor for shadow map - VkViewport viewport = { 0, 0, SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT, 0.0f, 1.0f }; - VkRect2D scissor = { - { 0, 0}, - {SHADOW_MAP_WIDTH, SHADOW_MAP_HEIGHT} - }; - m_disp.cmdSetViewport(cmd, 0, 1, &viewport); - m_disp.cmdSetScissor(cmd, 0, 1, &scissor); - - // Draw models for shadow map - m_renderer.DrawModelsForShadowMap(m_disp, m_debugUtils, cmd, modelManager, scene); - - m_disp.cmdEndRendering(cmd); - - // Transition shadow map image to shader read-only optimal - modelManager.TransitionImageLayout(m_disp, m_graphicsQueue, m_commandPool, m_shadowMap.image, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - - m_debugUtils.EndDebugMarker(cmd); - - return 0; -} - -void VulkanContext::CreateShadowMapStagingBuffer() -{ - m_shadowMapStagingBufferSize = sizeof(float); - SlimeUtil::CreateBuffer("ShadowMapPixelStagingBuffer", m_allocator, m_shadowMapStagingBufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU, m_shadowMapStagingBuffer, m_shadowMapStagingBufferAllocation); -} - -void VulkanContext::DestroyShadowMapStagingBuffer() -{ - if (m_shadowMapStagingBuffer != VK_NULL_HANDLE) - { - vmaDestroyBuffer(m_allocator, m_shadowMapStagingBuffer, m_shadowMapStagingBufferAllocation); - m_shadowMapStagingBuffer = VK_NULL_HANDLE; - m_shadowMapStagingBufferAllocation = VK_NULL_HANDLE; - } -} - -float VulkanContext::GetShadowMapPixelValue(ModelManager& modelManager, int x, int y) -{ - // Ensure x and y are within bounds - x = std::clamp(x, 0, static_cast(SHADOW_MAP_WIDTH) - 1); - y = std::clamp(y, 0, static_cast(SHADOW_MAP_HEIGHT) - 1); - - // Create the staging buffer if it doesn't exist - if (m_shadowMapStagingBuffer == VK_NULL_HANDLE) - { - CreateShadowMapStagingBuffer(); - } - - // Create command buffer for copy operation - VkCommandBuffer commandBuffer = SlimeUtil::BeginSingleTimeCommands(m_disp, m_commandPool); - - // Transition shadow map image layout for transfer - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = m_shadowMap.image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - - m_disp.cmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - - // Copy image to buffer - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = 0; - region.bufferImageHeight = 0; - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - region.imageSubresource.mipLevel = 0; - region.imageSubresource.baseArrayLayer = 0; - region.imageSubresource.layerCount = 1; - region.imageOffset = { x, y, 0 }; - region.imageExtent = { 1, 1, 1 }; - - m_disp.cmdCopyImageToBuffer(commandBuffer, m_shadowMap.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_shadowMapStagingBuffer, 1, ®ion); - - // Transition shadow map image layout back - barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - m_disp.cmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - - SlimeUtil::EndSingleTimeCommands(m_disp, m_graphicsQueue, m_commandPool, commandBuffer); - - // Read data from staging buffer - float pixelValue; - void* data; - vmaMapMemory(m_allocator, m_shadowMapStagingBufferAllocation, &data); - memcpy(&pixelValue, data, sizeof(float)); - vmaUnmapMemory(m_allocator, m_shadowMapStagingBufferAllocation); - - return pixelValue; -} - -// Shadow map debug -void VulkanContext::RenderShadowMapInspector(ModelManager& modelManager) -{ - ImGui::Begin("Shadow Map Inspector"); - - // Window size and aspect ratio - ImVec2 windowSize = ImGui::GetContentRegionAvail(); - float shadowmapAspectRatio = SHADOW_MAP_WIDTH / (float) SHADOW_MAP_HEIGHT; - - // Zoom and pan controls - static float zoom = 1.0f; - static ImVec2 pan = ImVec2(0.0f, 0.0f); - ImGui::SliderFloat("Zoom", &zoom, 0.1f, 10.0f); - ImGui::DragFloat2("Pan", &pan.x, 0.01f); - - // Contrast control - static float contrast = 1.0f; - ImGui::SliderFloat("Contrast", &contrast, 0.1f, 5.0f); - - // Display a depth scale - ImGui::Text("Depth Scale:"); - ImVec2 scaleSize(200, 20); - ImVec2 scalePos = ImGui::GetCursorScreenPos(); - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - for (int i = 0; i < 100; i++) - { - float t = i / 99.0f; - float enhancedT = std::pow((t - 0.5f) * contrast + 0.5f, 2.2f); - enhancedT = std::clamp(enhancedT, 0.0f, 1.0f); - ImU32 color = ImGui::ColorConvertFloat4ToU32(ImVec4(enhancedT, enhancedT, enhancedT, 1.0f)); - drawList->AddRectFilled(ImVec2(scalePos.x + t * scaleSize.x, scalePos.y), ImVec2(scalePos.x + (t + 0.01f) * scaleSize.x, scalePos.y + scaleSize.y), color); - } - drawList->AddRect(scalePos, ImVec2(scalePos.x + scaleSize.x, scalePos.y + scaleSize.y), IM_COL32_WHITE); - - ImGui::Dummy(scaleSize); - ImGui::Text("0.0"); - ImGui::SameLine(185); - ImGui::Text("1.0"); - - // Calculate image size - ImVec2 imageSize; - if (windowSize.x / windowSize.y > shadowmapAspectRatio) - { - imageSize = ImVec2(windowSize.y * shadowmapAspectRatio, windowSize.y); - } - else - { - imageSize = ImVec2(windowSize.x, windowSize.x / shadowmapAspectRatio); - } - - // Apply zoom and pan - imageSize.x *= zoom; - imageSize.y *= zoom; - - // Display shadow map - ImGui::BeginChild("ShadowMapRegion", windowSize, true, ImGuiWindowFlags_HorizontalScrollbar); - ImGui::SetCursorPos(ImVec2(pan.x, pan.y)); - // Flip the image vertically - ImGui::Image((ImTextureID) m_shadowMapId, imageSize, ImVec2(0, 1), ImVec2(1, 0)); - - // Pixel info on hover - if (ImGui::IsItemHovered()) - { - // Disable window manipulation while hovering - ImGui::SetWindowFocus(); - - ImVec2 mousePos = ImGui::GetMousePos(); - ImVec2 imagePos = ImGui::GetItemRectMin(); - ImVec2 relativePos = ImVec2(mousePos.x - imagePos.x, mousePos.y - imagePos.y); - - int pixelX = (int) ((relativePos.x / imageSize.x) * SHADOW_MAP_WIDTH); - int pixelY = (int) ((relativePos.y / imageSize.y) * SHADOW_MAP_HEIGHT); - - float pixelValue = GetShadowMapPixelValue(modelManager, pixelX, pixelY); - - // Apply contrast enhancement - float enhancedValue = std::pow((pixelValue - 0.5f) * contrast + 0.5f, 2.2f); - enhancedValue = std::clamp(enhancedValue, 0.0f, 1.0f); - - ImGui::BeginTooltip(); - ImGui::Text("Pixel: (%d, %d)", pixelX, pixelY); - ImGui::Text("Depth: %.3f", pixelValue); - ImGui::Text("Enhanced: %.3f", enhancedValue); - - // Display a color swatch representing the depth - ImVec4 depthColor(enhancedValue, enhancedValue, enhancedValue, 1.0f); - ImGui::ColorButton("Depth Color", depthColor, 0, ImVec2(40, 20)); - - ImGui::EndTooltip(); - - float wheel = ImGui::GetIO().MouseWheel; - if (ImGui::GetIO().KeyShift && wheel != 0) - { - const float zoomSpeed = 0.1f; - zoom *= (1.0f + wheel * zoomSpeed); - zoom = std::clamp(zoom, 0.1f, 10.0f); - - // Adjust pan to zoom towards mouse position - ImVec2 mousePos = ImGui::GetMousePos(); - ImVec2 center = ImGui::GetWindowPos() + windowSize * 0.5f; - pan += (mousePos - center) * (wheel * zoomSpeed); - } - - // Left click and drag to pan - if (ImGui::IsMouseDragging(0)) - { - ImVec2 delta = ImGui::GetIO().MouseDelta; - pan += delta; - } - } - - ImGui::EndChild(); - - ImGui::End(); -} - -int VulkanContext::Draw(VkCommandBuffer& cmd, int imageIndex, ModelManager& modelManager, DescriptorManager& descriptorManager, Scene* scene) -{ - if (SlimeUtil::BeginCommandBuffer(m_disp, cmd) != 0) - return -1; - - // Generate shadow map - if (GenerateShadowMap(cmd, modelManager, descriptorManager, scene) != 0) - return -1; - - m_renderer.SetupViewportAndScissor(m_swapchain, m_disp, cmd); - SlimeUtil::SetupDepthTestingAndLineWidth(m_disp, cmd); - - // Transition color image to color attachment optimal - modelManager.TransitionImageLayout(m_disp, m_graphicsQueue, m_commandPool, m_swapchainImages[imageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - // Transition depth image to depth attachment optimal - modelManager.TransitionImageLayout(m_disp, m_graphicsQueue, m_commandPool, m_depthImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); - - VkRenderingAttachmentInfo colorAttachmentInfo = {}; - colorAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - colorAttachmentInfo.imageView = m_swapchainImageViews[imageIndex]; - colorAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - colorAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - colorAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachmentInfo.clearValue = { .color = { { 0.05f, 0.05f, 0.05f, 0.0f } } }; - - VkRenderingAttachmentInfo depthAttachmentInfo = {}; - depthAttachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; - depthAttachmentInfo.imageView = m_depthImageView; - depthAttachmentInfo.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; - depthAttachmentInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachmentInfo.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - depthAttachmentInfo.clearValue.depthStencil.depth = 1.f; - - VkRenderingInfo renderingInfo = {}; - renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; - renderingInfo.renderArea = { - .offset = {0, 0}, - .extent = m_swapchain.extent - }; - renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachmentInfo; - renderingInfo.pDepthAttachment = &depthAttachmentInfo; - - m_disp.cmdBeginRendering(cmd, &renderingInfo); - - // Start the Dear ImGui frame - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Create a full screen docking space for ImGui - ImGuiDockNodeFlags dockFlags = ImGuiDockNodeFlags_PassthruCentralNode; - ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()->ID, ImGui::GetMainViewport(), dockFlags); - - if (scene) - { - m_renderer.DrawModels(m_disp, m_debugUtils, m_allocator, cmd, modelManager, descriptorManager, scene, &m_shadowMap); - - m_debugUtils.BeginDebugMarker(cmd, "Draw ImGui", debugUtil_BindDescriptorSetColour); - scene->Render(*this, modelManager); - } - - RenderShadowMapInspector(modelManager); - - ImGui::Render(); - ImDrawData* drawData = ImGui::GetDrawData(); - const bool isMinimized = (drawData->DisplaySize.x <= 0.0f || drawData->DisplaySize.y <= 0.0f); - if (!isMinimized) - { - // Record dear imgui primitives into command buffer - ImGui_ImplVulkan_RenderDrawData(drawData, cmd); - } - - m_debugUtils.EndDebugMarker(cmd); - - m_disp.cmdEndRendering(cmd); - - // Transition color image to present src layout - modelManager.TransitionImageLayout(m_disp, m_graphicsQueue, m_commandPool, m_swapchainImages[imageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); - - if (SlimeUtil::EndCommandBuffer(m_disp, cmd) != 0) - return -1; - - // Handle multi-viewport rendering here, outside of the main command buffer - ImGuiIO& io = ImGui::GetIO(); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - } - return 0; } @@ -1030,8 +594,8 @@ int VulkanContext::RenderFrame(ModelManager& modelManager, DescriptorManager& de // Wait for the frame to be finished VK_CHECK(m_disp.waitForFences(1, &m_inFlightFences[m_currentFrame], VK_TRUE, UINT64_MAX)); - uint32_t image_index; - VkResult result = m_disp.acquireNextImageKHR(m_swapchain, UINT64_MAX, m_availableSemaphores[m_currentFrame], VK_NULL_HANDLE, &image_index); + uint32_t imageIndex; + VkResult result = m_disp.acquireNextImageKHR(m_swapchain, UINT64_MAX, m_availableSemaphores[m_currentFrame], VK_NULL_HANDLE, &imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { @@ -1045,18 +609,18 @@ int VulkanContext::RenderFrame(ModelManager& modelManager, DescriptorManager& de } // Check if a previous frame is using this image (i.e. there is its fence to wait on) - if (m_imageInFlight[image_index] != VK_NULL_HANDLE) + if (m_imageInFlight[imageIndex] != VK_NULL_HANDLE) { - m_disp.waitForFences(1, &m_imageInFlight[image_index], VK_TRUE, UINT64_MAX); + m_disp.waitForFences(1, &m_imageInFlight[imageIndex], VK_TRUE, UINT64_MAX); } // Mark the image as now being in use by this frame - m_imageInFlight[image_index] = m_inFlightFences[m_currentFrame]; + m_imageInFlight[imageIndex] = m_inFlightFences[m_currentFrame]; // Begin command buffer recording - VkCommandBuffer cmd = m_renderCommandBuffers[image_index]; + VkCommandBuffer cmd = m_renderCommandBuffers[imageIndex]; - if (Draw(cmd, image_index, modelManager, descriptorManager, scene) != 0) + if (m_renderer.Draw(m_disp, cmd, modelManager, descriptorManager, m_allocator, m_commandPool, m_graphicsQueue, m_debugUtils, m_swapchain, m_swapchainImages, m_swapchainImageViews, imageIndex, scene) != 0) return -1; VkSubmitInfo submit_info = {}; @@ -1089,7 +653,7 @@ int VulkanContext::RenderFrame(ModelManager& modelManager, DescriptorManager& de VkSwapchainKHR swapchains[] = { m_swapchain }; present_info.swapchainCount = 1; present_info.pSwapchains = swapchains; - present_info.pImageIndices = &image_index; + present_info.pImageIndices = &imageIndex; result = m_disp.queuePresentKHR(m_presentQueue, &present_info); @@ -1146,16 +710,7 @@ int VulkanContext::Cleanup(ShaderManager& shaderManager, ModelManager& modelMana descriptorManager.Cleanup(); - // Clean up old depth image and image view - vmaDestroyImage(m_allocator, m_depthImage, m_depthImageAllocation); - m_disp.destroyImageView(m_depthImageView, nullptr); - - // Clean up shadow map image and image view - vmaDestroyImage(m_allocator, m_shadowMap.image, m_shadowMap.allocation); - m_disp.destroyImageView(m_shadowMap.imageView, nullptr); - m_disp.destroySampler(m_shadowMap.sampler, nullptr); - - DestroyShadowMapStagingBuffer(); + m_renderer.CleanUp(m_disp, m_allocator); shaderManager.CleanupShaderModules(m_disp);