From 0d819f2b9a71e862bce8799c6b0c1304e62219b3 Mon Sep 17 00:00:00 2001 From: hachem Date: Fri, 22 Aug 2025 12:24:05 +0200 Subject: [add]: add HDRI --- src/Core/HDRIManager.cpp | 64 +++++++++++ src/Core/HDRIManager.h | 46 ++++++++ src/Engine/Engine.cpp | 24 +++- src/Engine/Engine.h | 28 +++-- src/Platform/OpenGL/OpenGLFramebuffer.cpp | 1 - src/Platform/OpenGL/OpenGLShader.h | 1 + src/Platform/OpenGL/OpenGLTexture.cpp | 181 +++++++++++++++++++++++++++++- src/Platform/OpenGL/OpenGLTexture.h | 31 +++++ src/Platform/Vulkan/VulkanShader.h | 1 + src/Platform/Vulkan/VulkanTexture.cpp | 49 +++++++- src/Platform/Vulkan/VulkanTexture.h | 27 +++++ src/Rendering/Shader.h | 1 + src/Rendering/Texture.cpp | 30 +++++ src/Rendering/Texture.h | 8 ++ src/States/ConfigState.h | 1 - src/States/SimulationState.cpp | 39 ++++++- src/States/WorldBuilderState.cpp | 173 ++++++++++++++++++++++++++-- src/States/WorldBuilderState.h | 10 ++ 18 files changed, 678 insertions(+), 37 deletions(-) create mode 100644 src/Core/HDRIManager.cpp create mode 100644 src/Core/HDRIManager.h (limited to 'src') diff --git a/src/Core/HDRIManager.cpp b/src/Core/HDRIManager.cpp new file mode 100644 index 0000000..6e420a6 --- /dev/null +++ b/src/Core/HDRIManager.cpp @@ -0,0 +1,64 @@ +#include "HDRIManager.h" +#include + +namespace Donut +{ + Ref HDRIManager::LoadHDRI(const std::string& path) + { + auto it = m_HDRICache.find(path); + if (it != m_HDRICache.end()) + { + DONUT_INFO("HDRI already cached: {}", path); + return it->second; + } + + DONUT_INFO("Loading HDRI: {}", path); + auto hdri = CubemapTexture::CreateFromHDRI(path); + + if (hdri) + { + m_HDRICache[path] = hdri; + DONUT_INFO("Successfully loaded and cached HDRI: {}", path); + } + else + DONUT_ERROR("Failed to load HDRI: {}", path); + + return hdri; + } + + void HDRIManager::SetCurrentHDRI(const std::string& path) + { + auto hdri = LoadHDRI(path); + if (hdri) + { + m_CurrentHDRI = hdri; + DONUT_INFO("Set current HDRI to: {}", path); + } + else + DONUT_ERROR("Failed to set current HDRI: {}", path); + } + + std::string HDRIManager::GetHDRIName(const std::string& path) const + { + std::filesystem::path filePath(path); + std::string filename = filePath.stem().string(); + std::string name = filename; + + for (size_t i = 0; i < name.length(); ++i) + { + if (name[i] == '_') + name[i] = ' '; + else if (i == 0 || name[i-1] == ' ') + name[i] = std::toupper(name[i]); + } + + return name; + } + + void HDRIManager::ClearCache() + { + m_HDRICache.clear(); + m_CurrentHDRI.reset(); + DONUT_INFO("HDRI cache cleared"); + } +}; diff --git a/src/Core/HDRIManager.h b/src/Core/HDRIManager.h new file mode 100644 index 0000000..ebefb3a --- /dev/null +++ b/src/Core/HDRIManager.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Core/Log.h" +#include "Rendering/Texture.h" + +#include +#include +#include + +namespace Donut +{ + class HDRIManager + { + public: + static HDRIManager& Get() + { + static HDRIManager instance; + return instance; + } + + Ref LoadHDRI(const std::string& path); + Ref GetCurrentHDRI() const { return m_CurrentHDRI; } + + void SetCurrentHDRI(const std::string& path); + const std::vector& GetAvailableHDRI() const { return m_AvailableHDRI; } + std::string GetHDRIName(const std::string& path) const; + void ClearCache(); + private: + HDRIManager() = default; + ~HDRIManager() = default; + + HDRIManager(const HDRIManager&) = delete; + HDRIManager& operator=(const HDRIManager&) = delete; + + std::unordered_map> m_HDRICache; + + Ref m_CurrentHDRI; + + std::vector m_AvailableHDRI = + { + "Assets/HDRI/HDR_blue_nebulae-1.hdr", + "Assets/HDRI/HDR_subdued_blue_nebulae.hdr", + "Assets/HDRI/HDR_subdued_multi_nebulae.hdr" + }; + }; +}; diff --git a/src/Engine/Engine.cpp b/src/Engine/Engine.cpp index 20a295f..a947357 100644 --- a/src/Engine/Engine.cpp +++ b/src/Engine/Engine.cpp @@ -10,6 +10,7 @@ #include "Engine.h" #include "Core/Log.h" +#include "Core/HDRIManager.h" #include "Rendering/VertexBuffer.h" #include "Rendering/IndexBuffer.h" @@ -36,15 +37,25 @@ namespace Donut m_ComputeProgram = CreateComputeProgram("Assets/Shaders/Geodesic.glsl"); m_BlurShader = Ref(Shader::Create("Assets/Shaders/Blur.glsl")); + auto& hdriManager = HDRIManager::Get(); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + if (!m_HDRIEnvironment) + { + hdriManager.SetCurrentHDRI("Assets/HDRI/HDR_blue_nebulae-1.hdr"); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + if (!m_HDRIEnvironment) + DONUT_WARN("Failed to load default HDRI, using fallback"); + } + m_CameraUBO = UniformBuffer::Create(128, 1); - m_DiskUBO = UniformBuffer::Create(sizeof(float) * 5, 2); // Added density parameter + m_DiskUBO = UniformBuffer::Create(sizeof(float) * 5, 2); uint32_t objUBOSize = sizeof(int) + 3 * sizeof(float) + 16 * (sizeof(glm::vec4) + sizeof(glm::vec4)) + 16 * sizeof(float); m_ObjectsUBO = UniformBuffer::Create(objUBOSize, 3); - m_SimulationUBO = UniformBuffer::Create(sizeof(int) * 2 + sizeof(float) * 2, 4); // time added to existing float + m_SimulationUBO = UniformBuffer::Create(sizeof(int) * 2 + sizeof(float) * 2, 4); auto result = QuadVAO(); m_QuadVAO = result.first; @@ -117,6 +128,9 @@ namespace Donut void Engine::DispatchCompute(const Camera& cam) { + auto& hdriManager = HDRIManager::Get(); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + int cw = GetComputeWidth(); int ch = m_ComputeHeight; @@ -128,7 +142,10 @@ namespace Donut UploadObjectsUBO(m_Objects); UploadSimulationUBO(); m_Texture->BindAsImage(0, false); - + + if (m_HDRIEnvironment) + m_HDRIEnvironment->Bind(5); + uint32_t groupsX = static_cast(std::ceil(cw / 16.0f)); uint32_t groupsY = static_cast(std::ceil(ch / 16.0f)); m_ComputeProgram->Dispatch(groupsX, groupsY, 1); @@ -509,4 +526,5 @@ namespace Donut RenderCommand::SetViewport(0, 0, originalWidth, originalHeight); } + } diff --git a/src/Engine/Engine.h b/src/Engine/Engine.h index 83f0b19..c3cfdd0 100644 --- a/src/Engine/Engine.h +++ b/src/Engine/Engine.h @@ -101,30 +101,34 @@ namespace Donut void SetMaxStepsStatic(int steps) { m_MaxStepsStatic = steps; } void SetEarlyExitDistance(float distance) { m_EarlyExitDistance = distance; } - float GetDiskThickness() const { return m_DiskThickness; } + float GetDiskThickness() const { return m_DiskThickness; } void SetDiskThickness(float thickness) { m_DiskThickness = thickness; } - float GetDiskDensity() const { return m_DiskDensity; } - void SetDiskDensity(float density) { m_DiskDensity = density; } + float GetDiskDensity() const { return m_DiskDensity; } + void SetDiskDensity(float density) { m_DiskDensity = density; } - float GetRotationSpeed() const { return m_RotationSpeed; } - void SetRotationSpeed(float speed) { m_RotationSpeed = speed; } + float GetRotationSpeed() const { return m_RotationSpeed; } + void SetRotationSpeed(float speed) { m_RotationSpeed = speed; } - float GetBlurStrength() const { return m_BlurStrength; } - void SetBlurStrength(float strength) { m_BlurStrength = strength; } + float GetBlurStrength() const { return m_BlurStrength; } + void SetBlurStrength(float strength) { m_BlurStrength = strength; } - float GetGlowIntensity() const { return m_GlowIntensity; } + float GetGlowIntensity() const { return m_GlowIntensity; } void SetGlowIntensity(float intensity) { m_GlowIntensity = intensity; } void LoadObjectsFromScene(const std::vector& objects); void ExportHighResFrame(const std::string& filename, int width = 4096, int height = 3072); void PrintObjectInfo() const; + + void SetHDRIEnvironment(Ref hdri) { m_HDRIEnvironment = hdri; } + Ref GetHDRIEnvironment() const { return m_HDRIEnvironment; } private: Ref CreateComputeProgram(const char* path); std::pair, Ref> QuadVAO(); private: Ref m_QuadVAO; Ref m_Texture; + Ref m_HDRIEnvironment; Ref m_ShaderProgram; Ref m_ComputeProgram; Ref m_BlurShader; @@ -135,8 +139,8 @@ namespace Donut int m_Width; int m_Height; - float m_Width_f = 100*10e10f; - float m_Height_f = 75*10e10f; + float m_Width_f = 100.0f*1e10f; + float m_Height_f = 75.0f*1e10f; int m_TargetFPS = 60; float m_CurrentFPS = 60.0f; @@ -150,9 +154,9 @@ namespace Donut int m_MaxStepsMoving = 60000; int m_MaxStepsStatic = 30000; - float m_EarlyExitDistance = 5e11f; + float m_EarlyExitDistance = 5.0e11f; - float m_DiskThickness = 0.1; + float m_DiskThickness = 0.1f; float m_DiskDensity = 0.1f; float m_RotationSpeed = 1.0f; float m_BlurStrength = 2.0f; diff --git a/src/Platform/OpenGL/OpenGLFramebuffer.cpp b/src/Platform/OpenGL/OpenGLFramebuffer.cpp index 523d748..3bb62f6 100644 --- a/src/Platform/OpenGL/OpenGLFramebuffer.cpp +++ b/src/Platform/OpenGL/OpenGLFramebuffer.cpp @@ -123,7 +123,6 @@ namespace Donut bool multisample = m_Specification.Samples > 1; - // Attachments if (m_ColorAttachmentSpecifications.size()) { m_ColorAttachments.resize(m_ColorAttachmentSpecifications.size()); diff --git a/src/Platform/OpenGL/OpenGLShader.h b/src/Platform/OpenGL/OpenGLShader.h index e9a7dc2..99a150b 100644 --- a/src/Platform/OpenGL/OpenGLShader.h +++ b/src/Platform/OpenGL/OpenGLShader.h @@ -32,6 +32,7 @@ namespace Donut virtual void MemoryBarrier(uint32_t barriers) override; virtual const std::string& GetName() const override { return m_Name; } + virtual uint32_t GetRendererID() const override { return m_RendererID; } void UploadUniformInt( const std::string& name, int value); void UploadUniformIntArray(const std::string& name, int* values, uint32_t count); diff --git a/src/Platform/OpenGL/OpenGLTexture.cpp b/src/Platform/OpenGL/OpenGLTexture.cpp index e12dea4..d9033dc 100644 --- a/src/Platform/OpenGL/OpenGLTexture.cpp +++ b/src/Platform/OpenGL/OpenGLTexture.cpp @@ -1,5 +1,13 @@ #include "OpenGLTexture.h" +#include "Rendering/Shader.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include +#include +#include + namespace Donut { OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height) @@ -18,7 +26,6 @@ namespace Donut glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_REPEAT); } - // TODO(Hachem): Texture Loading OpenGLTexture2D::OpenGLTexture2D(const std::string& path) : m_Path(path) { @@ -69,4 +76,176 @@ namespace Donut GLenum access = readOnly ? GL_READ_ONLY : GL_WRITE_ONLY; glBindImageTexture(slot, m_RendererID, 0, GL_FALSE, 0, access, m_InternalFormat); } + + OpenGLCubemapTexture::OpenGLCubemapTexture(uint32_t width, uint32_t height) + : m_Width(width), m_Height(height) + { + m_InternalFormat = GL_RGB16F; + m_DataFormat = GL_RGB; + + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_RendererID); + glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); + + glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + OpenGLCubemapTexture::OpenGLCubemapTexture(const std::string& path) + : m_Path(path) + { + m_Width = 1024; + m_Height = 1024; + m_InternalFormat = GL_RGB16F; + m_DataFormat = GL_RGB; + + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_RendererID); + glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); + + glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + LoadHDRI(path); + } + + OpenGLCubemapTexture::~OpenGLCubemapTexture() + { + glDeleteTextures(1, &m_RendererID); + } + + void OpenGLCubemapTexture::LoadHDRI(const std::string& path) + { + stbi_set_flip_vertically_on_load(true); + int width, height, channels; + float* hdrData = stbi_loadf(path.c_str(), &width, &height, &channels, 3); + + if (!hdrData) + { + DONUT_ERROR("Failed to load HDRI: {}", path); + float defaultSky[6 * 4] = + { + 0.5f, 0.7f, 1.0f, 1.0f, // Right + 0.5f, 0.7f, 1.0f, 1.0f, // Left + 0.5f, 0.7f, 1.0f, 1.0f, // Top + 0.5f, 0.7f, 1.0f, 1.0f, // Bottom + 0.5f, 0.7f, 1.0f, 1.0f, // Front + 0.5f, 0.7f, 1.0f, 1.0f // Back + }; + + for (int i = 0; i < 6; ++i) + glTextureSubImage3D(m_RendererID, 0, 0, 0, i, 1, 1, 1, GL_RGBA, GL_FLOAT, &defaultSky[i * 4]); + return; + } + + ConvertEquirectangularToCubemap(hdrData, width, height); + stbi_image_free(hdrData); + + DONUT_INFO("Successfully loaded HDRI: {} ({}x{})", path, width, height); + } + + void OpenGLCubemapTexture::ConvertEquirectangularToCubemap(float* hdrData, int width, int height) + { + uint32_t captureFBO, captureRBO; + glGenFramebuffers(1, &captureFBO); + glGenRenderbuffers(1, &captureRBO); + + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + glBindRenderbuffer(GL_RENDERBUFFER, captureRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, m_Width, m_Height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO); + + uint32_t hdrTexture; + glGenTextures(1, &hdrTexture); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, hdrData); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + auto equirectShader = Shader::Create("Assets/Shaders/EquirectToCubemap.glsl"); + if (!equirectShader) + { + DONUT_ERROR("Failed to create equirectangular to cubemap shader"); + return; + } + + uint32_t shaderProgram = equirectShader->GetRendererID(); + + float vertices[] = + { + -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f + }; + + uint32_t cubeVAO, cubeVBO; + glGenVertexArrays(1, &cubeVAO); + glGenBuffers(1, &cubeVBO); + glBindVertexArray(cubeVAO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + + glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); + glm::mat4 captureViews[] = + { + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), + glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) + }; + + glUseProgram(shaderProgram); + glUniform1i(glGetUniformLocation(shaderProgram, "equirectangularMap"), 0); + glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, &captureProjection[0][0]); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, hdrTexture); + + glViewport(0, 0, m_Width, m_Height); + glBindFramebuffer(GL_FRAMEBUFFER, captureFBO); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, &captureViews[i][0][0]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_RendererID, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + } + glBindVertexArray(0); + + glDeleteVertexArrays(1, &cubeVAO); + glDeleteBuffers(1, &cubeVBO); + glDeleteProgram(shaderProgram); + glDeleteTextures(1, &hdrTexture); + glDeleteFramebuffers(1, &captureFBO); + glDeleteRenderbuffers(1, &captureRBO); + } + + void OpenGLCubemapTexture::SetData(void* data, uint32_t size) + { + DONUT_WARN("SetData not implemented for cubemaps"); + } + + void OpenGLCubemapTexture::Bind(uint32_t slot) const + { + glBindTextureUnit(slot, m_RendererID); + } + + void OpenGLCubemapTexture::BindAsImage(uint32_t slot, bool readOnly) const + { + GLenum access = readOnly ? GL_READ_ONLY : GL_WRITE_ONLY; + glBindImageTexture(slot, m_RendererID, 0, GL_TRUE, 0, access, m_InternalFormat); + } }; \ No newline at end of file diff --git a/src/Platform/OpenGL/OpenGLTexture.h b/src/Platform/OpenGL/OpenGLTexture.h index 1c3cb05..220df09 100644 --- a/src/Platform/OpenGL/OpenGLTexture.h +++ b/src/Platform/OpenGL/OpenGLTexture.h @@ -34,4 +34,35 @@ namespace Donut uint32_t m_RendererID; GLenum m_InternalFormat, m_DataFormat; }; + + class OpenGLCubemapTexture + : public CubemapTexture + { + public: + OpenGLCubemapTexture(uint32_t width, uint32_t height); + OpenGLCubemapTexture(const std::string& path); + virtual ~OpenGLCubemapTexture(); + + virtual uint32_t GetWidth() const override { return m_Width; } + virtual uint32_t GetHeight() const override { return m_Height; } + virtual uint32_t GetRendererID() const override { return m_RendererID; } + + virtual void SetData(void* data, uint32_t size) override; + virtual void Bind(uint32_t slot = 0) const override; + virtual void BindAsImage(uint32_t slot = 0, bool readOnly = false) const override; + + virtual bool operator==(const Texture& other) const override + { + return m_RendererID == other.GetRendererID(); + } + + private: + void LoadHDRI(const std::string& path); + void ConvertEquirectangularToCubemap(float* hdrData, int width, int height); + + std::string m_Path; + uint32_t m_Width, m_Height; + uint32_t m_RendererID; + GLenum m_InternalFormat, m_DataFormat; + }; } diff --git a/src/Platform/Vulkan/VulkanShader.h b/src/Platform/Vulkan/VulkanShader.h index 2b75331..138a742 100644 --- a/src/Platform/Vulkan/VulkanShader.h +++ b/src/Platform/Vulkan/VulkanShader.h @@ -32,6 +32,7 @@ namespace Donut virtual void MemoryBarrier(uint32_t barriers) override; virtual const std::string& GetName() const override { return m_Name; } + virtual uint32_t GetRendererID() const override { return m_RendererID; } void UploadUniformInt(const std::string& name, int value); void UploadUniformIntArray(const std::string& name, int* values, uint32_t count); diff --git a/src/Platform/Vulkan/VulkanTexture.cpp b/src/Platform/Vulkan/VulkanTexture.cpp index 623e540..4145130 100644 --- a/src/Platform/Vulkan/VulkanTexture.cpp +++ b/src/Platform/Vulkan/VulkanTexture.cpp @@ -5,32 +5,69 @@ namespace Donut VulkanTexture2D::VulkanTexture2D(uint32_t width, uint32_t height) : m_Width(width), m_Height(height) { - // TODO(Hachem): Implement Vulkan texture creation with dimensions + m_InternalFormat = 0; + m_DataFormat = 0; + m_RendererID = 0; } VulkanTexture2D::VulkanTexture2D(const std::string& path) : m_Path(path) { - // TODO(Hachem): Implement Vulkan texture creation from file + m_Width = 1; + m_Height = 1; + m_InternalFormat = 0; + m_DataFormat = 0; + m_RendererID = 0; } VulkanTexture2D::~VulkanTexture2D() { - // TODO(Hachem): Implement Vulkan texture cleanup } void VulkanTexture2D::SetData(void* data, uint32_t size) { - // TODO(Hachem): Implement Vulkan texture data setting } void VulkanTexture2D::Bind(uint32_t slot) const { - // TODO(Hachem): Implement Vulkan texture binding } void VulkanTexture2D::BindAsImage(uint32_t slot, bool readOnly) const { - // TODO(Hachem): Implement Vulkan texture image binding + } + + // Vulkan Cubemap Implementation (Placeholder) + VulkanCubemapTexture::VulkanCubemapTexture(uint32_t width, uint32_t height) + : m_Width(width), m_Height(height) + { + m_InternalFormat = 0; + m_DataFormat = 0; + m_RendererID = 0; + } + + VulkanCubemapTexture::VulkanCubemapTexture(const std::string& path) + : m_Path(path) + { + m_Width = 1024; + m_Height = 1024; + m_InternalFormat = 0; + m_DataFormat = 0; + m_RendererID = 0; + } + + VulkanCubemapTexture::~VulkanCubemapTexture() + { + } + + void VulkanCubemapTexture::SetData(void* data, uint32_t size) + { + } + + void VulkanCubemapTexture::Bind(uint32_t slot) const + { + } + + void VulkanCubemapTexture::BindAsImage(uint32_t slot, bool readOnly) const + { } }; diff --git a/src/Platform/Vulkan/VulkanTexture.h b/src/Platform/Vulkan/VulkanTexture.h index ad711f9..dd9e1df 100644 --- a/src/Platform/Vulkan/VulkanTexture.h +++ b/src/Platform/Vulkan/VulkanTexture.h @@ -30,4 +30,31 @@ namespace Donut uint32_t m_RendererID; uint32_t m_InternalFormat, m_DataFormat; }; + + class VulkanCubemapTexture + : public CubemapTexture + { + public: + VulkanCubemapTexture(uint32_t width, uint32_t height); + VulkanCubemapTexture(const std::string& path); + virtual ~VulkanCubemapTexture(); + + virtual uint32_t GetWidth() const override { return m_Width; } + virtual uint32_t GetHeight() const override { return m_Height; } + virtual uint32_t GetRendererID() const override { return m_RendererID; } + + virtual void SetData(void* data, uint32_t size) override; + virtual void Bind(uint32_t slot = 0) const override; + virtual void BindAsImage(uint32_t slot = 0, bool readOnly = false) const override; + + virtual bool operator==(const Texture& other) const override + { + return m_RendererID == ((VulkanCubemapTexture&)other).m_RendererID; + } + private: + std::string m_Path; + uint32_t m_Width, m_Height; + uint32_t m_RendererID; + uint32_t m_InternalFormat, m_DataFormat; + }; }; diff --git a/src/Rendering/Shader.h b/src/Rendering/Shader.h index e2d8d2a..483102d 100644 --- a/src/Rendering/Shader.h +++ b/src/Rendering/Shader.h @@ -34,6 +34,7 @@ namespace Donut virtual void MemoryBarrier(uint32_t barriers) = 0; virtual const std::string& GetName() const = 0; + virtual uint32_t GetRendererID() const = 0; static Shader* Create(const std::string& filepath); static Shader* Create(const std::string& name, const std::string& vertexSrc, const std::string& fragmentSrc); diff --git a/src/Rendering/Texture.cpp b/src/Rendering/Texture.cpp index a96b816..93b8a75 100644 --- a/src/Rendering/Texture.cpp +++ b/src/Rendering/Texture.cpp @@ -35,4 +35,34 @@ namespace Donut return nullptr; } } + + Ref CubemapTexture::Create(uint32_t width, uint32_t height) + { + switch (Renderer::GetAPI()) + { + case RendererAPI::API::OpenGL: + return CreateRef(width, height); + case RendererAPI::API::Vulkan: + return CreateRef(width, height); + case RendererAPI::API::None: + return nullptr; + default: + return nullptr; + } + } + + Ref CubemapTexture::CreateFromHDRI(const std::string& path) + { + switch (Renderer::GetAPI()) + { + case RendererAPI::API::OpenGL: + return CreateRef(path); + case RendererAPI::API::Vulkan: + return CreateRef(path); + case RendererAPI::API::None: + return nullptr; + default: + return nullptr; + } + } }; \ No newline at end of file diff --git a/src/Rendering/Texture.h b/src/Rendering/Texture.h index 21941ee..3e53b2d 100644 --- a/src/Rendering/Texture.h +++ b/src/Rendering/Texture.h @@ -31,4 +31,12 @@ namespace Donut static Ref Create(uint32_t width, uint32_t height); static Ref Create(const std::string& path); }; + + class CubemapTexture + : public Texture + { + public: + static Ref Create(uint32_t width, uint32_t height); + static Ref CreateFromHDRI(const std::string& path); + }; }; \ No newline at end of file diff --git a/src/States/ConfigState.h b/src/States/ConfigState.h index baee8fa..ad7e238 100644 --- a/src/States/ConfigState.h +++ b/src/States/ConfigState.h @@ -42,7 +42,6 @@ namespace Donut bool m_ShowDebugInfo = false; bool m_EnableAntiAliasing = true; - // Theme settings int m_SelectedTheme = 0; // 0=Dark, 1=Light, 2=Blue }; }; \ No newline at end of file diff --git a/src/States/SimulationState.cpp b/src/States/SimulationState.cpp index 4d388fd..12c9397 100644 --- a/src/States/SimulationState.cpp +++ b/src/States/SimulationState.cpp @@ -1,6 +1,7 @@ #include "SimulationState.h" #include "Rendering/Renderer.h" #include "Core/Application.h" +#include "Core/HDRIManager.h" #include "Core/Window.h" #include "Core/Event.h" #include "Core/SettingsManager.h" @@ -10,6 +11,7 @@ #include #include #include +#include namespace Donut { @@ -27,7 +29,6 @@ namespace Donut engine.SetEarlyExitDistance(settings.simulation.earlyExitDistance); engine.GetGravity() = settings.simulation.gravityEnabled; - // Load accretion disk settings engine.SetDiskThickness(settings.simulation.diskThickness); engine.SetDiskDensity(settings.simulation.diskDensity); engine.SetRotationSpeed(settings.simulation.rotationSpeed); @@ -166,9 +167,7 @@ namespace Donut ImGui::Text("Objects: %zu", engine.GetObjects().size()); if (ImGui::Button("Print Object Info")) - { engine.PrintObjectInfo(); - } int computeHeight = engine.GetComputeHeight(); if (ImGui::SliderInt("Compute Height", &computeHeight, 64, 2048)) @@ -282,6 +281,40 @@ namespace Donut ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "HDRI Environment"); + ImGui::Separator(); + + static int selectedHDRI = 0; + ImGui::PushID("SimulationHDRI"); + auto& hdriManager = HDRIManager::Get(); + const auto& availableHDRI = hdriManager.GetAvailableHDRI(); + + static std::vector hdriOptionNames; + static std::vector hdriOptions; + + if (hdriOptionNames.size() != availableHDRI.size()) + { + hdriOptionNames.clear(); + hdriOptions.clear(); + + for (const auto& path : availableHDRI) + { + hdriOptionNames.push_back(hdriManager.GetHDRIName(path)); + hdriOptions.push_back(hdriOptionNames.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selectedHDRI, hdriOptions.data(), static_cast(hdriOptions.size()))) + { + auto& hdriManager = HDRIManager::Get(); + hdriManager.SetCurrentHDRI(availableHDRI[selectedHDRI]); + engine.SetHDRIEnvironment(hdriManager.GetCurrentHDRI()); + } + ImGui::TextDisabled("HDRI provides background and lighting for the simulation"); + ImGui::PopID(); + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "Camera"); ImGui::Separator(); diff --git a/src/States/WorldBuilderState.cpp b/src/States/WorldBuilderState.cpp index 70ef3d4..5a27ea1 100644 --- a/src/States/WorldBuilderState.cpp +++ b/src/States/WorldBuilderState.cpp @@ -2,6 +2,7 @@ #include "Core/Application.h" #include "Core/Window.h" +#include "Core/HDRIManager.h" #include "Rendering/Renderer.h" #include "Rendering/Shader.h" @@ -23,6 +24,7 @@ #include #include #include +#include namespace Donut { @@ -42,13 +44,29 @@ namespace Donut m_Camera.SetElevation(static_cast(std::numbers::pi) / 3.0f); m_Camera.UpdateOrbital(); - // Initialize rendering resources once m_SphereShader = Ref(Shader::Create("Assets/Shaders/Sphere.glsl")); + m_SkyboxShader = Ref(Shader::Create("Assets/Shaders/Skybox.glsl")); + + if (!m_SphereShader) + DONUT_ERROR("Failed to create sphere shader"); + if (!m_SkyboxShader) + DONUT_ERROR("Failed to create skybox shader"); + InitializeSphereGeometry(); + InitializeSkyboxGeometry(); + + auto& hdriManager = HDRIManager::Get(); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + if (!m_HDRIEnvironment) + { + hdriManager.SetCurrentHDRI("Assets/HDRI/HDR_blue_nebulae-1.hdr"); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + if (!m_HDRIEnvironment) + DONUT_WARN("Failed to load default HDRI for WorldBuilder, using fallback"); + } - // Initialize the black hole at the center - Material blackHoleMaterial(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, 0.0f); // Pure black, no specular, no emission - m_BlackHole = Object(glm::vec3(0.0f, 0.0f, 0.0f), 2.0f, blackHoleMaterial); // Position at origin, radius 2 + Material blackHoleMaterial(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, 0.0f); + m_BlackHole = Object(glm::vec3(0.0f, 0.0f, 0.0f), 2.0f, blackHoleMaterial); m_BlackHoleInitialized = true; m_Initialized = true; @@ -90,8 +108,15 @@ namespace Donut void WorldBuilderState::OnRender() { + auto& hdriManager = HDRIManager::Get(); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + RenderCommand::SetClearColor(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); RenderCommand::Clear(); + + if (m_HDRIEnvironment) + RenderSkybox(); + RenderScene(); } @@ -347,7 +372,6 @@ namespace Donut ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "New Sphere"); ImGui::Separator(); - // Show object count and limit ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "Objects: %zu/16", m_Scene.objs.size()); ImGui::Text("Position:"); @@ -367,7 +391,6 @@ namespace Donut ImGui::Spacing(); - // Disable button if object limit reached if (m_Scene.objs.size() >= 16) { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); @@ -468,6 +491,44 @@ namespace Donut ImGui::Spacing(); ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "White rim around sphere silhouette"); } + + ImGui::Spacing(); + + if (ImGui::CollapsingHeader("HDRI Environment", nullptr)) + { + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "HDRI Settings:"); + + static int selectedHDRI = 0; + ImGui::PushID("WorldBuilderHDRI"); + auto& hdriManager = HDRIManager::Get(); + const auto& availableHDRI = hdriManager.GetAvailableHDRI(); + + static std::vector hdriOptionNames; + static std::vector hdriOptions; + + if (hdriOptionNames.size() != availableHDRI.size()) + { + hdriOptionNames.clear(); + hdriOptions.clear(); + + for (const auto& path : availableHDRI) + { + hdriOptionNames.push_back(hdriManager.GetHDRIName(path)); + hdriOptions.push_back(hdriOptionNames.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selectedHDRI, hdriOptions.data(), static_cast(hdriOptions.size()))) + { + auto& hdriManager = HDRIManager::Get(); + hdriManager.SetCurrentHDRI(availableHDRI[selectedHDRI]); + m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); + } + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "HDRI provides background skybox and lighting for the scene"); + ImGui::PopID(); + } ImGui::Spacing(); ImGui::Separator(); @@ -486,11 +547,8 @@ namespace Donut if (ImGui::Button("Start Simulation", ImVec2(ImGui::GetWindowWidth() - 20, 30))) { - // Load objects from WorldBuilder scene into Engine auto& engine = Application::Get().GetEngine(); engine.LoadObjectsFromScene(m_Scene.objs); - - // Switch to simulation state Application::Get().GetStateManager().SwitchToState("Simulation"); } @@ -551,7 +609,6 @@ namespace Donut void WorldBuilderState::AddSphere() { - // Check if we've reached the object limit (16 objects) if (m_Scene.objs.size() >= 16) { DONUT_WARN("Cannot add more objects. Maximum of 16 objects reached."); @@ -768,6 +825,12 @@ namespace Donut m_SphereShader->SetFloat3("u_OutlineColor", m_OutlineColor); m_SphereShader->SetFloat("u_OutlineWidth", m_OutlineWidth); + if (m_HDRIEnvironment) + { + m_HDRIEnvironment->Bind(1); + m_SphereShader->SetInt("u_HDRIEnvironment", 1); + } + if (m_BlackHoleInitialized) { glm::mat4 blackHoleTransform = glm::translate(glm::mat4(1.0f), m_BlackHole.m_Centre); @@ -818,4 +881,94 @@ namespace Donut RenderCommand::DisableDepthTest(); } + + + + void WorldBuilderState::InitializeSkyboxGeometry() + { + float skyboxVertices[] = + { + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f + }; + + auto vertexBuffer = Ref(VertexBuffer::Create(skyboxVertices, sizeof(skyboxVertices))); + VertexBufferLayout layout; + layout.Push(3); + vertexBuffer->SetLayout(layout); + + m_SkyboxVAO = Ref(VertexArray::Create()); + m_SkyboxVAO->AddVertexBuffer(vertexBuffer); + } + + void WorldBuilderState::RenderSkybox() + { + if (!m_SkyboxShader || !m_SkyboxVAO || !m_HDRIEnvironment) + { + return; + } + + GLFWwindow* window = static_cast(Application::Get().GetWindow().GetNativeWindow()); + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + RenderCommand::SetViewport(0, 0, width, height); + RenderCommand::DisableDepthTest(); + + glm::mat4 view = m_Camera.GetViewMatrix(); + glm::mat4 projection = m_Camera.GetProjectionMatrix(); + + view = glm::mat4(glm::mat3(view)); + + m_SkyboxShader->Bind(); + m_SkyboxShader->SetMat4("u_View", view); + m_SkyboxShader->SetMat4("u_Projection", projection); + + m_HDRIEnvironment->Bind(0); + m_SkyboxShader->SetInt("u_Skybox", 0); + + m_SkyboxVAO->Bind(); + RenderCommand::DrawArrays(36); + + RenderCommand::EnableDepthTest(); + } }; diff --git a/src/States/WorldBuilderState.h b/src/States/WorldBuilderState.h index cbe8b9e..29662db 100644 --- a/src/States/WorldBuilderState.h +++ b/src/States/WorldBuilderState.h @@ -47,6 +47,10 @@ namespace Donut Ref m_SphereShader; Ref m_SphereVAO; + Ref m_HDRIEnvironment; + + Ref m_SkyboxShader; + Ref m_SkyboxVAO; glm::vec3 m_NewObjectPosition = glm::vec3(0.0f, 0.0f, 0.0f); float m_NewObjectRadius = 1.0f; @@ -72,5 +76,11 @@ namespace Donut Object m_BlackHole; bool m_BlackHoleInitialized = false; + + void SetHDRIEnvironment(Ref hdri) { m_HDRIEnvironment = hdri; } + Ref GetHDRIEnvironment() const { return m_HDRIEnvironment; } + + void InitializeSkyboxGeometry(); + void RenderSkybox(); }; }; -- cgit v1.3