diff --git a/examples/cornell_box b/examples/cornell_box index 250e4b7..9106b6a 100644 Binary files a/examples/cornell_box and b/examples/cornell_box differ diff --git a/examples/cornell_box.cpp b/examples/cornell_box.cpp index de2b731..4e2ec8f 100644 --- a/examples/cornell_box.cpp +++ b/examples/cornell_box.cpp @@ -445,7 +445,7 @@ int main() { config.samples_per_pixel_ = 1; config.max_ray_depth_ = 4; config.enable_accumulation_ = true; - config.enable_denoising_ = false; + config.enable_denoising_ = true; g_renderer = std::make_unique(config); if (!g_renderer->initialize()) { diff --git a/include/core/denoiser.h b/include/core/denoiser.h new file mode 100644 index 0000000..50ad16b --- /dev/null +++ b/include/core/denoiser.h @@ -0,0 +1,69 @@ +#ifndef ARE_INCLUDE_CORE_DENOISER_H +#define ARE_INCLUDE_CORE_DENOISER_H + +#include "basic/types.h" +#include "resource/shader.h" +#include + +namespace are { + +/** + * @brief Mean filter denoiser using compute shader + */ +class Denoiser { +public: + /** + * @brief Construct denoiser + * @param width Output width + * @param height Output height + */ + Denoiser(uint width, uint height); + + /** + * @brief Destroy denoiser + */ + ~Denoiser(); + + /** + * @brief Initialize GPU resources + * @param shader Denoise compute shader (managed by ShaderManager) + * @return True on success + */ + bool initialize(const std::shared_ptr& shader); + + /** + * @brief Release GPU resources + */ + void release(); + + /** + * @brief Resize internal targets + * @param width New width + * @param height New height + */ + void resize(uint width, uint height); + + /** + * @brief Apply mean filter + * @param input_texture RGBA32F input texture + * @param radius Filter radius (1 => 3x3) + * @return Output texture handle (internal) + */ + TextureHandle denoise(TextureHandle input_texture, int radius); + +private: + uint width_; + uint height_; + std::shared_ptr shader_; + TextureHandle output_texture_; + bool initialized_; + + /** + * @brief Create output texture + */ + void create_output_texture_(); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_DENOISER_H diff --git a/include/core/renderer.h b/include/core/renderer.h index b0b29b6..f051a01 100644 --- a/include/core/renderer.h +++ b/include/core/renderer.h @@ -6,6 +6,7 @@ #include "core/gbuffer.h" #include "core/raytracer.h" #include "core/screen_blit.h" +#include "core/denoiser.h" #include "core/shader_manager.h" #include @@ -66,6 +67,7 @@ private: std::unique_ptr raytracer_; std::unique_ptr shader_manager_; std::unique_ptr screen_blit_; + std::unique_ptr denoiser_; bool initialized_; uint frame_count_; diff --git a/include/core/shader_manager.h b/include/core/shader_manager.h index c48665a..2d8a19c 100644 --- a/include/core/shader_manager.h +++ b/include/core/shader_manager.h @@ -54,10 +54,15 @@ public: /// @return Ray tracing shader const std::shared_ptr& get_raytracing_shader() const { return raytracing_shader_; } + /// @brief Get mean denoise compute shader + /// @return Denoise shader (nullptr if not loaded) + const std::shared_ptr& get_denoise_shader() const { return denoise_shader_; } + private: std::unordered_map> shader_cache_; std::shared_ptr gbuffer_shader_; std::shared_ptr raytracing_shader_; + std::shared_ptr denoise_shader_; bool initialized_; diff --git a/shaders/denoiser.comp b/shaders/denoiser.comp new file mode 100644 index 0000000..5cf4d29 --- /dev/null +++ b/shaders/denoiser.comp @@ -0,0 +1,28 @@ +#version 430 core + +layout(local_size_x = 16, local_size_y = 16) in; + +layout(binding = 0, rgba32f) uniform readonly image2D u_input; +layout(binding = 1, rgba32f) uniform writeonly image2D u_output; + +uniform int u_radius; // 1 => 3x3, 2 => 5x5 + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(u_output); + if (p.x >= size.x || p.y >= size.y) return; + + vec3 sum = vec3(0.0); + int count = 0; + + for (int dy = -u_radius; dy <= u_radius; ++dy) { + for (int dx = -u_radius; dx <= u_radius; ++dx) { + ivec2 q = clamp(p + ivec2(dx, dy), ivec2(0), size - ivec2(1)); + sum += imageLoad(u_input, q).rgb; + count += 1; + } + } + + vec3 out_color = sum / float(count); + imageStore(u_output, p, vec4(out_color, 1.0)); +} diff --git a/src/core/denoiser.cpp b/src/core/denoiser.cpp new file mode 100644 index 0000000..d96326d --- /dev/null +++ b/src/core/denoiser.cpp @@ -0,0 +1,93 @@ +#include "core/denoiser.h" +#include "basic/constants.h" +#include "utils/logger.h" +#include + +namespace are { + +Denoiser::Denoiser(uint width, uint height) + : width_(width) + , height_(height) + , output_texture_(INVALID_HANDLE) + , initialized_(false) { +} + +Denoiser::~Denoiser() { + release(); +} + +bool Denoiser::initialize(const std::shared_ptr& shader) { + if (initialized_) return true; + + if (!shader || !shader->is_valid()) { + Logger::error("Invalid denoise shader"); + return false; + } + + shader_ = shader; + create_output_texture_(); + + initialized_ = true; + Logger::info("Denoiser initialized"); + return true; +} + +void Denoiser::release() { + if (!initialized_) return; + + shader_.reset(); + + if (output_texture_ != INVALID_HANDLE) { + glDeleteTextures(1, &output_texture_); + output_texture_ = INVALID_HANDLE; + } + + initialized_ = false; +} + +void Denoiser::resize(uint width, uint height) { + if (width == width_ && height == height_) return; + width_ = width; + height_ = height; + + if (!initialized_) return; + + if (output_texture_ != INVALID_HANDLE) { + glDeleteTextures(1, &output_texture_); + output_texture_ = INVALID_HANDLE; + } + create_output_texture_(); +} + +TextureHandle Denoiser::denoise(TextureHandle input_texture, int radius) { + if (!initialized_) return input_texture; + + radius = (radius < 0) ? 0 : radius; + + shader_->use(); + + glBindImageTexture(0, input_texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); + glBindImageTexture(1, output_texture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); + + shader_->set_int("u_radius", radius); + + uint groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X; + uint groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y; + glDispatchCompute(groups_x, groups_y, 1); + + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + + return output_texture_; +} + +void Denoiser::create_output_texture_() { + glGenTextures(1, &output_texture_); + glBindTexture(GL_TEXTURE_2D, output_texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width_, height_, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +} // namespace are diff --git a/src/core/renderer.cpp b/src/core/renderer.cpp index bb1d2ec..a5fa922 100644 --- a/src/core/renderer.cpp +++ b/src/core/renderer.cpp @@ -66,6 +66,13 @@ bool Renderer::initialize() { Logger::error("Failed to initialize screen blit"); return false; } + + denoiser_ = std::make_unique(config_.width_, config_.height_); + const auto& denoise_shader = shader_manager_->get_denoise_shader(); + if (!denoiser_->initialize(denoise_shader)) { + Logger::error("Failed to initialize denoiser"); + return false; + } initialized_ = true; Logger::info("Aurora Rendering Engine initialized successfully"); @@ -98,6 +105,11 @@ void Renderer::shutdown() { shader_manager_.reset(); } + if (denoiser_) { + denoiser_->release(); + denoiser_.reset(); + } + initialized_ = false; Logger::info("Aurora Rendering Engine shut down"); } @@ -148,11 +160,18 @@ RenderStats Renderer::render(const Scene& scene, TextureHandle output_texture) { auto raytrace_end = std::chrono::high_resolution_clock::now(); stats.raytrace_time_ms_ = std::chrono::duration(raytrace_end - raytrace_start).count(); - // Phase 3: Blit to screen if output is default framebuffer - if (created_temp_texture && output_texture == 0) { - screen_blit_->blit_fullscreen(rt_output); - glDeleteTextures(1, &rt_output); - } + // Phase 3: Denoise texture + TextureHandle final_output = rt_output; + + if (config_.enable_denoising_ && denoiser_) { + final_output = denoiser_->denoise(rt_output, 1); // radius=1 => 3x3 mean + } + + // Phase 4: Blit to screen if output is default framebuffer + if (created_temp_texture && output_texture == 0) { + screen_blit_->blit_fullscreen(final_output); + glDeleteTextures(1, &rt_output); + } // Calculate total frame time auto end_time = std::chrono::high_resolution_clock::now(); @@ -182,6 +201,7 @@ void Renderer::resize(uint width, uint height) { if (initialized_) { gbuffer_->resize(width, height); raytracer_->resize(width, height); + denoiser_->resize(width, height); Logger::info("Renderer resized to " + std::to_string(width) + "x" + std::to_string(height)); } diff --git a/src/core/shader_manager.cpp b/src/core/shader_manager.cpp index 6c9c48f..f18c0d9 100644 --- a/src/core/shader_manager.cpp +++ b/src/core/shader_manager.cpp @@ -39,6 +39,7 @@ void ShaderManager::release() { gbuffer_shader_.reset(); raytracing_shader_.reset(); + denoise_shader_.reset(); initialized_ = false; Logger::info("ShaderManager released"); @@ -108,6 +109,15 @@ bool ShaderManager::load_builtin_shaders_() { shader_cache_["raytracing"] = raytracing_shader_; Logger::info("Ray tracing shader loaded successfully"); + Logger::info("Loading denoise compute shader..."); + denoise_shader_ = std::make_shared(); + if (!denoise_shader_->load_compute("shaders/denoiser.comp")) { + Logger::error("Failed to load denoise shader"); + return false; + } + shader_cache_["denoise"] = denoise_shader_; + Logger::info("Denoise shader loaded successfully"); + return true; }