hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2025-08-22 12:24:05 +0200
committerhachem <im@hachem.wtf>2025-08-22 12:24:05 +0200
commit2c8002d30f291cba832a5750212009d48238c308 (patch)
tree4aafc468441b3e32c267040830b8e0b5af859faf
parentb9de06951631d9969c52c6ed95de17a6d8aad121 (diff)
feat: add HDRI
-rw-r--r--assets/hdri/hdr_blue_nebulae_1.hdrbin0 -> 62208386 bytes
-rw-r--r--assets/hdri/hdr_subdued_blue_nebulae.hdrbin0 -> 62041084 bytes
-rw-r--r--assets/hdri/hdr_subdued_multi_nebulae.hdrbin0 -> 64541302 bytes
-rw-r--r--assets/shaders/equirect_to_cubemap.glsl33
-rw-r--r--assets/shaders/geodesic.glsl12
-rw-r--r--assets/shaders/skybox.glsl31
-rw-r--r--assets/shaders/sphere.glsl6
-rw-r--r--branding/screenshot_1.pngbin0 -> 566012 bytes
-rw-r--r--branding/screenshot_2.png (renamed from branding/export.png)bin916806 -> 916806 bytes
-rw-r--r--src/core/hdri_manager.cpp64
-rw-r--r--src/core/hdri_manager.h46
-rw-r--r--src/engine/engine.cpp20
-rw-r--r--src/engine/engine.h28
-rw-r--r--src/platform/opengl/opengl_shader.h1
-rw-r--r--src/platform/opengl/opengl_texture.cpp180
-rw-r--r--src/platform/opengl/opengl_texture.h31
-rw-r--r--src/platform/vulkan/vulkan_shader.h1
-rw-r--r--src/platform/vulkan/vulkan_texture.cpp42
-rw-r--r--src/platform/vulkan/vulkan_texture.h27
-rw-r--r--src/rendering/shader.h1
-rw-r--r--src/rendering/texture.cpp30
-rw-r--r--src/rendering/texture.h8
-rw-r--r--src/states/simulation_state.cpp38
-rw-r--r--src/states/world_builder_state.cpp162
-rw-r--r--src/states/world_builder_state.h10
25 files changed, 753 insertions, 18 deletions
diff --git a/assets/hdri/hdr_blue_nebulae_1.hdr b/assets/hdri/hdr_blue_nebulae_1.hdr
new file mode 100644
index 0000000..f34b25d
--- /dev/null
+++ b/assets/hdri/hdr_blue_nebulae_1.hdr
Binary files differ
diff --git a/assets/hdri/hdr_subdued_blue_nebulae.hdr b/assets/hdri/hdr_subdued_blue_nebulae.hdr
new file mode 100644
index 0000000..ea34298
--- /dev/null
+++ b/assets/hdri/hdr_subdued_blue_nebulae.hdr
Binary files differ
diff --git a/assets/hdri/hdr_subdued_multi_nebulae.hdr b/assets/hdri/hdr_subdued_multi_nebulae.hdr
new file mode 100644
index 0000000..da88396
--- /dev/null
+++ b/assets/hdri/hdr_subdued_multi_nebulae.hdr
Binary files differ
diff --git a/assets/shaders/equirect_to_cubemap.glsl b/assets/shaders/equirect_to_cubemap.glsl
new file mode 100644
index 0000000..2b99c9b
--- /dev/null
+++ b/assets/shaders/equirect_to_cubemap.glsl
@@ -0,0 +1,33 @@
+#type vertex
+
+#version 330 core
+layout (location = 0) in vec3 aPos;
+out vec3 WorldPos;
+uniform mat4 projection;
+uniform mat4 view;
+void main()
+{
+ WorldPos = aPos;
+ gl_Position = projection * view * vec4(WorldPos, 1.0);
+}
+
+#type fragment
+
+#version 330 core
+out vec4 FragColor;
+in vec3 WorldPos;
+uniform sampler2D equirectangularMap;
+const vec2 invAtan = vec2(0.1591, 0.3183);
+vec2 SampleSphericalMap(vec3 v)
+{
+ vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
+ uv *= invAtan;
+ uv += 0.5;
+ return uv;
+}
+void main()
+{
+ vec2 uv = SampleSphericalMap(normalize(WorldPos));
+ vec3 color = texture(equirectangularMap, uv).rgb;
+ FragColor = vec4(color, 1.0);
+}
diff --git a/assets/shaders/geodesic.glsl b/assets/shaders/geodesic.glsl
index 70e41ef..2e69c72 100644
--- a/assets/shaders/geodesic.glsl
+++ b/assets/shaders/geodesic.glsl
@@ -2,6 +2,7 @@
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0, rgba8) writeonly uniform image2D outImage;
+layout(binding = 5) uniform samplerCube u_HDRIEnvironment;
layout(std140, binding = 1) uniform Camera
{
vec3 camPos; float _pad0;
@@ -55,6 +56,11 @@ vec4 objectColor = vec4(0.0);
vec3 hitCenter = vec3(0.0);
float hitRadius = 0.0;
+vec3 SampleHDRI(vec3 direction)
+{
+ return texture(u_HDRIEnvironment, direction).rgb;
+}
+
float hash(float p)
{
p = fract(p * 0.1031);
@@ -471,7 +477,11 @@ void main()
color = vec4(shaded, objectColor.a);
color = mix(accumulatedColor, color, color.a);
} else
- color = accumulatedColor;
+ {
+ vec3 rayDirection = normalize(vec3(ray.x, ray.y, ray.z) - cam.camPos);
+ vec3 hdriColor = SampleHDRI(rayDirection);
+ color = vec4(mix(accumulatedColor.rgb, hdriColor, 1.0 - accumulatedColor.a), 1.0);
+ }
imageStore(outImage, pix, color);
}
diff --git a/assets/shaders/skybox.glsl b/assets/shaders/skybox.glsl
new file mode 100644
index 0000000..654bed1
--- /dev/null
+++ b/assets/shaders/skybox.glsl
@@ -0,0 +1,31 @@
+#type vertex
+
+#version 330 core
+
+layout (location = 0) in vec3 aPos;
+
+out vec3 TexCoords;
+
+uniform mat4 u_Projection;
+uniform mat4 u_View;
+
+void main()
+{
+ TexCoords = aPos;
+ vec4 pos = u_Projection * u_View * vec4(aPos, 1.0);
+ gl_Position = pos.xyww;
+}
+
+#type fragment
+
+#version 330 core
+
+out vec4 FragColor;
+in vec3 TexCoords;
+
+uniform samplerCube u_Skybox;
+
+void main()
+{
+ FragColor = texture(u_Skybox, TexCoords);
+}
diff --git a/assets/shaders/sphere.glsl b/assets/shaders/sphere.glsl
index 23e8f54..2d1a46e 100644
--- a/assets/shaders/sphere.glsl
+++ b/assets/shaders/sphere.glsl
@@ -33,6 +33,7 @@ uniform vec3 u_CameraPos;
uniform int u_IsSelected;
uniform vec3 u_OutlineColor;
uniform float u_OutlineWidth;
+uniform samplerCube u_HDRIEnvironment;
out vec4 o_FragColor;
@@ -43,10 +44,13 @@ void main()
vec3 viewDir = normalize(u_CameraPos - v_WorldPos);
vec3 reflectDir = reflect(-lightDir, normal);
+ vec3 hdriLight = texture(u_HDRIEnvironment, normal).rgb;
+ float hdriIntensity = 0.3;
+
float diff = max(dot(normal, lightDir), 0.0);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
- vec3 diffuse = u_Color * diff;
+ vec3 diffuse = u_Color * (diff + hdriIntensity * hdriLight);
vec3 specular = vec3(u_Specular) * spec;
vec3 emission = u_Color * u_Emission;
diff --git a/branding/screenshot_1.png b/branding/screenshot_1.png
new file mode 100644
index 0000000..7175ab6
--- /dev/null
+++ b/branding/screenshot_1.png
Binary files differ
diff --git a/branding/export.png b/branding/screenshot_2.png
index 2aece5d..2aece5d 100644
--- a/branding/export.png
+++ b/branding/screenshot_2.png
Binary files differ
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();
};
};