diff options
| author | hachem <im@hachem.wtf> | 2025-08-22 12:24:05 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2025-08-22 12:24:05 +0200 |
| commit | 2c8002d30f291cba832a5750212009d48238c308 (patch) | |
| tree | 4aafc468441b3e32c267040830b8e0b5af859faf /src | |
| parent | b9de06951631d9969c52c6ed95de17a6d8aad121 (diff) | |
feat: add HDRI
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hdri_manager.cpp | 64 | ||||
| -rw-r--r-- | src/core/hdri_manager.h | 46 | ||||
| -rw-r--r-- | src/engine/engine.cpp | 20 | ||||
| -rw-r--r-- | src/engine/engine.h | 28 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_shader.h | 1 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_texture.cpp | 180 | ||||
| -rw-r--r-- | src/platform/opengl/opengl_texture.h | 31 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_shader.h | 1 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_texture.cpp | 42 | ||||
| -rw-r--r-- | src/platform/vulkan/vulkan_texture.h | 27 | ||||
| -rw-r--r-- | src/rendering/shader.h | 1 | ||||
| -rw-r--r-- | src/rendering/texture.cpp | 30 | ||||
| -rw-r--r-- | src/rendering/texture.h | 8 | ||||
| -rw-r--r-- | src/states/simulation_state.cpp | 38 | ||||
| -rw-r--r-- | src/states/world_builder_state.cpp | 162 | ||||
| -rw-r--r-- | src/states/world_builder_state.h | 10 |
16 files changed, 673 insertions, 16 deletions
diff --git a/src/core/hdri_manager.cpp b/src/core/hdri_manager.cpp new file mode 100644 index 0000000..1a5521e --- /dev/null +++ b/src/core/hdri_manager.cpp @@ -0,0 +1,64 @@ +#include "hdri_manager.h" +#include <filesystem> + +namespace Donut +{ + Ref<CubemapTexture> HDRIManager::load_hdri(const std::string& path) + { + auto it = m_hdri_cache.find(path); + if (it != m_hdri_cache.end()) + { + DONUT_INFO("HDRI already cached: {}", path); + return it->second; + } + + DONUT_INFO("Loading HDRI: {}", path); + auto hdri = CubemapTexture::create_from_hdri(path); + + if (hdri) + { + m_hdri_cache[path] = hdri; + DONUT_INFO("Successfully loaded and cached HDRI: {}", path); + } + else + DONUT_ERROR("Failed to load HDRI: {}", path); + + return hdri; + } + + void HDRIManager::set_current_hdri(const std::string& path) + { + auto hdri = load_hdri(path); + if (hdri) + { + m_current_hdri = hdri; + DONUT_INFO("Set current HDRI to: {}", path); + } + else + DONUT_ERROR("Failed to set current HDRI: {}", path); + } + + std::string HDRIManager::get_hdri_name(const std::string& path) const + { + std::filesystem::path file_path(path); + std::string filename = file_path.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::clear_cache() + { + m_hdri_cache.clear(); + m_current_hdri.reset(); + DONUT_INFO("HDRI cache cleared"); + } +}; diff --git a/src/core/hdri_manager.h b/src/core/hdri_manager.h new file mode 100644 index 0000000..5ccd402 --- /dev/null +++ b/src/core/hdri_manager.h @@ -0,0 +1,46 @@ +#pragma once + +#include "core/log.h" +#include "rendering/texture.h" + +#include <string> +#include <unordered_map> +#include <memory> + +namespace Donut +{ + class HDRIManager + { + public: + static HDRIManager& get() + { + static HDRIManager instance; + return instance; + } + + Ref<CubemapTexture> load_hdri(const std::string& path); + Ref<CubemapTexture> get_current_hdri() const { return m_current_hdri; } + + void set_current_hdri(const std::string& path); + const std::vector<std::string>& get_available_hdri() const { return m_available_hdri; } + std::string get_hdri_name(const std::string& path) const; + void clear_cache(); + private: + HDRIManager() = default; + ~HDRIManager() = default; + + HDRIManager(const HDRIManager&) = delete; + HDRIManager& operator=(const HDRIManager&) = delete; + + std::unordered_map<std::string, Ref<CubemapTexture>> m_hdri_cache; + + Ref<CubemapTexture> m_current_hdri; + + std::vector<std::string> m_available_hdri = + { + "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 9a88e9e..451684b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -10,6 +10,7 @@ #include "engine.h" #include "core/log.h" +#include "core/hdri_manager.h" #include "rendering/vertex_buffer.h" #include "rendering/index_buffer.h" @@ -36,6 +37,16 @@ namespace Donut m_compute_program = create_compute_program("assets/shaders/geodesic.glsl"); m_blur_shader = Ref<Shader>(Shader::create("assets/shaders/blur.glsl")); + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + { + hdri_manager.set_current_hdri("assets/hdri/hdr_blue_nebulae_1.hdr"); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + DONUT_WARN("Failed to load default HDRI, using fallback"); + } + m_camera_ubo = UniformBuffer::create(128, 1); m_disk_ubo = UniformBuffer::create(sizeof(float) * 5, 2); @@ -117,6 +128,9 @@ namespace Donut void Engine::dispatch_compute(const Camera& cam) { + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + int cw = get_compute_width(); int ch = m_compute_height; @@ -128,7 +142,10 @@ namespace Donut upload_objects_ubo(m_objects); upload_simulation_ubo(); m_texture->bind_as_image(0, false); - + + if (m_hdri_environment) + m_hdri_environment->bind(5); + uint32_t groups_x = static_cast<uint32_t>(std::ceil(cw / 16.0f)); uint32_t groups_y = static_cast<uint32_t>(std::ceil(ch / 16.0f)); m_compute_program->dispatch(groups_x, groups_y, 1); @@ -508,4 +525,5 @@ namespace Donut RenderCommand::set_viewport(0, 0, original_width, original_height); } + } diff --git a/src/engine/engine.h b/src/engine/engine.h index 95cd1f0..88e0058 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -101,30 +101,34 @@ namespace Donut void set_max_steps_static(int steps) { m_max_steps_static = steps; } void set_early_exit_distance(float distance) { m_early_exit_distance = distance; } - float get_disk_thickness() const { return m_disk_thickness; } + float get_disk_thickness() const { return m_disk_thickness; } void set_disk_thickness(float thickness) { m_disk_thickness = thickness; } - float get_disk_density() const { return m_disk_density; } - void set_disk_density(float density) { m_disk_density = density; } + float get_disk_density() const { return m_disk_density; } + void set_disk_density(float density) { m_disk_density = density; } - float get_rotation_speed() const { return m_rotation_speed; } - void set_rotation_speed(float speed) { m_rotation_speed = speed; } + float get_rotation_speed() const { return m_rotation_speed; } + void set_rotation_speed(float speed) { m_rotation_speed = speed; } - float get_blur_strength() const { return m_blur_strength; } - void set_blur_strength(float strength) { m_blur_strength = strength; } + float get_blur_strength() const { return m_blur_strength; } + void set_blur_strength(float strength) { m_blur_strength = strength; } - float get_glow_intensity() const { return m_glow_intensity; } + float get_glow_intensity() const { return m_glow_intensity; } void set_glow_intensity(float intensity) { m_glow_intensity = intensity; } void load_objects_from_scene(const std::vector<Donut::Object>& objects); void export_high_res_frame(const std::string& filename, int width = 4096, int height = 3072); void print_object_info() const; + + void set_hdri_environment(Ref<CubemapTexture> hdri) { m_hdri_environment = hdri; } + Ref<CubemapTexture> get_hdri_environment() const { return m_hdri_environment; } private: Ref<Shader> create_compute_program(const char* path); std::pair<Ref<VertexArray>, Ref<Texture2D>> quad_vao(); private: Ref<VertexArray> m_quad_vao; Ref<Texture2D> m_texture; + Ref<CubemapTexture> m_hdri_environment; Ref<Shader> m_shader_program; Ref<Shader> m_compute_program; Ref<Shader> m_blur_shader; @@ -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_target_fps = 60; float m_current_fps = 60.0f; @@ -150,9 +154,9 @@ namespace Donut int m_max_steps_moving = 60000; int m_max_steps_static = 30000; - float m_early_exit_distance = 5e11f; + float m_early_exit_distance = 5.0e11f; - float m_disk_thickness = 0.1; + float m_disk_thickness = 0.1f; float m_disk_density = 0.1f; float m_rotation_speed = 1.0f; float m_blur_strength = 2.0f; diff --git a/src/platform/opengl/opengl_shader.h b/src/platform/opengl/opengl_shader.h index 6b1ceec..71e20a5 100644 --- a/src/platform/opengl/opengl_shader.h +++ b/src/platform/opengl/opengl_shader.h @@ -32,6 +32,7 @@ namespace Donut virtual void memory_barrier(uint32_t barriers) override; virtual const std::string& get_name() const override { return m_name; } + virtual uint32_t get_renderer_id() const override { return m_renderer_id; } void upload_uniform_int( const std::string& name, int value); void upload_uniform_int_array(const std::string& name, int* values, uint32_t count); diff --git a/src/platform/opengl/opengl_texture.cpp b/src/platform/opengl/opengl_texture.cpp index 5d05fb4..14ae214 100644 --- a/src/platform/opengl/opengl_texture.cpp +++ b/src/platform/opengl/opengl_texture.cpp @@ -1,5 +1,13 @@ #include "opengl_texture.h" +#include "rendering/shader.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> + namespace Donut { OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height) @@ -68,4 +76,176 @@ namespace Donut GLenum access = read_only ? GL_READ_ONLY : GL_WRITE_ONLY; glBindImageTexture(slot, m_renderer_id, 0, GL_FALSE, 0, access, m_internal_format); } + + OpenGLCubemapTexture::OpenGLCubemapTexture(uint32_t width, uint32_t height) + : m_width(width), m_height(height) + { + m_internal_format = GL_RGB16F; + m_data_format = GL_RGB; + + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_renderer_id); + glTextureStorage2D(m_renderer_id, 1, m_internal_format, m_width, m_height); + + glTextureParameteri(m_renderer_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(m_renderer_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + OpenGLCubemapTexture::OpenGLCubemapTexture(const std::string& path) + : m_path(path) + { + m_width = 1024; + m_height = 1024; + m_internal_format = GL_RGB16F; + m_data_format = GL_RGB; + + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_renderer_id); + glTextureStorage2D(m_renderer_id, 1, m_internal_format, m_width, m_height); + + glTextureParameteri(m_renderer_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(m_renderer_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(m_renderer_id, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + load_hdri(path); + } + + OpenGLCubemapTexture::~OpenGLCubemapTexture() + { + glDeleteTextures(1, &m_renderer_id); + } + + void OpenGLCubemapTexture::load_hdri(const std::string& path) + { + stbi_set_flip_vertically_on_load(true); + int width, height, channels; + float* hdr_data = stbi_loadf(path.c_str(), &width, &height, &channels, 3); + + if (!hdr_data) + { + DONUT_ERROR("Failed to load HDRI: {}", path); + float default_sky[6 * 4] = + { + 0.5f, 0.7f, 1.0f, 1.0f, + 0.5f, 0.7f, 1.0f, 1.0f, + 0.5f, 0.7f, 1.0f, 1.0f, + 0.5f, 0.7f, 1.0f, 1.0f, + 0.5f, 0.7f, 1.0f, 1.0f, + 0.5f, 0.7f, 1.0f, 1.0f + }; + + for (int i = 0; i < 6; ++i) + glTextureSubImage3D(m_renderer_id, 0, 0, 0, i, 1, 1, 1, GL_RGBA, GL_FLOAT, &default_sky[i * 4]); + return; + } + + convert_equirectangular_to_cubemap(hdr_data, width, height); + stbi_image_free(hdr_data); + + DONUT_INFO("Successfully loaded HDRI: {} ({}x{})", path, width, height); + } + + void OpenGLCubemapTexture::convert_equirectangular_to_cubemap(float* hdr_data, int width, int height) + { + uint32_t capture_fbo, capture_rbo; + glGenFramebuffers(1, &capture_fbo); + glGenRenderbuffers(1, &capture_rbo); + + glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo); + glBindRenderbuffer(GL_RENDERBUFFER, capture_rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, m_width, m_height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, capture_rbo); + + uint32_t hdr_texture; + glGenTextures(1, &hdr_texture); + glBindTexture(GL_TEXTURE_2D, hdr_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, hdr_data); + 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 equirect_shader = Shader::create("assets/shaders/equirect_to_cubemap.glsl"); + if (!equirect_shader) + { + DONUT_ERROR("Failed to create equirectangular to cubemap shader"); + return; + } + + uint32_t shader_program = equirect_shader->get_renderer_id(); + + 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 cube_vao, cube_vbo; + glGenVertexArrays(1, &cube_vao); + glGenBuffers(1, &cube_vbo); + glBindVertexArray(cube_vao); + glBindBuffer(GL_ARRAY_BUFFER, cube_vbo); + 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 capture_projection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); + glm::mat4 capture_views[] = + { + 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(shader_program); + glUniform1i(glGetUniformLocation(shader_program, "equirectangularMap"), 0); + glUniformMatrix4fv(glGetUniformLocation(shader_program, "projection"), 1, GL_FALSE, &capture_projection[0][0]); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, hdr_texture); + + glViewport(0, 0, m_width, m_height); + glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo); + for (unsigned int i = 0; i < 6; ++i) + { + glUniformMatrix4fv(glGetUniformLocation(shader_program, "view"), 1, GL_FALSE, &capture_views[i][0][0]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_renderer_id, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glBindVertexArray(cube_vao); + glDrawArrays(GL_TRIANGLES, 0, 36); + } + glBindVertexArray(0); + + glDeleteVertexArrays(1, &cube_vao); + glDeleteBuffers(1, &cube_vbo); + glDeleteProgram(shader_program); + glDeleteTextures(1, &hdr_texture); + glDeleteFramebuffers(1, &capture_fbo); + glDeleteRenderbuffers(1, &capture_rbo); + } + + void OpenGLCubemapTexture::set_data(void* data, uint32_t size) + { + DONUT_WARN("SetData not implemented for cubemaps"); + } + + void OpenGLCubemapTexture::bind(uint32_t slot) const + { + glBindTextureUnit(slot, m_renderer_id); + } + + void OpenGLCubemapTexture::bind_as_image(uint32_t slot, bool read_only) const + { + GLenum access = read_only ? GL_READ_ONLY : GL_WRITE_ONLY; + glBindImageTexture(slot, m_renderer_id, 0, GL_TRUE, 0, access, m_internal_format); + } }; diff --git a/src/platform/opengl/opengl_texture.h b/src/platform/opengl/opengl_texture.h index 705d770..5e5f6b2 100644 --- a/src/platform/opengl/opengl_texture.h +++ b/src/platform/opengl/opengl_texture.h @@ -34,4 +34,35 @@ namespace Donut uint32_t m_renderer_id; GLenum m_internal_format, m_data_format; }; + + class OpenGLCubemapTexture + : public CubemapTexture + { + public: + OpenGLCubemapTexture(uint32_t width, uint32_t height); + OpenGLCubemapTexture(const std::string& path); + virtual ~OpenGLCubemapTexture(); + + virtual uint32_t get_width() const override { return m_width; } + virtual uint32_t get_height() const override { return m_height; } + virtual uint32_t get_renderer_id() const override { return m_renderer_id; } + + virtual void set_data(void* data, uint32_t size) override; + virtual void bind(uint32_t slot = 0) const override; + virtual void bind_as_image(uint32_t slot = 0, bool read_only = false) const override; + + virtual bool operator==(const Texture& other) const override + { + return m_renderer_id == other.get_renderer_id(); + } + + private: + void load_hdri(const std::string& path); + void convert_equirectangular_to_cubemap(float* hdr_data, int width, int height); + + std::string m_path; + uint32_t m_width, m_height; + uint32_t m_renderer_id; + GLenum m_internal_format, m_data_format; + }; } diff --git a/src/platform/vulkan/vulkan_shader.h b/src/platform/vulkan/vulkan_shader.h index fdf9e65..1619522 100644 --- a/src/platform/vulkan/vulkan_shader.h +++ b/src/platform/vulkan/vulkan_shader.h @@ -32,6 +32,7 @@ namespace Donut virtual void memory_barrier(uint32_t barriers) override; virtual const std::string& get_name() const override { return m_name; } + virtual uint32_t get_renderer_id() const override { return m_renderer_id; } void upload_uniform_int(const std::string& name, int value); void upload_uniform_int_array(const std::string& name, int* values, uint32_t count); diff --git a/src/platform/vulkan/vulkan_texture.cpp b/src/platform/vulkan/vulkan_texture.cpp index e63468d..d2b1e20 100644 --- a/src/platform/vulkan/vulkan_texture.cpp +++ b/src/platform/vulkan/vulkan_texture.cpp @@ -5,11 +5,19 @@ namespace Donut VulkanTexture2D::VulkanTexture2D(uint32_t width, uint32_t height) : m_width(width), m_height(height) { + m_internal_format = 0; + m_data_format = 0; + m_renderer_id = 0; } VulkanTexture2D::VulkanTexture2D(const std::string& path) : m_path(path) { + m_width = 1; + m_height = 1; + m_internal_format = 0; + m_data_format = 0; + m_renderer_id = 0; } VulkanTexture2D::~VulkanTexture2D() @@ -27,4 +35,38 @@ namespace Donut void VulkanTexture2D::bind_as_image(uint32_t slot, bool read_only) const { } + + VulkanCubemapTexture::VulkanCubemapTexture(uint32_t width, uint32_t height) + : m_width(width), m_height(height) + { + m_internal_format = 0; + m_data_format = 0; + m_renderer_id = 0; + } + + VulkanCubemapTexture::VulkanCubemapTexture(const std::string& path) + : m_path(path) + { + m_width = 1024; + m_height = 1024; + m_internal_format = 0; + m_data_format = 0; + m_renderer_id = 0; + } + + VulkanCubemapTexture::~VulkanCubemapTexture() + { + } + + void VulkanCubemapTexture::set_data(void* data, uint32_t size) + { + } + + void VulkanCubemapTexture::bind(uint32_t slot) const + { + } + + void VulkanCubemapTexture::bind_as_image(uint32_t slot, bool read_only) const + { + } }; diff --git a/src/platform/vulkan/vulkan_texture.h b/src/platform/vulkan/vulkan_texture.h index a9ac920..d1b5dc3 100644 --- a/src/platform/vulkan/vulkan_texture.h +++ b/src/platform/vulkan/vulkan_texture.h @@ -30,4 +30,31 @@ namespace Donut uint32_t m_renderer_id; uint32_t m_internal_format, m_data_format; }; + + class VulkanCubemapTexture + : public CubemapTexture + { + public: + VulkanCubemapTexture(uint32_t width, uint32_t height); + VulkanCubemapTexture(const std::string& path); + virtual ~VulkanCubemapTexture(); + + virtual uint32_t get_width() const override { return m_width; } + virtual uint32_t get_height() const override { return m_height; } + virtual uint32_t get_renderer_id() const override { return m_renderer_id; } + + virtual void set_data(void* data, uint32_t size) override; + virtual void bind(uint32_t slot = 0) const override; + virtual void bind_as_image(uint32_t slot = 0, bool read_only = false) const override; + + virtual bool operator==(const Texture& other) const override + { + return m_renderer_id == ((VulkanCubemapTexture&)other).m_renderer_id; + } + private: + std::string m_path; + uint32_t m_width, m_height; + uint32_t m_renderer_id; + uint32_t m_internal_format, m_data_format; + }; }; diff --git a/src/rendering/shader.h b/src/rendering/shader.h index 814f0cf..3e05132 100644 --- a/src/rendering/shader.h +++ b/src/rendering/shader.h @@ -34,6 +34,7 @@ namespace Donut virtual void memory_barrier(uint32_t barriers) = 0; virtual const std::string& get_name() const = 0; + virtual uint32_t get_renderer_id() const = 0; static Shader* create(const std::string& filepath); static Shader* create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); diff --git a/src/rendering/texture.cpp b/src/rendering/texture.cpp index e14c09c..a33edd0 100644 --- a/src/rendering/texture.cpp +++ b/src/rendering/texture.cpp @@ -35,4 +35,34 @@ namespace Donut return nullptr; } } + + Ref<CubemapTexture> CubemapTexture::create(uint32_t width, uint32_t height) + { + switch (Renderer::get_api()) + { + case RendererAPI::API::OpenGL: + return create_ref<OpenGLCubemapTexture>(width, height); + case RendererAPI::API::Vulkan: + return create_ref<VulkanCubemapTexture>(width, height); + case RendererAPI::API::None: + return nullptr; + default: + return nullptr; + } + } + + Ref<CubemapTexture> CubemapTexture::create_from_hdri(const std::string& path) + { + switch (Renderer::get_api()) + { + case RendererAPI::API::OpenGL: + return create_ref<OpenGLCubemapTexture>(path); + case RendererAPI::API::Vulkan: + return create_ref<VulkanCubemapTexture>(path); + case RendererAPI::API::None: + return nullptr; + default: + return nullptr; + } + } }; diff --git a/src/rendering/texture.h b/src/rendering/texture.h index cc5c59e..195605c 100644 --- a/src/rendering/texture.h +++ b/src/rendering/texture.h @@ -31,4 +31,12 @@ namespace Donut static Ref<Texture2D> create(uint32_t width, uint32_t height); static Ref<Texture2D> create(const std::string& path); }; + + class CubemapTexture + : public Texture + { + public: + static Ref<CubemapTexture> create(uint32_t width, uint32_t height); + static Ref<CubemapTexture> create_from_hdri(const std::string& path); + }; }; diff --git a/src/states/simulation_state.cpp b/src/states/simulation_state.cpp index 9f1e0a7..540f236 100644 --- a/src/states/simulation_state.cpp +++ b/src/states/simulation_state.cpp @@ -1,6 +1,7 @@ #include "simulation_state.h" #include "rendering/renderer.h" #include "core/application.h" +#include "core/hdri_manager.h" #include "core/window.h" #include "core/event.h" #include "core/settings_manager.h" @@ -10,6 +11,7 @@ #include <chrono> #include <iomanip> #include <sstream> +#include <vector> namespace Donut { @@ -165,9 +167,7 @@ namespace Donut ImGui::Text("Objects: %zu", engine.get_objects().size()); if (ImGui::Button("Print Object Info")) - { engine.print_object_info(); - } int compute_height = engine.get_compute_height(); if (ImGui::SliderInt("Compute Height", &compute_height, 64, 2048)) @@ -281,6 +281,40 @@ namespace Donut ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.9f, 0.9f, 1.0f, 1.0f), "HDRI Environment"); + ImGui::Separator(); + + static int selected_hdri = 0; + ImGui::PushID("SimulationHDRI"); + auto& hdri_manager = HDRIManager::get(); + const auto& available_hdri = hdri_manager.get_available_hdri(); + + static std::vector<std::string> hdri_option_names; + static std::vector<const char*> hdri_options; + + if (hdri_option_names.size() != available_hdri.size()) + { + hdri_option_names.clear(); + hdri_options.clear(); + + for (const auto& path : available_hdri) + { + hdri_option_names.push_back(hdri_manager.get_hdri_name(path)); + hdri_options.push_back(hdri_option_names.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selected_hdri, hdri_options.data(), static_cast<int>(hdri_options.size()))) + { + auto& hdri_manager = HDRIManager::get(); + hdri_manager.set_current_hdri(available_hdri[selected_hdri]); + engine.set_hdri_environment(hdri_manager.get_current_hdri()); + } + 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/world_builder_state.cpp b/src/states/world_builder_state.cpp index f28f70a..c0999d4 100644 --- a/src/states/world_builder_state.cpp +++ b/src/states/world_builder_state.cpp @@ -2,6 +2,7 @@ #include "core/application.h" #include "core/window.h" +#include "core/hdri_manager.h" #include "rendering/renderer.h" #include "rendering/shader.h" @@ -23,6 +24,7 @@ #include <fstream> #include <sstream> #include <limits> +#include <vector> namespace Donut { @@ -43,7 +45,25 @@ namespace Donut m_camera.update_orbital(); m_sphere_shader = Ref<Shader>(Shader::create("assets/shaders/sphere.glsl")); + m_skybox_shader = Ref<Shader>(Shader::create("assets/shaders/skybox.glsl")); + + if (!m_sphere_shader) + DONUT_ERROR("Failed to create sphere shader"); + if (!m_skybox_shader) + DONUT_ERROR("Failed to create skybox shader"); + initialize_sphere_geometry(); + initialize_skybox_geometry(); + + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + { + hdri_manager.set_current_hdri("assets/hdri/hdr_blue_nebulae_1.hdr"); + m_hdri_environment = hdri_manager.get_current_hdri(); + if (!m_hdri_environment) + DONUT_WARN("Failed to load default HDRI for WorldBuilder, using fallback"); + } Material black_hole_material(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, 0.0f); m_black_hole = Object(glm::vec3(0.0f, 0.0f, 0.0f), 2.0f, black_hole_material); @@ -88,8 +108,15 @@ namespace Donut void WorldBuilderState::on_render() { + auto& hdri_manager = HDRIManager::get(); + m_hdri_environment = hdri_manager.get_current_hdri(); + RenderCommand::set_clear_color(glm::vec4(0.1f, 0.1f, 0.1f, 1.0f)); RenderCommand::clear(); + + if (m_hdri_environment) + render_skybox(); + render_scene(); } @@ -464,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 selected_hdri = 0; + ImGui::PushID("WorldBuilderHDRI"); + auto& hdri_manager = HDRIManager::get(); + const auto& available_hdri = hdri_manager.get_available_hdri(); + + static std::vector<std::string> hdri_option_names; + static std::vector<const char*> hdri_options; + + if (hdri_option_names.size() != available_hdri.size()) + { + hdri_option_names.clear(); + hdri_options.clear(); + + for (const auto& path : available_hdri) + { + hdri_option_names.push_back(hdri_manager.get_hdri_name(path)); + hdri_options.push_back(hdri_option_names.back().c_str()); + } + } + + if (ImGui::Combo("HDRI Environment", &selected_hdri, hdri_options.data(), static_cast<int>(hdri_options.size()))) + { + auto& hdri_manager = HDRIManager::get(); + hdri_manager.set_current_hdri(available_hdri[selected_hdri]); + m_hdri_environment = hdri_manager.get_current_hdri(); + } + + 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(); @@ -484,7 +549,6 @@ namespace Donut { auto& engine = Application::get().get_engine(); engine.load_objects_from_scene(m_scene.objs); - Application::get().get_state_manager().switch_to_state("Simulation"); } @@ -761,6 +825,12 @@ namespace Donut m_sphere_shader->set_float3("u_OutlineColor", m_outline_color); m_sphere_shader->set_float("u_OutlineWidth", m_outline_width); + if (m_hdri_environment) + { + m_hdri_environment->bind(1); + m_sphere_shader->set_int("u_HDRIEnvironment", 1); + } + if (m_black_hole_initialized) { glm::mat4 black_hole_transform = glm::translate(glm::mat4(1.0f), m_black_hole.m_centre); @@ -811,4 +881,94 @@ namespace Donut RenderCommand::disable_depth_test(); } + + + + void WorldBuilderState::initialize_skybox_geometry() + { + float skybox_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 + }; + + auto vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(skybox_vertices, sizeof(skybox_vertices))); + VertexBufferLayout layout; + layout.push<float>(3); + vertex_buffer->set_layout(layout); + + m_skybox_vao = Ref<VertexArray>(VertexArray::create()); + m_skybox_vao->add_vertex_buffer(vertex_buffer); + } + + void WorldBuilderState::render_skybox() + { + if (!m_skybox_shader || !m_skybox_vao || !m_hdri_environment) + { + return; + } + + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + RenderCommand::set_viewport(0, 0, width, height); + RenderCommand::disable_depth_test(); + + glm::mat4 view = m_camera.get_view_matrix(); + glm::mat4 projection = m_camera.get_projection_matrix(); + + view = glm::mat4(glm::mat3(view)); + + m_skybox_shader->bind(); + m_skybox_shader->set_mat4("u_View", view); + m_skybox_shader->set_mat4("u_Projection", projection); + + m_hdri_environment->bind(0); + m_skybox_shader->set_int("u_Skybox", 0); + + m_skybox_vao->bind(); + RenderCommand::draw_arrays(36); + + RenderCommand::enable_depth_test(); + } }; diff --git a/src/states/world_builder_state.h b/src/states/world_builder_state.h index 866ae59..edfb346 100644 --- a/src/states/world_builder_state.h +++ b/src/states/world_builder_state.h @@ -47,6 +47,10 @@ namespace Donut Ref<Shader> m_sphere_shader; Ref<VertexArray> m_sphere_vao; + Ref<CubemapTexture> m_hdri_environment; + + Ref<Shader> m_skybox_shader; + Ref<VertexArray> m_skybox_vao; glm::vec3 m_new_object_position = glm::vec3(0.0f, 0.0f, 0.0f); float m_new_object_radius = 1.0f; @@ -72,5 +76,11 @@ namespace Donut Object m_black_hole; bool m_black_hole_initialized = false; + + void set_hdri_environment(Ref<CubemapTexture> hdri) { m_hdri_environment = hdri; } + Ref<CubemapTexture> get_hdri_environment() const { return m_hdri_environment; } + + void initialize_skybox_geometry(); + void render_skybox(); }; }; |
