Compare commits
4 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5bb7d461a4 | |
|
|
9259bb9c6f | |
|
|
25547365e8 | |
|
|
adfe38a453 |
|
|
@ -37,3 +37,7 @@ Makefile
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Some testing scripts for me qwq
|
||||||
|
cornell_box_build.sh
|
||||||
|
cornell_box_metal_sphere_build.sh
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -306,7 +306,7 @@ void setup_cornell_box() {
|
||||||
g_scene->add_mesh(tall_box);
|
g_scene->add_mesh(tall_box);
|
||||||
|
|
||||||
// Short box (metal, right side)
|
// Short box (metal, right side)
|
||||||
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), glass_id);
|
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), emissive_sphere_id);
|
||||||
short_box->upload_to_gpu();
|
short_box->upload_to_gpu();
|
||||||
g_scene->add_mesh(short_box);
|
g_scene->add_mesh(short_box);
|
||||||
|
|
||||||
|
|
@ -554,6 +554,9 @@ int main() {
|
||||||
config.rt_config.enable_accumulation = true;
|
config.rt_config.enable_accumulation = true;
|
||||||
config.enable_denoising = false;
|
config.enable_denoising = false;
|
||||||
|
|
||||||
|
config.sr_config.enabled = true;
|
||||||
|
config.sr_config.scaling = 4;
|
||||||
|
|
||||||
g_renderer = std::make_unique<Renderer>(config);
|
g_renderer = std::make_unique<Renderer>(config);
|
||||||
if (!g_renderer->initialize()) {
|
if (!g_renderer->initialize()) {
|
||||||
ARE_LOG_ERROR("Failed to initialize renderer");
|
ARE_LOG_ERROR("Failed to initialize renderer");
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -536,6 +536,10 @@ int main() {
|
||||||
config.rt_config.enable_accumulation = true;
|
config.rt_config.enable_accumulation = true;
|
||||||
config.enable_denoising = false;
|
config.enable_denoising = false;
|
||||||
|
|
||||||
|
|
||||||
|
config.sr_config.enabled = true;
|
||||||
|
config.sr_config.scaling = 4.0;
|
||||||
|
|
||||||
g_renderer = std::make_unique<Renderer>(config);
|
g_renderer = std::make_unique<Renderer>(config);
|
||||||
if (!g_renderer->initialize()) {
|
if (!g_renderer->initialize()) {
|
||||||
ARE_LOG_ERROR("Failed to initialize renderer");
|
ARE_LOG_ERROR("Failed to initialize renderer");
|
||||||
|
|
|
||||||
|
|
@ -16,114 +16,62 @@ namespace are {
|
||||||
// Compute shader based ray tracer
|
// Compute shader based ray tracer
|
||||||
class RayTracer {
|
class RayTracer {
|
||||||
public:
|
public:
|
||||||
/*
|
|
||||||
* @brief Constructor
|
|
||||||
* @param width Output width
|
|
||||||
* @param height Output height
|
|
||||||
* @param config Ray tracer configuration
|
|
||||||
*/
|
|
||||||
RayTracer(uint width, uint height, const RayTracerConfig &config);
|
RayTracer(uint width, uint height, const RayTracerConfig &config);
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~RayTracer();
|
~RayTracer();
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Initialize ray tracer
|
|
||||||
* @return True if initialization succeeded
|
|
||||||
*/
|
|
||||||
bool initialize(const std::shared_ptr<Shader> &shader);
|
bool initialize(const std::shared_ptr<Shader> &shader);
|
||||||
|
|
||||||
// Release resources
|
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @brief Trace rays using G-Buffer as input
|
* @brief Trace rays using G-Buffer as input
|
||||||
* @param scene Scene data
|
* @param scene Scene data
|
||||||
* @param gbuffer G-Buffer containing geometry information
|
* @param gbuffer G-Buffer containing geometry information
|
||||||
* @param output_texture Output texture for ray traced result
|
* @param output_image Low-resolution output (W/block × H/block in SR mode)
|
||||||
|
* @param sr_scaling Pixel ratio for super resolution (1 = disabled, e.g. 4 = 4× fewer pixels)
|
||||||
|
* @param sr_jitter Jitter frame index 0..scaling-1
|
||||||
|
* @param sr_accum Full-resolution accumulation texture (SR mode only, 0 = disabled)
|
||||||
*/
|
*/
|
||||||
void trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_texture);
|
void trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_image,
|
||||||
|
uint sr_scaling = 1, uint sr_jitter = 0, TextureHandle sr_accum = 0);
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Resize output
|
|
||||||
* @param width New width
|
|
||||||
* @param height New height
|
|
||||||
*/
|
|
||||||
void resize(uint width, uint height);
|
void resize(uint width, uint height);
|
||||||
|
|
||||||
// Reset accumulation buffer
|
|
||||||
void reset_accumulation();
|
void reset_accumulation();
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Get current configuration
|
|
||||||
* @return Current configuration
|
|
||||||
*/
|
|
||||||
const RayTracerConfig &get_config() const {
|
const RayTracerConfig &get_config() const {
|
||||||
return config_;
|
return config_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Update configuration
|
|
||||||
* @param config New configuration
|
|
||||||
*/
|
|
||||||
void set_config(const RayTracerConfig &config);
|
void set_config(const RayTracerConfig &config);
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Rebuild BVH from scene
|
|
||||||
* @param scene Scene to build BVH from
|
|
||||||
* @return True if build succeeded
|
|
||||||
*/
|
|
||||||
bool rebuild_bvh(const Scene &scene);
|
bool rebuild_bvh(const Scene &scene);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint width_;
|
uint width_, height_;
|
||||||
uint height_;
|
|
||||||
RayTracerConfig config_;
|
RayTracerConfig config_;
|
||||||
|
|
||||||
// Scene data hash for change detection
|
|
||||||
uint materials_hash_;
|
uint materials_hash_;
|
||||||
uint lights_hash_;
|
uint lights_hash_;
|
||||||
|
|
||||||
// Texture arrays for PBR materials
|
GLuint texture_arrays_[6];
|
||||||
GLuint texture_arrays_[6]; // albedo, normal, metallic, roughness, ao, emission
|
uint texture_array_sizes_[6];
|
||||||
uint texture_array_sizes_[6]; // Number of textures in each array
|
uint texture_config_hash_;
|
||||||
|
uint texture_slot_hashes_[6];
|
||||||
// Texture array caching (content hash based)
|
bool texture_arrays_dirty_;
|
||||||
uint texture_config_hash_; // Hash of entire texture configuration
|
|
||||||
uint texture_slot_hashes_[6]; // Hash per slot for incremental rebuild
|
|
||||||
bool texture_arrays_dirty_; // Dirty flag for texture arrays
|
|
||||||
|
|
||||||
std::shared_ptr<Shader> compute_shader_;
|
std::shared_ptr<Shader> compute_shader_;
|
||||||
TextureHandle accumulation_texture_;
|
TextureHandle accumulation_texture_;
|
||||||
BufferHandle material_buffer_;
|
BufferHandle material_buffer_;
|
||||||
BufferHandle light_buffer_;
|
BufferHandle light_buffer_;
|
||||||
|
|
||||||
// BVH related
|
|
||||||
std::unique_ptr<BVH> bvh_;
|
std::unique_ptr<BVH> bvh_;
|
||||||
Buffer bvh_node_buffer_;
|
Buffer bvh_node_buffer_;
|
||||||
Buffer bvh_triangle_buffer_; ///< Compact triangle data (intersection only)
|
Buffer bvh_triangle_buffer_;
|
||||||
Buffer bvh_attr_buffer_; ///< Triangle attributes (fetched on hit)
|
Buffer bvh_attr_buffer_;
|
||||||
bool bvh_built_;
|
bool bvh_built_;
|
||||||
|
|
||||||
uint frame_count_;
|
uint frame_count_;
|
||||||
bool initialized_;
|
bool initialized_;
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Upload scene data to GPU buffers
|
|
||||||
* @param scene Scene to upload
|
|
||||||
*/
|
|
||||||
void upload_scene_data_(const Scene &scene);
|
void upload_scene_data_(const Scene &scene);
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Bind G-Buffer textures to compute shader
|
|
||||||
* @param gbuffer G-Buffer to bind
|
|
||||||
*/
|
|
||||||
void bind_gbuffer_(const GBuffer &gbuffer);
|
void bind_gbuffer_(const GBuffer &gbuffer);
|
||||||
|
|
||||||
/*
|
|
||||||
* @brief Build texture arrays from scene materials
|
|
||||||
* @param scene Scene containing materials
|
|
||||||
*/
|
|
||||||
void build_texture_arrays_(const Scene &scene);
|
void build_texture_arrays_(const Scene &scene);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "core/raytracer.h"
|
#include "core/raytracer.h"
|
||||||
#include "core/screen_blit.h"
|
#include "core/screen_blit.h"
|
||||||
#include "core/shader_manager.h"
|
#include "core/shader_manager.h"
|
||||||
|
#include "core/super_resolution.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "utils/config.h"
|
#include "utils/config.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -73,11 +74,11 @@ private:
|
||||||
std::unique_ptr<ShaderManager> shader_manager_;
|
std::unique_ptr<ShaderManager> shader_manager_;
|
||||||
std::unique_ptr<ScreenBlit> screen_blit_;
|
std::unique_ptr<ScreenBlit> screen_blit_;
|
||||||
std::unique_ptr<Denoiser> denoiser_;
|
std::unique_ptr<Denoiser> denoiser_;
|
||||||
|
std::unique_ptr<SuperResolution> super_resolution_;
|
||||||
|
|
||||||
TextureHandle rt_output_texture_;
|
TextureHandle rt_output_texture_;
|
||||||
|
|
||||||
bool initialized_;
|
bool initialized_;
|
||||||
uint frame_count_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,21 @@ public:
|
||||||
return screen_blit_shader_;
|
return screen_blit_shader_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Get super resolution shader
|
||||||
|
* @return Super resolution shader (nullptr if not loaded)
|
||||||
|
*/
|
||||||
|
const std::shared_ptr<Shader> &get_super_resolution_shader() const {
|
||||||
|
return super_resolution_shader_;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, std::shared_ptr<Shader>> shader_cache_;
|
std::unordered_map<std::string, std::shared_ptr<Shader>> shader_cache_;
|
||||||
std::shared_ptr<Shader> gbuffer_shader_;
|
std::shared_ptr<Shader> gbuffer_shader_;
|
||||||
std::shared_ptr<Shader> raytracing_shader_;
|
std::shared_ptr<Shader> raytracing_shader_;
|
||||||
std::shared_ptr<Shader> denoise_shader_;
|
std::shared_ptr<Shader> denoise_shader_;
|
||||||
std::shared_ptr<Shader> screen_blit_shader_;
|
std::shared_ptr<Shader> screen_blit_shader_;
|
||||||
|
std::shared_ptr<Shader> super_resolution_shader_;
|
||||||
|
|
||||||
bool initialized_;
|
bool initialized_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#ifndef ARE_INCLUDE_CORE_SUPER_RESOLUTION_H
|
||||||
|
#define ARE_INCLUDE_CORE_SUPER_RESOLUTION_H
|
||||||
|
|
||||||
|
#include "basic/types.h"
|
||||||
|
#include "resource/shader.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
// Super resolution sparse ray tracing system
|
||||||
|
// Renders a subset of pixels each frame via a jitter pattern and
|
||||||
|
// accumulates across multiple frames inside the RT compute shader.
|
||||||
|
class SuperResolution {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* @brief Constructor
|
||||||
|
* @param full_width Full-resolution output width
|
||||||
|
* @param full_height Full-resolution output height
|
||||||
|
* @param config Super resolution configuration (enabled, scaling)
|
||||||
|
*/
|
||||||
|
SuperResolution(uint full_width, uint full_height, const SuperResolutionConfig &config);
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~SuperResolution();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Allocate textures and bind the upscale compute shader
|
||||||
|
* @param shader Super-resolution compute shader (super_resolution.comp)
|
||||||
|
* @return True if successful
|
||||||
|
*/
|
||||||
|
bool initialize(const std::shared_ptr<Shader> &shader);
|
||||||
|
|
||||||
|
// Release all GPU resources
|
||||||
|
void release();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Low-resolution texture written by RayTracer each frame.
|
||||||
|
* Dimensions are W/scaling × H/scaling.
|
||||||
|
* @return Texture handle
|
||||||
|
*/
|
||||||
|
TextureHandle get_low_res_rt_texture() const {
|
||||||
|
return low_res_rt_texture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Full-resolution accumulation buffer (binding 4 in the RT shader).
|
||||||
|
* RGB = running average colour, A = 1.0 once sampled.
|
||||||
|
* @return Texture handle
|
||||||
|
*/
|
||||||
|
TextureHandle get_accumulated_rt_texture() const {
|
||||||
|
return accumulated_rt_texture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Run the upscale compute pass: tonemap accumulated RT, output final image.
|
||||||
|
* Unrendered pixels (alpha == 0) appear black.
|
||||||
|
* @return Upscaled output texture handle (full resolution)
|
||||||
|
*/
|
||||||
|
TextureHandle upscale();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Advance to the next jitter frame within the current cycle.
|
||||||
|
* Cycles from 0 to scaling-1, wrapping back to 0.
|
||||||
|
*/
|
||||||
|
void advance_jitter_frame();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Reset jitter frame to 0 and clear the accumulation texture to black.
|
||||||
|
* Called on scene changes and initialisation.
|
||||||
|
*/
|
||||||
|
void reset_accumulation();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Resize all internal textures for a new output resolution
|
||||||
|
* @param full_width New full-resolution width
|
||||||
|
* @param full_height New full-resolution height
|
||||||
|
*/
|
||||||
|
void resize(uint full_width, uint full_height);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Current jitter frame index (0 .. scaling-1)
|
||||||
|
* @return Jitter frame index
|
||||||
|
*/
|
||||||
|
uint get_current_jitter_frame() const {
|
||||||
|
return current_jitter_frame_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Read-only access to the active configuration
|
||||||
|
* @return Current SuperResolutionConfig
|
||||||
|
*/
|
||||||
|
const SuperResolutionConfig &get_config() const {
|
||||||
|
return config_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Low-resolution dispatch width (full_width / sqrt(scaling))
|
||||||
|
* @return Width in pixels
|
||||||
|
*/
|
||||||
|
uint get_low_res_width() const {
|
||||||
|
return low_res_w_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Low-resolution dispatch height (full_height / sqrt(scaling))
|
||||||
|
* @return Height in pixels
|
||||||
|
*/
|
||||||
|
uint get_low_res_height() const {
|
||||||
|
return low_res_h_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint full_width_, full_height_, low_res_w_, low_res_h_;
|
||||||
|
SuperResolutionConfig config_;
|
||||||
|
uint current_jitter_frame_;
|
||||||
|
|
||||||
|
TextureHandle low_res_rt_texture_; // W/block × H/block – per-frame RT output
|
||||||
|
TextureHandle accumulated_rt_texture_; // W × H – running average (binding 4)
|
||||||
|
TextureHandle upscaled_texture_; // W × H – final tonemapped output
|
||||||
|
|
||||||
|
std::shared_ptr<Shader> compute_shader_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
|
||||||
|
// Create / recreate all internal textures
|
||||||
|
void create_textures_();
|
||||||
|
|
||||||
|
// sqrt(scaling) – the block side length in pixels
|
||||||
|
uint compute_block_size_() const;
|
||||||
|
|
||||||
|
// Clears accumulated_rt_texture_ to (0,0,0,0) via FBO colour clear
|
||||||
|
void clear_accumulation_texture_() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_CORE_SUPER_RESOLUTION_H
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#ifndef ARE_INCLUDE_UTILS_CONFIG_H
|
#ifndef ARE_INCLUDE_UTILS_CONFIG_H
|
||||||
#define ARE_INCLUDE_UTILS_CONFIG_H
|
#define ARE_INCLUDE_UTILS_CONFIG_H
|
||||||
|
|
||||||
|
#include "basic/types.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "basic/types.h"
|
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
|
|
@ -17,6 +17,12 @@ struct RayTracerConfig {
|
||||||
bool use_bvh = true;
|
bool use_bvh = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Super resolution configuration
|
||||||
|
struct SuperResolutionConfig {
|
||||||
|
bool enabled = false; // Enable the super resolution mode
|
||||||
|
uint scaling = 4; // Pixel ratio: how many times fewer pixels rendered per frame
|
||||||
|
// scaling=4 → 1/4 pixels, 2×2 blocks, 4 jitter positions
|
||||||
|
};
|
||||||
|
|
||||||
// Configuration struct for renderer
|
// Configuration struct for renderer
|
||||||
struct RendererConfig {
|
struct RendererConfig {
|
||||||
|
|
@ -24,8 +30,7 @@ struct RendererConfig {
|
||||||
uint output_height;
|
uint output_height;
|
||||||
RayTracerConfig rt_config;
|
RayTracerConfig rt_config;
|
||||||
bool enable_denoising;
|
bool enable_denoising;
|
||||||
bool enable_sr; // Enable the super resolution mode
|
SuperResolutionConfig sr_config;
|
||||||
double sr_scaling; // The magnification of super-resolution
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) {
|
||||||
|
|
||||||
// Moller-Trumbore triangle intersection using compact triangle (precomputed edges)
|
// Moller-Trumbore triangle intersection using compact triangle (precomputed edges)
|
||||||
// Uses TriangleCompactGpu: v0_material, e1=v1-v0, e2=v2-v0
|
// Uses TriangleCompactGpu: v0_material, e1=v1-v0, e2=v2-v0
|
||||||
bool intersect_triangle_compact(Ray ray, TriangleCompactGpu tri, inout HitInfo hit) {
|
// Returns true if hit, outputs barycentric coords (u, v) and distance t
|
||||||
|
bool intersect_triangle_compact(Ray ray, TriangleCompactGpu tri, float t_max, out float out_t, out float out_u, out float out_v) {
|
||||||
vec3 v0 = tri.v0_material.xyz;
|
vec3 v0 = tri.v0_material.xyz;
|
||||||
vec3 e1 = tri.e1.xyz;
|
vec3 e1 = tri.e1.xyz;
|
||||||
vec3 e2 = tri.e2.xyz;
|
vec3 e2 = tri.e2.xyz;
|
||||||
|
|
@ -60,18 +61,37 @@ bool intersect_triangle_compact(Ray ray, TriangleCompactGpu tri, inout HitInfo h
|
||||||
if (v < 0.0 || u + v > 1.0) return false;
|
if (v < 0.0 || u + v > 1.0) return false;
|
||||||
|
|
||||||
float t = dot(e2, qvec) * inv_det;
|
float t = dot(e2, qvec) * inv_det;
|
||||||
if (t < EPSILON || t >= hit.t) return false;
|
if (t < EPSILON || t >= t_max) return false;
|
||||||
|
|
||||||
float w = 1.0 - u - v;
|
out_t = t;
|
||||||
|
out_u = u;
|
||||||
|
out_v = v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch attributes only after confirmed hit
|
// Check if triangle is hit (for shadow rays - no barycentric needed)
|
||||||
TriangleAttrGpu attr = bvh_attrs[gl_GlobalInvocationID.x];
|
bool intersect_triangle_any(Ray ray, TriangleCompactGpu tri, float t_max) {
|
||||||
// We need the triangle index, not invocation ID. Use a different approach.
|
vec3 v0 = tri.v0_material.xyz;
|
||||||
|
vec3 e1 = tri.e1.xyz;
|
||||||
|
vec3 e2 = tri.e2.xyz;
|
||||||
|
|
||||||
|
vec3 pvec = cross(ray.direction, e2);
|
||||||
|
float det = dot(e1, pvec);
|
||||||
|
|
||||||
|
if (abs(det) < EPSILON) return false;
|
||||||
|
float inv_det = 1.0 / det;
|
||||||
|
|
||||||
|
vec3 tvec = ray.origin - v0;
|
||||||
|
float u = dot(tvec, pvec) * inv_det;
|
||||||
|
if (u < 0.0 || u > 1.0) return false;
|
||||||
|
|
||||||
|
vec3 qvec = cross(tvec, e1);
|
||||||
|
float v = dot(ray.direction, qvec) * inv_det;
|
||||||
|
if (v < 0.0 || u + v > 1.0) return false;
|
||||||
|
|
||||||
|
float t = dot(e2, qvec) * inv_det;
|
||||||
|
if (t < EPSILON || t >= t_max) return false;
|
||||||
|
|
||||||
hit.hit = true;
|
|
||||||
hit.t = t;
|
|
||||||
hit.position = ray.origin + t * ray.direction;
|
|
||||||
hit.material_id = as_uint(tri.v0_material.w);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,35 +151,17 @@ HitInfo trace_ray_bvh(Ray ray) {
|
||||||
for (uint i = 0u; i < count; ++i) {
|
for (uint i = 0u; i < count; ++i) {
|
||||||
uint tri_idx = left_first + i;
|
uint tri_idx = left_first + i;
|
||||||
TriangleCompactGpu tri = bvh_tris[tri_idx];
|
TriangleCompactGpu tri = bvh_tris[tri_idx];
|
||||||
vec3 v0 = tri.v0_material.xyz;
|
|
||||||
vec3 e1 = tri.e1.xyz;
|
|
||||||
vec3 e2 = tri.e2.xyz;
|
|
||||||
|
|
||||||
vec3 pvec = cross(ray.direction, e2);
|
float t, u, v;
|
||||||
float det = dot(e1, pvec);
|
if (intersect_triangle_compact(ray, tri, hit.t, t, u, v)) {
|
||||||
|
hit.hit = true;
|
||||||
if (abs(det) < EPSILON) continue;
|
hit.t = t;
|
||||||
float inv_det = 1.0 / det;
|
hit.position = ray.origin + t * ray.direction;
|
||||||
|
hit.material_id = as_uint(tri.v0_material.w);
|
||||||
vec3 tvec = ray.origin - v0;
|
hit_tri_idx = tri_idx;
|
||||||
float u = dot(tvec, pvec) * inv_det;
|
hit_u = u;
|
||||||
if (u < 0.0 || u > 1.0) continue;
|
hit_v = v;
|
||||||
|
}
|
||||||
vec3 qvec = cross(tvec, e1);
|
|
||||||
float v = dot(ray.direction, qvec) * inv_det;
|
|
||||||
if (v < 0.0 || u + v > 1.0) continue;
|
|
||||||
|
|
||||||
float t = dot(e2, qvec) * inv_det;
|
|
||||||
if (t < EPSILON || t >= hit.t) continue;
|
|
||||||
|
|
||||||
// Record hit but defer attribute fetch
|
|
||||||
hit.hit = true;
|
|
||||||
hit.t = t;
|
|
||||||
hit.position = ray.origin + t * ray.direction;
|
|
||||||
hit.material_id = as_uint(tri.v0_material.w);
|
|
||||||
hit_tri_idx = tri_idx;
|
|
||||||
hit_u = u;
|
|
||||||
hit_v = v;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint left = left_first;
|
uint left = left_first;
|
||||||
|
|
@ -239,28 +241,9 @@ bool trace_any_bvh(Ray ray, float t_max) {
|
||||||
if (count > 0u) {
|
if (count > 0u) {
|
||||||
for (uint i = 0u; i < count; ++i) {
|
for (uint i = 0u; i < count; ++i) {
|
||||||
TriangleCompactGpu tri = bvh_tris[left_first + i];
|
TriangleCompactGpu tri = bvh_tris[left_first + i];
|
||||||
vec3 v0 = tri.v0_material.xyz;
|
if (intersect_triangle_any(ray, tri, t_max)) {
|
||||||
vec3 e1 = tri.e1.xyz;
|
return true;
|
||||||
vec3 e2 = tri.e2.xyz;
|
}
|
||||||
|
|
||||||
vec3 pvec = cross(ray.direction, e2);
|
|
||||||
float det = dot(e1, pvec);
|
|
||||||
|
|
||||||
if (abs(det) < EPSILON) continue;
|
|
||||||
float inv_det = 1.0 / det;
|
|
||||||
|
|
||||||
vec3 tvec = ray.origin - v0;
|
|
||||||
float u = dot(tvec, pvec) * inv_det;
|
|
||||||
if (u < 0.0 || u > 1.0) continue;
|
|
||||||
|
|
||||||
vec3 qvec = cross(tvec, e1);
|
|
||||||
float v = dot(ray.direction, qvec) * inv_det;
|
|
||||||
if (v < 0.0 || u + v > 1.0) continue;
|
|
||||||
|
|
||||||
float t = dot(e2, qvec) * inv_det;
|
|
||||||
if (t < EPSILON || t >= t_max) continue;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint left = left_first;
|
uint left = left_first;
|
||||||
|
|
|
||||||
|
|
@ -83,11 +83,12 @@ ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint
|
||||||
r.scattered = true;
|
r.scattered = true;
|
||||||
r.attenuation = mat.albedo;
|
r.attenuation = mat.albedo;
|
||||||
|
|
||||||
vec3 dir = hit.normal + random_unit_vector(seed);
|
// Use cosine-weighted importance sampling for Lambertian BRDF
|
||||||
|
vec3 dir = sample_cosine_weighted(hit.normal, seed);
|
||||||
if (near_zero(dir)) dir = hit.normal;
|
if (near_zero(dir)) dir = hit.normal;
|
||||||
|
|
||||||
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(dir);
|
r.scattered_ray.direction = dir;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint se
|
||||||
|
|
||||||
r.scattered = true;
|
r.scattered = true;
|
||||||
r.scattered_ray.origin = hit.position + N * EPSILON;
|
r.scattered_ray.origin = hit.position + N * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(L);
|
r.scattered_ray.direction = L;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +159,7 @@ ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout ui
|
||||||
}
|
}
|
||||||
|
|
||||||
r.scattered_ray.origin = hit.position + dir * EPSILON;
|
r.scattered_ray.origin = hit.position + dir * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(dir);
|
r.scattered_ray.direction = dir;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,15 @@ uint init_seed(ivec2 pixel_coords, ivec2 image_size, uint frame_count) {
|
||||||
return pcg_hash(spatial + temporal);
|
return pcg_hash(spatial + temporal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// High-quality random float using 53-bit precision (double-like)
|
||||||
float random_float(inout uint seed) {
|
float random_float(inout uint seed) {
|
||||||
|
seed = pcg_hash(seed);
|
||||||
|
// Use upper 53 bits for better precision (matches IEEE double mantissa)
|
||||||
|
return float(seed >> 11) / 2097152.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard random float (32-bit precision)
|
||||||
|
float random_float_32(inout uint seed) {
|
||||||
seed = pcg_hash(seed);
|
seed = pcg_hash(seed);
|
||||||
return float(seed) / 4294967296.0;
|
return float(seed) / 4294967296.0;
|
||||||
}
|
}
|
||||||
|
|
@ -31,4 +39,8 @@ vec3 random_vec3(inout uint seed) {
|
||||||
return vec3(random_float(seed), random_float(seed), random_float(seed));
|
return vec3(random_float(seed), random_float(seed), random_float(seed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec3 random_vec3_32(inout uint seed) {
|
||||||
|
return vec3(random_float_32(seed), random_float_32(seed), random_float_32(seed));
|
||||||
|
}
|
||||||
|
|
||||||
#endif // RNG_GLSL
|
#endif // RNG_GLSL
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#ifndef SAMPLING_GLSL
|
#ifndef SAMPLING_GLSL
|
||||||
#define SAMPLING_GLSL
|
#define SAMPLING_GLSL
|
||||||
|
|
||||||
// Cosine-weighted hemisphere sampling (avoids infinite loop)
|
// Uniform sphere sampling
|
||||||
vec3 random_in_unit_sphere(inout uint seed) {
|
vec3 random_in_unit_sphere(inout uint seed) {
|
||||||
float z = 1.0 - 2.0 * random_float(seed);
|
float z = 1.0 - 2.0 * random_float(seed);
|
||||||
float r = sqrt(max(0.0, 1.0 - z * z));
|
float r = sqrt(max(0.0, 1.0 - z * z));
|
||||||
|
|
@ -15,6 +15,23 @@ vec3 random_unit_vector(inout uint seed) {
|
||||||
return normalize(random_in_unit_sphere(seed));
|
return normalize(random_in_unit_sphere(seed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cosine-weighted hemisphere sampling for Lambertian BRDF
|
||||||
|
// Matches pdf = cos(theta) / PI for faster convergence
|
||||||
|
vec3 sample_cosine_weighted(vec3 N, inout uint seed) {
|
||||||
|
// Malley method: uniform disk -> hemisphere
|
||||||
|
float r = sqrt(random_float(seed));
|
||||||
|
float phi = 2.0 * PI * random_float(seed);
|
||||||
|
float x = r * cos(phi);
|
||||||
|
float y = r * sin(phi);
|
||||||
|
float z = sqrt(max(0.0, 1.0 - x * x - y * y));
|
||||||
|
|
||||||
|
vec3 up = (abs(N.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||||
|
vec3 T = normalize(cross(up, N));
|
||||||
|
vec3 B = cross(N, T);
|
||||||
|
mat3 onb = mat3(T, B, N);
|
||||||
|
return onb * vec3(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
// Build orthonormal basis from normal vector
|
// Build orthonormal basis from normal vector
|
||||||
mat3 build_onb(vec3 N) {
|
mat3 build_onb(vec3 N) {
|
||||||
vec3 up = (abs(N.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
vec3 up = (abs(N.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||||
|
|
@ -32,7 +49,6 @@ vec3 sample_ggx_half_vector(float roughness, vec3 N, inout uint seed) {
|
||||||
float u1 = random_float(seed);
|
float u1 = random_float(seed);
|
||||||
float u2 = random_float(seed);
|
float u2 = random_float(seed);
|
||||||
|
|
||||||
// Clamp to avoid numerical issues at boundaries
|
|
||||||
u1 = clamp(u1, 0.001, 0.999);
|
u1 = clamp(u1, 0.001, 0.999);
|
||||||
|
|
||||||
// Spherical coordinates from GGX distribution
|
// Spherical coordinates from GGX distribution
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,102 @@ const uint sobol_dirs_7[32] = uint[32](
|
||||||
0x23a23a28u, 0x11d11d14u, 0x88e88e8au, 0x44744745u
|
0x23a23a28u, 0x11d11d14u, 0x88e88e8au, 0x44744745u
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Dimension 8: primitive polynomial 1+x (degree 2)
|
||||||
|
const uint sobol_dirs_8[32] = uint[32](
|
||||||
|
0x80000000u, 0x40000000u, 0x60000000u, 0x90000000u,
|
||||||
|
0xd8000000u, 0x6c000000u, 0x86000000u, 0xc9000000u,
|
||||||
|
0x6e800000u, 0x95c00000u, 0xe8600000u, 0x5c900000u,
|
||||||
|
0x86e80000u, 0xc95c0000u, 0x6e860000u, 0x95c90000u,
|
||||||
|
0xe86e8000u, 0x5c95c000u, 0x86e86000u, 0xc95c9000u,
|
||||||
|
0x6e86e800u, 0x95c95c00u, 0xe86e8600u, 0x5c95c900u,
|
||||||
|
0x86e86e80u, 0xc95c95c0u, 0x6e86e860u, 0x95c95c90u,
|
||||||
|
0xe86e86e8u, 0x5c95c95cu, 0x86e86e86u, 0xc95c95c9u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 9: primitive polynomial 1+x+x^2 (degree 3)
|
||||||
|
const uint sobol_dirs_9[32] = uint[32](
|
||||||
|
0x80000000u, 0xc0000000u, 0xa0000000u, 0xf0000000u,
|
||||||
|
0x88000000u, 0xcc000000u, 0xaa000000u, 0xff000000u,
|
||||||
|
0x80800000u, 0xc0c00000u, 0xa0a00000u, 0xf0f00000u,
|
||||||
|
0x88880000u, 0xcccc0000u, 0xaaaa0000u, 0xffff0000u,
|
||||||
|
0x80008000u, 0xc000c000u, 0xa000a000u, 0xf000f000u,
|
||||||
|
0x88008800u, 0xcc00cc00u, 0xaa00aa00u, 0xff00ff00u,
|
||||||
|
0x80808080u, 0xc0c0c0c0u, 0xa0a0a0a0u, 0xf0f0f0f0u,
|
||||||
|
0x88888888u, 0xccccccccu, 0xaaaaaaaau, 0xffffffffu
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 10: primitive polynomial 1+x+x^3 (degree 3)
|
||||||
|
const uint sobol_dirs_10[32] = uint[32](
|
||||||
|
0x80000000u, 0x40000000u, 0x20000000u, 0xd0000000u,
|
||||||
|
0xf8000000u, 0x6c000000u, 0x9a000000u, 0xc1000000u,
|
||||||
|
0x78800000u, 0xb4c00000u, 0x52600000u, 0xa9100000u,
|
||||||
|
0xd0880000u, 0xe84c0000u, 0x6ca60000u, 0x9a110000u,
|
||||||
|
0xc1088000u, 0x7884c000u, 0xb4c26000u, 0x52691000u,
|
||||||
|
0xa9108800u, 0xd0884c00u, 0xe84c2600u, 0x6ca69100u,
|
||||||
|
0x9a110880u, 0xc10884c0u, 0x7884c260u, 0xb4c26910u,
|
||||||
|
0x52691088u, 0xa910884cu, 0xd0884c26u, 0xe84c2691u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 11: primitive polynomial 1+x^2+x^3 (degree 3)
|
||||||
|
const uint sobol_dirs_11[32] = uint[32](
|
||||||
|
0x80000000u, 0xc0000000u, 0xa0000000u, 0x50000000u,
|
||||||
|
0xb8000000u, 0x6c000000u, 0x86000000u, 0x43000000u,
|
||||||
|
0xa1800000u, 0x5ec00000u, 0xb0600000u, 0x6c100000u,
|
||||||
|
0x86a80000u, 0x435c0000u, 0xa1860000u, 0x5ec10000u,
|
||||||
|
0xb06a8000u, 0x6c15c000u, 0x86a86000u, 0x435c1000u,
|
||||||
|
0xa186a800u, 0x5ec15c00u, 0xb06a8600u, 0x6c15c100u,
|
||||||
|
0x86a86a80u, 0x435c15c0u, 0xa186a860u, 0x5ec15c10u,
|
||||||
|
0xb06a86a8u, 0x6c15c15cu, 0x86a86a86u, 0x435c15c1u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 12: primitive polynomial 1+x+x^2+x^4 (degree 4)
|
||||||
|
const uint sobol_dirs_12[32] = uint[32](
|
||||||
|
0x80000000u, 0x40000000u, 0x20000000u, 0x10000000u,
|
||||||
|
0xf8000000u, 0xdc000000u, 0x6a000000u, 0x35000000u,
|
||||||
|
0x1a800000u, 0x8dc00000u, 0x46a00000u, 0x23500000u,
|
||||||
|
0x11a80000u, 0xf8dc0000u, 0xdc6a0000u, 0x6a350000u,
|
||||||
|
0x351a8000u, 0x1a8dc000u, 0x8dc6a000u, 0x46a35000u,
|
||||||
|
0x2351a800u, 0x11a8dc00u, 0xf8dc6a00u, 0xdc6a3500u,
|
||||||
|
0x6a351a80u, 0x351a8dc0u, 0x1a8dc6a0u, 0x8dc6a350u,
|
||||||
|
0x46a351a8u, 0x2351a8dcu, 0x11a8dc6au, 0xf8dc6a35u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 13: primitive polynomial 1+x+x^3+x^4 (degree 4)
|
||||||
|
const uint sobol_dirs_13[32] = uint[32](
|
||||||
|
0x80000000u, 0xc0000000u, 0xe0000000u, 0x70000000u,
|
||||||
|
0x38000000u, 0x9c000000u, 0x4e000000u, 0xa7000000u,
|
||||||
|
0xd3800000u, 0x69c00000u, 0xb4e00000u, 0x5a700000u,
|
||||||
|
0x2d380000u, 0x169c0000u, 0x8b4e0000u, 0x45a70000u,
|
||||||
|
0xa2d38000u, 0xd169c000u, 0x68b4e000u, 0xb45a7000u,
|
||||||
|
0x5a2d3800u, 0x2d169c00u, 0x168b4e00u, 0x8b45a700u,
|
||||||
|
0x45a2d380u, 0xa2d169c0u, 0xd168b4e0u, 0x68b45a70u,
|
||||||
|
0xb45a2d38u, 0x5a2d169cu, 0x2d168b4eu, 0x168b45a7u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 14: primitive polynomial 1+x^2+x^4 (degree 4)
|
||||||
|
const uint sobol_dirs_14[32] = uint[32](
|
||||||
|
0x80000000u, 0xc0000000u, 0xa0000000u, 0x50000000u,
|
||||||
|
0x28000000u, 0x14000000u, 0x8a000000u, 0x45000000u,
|
||||||
|
0xa2800000u, 0xd1400000u, 0xe8a00000u, 0x74500000u,
|
||||||
|
0x3a280000u, 0x1d140000u, 0x8e8a0000u, 0x47450000u,
|
||||||
|
0x23a28000u, 0x11d14000u, 0x88e8a000u, 0x44745000u,
|
||||||
|
0xa23a2800u, 0xd11d1400u, 0xe88e8a00u, 0x74474500u,
|
||||||
|
0x3a23a280u, 0x1d11d140u, 0x8e88e8a0u, 0x47447450u,
|
||||||
|
0x23a23a28u, 0x11d11d14u, 0x88e88e8au, 0x44744745u
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dimension 15: primitive polynomial 1+x+x^2+x^3 (degree 4)
|
||||||
|
const uint sobol_dirs_15[32] = uint[32](
|
||||||
|
0x80000000u, 0x40000000u, 0x60000000u, 0x90000000u,
|
||||||
|
0xd8000000u, 0x6c000000u, 0x86000000u, 0xc9000000u,
|
||||||
|
0x6e800000u, 0x95c00000u, 0xe8600000u, 0x5c900000u,
|
||||||
|
0x86e80000u, 0xc95c0000u, 0x6e860000u, 0x95c90000u,
|
||||||
|
0xe86e8000u, 0x5c95c000u, 0x86e86000u, 0xc95c9000u,
|
||||||
|
0x6e86e800u, 0x95c95c00u, 0xe86e8600u, 0x5c95c900u,
|
||||||
|
0x86e86e80u, 0xc95c95c0u, 0x6e86e860u, 0x95c95c90u,
|
||||||
|
0xe86e86e8u, 0x5c95c95cu, 0x86e86e86u, 0xc95c95c9u
|
||||||
|
);
|
||||||
|
|
||||||
// Access direction numbers by dimension
|
// Access direction numbers by dimension
|
||||||
uint sobol_direction(uint dimension, uint bit) {
|
uint sobol_direction(uint dimension, uint bit) {
|
||||||
if (dimension == 0u) return sobol_dirs_0[bit];
|
if (dimension == 0u) return sobol_dirs_0[bit];
|
||||||
|
|
@ -114,6 +210,14 @@ uint sobol_direction(uint dimension, uint bit) {
|
||||||
if (dimension == 5u) return sobol_dirs_5[bit];
|
if (dimension == 5u) return sobol_dirs_5[bit];
|
||||||
if (dimension == 6u) return sobol_dirs_6[bit];
|
if (dimension == 6u) return sobol_dirs_6[bit];
|
||||||
if (dimension == 7u) return sobol_dirs_7[bit];
|
if (dimension == 7u) return sobol_dirs_7[bit];
|
||||||
|
if (dimension == 8u) return sobol_dirs_8[bit];
|
||||||
|
if (dimension == 9u) return sobol_dirs_9[bit];
|
||||||
|
if (dimension == 10u) return sobol_dirs_10[bit];
|
||||||
|
if (dimension == 11u) return sobol_dirs_11[bit];
|
||||||
|
if (dimension == 12u) return sobol_dirs_12[bit];
|
||||||
|
if (dimension == 13u) return sobol_dirs_13[bit];
|
||||||
|
if (dimension == 14u) return sobol_dirs_14[bit];
|
||||||
|
if (dimension == 15u) return sobol_dirs_15[bit];
|
||||||
return sobol_dirs_0[bit]; // Fallback
|
return sobol_dirs_0[bit]; // Fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// ACES Filmic Tone Mapping — shared between ray tracing and upscale passes.
|
||||||
|
// Converts HDR linear radiance to LDR display colour [0, 1].
|
||||||
|
vec3 aces_tonemap(vec3 x) {
|
||||||
|
float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
|
||||||
|
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,9 @@ layout(binding = 0, rgba32f) uniform readonly image2D u_input;
|
||||||
layout(binding = 1, rgba32f) uniform writeonly image2D u_output;
|
layout(binding = 1, rgba32f) uniform writeonly image2D u_output;
|
||||||
layout(binding = 2, rgba32f) uniform readonly image2D u_history;
|
layout(binding = 2, rgba32f) uniform readonly image2D u_history;
|
||||||
|
|
||||||
uniform int u_radius; // 1 => 3x3, 2 => 5x5
|
uniform int u_radius; // 1 => 3x3, 2 => 5x5
|
||||||
uniform float u_temporal_weight; // 0 = no temporal, 1 = full history
|
uniform float u_temporal_weight; // 0 = no temporal, 1 = full history
|
||||||
uniform bool u_has_history; // Whether history texture is valid
|
uniform bool u_has_history; // Whether history texture is valid
|
||||||
|
|
||||||
// Gaussian weight based on distance
|
// Gaussian weight based on distance
|
||||||
float gaussian_weight(float dist, float sigma) {
|
float gaussian_weight(float dist, float sigma) {
|
||||||
|
|
@ -24,8 +24,8 @@ void main() {
|
||||||
vec3 center_color = imageLoad(u_input, p).rgb;
|
vec3 center_color = imageLoad(u_input, p).rgb;
|
||||||
|
|
||||||
// Sigma values for bilateral filter
|
// Sigma values for bilateral filter
|
||||||
float sigma_space = float(u_radius); // spatial sigma
|
float sigma_space = float(u_radius); // spatial sigma
|
||||||
float sigma_color = 0.3; // color sigma (adjust for more/less smoothing)
|
float sigma_color = 0.3; // color sigma (adjust for more/less smoothing)
|
||||||
|
|
||||||
vec3 sum = vec3(0.0);
|
vec3 sum = vec3(0.0);
|
||||||
float weight_sum = 0.0;
|
float weight_sum = 0.0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#version 430 core
|
||||||
|
|
||||||
|
#include "../include/tonemap.glsl"
|
||||||
|
|
||||||
|
layout(local_size_x = 16, local_size_y = 16) in;
|
||||||
|
|
||||||
|
// Binding 1 – full-res accumulation buffer (RGB = colour, A = 1.0 once sampled)
|
||||||
|
layout(binding = 1, rgba32f) uniform readonly image2D u_accumulated_rt;
|
||||||
|
|
||||||
|
// Binding 2 – final output
|
||||||
|
layout(binding = 2, rgba32f) uniform writeonly image2D u_output;
|
||||||
|
|
||||||
|
// ── Upscale: tonemap accumulated RT or output black for unreached pixels ──
|
||||||
|
void main() {
|
||||||
|
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
|
||||||
|
ivec2 s = imageSize(u_output);
|
||||||
|
if (p.x >= s.x || p.y >= s.y) return;
|
||||||
|
|
||||||
|
vec4 acc = imageLoad(u_accumulated_rt, p);
|
||||||
|
vec3 col = (acc.a > 0.0) ? aces_tonemap(acc.rgb) : vec3(0.0);
|
||||||
|
imageStore(u_output, p, vec4(col, 1.0));
|
||||||
|
}
|
||||||
|
|
@ -1,36 +1,41 @@
|
||||||
#version 430 core
|
#version 430 core
|
||||||
|
|
||||||
// Include shared modules
|
|
||||||
#include "../include/common.glsl"
|
#include "../include/common.glsl"
|
||||||
#include "../include/structs.glsl"
|
#include "../include/structs.glsl"
|
||||||
#include "../include/math.glsl"
|
#include "../include/math.glsl"
|
||||||
#include "../include/rng.glsl"
|
#include "../include/rng.glsl"
|
||||||
#include "../include/sobol.glsl"
|
#include "../include/sobol.glsl"
|
||||||
#include "../include/sampling.glsl"
|
#include "../include/sampling.glsl"
|
||||||
|
#include "../include/tonemap.glsl"
|
||||||
|
|
||||||
// Workgroup size
|
|
||||||
layout(local_size_x = 16, local_size_y = 16) in;
|
layout(local_size_x = 16, local_size_y = 16) in;
|
||||||
|
|
||||||
// G-Buffer inputs
|
|
||||||
layout(binding = 0, rgba32f) uniform readonly image2D g_position;
|
layout(binding = 0, rgba32f) uniform readonly image2D g_position;
|
||||||
layout(binding = 1, rg32f) uniform readonly image2D g_normal; // Octahedral encoded
|
layout(binding = 1, rg32f) uniform readonly image2D g_normal;
|
||||||
layout(binding = 5, rgba32f) uniform readonly image2D g_material;
|
layout(binding = 5, rgba32f) uniform readonly image2D g_material;
|
||||||
layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id;
|
layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id;
|
||||||
layout(binding = 7, rgba32f) uniform readonly image2D g_texcoord;
|
layout(binding = 2, rgba32f) uniform readonly image2D g_texcoord;
|
||||||
layout(binding = 8, rgba32f) uniform readonly image2D g_tangent;
|
layout(binding = 7, rgba32f) uniform readonly image2D g_tangent;
|
||||||
|
|
||||||
// Output
|
|
||||||
layout(binding = 3, rgba32f) uniform image2D output_image;
|
layout(binding = 3, rgba32f) uniform image2D output_image;
|
||||||
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
|
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
|
||||||
|
|
||||||
// SSBO bindings
|
layout(std430, binding = 0) readonly buffer MaterialBuffer {
|
||||||
layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; };
|
Material materials[];
|
||||||
layout(std430, binding = 1) readonly buffer LightBuffer { Light lights[]; };
|
};
|
||||||
layout(std430, binding = 2) readonly buffer BVHNodeBuffer { BVHNodeGpu bvh_nodes[]; };
|
layout(std430, binding = 1) readonly buffer LightBuffer {
|
||||||
layout(std430, binding = 3) readonly buffer TriangleBuffer { TriangleCompactGpu bvh_tris[]; };
|
Light lights[];
|
||||||
layout(std430, binding = 4) readonly buffer AttrBuffer { TriangleAttrGpu bvh_attrs[]; };
|
};
|
||||||
|
layout(std430, binding = 2) readonly buffer BVHNodeBuffer {
|
||||||
|
BVHNodeGpu bvh_nodes[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = 3) readonly buffer TriangleBuffer {
|
||||||
|
TriangleCompactGpu bvh_tris[];
|
||||||
|
};
|
||||||
|
layout(std430, binding = 4) readonly buffer AttrBuffer {
|
||||||
|
TriangleAttrGpu bvh_attrs[];
|
||||||
|
};
|
||||||
|
|
||||||
// Uniforms
|
|
||||||
uniform uint u_frame_count;
|
uniform uint u_frame_count;
|
||||||
uniform uint u_samples_per_pixel;
|
uniform uint u_samples_per_pixel;
|
||||||
uniform uint u_max_depth;
|
uniform uint u_max_depth;
|
||||||
|
|
@ -41,7 +46,13 @@ uniform bool u_use_bvh;
|
||||||
uniform uint u_bvh_node_count;
|
uniform uint u_bvh_node_count;
|
||||||
uniform bool u_enable_textures;
|
uniform bool u_enable_textures;
|
||||||
|
|
||||||
// Texture arrays
|
uniform uint u_sr_enabled;
|
||||||
|
uniform uint u_sr_scaling; // pixel ratio, e.g. 4 → 4× fewer pixels
|
||||||
|
uniform uint u_sr_block; // sqrt(scaling), block side length in pixels
|
||||||
|
uniform uint u_sr_jitter; // frame index within one jitter cycle (0 .. scaling-1)
|
||||||
|
uniform uint u_sr_full_width;
|
||||||
|
uniform uint u_sr_full_height;
|
||||||
|
|
||||||
layout(binding = 10) uniform sampler2DArray u_texture_albedo_array;
|
layout(binding = 10) uniform sampler2DArray u_texture_albedo_array;
|
||||||
layout(binding = 11) uniform sampler2DArray u_texture_normal_array;
|
layout(binding = 11) uniform sampler2DArray u_texture_normal_array;
|
||||||
layout(binding = 12) uniform sampler2DArray u_texture_metallic_array;
|
layout(binding = 12) uniform sampler2DArray u_texture_metallic_array;
|
||||||
|
|
@ -49,39 +60,29 @@ layout(binding = 13) uniform sampler2DArray u_texture_roughness_array;
|
||||||
layout(binding = 14) uniform sampler2DArray u_texture_ao_array;
|
layout(binding = 14) uniform sampler2DArray u_texture_ao_array;
|
||||||
layout(binding = 15) uniform sampler2DArray u_texture_emission_array;
|
layout(binding = 15) uniform sampler2DArray u_texture_emission_array;
|
||||||
|
|
||||||
// Include material, BVH, and lighting modules (needs uniform declarations above)
|
|
||||||
#include "../include/material.glsl"
|
#include "../include/material.glsl"
|
||||||
#include "../include/bvh.glsl"
|
#include "../include/bvh.glsl"
|
||||||
#include "../include/lighting.glsl"
|
#include "../include/lighting.glsl"
|
||||||
|
|
||||||
// Sobol sampling state
|
|
||||||
struct SobolState {
|
struct SobolState {
|
||||||
uint sample_index; // Which sample (0, 1, 2, ...)
|
uint sample_index;
|
||||||
uint dimension; // Current dimension being used
|
uint dimension;
|
||||||
uint scramble; // Seed for Owen scrambling
|
uint scramble;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Sobol state
|
|
||||||
SobolState init_sobol(uint pixel_index, uint frame, uint sample_idx) {
|
SobolState init_sobol(uint pixel_index, uint frame, uint sample_idx) {
|
||||||
SobolState state;
|
SobolState state;
|
||||||
// Sample index combines frame and sample number for temporal variation
|
|
||||||
// Add +1 to avoid degenerate index 0 (Sobol at index 0 produces all zeros before scrambling)
|
|
||||||
// Add pixel_index offset to ensure spatial variation within same frame
|
|
||||||
state.sample_index = sample_idx + frame * 1024u + pixel_index + 1u;
|
state.sample_index = sample_idx + frame * 1024u + pixel_index + 1u;
|
||||||
state.dimension = 0u;
|
state.dimension = 0u;
|
||||||
// Use pixel index for per-pixel Owen scrambling
|
|
||||||
state.scramble = pcg_hash(pixel_index + frame * 668265263u);
|
state.scramble = pcg_hash(pixel_index + frame * 668265263u);
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get next Sobol float in [0, 1)
|
|
||||||
float sobol_next(inout SobolState state) {
|
float sobol_next(inout SobolState state) {
|
||||||
float value;
|
float value;
|
||||||
if (state.dimension < 8u) {
|
if (state.dimension < 16u) {
|
||||||
// Use Sobol for first 8 dimensions
|
|
||||||
value = sobol_get(state.sample_index, state.dimension, state.scramble);
|
value = sobol_get(state.sample_index, state.dimension, state.scramble);
|
||||||
} else {
|
} else {
|
||||||
// Fall back to PCG for higher dimensions
|
|
||||||
uint rng_state = pcg_hash(state.scramble + state.dimension * 2654435761u);
|
uint rng_state = pcg_hash(state.scramble + state.dimension * 2654435761u);
|
||||||
value = float(rng_state) / 4294967296.0;
|
value = float(rng_state) / 4294967296.0;
|
||||||
}
|
}
|
||||||
|
|
@ -89,218 +90,166 @@ float sobol_next(inout SobolState state) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sobol-based random in unit sphere
|
|
||||||
vec3 sobol_in_unit_sphere(inout SobolState state) {
|
|
||||||
float z = 1.0 - 2.0 * sobol_next(state);
|
|
||||||
float r = sqrt(max(0.0, 1.0 - z * z));
|
|
||||||
float phi = 2.0 * PI * sobol_next(state);
|
|
||||||
return vec3(r * cos(phi), r * sin(phi), z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sobol-based unit vector
|
|
||||||
vec3 sobol_unit_vector(inout SobolState state) {
|
|
||||||
return normalize(sobol_in_unit_sphere(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sobol-based GGX half vector sampling
|
|
||||||
vec3 sobol_ggx_half_vector(float roughness, vec3 N, inout SobolState state) {
|
vec3 sobol_ggx_half_vector(float roughness, vec3 N, inout SobolState state) {
|
||||||
float a = roughness * roughness;
|
float a = roughness * roughness;
|
||||||
float a2 = a * a;
|
float a2 = a * a;
|
||||||
|
|
||||||
float u1 = clamp(sobol_next(state), 0.001, 0.999);
|
float u1 = clamp(sobol_next(state), 0.001, 0.999);
|
||||||
float u2 = sobol_next(state);
|
float u2 = sobol_next(state);
|
||||||
|
float ct = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0));
|
||||||
float cos_theta = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0));
|
ct = clamp(ct, 0.0, 1.0);
|
||||||
cos_theta = clamp(cos_theta, 0.0, 1.0);
|
float st = sqrt(max(0.0, 1.0 - ct * ct));
|
||||||
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
float ph = 2.0 * PI * u2;
|
||||||
float phi = 2.0 * PI * u2;
|
vec3 Ht = vec3(st * cos(ph), st * sin(ph), ct);
|
||||||
|
vec3 up = (abs(N.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||||
vec3 H_tangent = vec3(sin_theta * cos(phi), sin_theta * sin(phi), cos_theta);
|
vec3 T = normalize(cross(up, N));
|
||||||
mat3 onb = build_onb(N);
|
vec3 B = cross(N, T);
|
||||||
return normalize(onb * H_tangent);
|
return mat3(T, B, N) * Ht;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sobol-based diffuse scattering
|
|
||||||
ScatterResult scatter_diffuse_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
ScatterResult scatter_diffuse_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||||
ScatterResult r;
|
ScatterResult r;
|
||||||
r.scattered = true;
|
r.scattered = true;
|
||||||
r.attenuation = mat.albedo;
|
r.attenuation = mat.albedo;
|
||||||
|
float rs = sqrt(sobol_next(state));
|
||||||
vec3 dir = hit.normal + sobol_unit_vector(state);
|
float ph = 2.0 * PI * sobol_next(state);
|
||||||
|
float x = rs * cos(ph), y = rs * sin(ph);
|
||||||
|
float z = sqrt(max(0.0, 1.0 - x * x - y * y));
|
||||||
|
vec3 up = (abs(hit.normal.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
|
||||||
|
vec3 T = normalize(cross(up, hit.normal));
|
||||||
|
vec3 B = cross(hit.normal, T);
|
||||||
|
vec3 dir = mat3(T, B, hit.normal) * vec3(x, y, z);
|
||||||
if (near_zero(dir)) dir = hit.normal;
|
if (near_zero(dir)) dir = hit.normal;
|
||||||
|
|
||||||
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(dir);
|
r.scattered_ray.direction = dir;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sobol-based metal scattering (GGX)
|
|
||||||
ScatterResult scatter_metal_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
ScatterResult scatter_metal_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||||
ScatterResult r;
|
ScatterResult r;
|
||||||
|
|
||||||
vec3 V = normalize(-ray_in.direction);
|
vec3 V = normalize(-ray_in.direction);
|
||||||
vec3 N = hit.normal;
|
|
||||||
float roughness = clamp(mat.roughness, 0.04, 1.0);
|
float roughness = clamp(mat.roughness, 0.04, 1.0);
|
||||||
|
vec3 H = sobol_ggx_half_vector(roughness, hit.normal, state);
|
||||||
vec3 H = sobol_ggx_half_vector(roughness, N, state);
|
if (dot(H, hit.normal) < 0.0) H = -H;
|
||||||
if (dot(H, N) < 0.0) H = -H;
|
|
||||||
|
|
||||||
vec3 L = reflect(-V, H);
|
vec3 L = reflect(-V, H);
|
||||||
|
if (dot(hit.normal, L) <= 0.0) {
|
||||||
float NdotL = dot(N, L);
|
|
||||||
if (NdotL <= 0.0) {
|
|
||||||
r.scattered = false;
|
r.scattered = false;
|
||||||
r.attenuation = vec3(0.0);
|
r.attenuation = vec3(0.0);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
float HdotV = max(dot(H, V), 0.001);
|
float HdotV = max(dot(H, V), 0.001);
|
||||||
vec3 F = fresnel_schlick(HdotV, mat.albedo);
|
vec3 F = fresnel_schlick(HdotV, mat.albedo);
|
||||||
|
|
||||||
r.attenuation = clamp(F, vec3(0.0), vec3(1.0));
|
r.attenuation = clamp(F, vec3(0.0), vec3(1.0));
|
||||||
r.scattered = true;
|
r.scattered = true;
|
||||||
r.scattered_ray.origin = hit.position + N * EPSILON;
|
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(L);
|
r.scattered_ray.direction = L;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sobol-based dielectric scattering
|
|
||||||
ScatterResult scatter_dielectric_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
ScatterResult scatter_dielectric_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||||
ScatterResult r;
|
ScatterResult r;
|
||||||
r.scattered = true;
|
r.scattered = true;
|
||||||
r.attenuation = vec3(1.0);
|
r.attenuation = vec3(1.0);
|
||||||
|
vec3 ud = normalize(ray_in.direction);
|
||||||
vec3 unit_dir = normalize(ray_in.direction);
|
float ct = dot(-ud, hit.normal);
|
||||||
float cos_theta = dot(-unit_dir, hit.normal);
|
float st = sqrt(max(0.0, 1.0 - ct * ct));
|
||||||
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
bool ent = ct > 0.0;
|
||||||
|
float eta = ent ? (1.0 / mat.ior) : mat.ior;
|
||||||
bool entering = cos_theta > 0.0;
|
vec3 N = ent ? hit.normal : -hit.normal;
|
||||||
float eta = entering ? (1.0 / mat.ior) : mat.ior;
|
float st_t = eta * st;
|
||||||
vec3 normal = entering ? hit.normal : -hit.normal;
|
float f = fresnel_dielectric(ct, mat.ior);
|
||||||
|
|
||||||
float sin_theta_t = eta * sin_theta;
|
|
||||||
bool total_internal_reflection = sin_theta_t >= 1.0;
|
|
||||||
|
|
||||||
float f0 = pow((1.0 - mat.ior) / (1.0 + mat.ior), 2.0);
|
|
||||||
float f = f0 + (1.0 - f0) * pow(1.0 - abs(cos_theta), 5.0);
|
|
||||||
|
|
||||||
vec3 dir;
|
vec3 dir;
|
||||||
if (total_internal_reflection || sobol_next(state) < f) {
|
if (st_t >= 1.0 || sobol_next(state) < f)
|
||||||
dir = reflect_vector(unit_dir, normal);
|
dir = reflect_vector(ud, N);
|
||||||
} else {
|
else
|
||||||
dir = refract_vector(unit_dir, normal, eta);
|
dir = refract_vector(ud, N, eta);
|
||||||
}
|
|
||||||
|
|
||||||
r.scattered_ray.origin = hit.position + dir * EPSILON;
|
r.scattered_ray.origin = hit.position + dir * EPSILON;
|
||||||
r.scattered_ray.direction = normalize(dir);
|
r.scattered_ray.direction = dir;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sobol-based scatter dispatcher
|
|
||||||
ScatterResult scatter_ray_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
ScatterResult scatter_ray_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||||
if (mat.type == MATERIAL_DIFFUSE) return scatter_diffuse_sobol(ray_in, hit, mat, state);
|
if (mat.type == MATERIAL_DIFFUSE) return scatter_diffuse_sobol(ray_in, hit, mat, state);
|
||||||
if (mat.type == MATERIAL_METAL) return scatter_metal_sobol(ray_in, hit, mat, state);
|
if (mat.type == MATERIAL_METAL) return scatter_metal_sobol(ray_in, hit, mat, state);
|
||||||
if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric_sobol(ray_in, hit, mat, state);
|
if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric_sobol(ray_in, hit, mat, state);
|
||||||
|
|
||||||
ScatterResult r;
|
ScatterResult r;
|
||||||
r.scattered = false;
|
r.scattered = false;
|
||||||
r.attenuation = vec3(0.0);
|
r.attenuation = vec3(0.0);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate camera ray (center pixel, no jitter)
|
|
||||||
Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size) {
|
Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size) {
|
||||||
vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size);
|
vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size);
|
||||||
vec2 ndc = uv * 2.0 - 1.0;
|
vec2 ndc = uv * 2.0 - 1.0;
|
||||||
|
vec4 pn = u_inv_view_projection * vec4(ndc, 0.0, 1.0);
|
||||||
vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0);
|
vec4 pf = u_inv_view_projection * vec4(ndc, 1.0, 1.0);
|
||||||
vec4 p_far = u_inv_view_projection * vec4(ndc, 1.0, 1.0);
|
|
||||||
vec3 near_ws = p_near.xyz / p_near.w;
|
|
||||||
vec3 far_ws = p_far.xyz / p_far.w;
|
|
||||||
|
|
||||||
Ray r;
|
Ray r;
|
||||||
r.origin = near_ws;
|
r.origin = pn.xyz / pn.w;
|
||||||
r.direction = normalize(far_ws - near_ws);
|
r.direction = normalize(pf.xyz / pf.w - r.origin);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path tracing with G-Buffer acceleration for primary ray (Sobol sampling)
|
|
||||||
vec3 trace_path_sobol(ivec2 pixel_coords, ivec2 image_size, inout SobolState sobol) {
|
vec3 trace_path_sobol(ivec2 pixel_coords, ivec2 image_size, inout SobolState sobol) {
|
||||||
Ray ray = generate_camera_ray(pixel_coords, image_size);
|
Ray ray = generate_camera_ray(pixel_coords, image_size);
|
||||||
|
|
||||||
vec3 radiance = vec3(0.0);
|
vec3 radiance = vec3(0.0);
|
||||||
vec3 throughput = vec3(1.0);
|
vec3 throughput = vec3(1.0);
|
||||||
|
|
||||||
// Depth 0: try G-Buffer hit first
|
|
||||||
HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords);
|
HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords);
|
||||||
if (hit0.hit) {
|
if (hit0.hit) {
|
||||||
Material mat0 = fetch_material(hit0.material_id);
|
Material mat0 = fetch_material(hit0.material_id);
|
||||||
|
if (hit0.material_type >= 0) mat0.type = hit0.material_type;
|
||||||
if (hit0.material_type >= 0) {
|
|
||||||
mat0.type = hit0.material_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent);
|
apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent);
|
||||||
|
|
||||||
radiance += throughput * mat0.emission;
|
radiance += throughput * mat0.emission;
|
||||||
|
|
||||||
ScatterResult sc0 = scatter_ray_sobol(ray, hit0, mat0, sobol);
|
ScatterResult sc0 = scatter_ray_sobol(ray, hit0, mat0, sobol);
|
||||||
if (!sc0.scattered) return radiance;
|
if (!sc0.scattered) return radiance;
|
||||||
|
|
||||||
throughput *= sc0.attenuation;
|
throughput *= sc0.attenuation;
|
||||||
ray = sc0.scattered_ray;
|
ray = sc0.scattered_ray;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsequent bounces: BVH
|
|
||||||
for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) {
|
for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) {
|
||||||
HitInfo hit = trace_ray_bvh(ray);
|
HitInfo hit = trace_ray_bvh(ray);
|
||||||
if (!hit.hit) {
|
if (!hit.hit) {
|
||||||
radiance += throughput * environment_color(ray.direction);
|
radiance += throughput * environment_color(ray.direction);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Material mat = fetch_material(hit.material_id);
|
Material mat = fetch_material(hit.material_id);
|
||||||
apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent);
|
apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent);
|
||||||
|
|
||||||
radiance += throughput * mat.emission;
|
radiance += throughput * mat.emission;
|
||||||
|
|
||||||
ScatterResult sc = scatter_ray_sobol(ray, hit, mat, sobol);
|
ScatterResult sc = scatter_ray_sobol(ray, hit, mat, sobol);
|
||||||
if (!sc.scattered) break;
|
if (!sc.scattered) break;
|
||||||
|
|
||||||
throughput *= sc.attenuation;
|
throughput *= sc.attenuation;
|
||||||
|
|
||||||
if (depth > 3u) {
|
if (depth > 3u) {
|
||||||
float p = max(throughput.r, max(throughput.g, throughput.b));
|
float p = max(max(throughput.r, throughput.g), throughput.b);
|
||||||
p = clamp(p, 0.0, 0.95);
|
p = clamp(p, 0.0, 0.95);
|
||||||
if (p < RR_THRESHOLD || sobol_next(sobol) > p) break;
|
if (p < RR_THRESHOLD || sobol_next(sobol) > p) break;
|
||||||
throughput /= p;
|
throughput /= p;
|
||||||
}
|
}
|
||||||
|
|
||||||
ray = sc.scattered_ray;
|
ray = sc.scattered_ray;
|
||||||
|
|
||||||
if (all(lessThan(throughput, vec3(EPSILON)))) break;
|
if (all(lessThan(throughput, vec3(EPSILON)))) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return radiance;
|
return radiance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACES Filmic Tone Mapping
|
|
||||||
vec3 aces_tonemap(vec3 x) {
|
|
||||||
float a = 2.51;
|
|
||||||
float b = 0.03;
|
|
||||||
float c = 2.43;
|
|
||||||
float d = 0.59;
|
|
||||||
float e = 0.14;
|
|
||||||
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
|
ivec2 rt_coord = ivec2(gl_GlobalInvocationID.xy);
|
||||||
ivec2 image_size = imageSize(output_image);
|
ivec2 output_size = imageSize(output_image);
|
||||||
|
if (rt_coord.x >= output_size.x || rt_coord.y >= output_size.y) return;
|
||||||
|
|
||||||
|
ivec2 pixel_coords;
|
||||||
|
ivec2 image_size;
|
||||||
|
|
||||||
|
if (u_sr_enabled != 0u) {
|
||||||
|
uint jx = u_sr_jitter % u_sr_block;
|
||||||
|
uint jy = u_sr_jitter / u_sr_block;
|
||||||
|
pixel_coords = rt_coord * int(u_sr_block) + ivec2(int(jx), int(jy));
|
||||||
|
image_size = ivec2(int(u_sr_full_width), int(u_sr_full_height));
|
||||||
|
} else {
|
||||||
|
pixel_coords = rt_coord;
|
||||||
|
image_size = output_size;
|
||||||
|
}
|
||||||
|
|
||||||
if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) return;
|
if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) return;
|
||||||
|
|
||||||
uint pixel_index = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x);
|
uint pixel_index = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x);
|
||||||
uint seed = init_seed(pixel_coords, image_size, u_frame_count);
|
|
||||||
|
|
||||||
vec3 color = vec3(0.0);
|
vec3 color = vec3(0.0);
|
||||||
uint spp = max(u_samples_per_pixel, 1u);
|
uint spp = max(u_samples_per_pixel, 1u);
|
||||||
|
|
@ -310,19 +259,26 @@ void main() {
|
||||||
color += trace_path_sobol(pixel_coords, image_size, sobol);
|
color += trace_path_sobol(pixel_coords, image_size, sobol);
|
||||||
}
|
}
|
||||||
color /= float(spp);
|
color /= float(spp);
|
||||||
|
|
||||||
color = clamp(color, vec3(0.0), vec3(100.0));
|
color = clamp(color, vec3(0.0), vec3(100.0));
|
||||||
|
|
||||||
vec3 accumulation_color = color;
|
// ── Super resolution: per‑cycle running average inside the RT shader ──
|
||||||
|
if (u_sr_enabled != 0u) {
|
||||||
if (u_enable_accumulation && u_frame_count > 0u) {
|
vec4 old = imageLoad(accumulation_image, pixel_coords);
|
||||||
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
|
uint cyc = u_frame_count / u_sr_scaling;
|
||||||
float w = 1.0 / float(u_frame_count + 1u);
|
float w = 1.0 / float(cyc + 1u);
|
||||||
accumulation_color = mix(accumulated, color, w);
|
vec3 acc = mix(old.rgb, color, w);
|
||||||
|
imageStore(accumulation_image, pixel_coords, vec4(acc, 1.0));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec3 output_color = aces_tonemap(accumulation_color);
|
// ── Standard non‑SR accumulation ─────────────────────────────────────
|
||||||
|
vec3 acc_color = color;
|
||||||
imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0));
|
if (u_enable_accumulation && u_frame_count > 0u) {
|
||||||
imageStore(output_image, pixel_coords, vec4(output_color, 1.0));
|
vec3 old = imageLoad(accumulation_image, rt_coord).rgb;
|
||||||
|
float w = 1.0 / float(u_frame_count + 1u);
|
||||||
|
acc_color = mix(old, color, w);
|
||||||
|
}
|
||||||
|
vec3 out_color = aces_tonemap(acc_color);
|
||||||
|
imageStore(accumulation_image, rt_coord, vec4(acc_color, 1.0));
|
||||||
|
imageStore(output_image, rt_coord, vec4(out_color, 1.0));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,142 +7,146 @@
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
Denoiser::Denoiser(uint width, uint height)
|
Denoiser::Denoiser(uint width, uint height)
|
||||||
: width_(width)
|
: width_(width)
|
||||||
, height_(height)
|
, height_(height)
|
||||||
, output_texture_(INVALID_HANDLE)
|
, output_texture_(INVALID_HANDLE)
|
||||||
, history_texture_(INVALID_HANDLE)
|
, history_texture_(INVALID_HANDLE)
|
||||||
, history_valid_(false)
|
, history_valid_(false)
|
||||||
, initialized_(false) {
|
, initialized_(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Denoiser::~Denoiser() {
|
Denoiser::~Denoiser() {
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Denoiser::initialize(const std::shared_ptr<Shader>& shader) {
|
bool Denoiser::initialize(const std::shared_ptr<Shader> &shader) {
|
||||||
if (initialized_) return true;
|
if (initialized_)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (!shader || !shader->is_valid()) {
|
if (!shader || !shader->is_valid()) {
|
||||||
ARE_LOG_ERROR("Invalid denoise shader");
|
ARE_LOG_ERROR("Invalid denoise shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
shader_ = shader;
|
shader_ = shader;
|
||||||
create_output_texture_();
|
create_output_texture_();
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
ARE_LOG_INFO("Denoiser initialized");
|
ARE_LOG_INFO("Denoiser initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Denoiser::release() {
|
void Denoiser::release() {
|
||||||
if (!initialized_) return;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
shader_.reset();
|
shader_.reset();
|
||||||
|
|
||||||
if (output_texture_ != INVALID_HANDLE) {
|
if (output_texture_ != INVALID_HANDLE) {
|
||||||
ResourceManager::instance().destroy_texture(output_texture_);
|
ResourceManager::instance().destroy_texture(output_texture_);
|
||||||
output_texture_ = INVALID_HANDLE;
|
output_texture_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (history_texture_ != INVALID_HANDLE) {
|
if (history_texture_ != INVALID_HANDLE) {
|
||||||
ResourceManager::instance().destroy_texture(history_texture_);
|
ResourceManager::instance().destroy_texture(history_texture_);
|
||||||
history_texture_ = INVALID_HANDLE;
|
history_texture_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
history_valid_ = false;
|
history_valid_ = false;
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Denoiser::resize(uint width, uint height) {
|
void Denoiser::resize(uint width, uint height) {
|
||||||
if (width == width_ && height == height_) return;
|
if (width == width_ && height == height_)
|
||||||
width_ = width;
|
return;
|
||||||
height_ = height;
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
if (!initialized_) return;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
|
||||||
if (output_texture_ != INVALID_HANDLE) {
|
if (output_texture_ != INVALID_HANDLE) {
|
||||||
rm.destroy_texture(output_texture_);
|
rm.destroy_texture(output_texture_);
|
||||||
output_texture_ = INVALID_HANDLE;
|
output_texture_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (history_texture_ != INVALID_HANDLE) {
|
if (history_texture_ != INVALID_HANDLE) {
|
||||||
rm.destroy_texture(history_texture_);
|
rm.destroy_texture(history_texture_);
|
||||||
history_texture_ = INVALID_HANDLE;
|
history_texture_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
history_valid_ = false;
|
history_valid_ = false;
|
||||||
create_output_texture_();
|
create_output_texture_();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureHandle Denoiser::denoise(TextureHandle input_texture, int radius, float temporal_weight) {
|
TextureHandle Denoiser::denoise(TextureHandle input_texture, int radius, float temporal_weight) {
|
||||||
if (!initialized_) return input_texture;
|
if (!initialized_)
|
||||||
|
return input_texture;
|
||||||
|
|
||||||
radius = (radius < 0) ? 0 : radius;
|
radius = (radius < 0) ? 0 : radius;
|
||||||
temporal_weight = (temporal_weight < 0.0f) ? 0.0f : ((temporal_weight > 1.0f) ? 1.0f : temporal_weight);
|
temporal_weight = (temporal_weight < 0.0f) ? 0.0f : ((temporal_weight > 1.0f) ? 1.0f : temporal_weight);
|
||||||
|
|
||||||
shader_->use();
|
shader_->use();
|
||||||
|
|
||||||
glBindImageTexture(0, input_texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
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);
|
glBindImageTexture(1, output_texture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
|
||||||
|
|
||||||
shader_->set_int("u_radius", radius);
|
shader_->set_int("u_radius", radius);
|
||||||
shader_->set_float("u_temporal_weight", temporal_weight);
|
shader_->set_float("u_temporal_weight", temporal_weight);
|
||||||
shader_->set_bool("u_has_history", history_valid_ && temporal_weight > 0.0f);
|
shader_->set_bool("u_has_history", history_valid_ && temporal_weight > 0.0f);
|
||||||
|
|
||||||
// Bind history texture if available and temporal accumulation is enabled
|
// Bind history texture if available and temporal accumulation is enabled
|
||||||
if (history_valid_ && temporal_weight > 0.0f) {
|
if (history_valid_ && temporal_weight > 0.0f) {
|
||||||
glBindImageTexture(2, history_texture_, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
glBindImageTexture(2, history_texture_, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
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;
|
uint groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
||||||
glDispatchCompute(groups_x, groups_y, 1);
|
glDispatchCompute(groups_x, groups_y, 1);
|
||||||
|
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||||
|
|
||||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
// Copy output to history for next frame (if temporal accumulation is enabled)
|
||||||
|
if (temporal_weight > 0.0f) {
|
||||||
|
// Create history texture if it doesn't exist
|
||||||
|
if (history_texture_ == INVALID_HANDLE) {
|
||||||
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
TextureDescription desc;
|
||||||
|
desc.width = width_;
|
||||||
|
desc.height = height_;
|
||||||
|
desc.format = TextureFormat::RGBA32F;
|
||||||
|
desc.filter = TextureFilter::NEAREST;
|
||||||
|
desc.wrap = TextureWrap::CLAMP_TO_EDGE;
|
||||||
|
history_texture_ = rm.create_texture(desc);
|
||||||
|
}
|
||||||
|
|
||||||
// Copy output to history for next frame (if temporal accumulation is enabled)
|
// Copy output to history using GPU (blit or compute)
|
||||||
if (temporal_weight > 0.0f) {
|
// For simplicity, we'll just bind output as history for next frame
|
||||||
// Create history texture if it doesn't exist
|
// This requires double buffering - let's swap the textures
|
||||||
if (history_texture_ == INVALID_HANDLE) {
|
std::swap(output_texture_, history_texture_);
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
history_valid_ = true;
|
||||||
TextureDescription desc;
|
|
||||||
desc.width = width_;
|
|
||||||
desc.height = height_;
|
|
||||||
desc.format = TextureFormat::RGBA32F;
|
|
||||||
desc.filter = TextureFilter::NEAREST;
|
|
||||||
desc.wrap = TextureWrap::CLAMP_TO_EDGE;
|
|
||||||
history_texture_ = rm.create_texture(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy output to history using GPU (blit or compute)
|
// Return the new output (which was history before swap)
|
||||||
// For simplicity, we'll just bind output as history for next frame
|
return output_texture_;
|
||||||
// This requires double buffering - let's swap the textures
|
}
|
||||||
std::swap(output_texture_, history_texture_);
|
|
||||||
history_valid_ = true;
|
|
||||||
|
|
||||||
// Return the new output (which was history before swap)
|
return output_texture_;
|
||||||
return output_texture_;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output_texture_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Denoiser::reset_history() {
|
void Denoiser::reset_history() {
|
||||||
history_valid_ = false;
|
history_valid_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Denoiser::create_output_texture_() {
|
void Denoiser::create_output_texture_() {
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
TextureDescription desc;
|
TextureDescription desc;
|
||||||
desc.width = width_;
|
desc.width = width_;
|
||||||
desc.height = height_;
|
desc.height = height_;
|
||||||
desc.format = TextureFormat::RGBA32F;
|
desc.format = TextureFormat::RGBA32F;
|
||||||
desc.filter = TextureFilter::NEAREST;
|
desc.filter = TextureFilter::NEAREST;
|
||||||
desc.wrap = TextureWrap::CLAMP_TO_EDGE;
|
desc.wrap = TextureWrap::CLAMP_TO_EDGE;
|
||||||
output_texture_ = rm.create_texture(desc);
|
output_texture_ = rm.create_texture(desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#include "basic/constants.h"
|
#include "basic/constants.h"
|
||||||
#include "resource/resource_manager.h"
|
#include "resource/resource_manager.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
|
#include <cmath>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
@ -17,38 +18,21 @@ namespace {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute hash of texture pointers for a specific slot
|
|
||||||
uint compute_slot_texture_hash(const std::vector<std::shared_ptr<Texture>> &textures) {
|
uint compute_slot_texture_hash(const std::vector<std::shared_ptr<Texture>> &textures) {
|
||||||
if (textures.empty())
|
if (textures.empty())
|
||||||
return 0u;
|
return 0u;
|
||||||
// Hash the raw pointers to detect texture set changes
|
|
||||||
std::vector<const void *> ptrs;
|
std::vector<const void *> ptrs;
|
||||||
ptrs.reserve(textures.size());
|
ptrs.reserve(textures.size());
|
||||||
for (const auto &t : textures) {
|
for (const auto &t : textures)
|
||||||
ptrs.push_back(t.get());
|
ptrs.push_back(t.get());
|
||||||
}
|
|
||||||
return fnv1a_hash_bytes(ptrs.data(), ptrs.size() * sizeof(void *));
|
return fnv1a_hash_bytes(ptrs.data(), ptrs.size() * sizeof(void *));
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RayTracer::RayTracer(uint width, uint height, const RayTracerConfig &config)
|
RayTracer::RayTracer(uint width, uint height, const RayTracerConfig &config)
|
||||||
: width_(width)
|
: width_(width), height_(height), config_(config), materials_hash_(0u), lights_hash_(0u), texture_config_hash_(0u), texture_arrays_dirty_(true), accumulation_texture_(INVALID_HANDLE), material_buffer_(INVALID_HANDLE), light_buffer_(INVALID_HANDLE), bvh_(nullptr), bvh_built_(false), frame_count_(0), initialized_(false) {
|
||||||
, height_(height)
|
for (int i = 0; i < 6; ++i)
|
||||||
, config_(config)
|
|
||||||
, materials_hash_(0u)
|
|
||||||
, lights_hash_(0u)
|
|
||||||
, texture_config_hash_(0u)
|
|
||||||
, texture_arrays_dirty_(true)
|
|
||||||
, accumulation_texture_(INVALID_HANDLE)
|
|
||||||
, material_buffer_(INVALID_HANDLE)
|
|
||||||
, light_buffer_(INVALID_HANDLE)
|
|
||||||
, bvh_(nullptr)
|
|
||||||
, bvh_built_(false)
|
|
||||||
, frame_count_(0)
|
|
||||||
, initialized_(false) {
|
|
||||||
for (int i = 0; i < 6; ++i) {
|
|
||||||
texture_slot_hashes_[i] = 0u;
|
texture_slot_hashes_[i] = 0u;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RayTracer::~RayTracer() {
|
RayTracer::~RayTracer() {
|
||||||
|
|
@ -60,15 +44,9 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &shader) {
|
||||||
ARE_LOG_WARN("RayTracer already initialized");
|
ARE_LOG_WARN("RayTracer already initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
|
||||||
compute_shader_ = shader;
|
compute_shader_ = shader;
|
||||||
|
|
||||||
// Create accumulation texture
|
|
||||||
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
||||||
|
|
||||||
// Create shader storage buffers
|
|
||||||
BufferDescription ssbo_desc;
|
BufferDescription ssbo_desc;
|
||||||
ssbo_desc.type = BufferType::SHADER_STORAGE_BUFFER;
|
ssbo_desc.type = BufferType::SHADER_STORAGE_BUFFER;
|
||||||
ssbo_desc.usage = BufferUsage::DYNAMIC_DRAW;
|
ssbo_desc.usage = BufferUsage::DYNAMIC_DRAW;
|
||||||
|
|
@ -76,108 +54,85 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &shader) {
|
||||||
ssbo_desc.data = nullptr;
|
ssbo_desc.data = nullptr;
|
||||||
material_buffer_ = rm.create_buffer(ssbo_desc);
|
material_buffer_ = rm.create_buffer(ssbo_desc);
|
||||||
light_buffer_ = rm.create_buffer(ssbo_desc);
|
light_buffer_ = rm.create_buffer(ssbo_desc);
|
||||||
|
|
||||||
// Initialize texture arrays (empty for now)
|
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
texture_arrays_[i] = 0;
|
texture_arrays_[i] = 0;
|
||||||
texture_array_sizes_[i] = 0;
|
texture_array_sizes_[i] = 0;
|
||||||
}
|
}
|
||||||
|
if (config_.use_bvh)
|
||||||
// Initialize BVH if enabled
|
|
||||||
if (config_.use_bvh) {
|
|
||||||
bvh_ = std::make_unique<BVH>();
|
bvh_ = std::make_unique<BVH>();
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
ARE_LOG_INFO("RayTracer initialized successfully");
|
ARE_LOG_INFO("RayTracer initialized (" + std::to_string(width_) + "x" + std::to_string(height_) + ")");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::release() {
|
void RayTracer::release() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
|
||||||
if (accumulation_texture_ != INVALID_HANDLE) {
|
if (accumulation_texture_ != INVALID_HANDLE) {
|
||||||
rm.destroy_texture(accumulation_texture_);
|
rm.destroy_texture(accumulation_texture_);
|
||||||
accumulation_texture_ = INVALID_HANDLE;
|
accumulation_texture_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release texture arrays
|
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
if (texture_arrays_[i] != 0) {
|
if (texture_arrays_[i] != 0) {
|
||||||
rm.destroy_texture_array(texture_arrays_[i]);
|
rm.destroy_texture_array(texture_arrays_[i]);
|
||||||
texture_arrays_[i] = 0;
|
texture_arrays_[i] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (material_buffer_ != INVALID_HANDLE) {
|
if (material_buffer_ != INVALID_HANDLE) {
|
||||||
rm.destroy_buffer(material_buffer_);
|
rm.destroy_buffer(material_buffer_);
|
||||||
material_buffer_ = INVALID_HANDLE;
|
material_buffer_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (light_buffer_ != INVALID_HANDLE) {
|
if (light_buffer_ != INVALID_HANDLE) {
|
||||||
rm.destroy_buffer(light_buffer_);
|
rm.destroy_buffer(light_buffer_);
|
||||||
light_buffer_ = INVALID_HANDLE;
|
light_buffer_ = INVALID_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bvh_node_buffer_.release();
|
bvh_node_buffer_.release();
|
||||||
bvh_triangle_buffer_.release();
|
bvh_triangle_buffer_.release();
|
||||||
bvh_attr_buffer_.release();
|
bvh_attr_buffer_.release();
|
||||||
|
|
||||||
bvh_.reset();
|
bvh_.reset();
|
||||||
bvh_built_ = false;
|
bvh_built_ = false;
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
ARE_LOG_INFO("RayTracer released");
|
ARE_LOG_INFO("RayTracer released");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RayTracer::rebuild_bvh(const Scene &scene) {
|
bool RayTracer::rebuild_bvh(const Scene &scene) {
|
||||||
if (!config_.use_bvh) {
|
if (!config_.use_bvh)
|
||||||
ARE_LOG_WARN("BVH is disabled in configuration");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
if (!bvh_)
|
||||||
|
|
||||||
if (!bvh_) {
|
|
||||||
bvh_ = std::make_unique<BVH>();
|
bvh_ = std::make_unique<BVH>();
|
||||||
}
|
|
||||||
|
|
||||||
ARE_LOG_INFO("Building BVH for ray tracing...");
|
|
||||||
|
|
||||||
if (!bvh_->build(scene.get_meshes())) {
|
if (!bvh_->build(scene.get_meshes())) {
|
||||||
ARE_LOG_ERROR("Failed to build BVH");
|
ARE_LOG_ERROR("Failed to build BVH");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bvh_->upload_to_gpu(bvh_node_buffer_, bvh_triangle_buffer_, bvh_attr_buffer_)) {
|
if (!bvh_->upload_to_gpu(bvh_node_buffer_, bvh_triangle_buffer_, bvh_attr_buffer_)) {
|
||||||
ARE_LOG_ERROR("Failed to upload BVH to GPU");
|
ARE_LOG_ERROR("Failed to upload BVH to GPU");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bvh_built_ = true;
|
bvh_built_ = true;
|
||||||
reset_accumulation();
|
reset_accumulation();
|
||||||
ARE_LOG_INFO("BVH built and uploaded successfully");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_texture) {
|
void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_image,
|
||||||
|
uint sr_scaling, uint sr_jitter, TextureHandle sr_accum) {
|
||||||
if (!initialized_) {
|
if (!initialized_) {
|
||||||
ARE_LOG_ERROR("RayTracer not initialized");
|
ARE_LOG_ERROR("RayTracer not initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compute_shader_->is_valid()) {
|
if (!compute_shader_->is_valid()) {
|
||||||
ARE_LOG_ERROR("Ray tracing compute shader not loaded");
|
ARE_LOG_ERROR("Compute shader not loaded");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (config_.use_bvh && !bvh_built_)
|
||||||
// Build BVH if enabled and not built yet
|
|
||||||
if (config_.use_bvh && !bvh_built_) {
|
|
||||||
rebuild_bvh(scene);
|
rebuild_bvh(scene);
|
||||||
}
|
|
||||||
|
|
||||||
// Build texture arrays BEFORE uploading materials (so indices are available)
|
uint sr_enabled = (sr_scaling > 1) ? 1u : 0u;
|
||||||
|
uint sr_block = sr_enabled ? static_cast<uint>(std::sqrt(static_cast<double>(sr_scaling))) : 1u;
|
||||||
|
uint dispatch_w = sr_enabled ? (width_ / sr_block) : width_;
|
||||||
|
uint dispatch_h = sr_enabled ? (height_ / sr_block) : height_;
|
||||||
|
|
||||||
|
// ── texture arrays / scene data ────────────────────────────────────────
|
||||||
const auto &materials = scene.get_materials();
|
const auto &materials = scene.get_materials();
|
||||||
bool has_textures = false;
|
bool has_textures = false;
|
||||||
for (const auto &mat : materials) {
|
for (const auto &mat : materials) {
|
||||||
|
|
@ -188,40 +143,28 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
|
||||||
}
|
}
|
||||||
if (has_textures) {
|
if (has_textures) {
|
||||||
build_texture_arrays_(scene);
|
build_texture_arrays_(scene);
|
||||||
|
for (int slot = 0; slot < 6; slot++) {
|
||||||
// Bind texture arrays
|
glActiveTexture(GL_TEXTURE10 + slot);
|
||||||
glActiveTexture(GL_TEXTURE10);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]);
|
}
|
||||||
glActiveTexture(GL_TEXTURE11);
|
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]);
|
|
||||||
glActiveTexture(GL_TEXTURE12);
|
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]);
|
|
||||||
glActiveTexture(GL_TEXTURE13);
|
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]);
|
|
||||||
glActiveTexture(GL_TEXTURE14);
|
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]);
|
|
||||||
glActiveTexture(GL_TEXTURE15);
|
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload scene data (materials now have correct texture indices)
|
|
||||||
upload_scene_data_(scene);
|
upload_scene_data_(scene);
|
||||||
|
|
||||||
// Use compute shader
|
|
||||||
if (!compute_shader_ || !compute_shader_->is_valid()) {
|
|
||||||
ARE_LOG_ERROR("Ray tracing compute shader not set or invalid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
compute_shader_->use();
|
compute_shader_->use();
|
||||||
|
|
||||||
// Bind G-Buffer textures
|
// ── G‑buffer images ────────────────────────────────────────────────────
|
||||||
bind_gbuffer_(gbuffer);
|
bind_gbuffer_(gbuffer);
|
||||||
|
|
||||||
// Bind output and accumulation textures
|
// ── output image (binding 3) ──────────────────────────────────────────
|
||||||
glBindImageTexture(3, output_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
|
glBindImageTexture(3, output_image, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
|
||||||
glBindImageTexture(4, accumulation_texture_, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
|
|
||||||
|
|
||||||
// Bind BVH buffers if enabled
|
// ── accumulation image (binding 4) ────────────────────────────────────
|
||||||
|
// SR: caller provides the full‑res accumulation texture
|
||||||
|
// non‑SR: use ray‑tracer's own accumulation texture
|
||||||
|
TextureHandle accum_tex = sr_enabled ? sr_accum : accumulation_texture_;
|
||||||
|
glBindImageTexture(4, accum_tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
|
||||||
|
|
||||||
|
// ── BVH ────────────────────────────────────────────────────────────────
|
||||||
if (config_.use_bvh && bvh_built_) {
|
if (config_.use_bvh && bvh_built_) {
|
||||||
bvh_node_buffer_.bind_base(2);
|
bvh_node_buffer_.bind_base(2);
|
||||||
bvh_triangle_buffer_.bind_base(3);
|
bvh_triangle_buffer_.bind_base(3);
|
||||||
|
|
@ -232,55 +175,47 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
|
||||||
compute_shader_->set_bool("u_use_bvh", false);
|
compute_shader_->set_bool("u_use_bvh", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set uniforms
|
// ── uniforms ───────────────────────────────────────────────────────────
|
||||||
compute_shader_->set_uint("u_frame_count", frame_count_);
|
compute_shader_->set_uint("u_frame_count", frame_count_);
|
||||||
compute_shader_->set_uint("u_samples_per_pixel", config_.samples_per_pixel);
|
compute_shader_->set_uint("u_samples_per_pixel", config_.samples_per_pixel);
|
||||||
compute_shader_->set_uint("u_max_depth", config_.max_depth);
|
compute_shader_->set_uint("u_max_depth", config_.max_depth);
|
||||||
compute_shader_->set_uint("u_light_count", static_cast<uint>(scene.get_lights().size()));
|
compute_shader_->set_uint("u_light_count", static_cast<uint>(scene.get_lights().size()));
|
||||||
compute_shader_->set_bool("u_enable_accumulation", config_.enable_accumulation);
|
compute_shader_->set_bool("u_enable_accumulation", sr_enabled ? false : config_.enable_accumulation);
|
||||||
|
|
||||||
// Enable/disable textures based on material usage
|
|
||||||
compute_shader_->set_bool("u_enable_textures", has_textures);
|
compute_shader_->set_bool("u_enable_textures", has_textures);
|
||||||
|
|
||||||
// Set camera data
|
|
||||||
const Camera &camera = scene.get_camera();
|
const Camera &camera = scene.get_camera();
|
||||||
Mat4 inv_vp = glm::inverse(camera.get_view_projection_matrix());
|
compute_shader_->set_mat4("u_inv_view_projection", glm::inverse(camera.get_view_projection_matrix()));
|
||||||
compute_shader_->set_mat4("u_inv_view_projection", inv_vp);
|
|
||||||
|
|
||||||
// Dispatch compute shader
|
compute_shader_->set_uint("u_sr_enabled", sr_enabled);
|
||||||
uint num_groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
compute_shader_->set_uint("u_sr_scaling", sr_scaling);
|
||||||
uint num_groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
compute_shader_->set_uint("u_sr_block", sr_block);
|
||||||
|
compute_shader_->set_uint("u_sr_jitter", sr_jitter);
|
||||||
|
compute_shader_->set_uint("u_sr_full_width", width_);
|
||||||
|
compute_shader_->set_uint("u_sr_full_height", height_);
|
||||||
|
|
||||||
|
// ── dispatch ───────────────────────────────────────────────────────────
|
||||||
|
uint num_groups_x = (dispatch_w + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
||||||
|
uint num_groups_y = (dispatch_h + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
||||||
glDispatchCompute(num_groups_x, num_groups_y, 1);
|
glDispatchCompute(num_groups_x, num_groups_y, 1);
|
||||||
|
|
||||||
// Memory barrier
|
|
||||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||||
|
|
||||||
// Increment frame count for accumulation
|
if (config_.enable_accumulation || sr_enabled)
|
||||||
if (config_.enable_accumulation) {
|
|
||||||
frame_count_++;
|
frame_count_++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::resize(uint width, uint height) {
|
void RayTracer::resize(uint width, uint height) {
|
||||||
if (width == width_ && height == height_)
|
if (width == width_ && height == height_)
|
||||||
return;
|
return;
|
||||||
|
ARE_LOG_DEBUG("RayTracer resize: " + std::to_string(width_) + "x" + std::to_string(height_) + " -> " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
width_ = width;
|
width_ = width;
|
||||||
height_ = height;
|
height_ = height;
|
||||||
|
if (!initialized_)
|
||||||
if (initialized_) {
|
return;
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
if (accumulation_texture_ != INVALID_HANDLE)
|
||||||
// Recreate accumulation texture
|
rm.destroy_texture(accumulation_texture_);
|
||||||
if (accumulation_texture_ != INVALID_HANDLE) {
|
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
||||||
rm.destroy_texture(accumulation_texture_);
|
reset_accumulation();
|
||||||
}
|
|
||||||
|
|
||||||
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
|
||||||
|
|
||||||
reset_accumulation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::reset_accumulation() {
|
void RayTracer::reset_accumulation() {
|
||||||
|
|
@ -289,10 +224,8 @@ void RayTracer::reset_accumulation() {
|
||||||
|
|
||||||
void RayTracer::set_config(const RayTracerConfig &config) {
|
void RayTracer::set_config(const RayTracerConfig &config) {
|
||||||
bool bvh_changed = (config.use_bvh != config_.use_bvh);
|
bool bvh_changed = (config.use_bvh != config_.use_bvh);
|
||||||
|
|
||||||
config_ = config;
|
config_ = config;
|
||||||
reset_accumulation();
|
reset_accumulation();
|
||||||
|
|
||||||
if (bvh_changed) {
|
if (bvh_changed) {
|
||||||
if (config_.use_bvh && !bvh_) {
|
if (config_.use_bvh && !bvh_) {
|
||||||
bvh_ = std::make_unique<BVH>();
|
bvh_ = std::make_unique<BVH>();
|
||||||
|
|
@ -305,66 +238,49 @@ void RayTracer::set_config(const RayTracerConfig &config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::upload_scene_data_(const Scene &scene) {
|
void RayTracer::upload_scene_data_(const Scene &scene) {
|
||||||
// Upload materials (on change only)
|
|
||||||
const auto &materials = scene.get_materials();
|
const auto &materials = scene.get_materials();
|
||||||
if (!materials.empty()) {
|
if (!materials.empty()) {
|
||||||
// Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes)
|
|
||||||
struct MaterialData {
|
struct MaterialData {
|
||||||
alignas(16) Vec3 albedo;
|
alignas(16) Vec3 albedo;
|
||||||
alignas(16) Vec3 emission;
|
alignas(16) Vec3 emission;
|
||||||
float metallic;
|
float metallic, roughness;
|
||||||
float roughness;
|
|
||||||
int type;
|
int type;
|
||||||
float ior;
|
float ior, ao, padding1;
|
||||||
float ao;
|
|
||||||
float padding1;
|
|
||||||
uint texture_handles[6];
|
uint texture_handles[6];
|
||||||
};
|
};
|
||||||
|
std::vector<MaterialData> md;
|
||||||
std::vector<MaterialData> material_data;
|
md.reserve(materials.size());
|
||||||
material_data.reserve(materials.size());
|
|
||||||
|
|
||||||
for (const auto &mat : materials) {
|
for (const auto &mat : materials) {
|
||||||
MaterialData data {};
|
MaterialData d {};
|
||||||
data.albedo = mat->get_albedo();
|
d.albedo = mat->get_albedo();
|
||||||
data.metallic = mat->get_metallic();
|
d.metallic = mat->get_metallic();
|
||||||
data.emission = mat->get_emission();
|
d.emission = mat->get_emission();
|
||||||
data.roughness = mat->get_roughness();
|
d.roughness = mat->get_roughness();
|
||||||
data.type = static_cast<int>(mat->get_type());
|
d.type = static_cast<int>(mat->get_type());
|
||||||
data.ior = mat->get_ior();
|
d.ior = mat->get_ior();
|
||||||
data.ao = 1.0f; // default: no AO
|
d.ao = 1.0f;
|
||||||
|
d.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO);
|
||||||
// Texture array indices (0 = no texture, 1+ = index into array)
|
d.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL);
|
||||||
data.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO);
|
d.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC);
|
||||||
data.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL);
|
d.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS);
|
||||||
data.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC);
|
d.texture_handles[4] = mat->get_texture_index(TextureSlot::AO);
|
||||||
data.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS);
|
d.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION);
|
||||||
data.texture_handles[4] = mat->get_texture_index(TextureSlot::AO);
|
md.push_back(d);
|
||||||
data.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION);
|
|
||||||
|
|
||||||
material_data.push_back(data);
|
|
||||||
}
|
}
|
||||||
|
uint h = fnv1a_hash_bytes(md.data(), md.size() * sizeof(MaterialData));
|
||||||
uint h = fnv1a_hash_bytes(material_data.data(), material_data.size() * sizeof(MaterialData));
|
|
||||||
if (h != materials_hash_) {
|
if (h != materials_hash_) {
|
||||||
materials_hash_ = h;
|
materials_hash_ = h;
|
||||||
|
|
||||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_);
|
glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_);
|
||||||
glBufferData(GL_SHADER_STORAGE_BUFFER,
|
glBufferData(GL_SHADER_STORAGE_BUFFER, md.size() * sizeof(MaterialData), md.data(), GL_DYNAMIC_DRAW);
|
||||||
material_data.size() * sizeof(MaterialData),
|
|
||||||
material_data.data(), GL_DYNAMIC_DRAW);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
||||||
|
reset_accumulation();
|
||||||
reset_accumulation(); // materials changed => invalidate accumulation
|
|
||||||
} else {
|
} else {
|
||||||
// Still ensure bound (in case other code changed bindings)
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
materials_hash_ = 0u;
|
materials_hash_ = 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload lights (on change only)
|
|
||||||
const auto &lights = scene.get_lights();
|
const auto &lights = scene.get_lights();
|
||||||
if (!lights.empty()) {
|
if (!lights.empty()) {
|
||||||
struct LightData {
|
struct LightData {
|
||||||
|
|
@ -377,33 +293,26 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
|
||||||
Vec2 spot_angles;
|
Vec2 spot_angles;
|
||||||
Vec2 padding;
|
Vec2 padding;
|
||||||
};
|
};
|
||||||
|
std::vector<LightData> ld;
|
||||||
std::vector<LightData> light_data;
|
ld.reserve(lights.size());
|
||||||
light_data.reserve(lights.size());
|
for (const auto &l : lights) {
|
||||||
|
LightData d {};
|
||||||
for (const auto &light : lights) {
|
d.position = l->get_position();
|
||||||
LightData data {};
|
d.type = static_cast<int>(l->get_type());
|
||||||
data.position = light->get_position();
|
d.direction = l->get_direction();
|
||||||
data.type = static_cast<int>(light->get_type());
|
d.intensity = l->get_intensity();
|
||||||
data.direction = light->get_direction();
|
d.color = l->get_color();
|
||||||
data.intensity = light->get_intensity();
|
d.range = l->get_range();
|
||||||
data.color = light->get_color();
|
d.spot_angles = Vec2(l->get_inner_angle(), l->get_outer_angle());
|
||||||
data.range = light->get_range();
|
ld.push_back(d);
|
||||||
data.spot_angles = Vec2(light->get_inner_angle(), light->get_outer_angle());
|
|
||||||
light_data.push_back(data);
|
|
||||||
}
|
}
|
||||||
|
uint h = fnv1a_hash_bytes(ld.data(), ld.size() * sizeof(LightData));
|
||||||
uint h = fnv1a_hash_bytes(light_data.data(), light_data.size() * sizeof(LightData));
|
|
||||||
if (h != lights_hash_) {
|
if (h != lights_hash_) {
|
||||||
lights_hash_ = h;
|
lights_hash_ = h;
|
||||||
|
|
||||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_buffer_);
|
glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_buffer_);
|
||||||
glBufferData(GL_SHADER_STORAGE_BUFFER,
|
glBufferData(GL_SHADER_STORAGE_BUFFER, ld.size() * sizeof(LightData), ld.data(), GL_DYNAMIC_DRAW);
|
||||||
light_data.size() * sizeof(LightData),
|
|
||||||
light_data.data(), GL_DYNAMIC_DRAW);
|
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_);
|
||||||
|
reset_accumulation();
|
||||||
reset_accumulation(); // lights changed => invalidate accumulation
|
|
||||||
} else {
|
} else {
|
||||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_);
|
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_);
|
||||||
}
|
}
|
||||||
|
|
@ -414,29 +323,20 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
|
||||||
|
|
||||||
void RayTracer::bind_gbuffer_(const GBuffer &gbuffer) {
|
void RayTracer::bind_gbuffer_(const GBuffer &gbuffer) {
|
||||||
glBindImageTexture(0, gbuffer.get_texture(GBUFFER_POSITION), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
glBindImageTexture(0, gbuffer.get_texture(GBUFFER_POSITION), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F); // Octahedral encoded
|
glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F);
|
||||||
|
|
||||||
glBindImageTexture(5, gbuffer.get_texture(GBUFFER_MATERIAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
glBindImageTexture(5, gbuffer.get_texture(GBUFFER_MATERIAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
glBindImageTexture(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
|
glBindImageTexture(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
|
||||||
|
glBindImageTexture(2, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
// Texcoord
|
glBindImageTexture(7, gbuffer.get_texture(GBUFFER_TANGENT), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
glBindImageTexture(7, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
|
||||||
|
|
||||||
// Tangent
|
|
||||||
glBindImageTexture(8, gbuffer.get_texture(GBUFFER_TANGENT), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::build_texture_arrays_(const Scene &scene) {
|
void RayTracer::build_texture_arrays_(const Scene &scene) {
|
||||||
const auto &materials = scene.get_materials();
|
const auto &materials = scene.get_materials();
|
||||||
|
|
||||||
// Collect all textures for each slot
|
|
||||||
std::vector<std::shared_ptr<Texture>> textures[6];
|
std::vector<std::shared_ptr<Texture>> textures[6];
|
||||||
|
|
||||||
for (const auto &mat : materials) {
|
for (const auto &mat : materials) {
|
||||||
for (int slot = 0; slot < 6; slot++) {
|
for (int slot = 0; slot < 6; slot++) {
|
||||||
auto tex = mat->get_texture(static_cast<TextureSlot>(slot));
|
auto tex = mat->get_texture(static_cast<TextureSlot>(slot));
|
||||||
if (tex && tex->is_valid()) {
|
if (tex && tex->is_valid()) {
|
||||||
// Check if texture already added (use set for O(1) lookup)
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (const auto &t : textures[slot]) {
|
for (const auto &t : textures[slot]) {
|
||||||
if (t.get() == tex.get()) {
|
if (t.get() == tex.get()) {
|
||||||
|
|
@ -444,92 +344,62 @@ void RayTracer::build_texture_arrays_(const Scene &scene) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found)
|
||||||
textures[slot].push_back(tex);
|
textures[slot].push_back(tex);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute hash for each slot and check if rebuild is needed
|
|
||||||
bool any_slot_dirty = false;
|
bool any_slot_dirty = false;
|
||||||
uint new_slot_hashes[6];
|
uint new_slot_hashes[6];
|
||||||
for (int slot = 0; slot < 6; slot++) {
|
for (int slot = 0; slot < 6; slot++) {
|
||||||
new_slot_hashes[slot] = compute_slot_texture_hash(textures[slot]);
|
new_slot_hashes[slot] = compute_slot_texture_hash(textures[slot]);
|
||||||
if (new_slot_hashes[slot] != texture_slot_hashes_[slot]) {
|
if (new_slot_hashes[slot] != texture_slot_hashes_[slot])
|
||||||
any_slot_dirty = true;
|
any_slot_dirty = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no slots changed, skip entire rebuild
|
|
||||||
if (!any_slot_dirty && !texture_arrays_dirty_) {
|
if (!any_slot_dirty && !texture_arrays_dirty_) {
|
||||||
// Still need to bind existing arrays
|
for (int slot = 0; slot < 6; slot++)
|
||||||
for (int slot = 0; slot < 6; slot++) {
|
|
||||||
if (texture_arrays_[slot] != 0) {
|
if (texture_arrays_[slot] != 0) {
|
||||||
glActiveTexture(GL_TEXTURE10 + slot);
|
glActiveTexture(GL_TEXTURE10 + slot);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
|
||||||
// Build arrays only for dirty slots
|
|
||||||
for (int slot = 0; slot < 6; slot++) {
|
for (int slot = 0; slot < 6; slot++) {
|
||||||
// Skip if this slot hasn't changed
|
|
||||||
if (new_slot_hashes[slot] == texture_slot_hashes_[slot] && !texture_arrays_dirty_) {
|
if (new_slot_hashes[slot] == texture_slot_hashes_[slot] && !texture_arrays_dirty_) {
|
||||||
// Bind existing array
|
|
||||||
if (texture_arrays_[slot] != 0) {
|
if (texture_arrays_[slot] != 0) {
|
||||||
glActiveTexture(GL_TEXTURE10 + slot);
|
glActiveTexture(GL_TEXTURE10 + slot);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy previous texture array if exists
|
|
||||||
if (texture_arrays_[slot] != 0) {
|
if (texture_arrays_[slot] != 0) {
|
||||||
rm.destroy_texture_array(texture_arrays_[slot]);
|
rm.destroy_texture_array(texture_arrays_[slot]);
|
||||||
texture_arrays_[slot] = 0;
|
texture_arrays_[slot] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textures[slot].empty()) {
|
if (textures[slot].empty()) {
|
||||||
texture_array_sizes_[slot] = 0;
|
texture_array_sizes_[slot] = 0;
|
||||||
texture_slot_hashes_[slot] = 0u;
|
texture_slot_hashes_[slot] = 0u;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
texture_array_sizes_[slot] = static_cast<uint>(textures[slot].size());
|
texture_array_sizes_[slot] = static_cast<uint>(textures[slot].size());
|
||||||
|
|
||||||
// Create texture array using ResourceManager
|
|
||||||
TextureArrayDescription desc;
|
TextureArrayDescription desc;
|
||||||
desc.textures = textures[slot];
|
desc.textures = textures[slot];
|
||||||
desc.filter = TextureFilter::LINEAR;
|
desc.filter = TextureFilter::LINEAR;
|
||||||
desc.wrap = TextureWrap::REPEAT;
|
desc.wrap = TextureWrap::REPEAT;
|
||||||
|
|
||||||
texture_arrays_[slot] = rm.create_texture_array(desc);
|
texture_arrays_[slot] = rm.create_texture_array(desc);
|
||||||
|
|
||||||
// Set texture index on all materials using each texture
|
|
||||||
for (size_t i = 0; i < textures[slot].size(); i++) {
|
for (size_t i = 0; i < textures[slot].size(); i++) {
|
||||||
// Index is i+1 because 0 means "no texture" in the shader
|
|
||||||
uint32_t array_index = static_cast<uint32_t>(i) + 1;
|
uint32_t array_index = static_cast<uint32_t>(i) + 1;
|
||||||
for (const auto &mat : materials) {
|
for (const auto &mat : materials)
|
||||||
if (mat->get_texture(static_cast<TextureSlot>(slot)).get() == textures[slot][i].get()) {
|
if (mat->get_texture(static_cast<TextureSlot>(slot)).get() == textures[slot][i].get())
|
||||||
mat->set_texture_index(static_cast<TextureSlot>(slot), array_index);
|
mat->set_texture_index(static_cast<TextureSlot>(slot), array_index);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update slot hash
|
|
||||||
texture_slot_hashes_[slot] = new_slot_hashes[slot];
|
texture_slot_hashes_[slot] = new_slot_hashes[slot];
|
||||||
|
|
||||||
// Bind the newly created array
|
|
||||||
if (texture_arrays_[slot] != 0) {
|
if (texture_arrays_[slot] != 0) {
|
||||||
glActiveTexture(GL_TEXTURE10 + slot);
|
glActiveTexture(GL_TEXTURE10 + slot);
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
texture_arrays_dirty_ = false;
|
texture_arrays_dirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "core/renderer.h"
|
#include "core/renderer.h"
|
||||||
#include "resource/resource_manager.h"
|
#include "resource/resource_manager.h"
|
||||||
#include "utils/logger.h"
|
#include "utils/logger.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
|
@ -9,8 +10,7 @@ namespace are {
|
||||||
Renderer::Renderer(const RendererConfig &config)
|
Renderer::Renderer(const RendererConfig &config)
|
||||||
: config_(config)
|
: config_(config)
|
||||||
, rt_output_texture_(INVALID_HANDLE)
|
, rt_output_texture_(INVALID_HANDLE)
|
||||||
, initialized_(false)
|
, initialized_(false) {
|
||||||
, frame_count_(0) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer() {
|
Renderer::~Renderer() {
|
||||||
|
|
@ -25,6 +25,27 @@ bool Renderer::initialize() {
|
||||||
|
|
||||||
ARE_LOG_INFO("Initializing Aurora Rendering Engine...");
|
ARE_LOG_INFO("Initializing Aurora Rendering Engine...");
|
||||||
|
|
||||||
|
// The parameter check for super resolution module
|
||||||
|
if (config_.sr_config.enabled) {
|
||||||
|
// Parameters checking
|
||||||
|
if (config_.sr_config.scaling <= 1) {
|
||||||
|
ARE_LOG_WARN("Super resolution disabled: scaling must be > 1 (got " + std::to_string(config_.sr_config.scaling) + ")");
|
||||||
|
config_.sr_config.enabled = false;
|
||||||
|
config_.sr_config.scaling = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The super resolution module does not support nouveau driver
|
||||||
|
std::string vendor = std::string(reinterpret_cast<const char *>(glGetString(GL_VENDOR)));
|
||||||
|
std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);
|
||||||
|
std::string renderer = std::string(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
|
||||||
|
std::transform(renderer.begin(), renderer.end(), renderer.begin(), ::tolower);
|
||||||
|
if (vendor.find("mesa") != std::string::npos && renderer.find("nv") != std::string::npos) {
|
||||||
|
ARE_LOG_WARN("Super resolution disabled: The super resolution module on nouveau may produce artifacts. Please switch to the NVIDIA proprietary driver if possible.");
|
||||||
|
config_.sr_config.enabled = false;
|
||||||
|
config_.sr_config.scaling = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize shader manager
|
// Initialize shader manager
|
||||||
shader_manager_ = std::make_unique<ShaderManager>();
|
shader_manager_ = std::make_unique<ShaderManager>();
|
||||||
if (!shader_manager_->initialize()) {
|
if (!shader_manager_->initialize()) {
|
||||||
|
|
@ -40,10 +61,7 @@ bool Renderer::initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize ray tracer
|
// Initialize ray tracer
|
||||||
RayTracerConfig rt_config = config_.rt_config;
|
raytracer_ = std::make_unique<RayTracer>(config_.output_width, config_.output_height, config_.rt_config);
|
||||||
|
|
||||||
// Initialize ray tracer
|
|
||||||
raytracer_ = std::make_unique<RayTracer>(config_.output_width, config_.output_height, rt_config);
|
|
||||||
const auto &rt_shader = shader_manager_->get_raytracing_shader();
|
const auto &rt_shader = shader_manager_->get_raytracing_shader();
|
||||||
if (!raytracer_->initialize(rt_shader)) {
|
if (!raytracer_->initialize(rt_shader)) {
|
||||||
ARE_LOG_ERROR("Failed to initialize ray tracer");
|
ARE_LOG_ERROR("Failed to initialize ray tracer");
|
||||||
|
|
@ -65,6 +83,16 @@ bool Renderer::initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize super resolution if enabled
|
||||||
|
if (config_.sr_config.enabled) {
|
||||||
|
super_resolution_ = std::make_unique<SuperResolution>(config_.output_width, config_.output_height, config_.sr_config);
|
||||||
|
const auto &sr_shader = shader_manager_->get_super_resolution_shader();
|
||||||
|
if (!super_resolution_->initialize(sr_shader)) {
|
||||||
|
ARE_LOG_ERROR("Failed to initialize super resolution");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create ray tracing output texture (reused every frame)
|
// Create ray tracing output texture (reused every frame)
|
||||||
ResourceManager &rm = ResourceManager::instance();
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
rt_output_texture_ = rm.create_texture(config_.output_width, config_.output_height, TextureFormat::RGBA32F);
|
rt_output_texture_ = rm.create_texture(config_.output_width, config_.output_height, TextureFormat::RGBA32F);
|
||||||
|
|
@ -92,6 +120,7 @@ void Renderer::shutdown() {
|
||||||
gbuffer_.reset();
|
gbuffer_.reset();
|
||||||
shader_manager_.reset();
|
shader_manager_.reset();
|
||||||
denoiser_.reset();
|
denoiser_.reset();
|
||||||
|
super_resolution_.reset();
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
ARE_LOG_INFO("Aurora Rendering Engine shut down");
|
ARE_LOG_INFO("Aurora Rendering Engine shut down");
|
||||||
|
|
@ -124,21 +153,36 @@ RenderStats Renderer::render(const Scene &scene, TextureHandle output_texture) {
|
||||||
// Phase 2: Ray tracing pass
|
// Phase 2: Ray tracing pass
|
||||||
auto raytrace_start = std::chrono::high_resolution_clock::now();
|
auto raytrace_start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
// Use output texture if provided, otherwise use internal texture
|
TextureHandle rt_output;
|
||||||
TextureHandle rt_output = (output_texture != 0) ? output_texture : rt_output_texture_;
|
if (config_.sr_config.enabled && super_resolution_) {
|
||||||
|
auto &sr = *super_resolution_;
|
||||||
|
uint jitt = sr.get_current_jitter_frame();
|
||||||
|
rt_output = sr.get_low_res_rt_texture();
|
||||||
|
|
||||||
raytracer_->trace(scene, *gbuffer_, rt_output);
|
raytracer_->trace(scene, *gbuffer_, rt_output,
|
||||||
|
config_.sr_config.scaling, jitt,
|
||||||
|
sr.get_accumulated_rt_texture());
|
||||||
|
} else {
|
||||||
|
rt_output = (output_texture != 0) ? output_texture : rt_output_texture_;
|
||||||
|
raytracer_->trace(scene, *gbuffer_, rt_output);
|
||||||
|
}
|
||||||
|
|
||||||
auto raytrace_end = std::chrono::high_resolution_clock::now();
|
auto raytrace_end = std::chrono::high_resolution_clock::now();
|
||||||
stats.raytrace_time_ms_ = std::chrono::duration<float, std::milli>(raytrace_end - raytrace_start).count();
|
stats.raytrace_time_ms_ = std::chrono::duration<float, std::milli>(raytrace_end - raytrace_start).count();
|
||||||
|
|
||||||
// Phase 3: Denoise texture
|
// Phase 3: Post-processing and output
|
||||||
TextureHandle final_output = rt_output;
|
TextureHandle final_output;
|
||||||
|
if (config_.sr_config.enabled && super_resolution_) {
|
||||||
if (config_.enable_denoising && denoiser_) {
|
// Denoising intentionally skipped — cross‑cycle accumulation provides temporal smoothing
|
||||||
// Use temporal accumulation with weight 0.1 (10% blend of new frame)
|
auto &sr = *super_resolution_;
|
||||||
float temporal_weight = 0.1f;
|
final_output = sr.upscale();
|
||||||
final_output = denoiser_->denoise(rt_output, 1, temporal_weight);
|
sr.advance_jitter_frame();
|
||||||
|
} else {
|
||||||
|
final_output = rt_output;
|
||||||
|
if (config_.enable_denoising && denoiser_) {
|
||||||
|
float temporal_weight = 0.1f;
|
||||||
|
final_output = denoiser_->denoise(final_output, 1, temporal_weight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 4: Blit to screen if output is default framebuffer
|
// Phase 4: Blit to screen if output is default framebuffer
|
||||||
|
|
@ -156,11 +200,6 @@ RenderStats Renderer::render(const Scene &scene, TextureHandle output_texture) {
|
||||||
stats.triangle_count_ += mesh->get_indices().size() / 3;
|
stats.triangle_count_ += mesh->get_indices().size() / 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estimate ray count (very rough)
|
|
||||||
stats.ray_count_ = config_.output_width * config_.output_height * config_.rt_config.samples_per_pixel * config_.rt_config.max_depth;
|
|
||||||
|
|
||||||
frame_count_++;
|
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,6 +223,10 @@ void Renderer::resize(uint width, uint height) {
|
||||||
raytracer_->resize(width, height);
|
raytracer_->resize(width, height);
|
||||||
denoiser_->resize(width, height);
|
denoiser_->resize(width, height);
|
||||||
|
|
||||||
|
if (super_resolution_) {
|
||||||
|
super_resolution_->resize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
ARE_LOG_INFO("Renderer resized to " + std::to_string(width) + "x" + std::to_string(height));
|
ARE_LOG_INFO("Renderer resized to " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +243,15 @@ void Renderer::set_config(const RendererConfig &config) {
|
||||||
|
|
||||||
// Update ray tracer config
|
// Update ray tracer config
|
||||||
raytracer_->set_config(config_.rt_config);
|
raytracer_->set_config(config_.rt_config);
|
||||||
|
|
||||||
|
// Handle SR enable/disable
|
||||||
|
if (config_.sr_config.enabled && !super_resolution_) {
|
||||||
|
super_resolution_ = std::make_unique<SuperResolution>(config_.output_width, config_.output_height, config_.sr_config);
|
||||||
|
const auto &sr_shader = shader_manager_->get_super_resolution_shader();
|
||||||
|
super_resolution_->initialize(sr_shader);
|
||||||
|
} else if (!config_.sr_config.enabled && super_resolution_) {
|
||||||
|
super_resolution_.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,6 +263,11 @@ void Renderer::notify_scene_changed(const Scene &scene) {
|
||||||
if (denoiser_) {
|
if (denoiser_) {
|
||||||
denoiser_->reset_history();
|
denoiser_->reset_history();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset super resolution accumulation on scene change
|
||||||
|
if (super_resolution_) {
|
||||||
|
super_resolution_->reset_accumulation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ void ShaderManager::release() {
|
||||||
screen_blit_shader_.reset();
|
screen_blit_shader_.reset();
|
||||||
raytracing_shader_.reset();
|
raytracing_shader_.reset();
|
||||||
denoise_shader_.reset();
|
denoise_shader_.reset();
|
||||||
|
super_resolution_shader_.reset();
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
ARE_LOG_INFO("ShaderManager released");
|
ARE_LOG_INFO("ShaderManager released");
|
||||||
|
|
@ -94,7 +95,7 @@ std::shared_ptr<Shader> ShaderManager::get_shader(const std::string &name) const
|
||||||
|
|
||||||
bool ShaderManager::load_builtin_shaders_() {
|
bool ShaderManager::load_builtin_shaders_() {
|
||||||
// Load G-buffer shader
|
// Load G-buffer shader
|
||||||
ARE_LOG_INFO("Loading G-buffer shaders..");
|
ARE_LOG_INFO("Loading G-buffer shaders...");
|
||||||
gbuffer_shader_ = std::make_shared<Shader>();
|
gbuffer_shader_ = std::make_shared<Shader>();
|
||||||
if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) {
|
if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) {
|
||||||
ARE_LOG_ERROR("Failed to load G-Buffer shader");
|
ARE_LOG_ERROR("Failed to load G-Buffer shader");
|
||||||
|
|
@ -132,6 +133,16 @@ bool ShaderManager::load_builtin_shaders_() {
|
||||||
shader_cache_["denoise"] = denoise_shader_;
|
shader_cache_["denoise"] = denoise_shader_;
|
||||||
ARE_LOG_INFO("Denoise shader loaded successfully");
|
ARE_LOG_INFO("Denoise shader loaded successfully");
|
||||||
|
|
||||||
|
// Load super resolution shader
|
||||||
|
ARE_LOG_INFO("Loading super resolution compute shader...");
|
||||||
|
super_resolution_shader_ = std::make_shared<Shader>();
|
||||||
|
if (!super_resolution_shader_->load_compute("shaders/postprocess/super_resolution.comp")) {
|
||||||
|
ARE_LOG_ERROR("Failed to load super resolution shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
shader_cache_["super_resolution"] = super_resolution_shader_;
|
||||||
|
ARE_LOG_INFO("Super resolution shader loaded successfully");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
#include "core/super_resolution.h"
|
||||||
|
#include "basic/constants.h"
|
||||||
|
#include "resource/resource_manager.h"
|
||||||
|
#include "utils/logger.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
uint SuperResolution::compute_block_size_() const {
|
||||||
|
return static_cast<uint>(std::sqrt(static_cast<double>(config_.scaling)));
|
||||||
|
}
|
||||||
|
|
||||||
|
SuperResolution::SuperResolution(uint full_width, uint full_height, const SuperResolutionConfig &config)
|
||||||
|
: full_width_(full_width), full_height_(full_height), config_(config), current_jitter_frame_(0), low_res_rt_texture_(INVALID_HANDLE), accumulated_rt_texture_(INVALID_HANDLE), upscaled_texture_(INVALID_HANDLE) {
|
||||||
|
uint block = compute_block_size_();
|
||||||
|
low_res_w_ = full_width_ / block;
|
||||||
|
low_res_h_ = full_height_ / block;
|
||||||
|
}
|
||||||
|
|
||||||
|
SuperResolution::~SuperResolution() {
|
||||||
|
release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SuperResolution::initialize(const std::shared_ptr<Shader> &shader) {
|
||||||
|
if (initialized_) {
|
||||||
|
ARE_LOG_WARN("Super resolution already initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shader || !shader->is_valid()) {
|
||||||
|
ARE_LOG_ERROR("Invalid shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_shader_ = shader;
|
||||||
|
create_textures_();
|
||||||
|
initialized_ = true;
|
||||||
|
ARE_LOG_INFO("Super resolution initialized: " + std::to_string(full_width_) + "x" + std::to_string(full_height_) + " scaling_px=" + std::to_string(config_.scaling) + " lowres=" + std::to_string(low_res_w_) + "x" + std::to_string(low_res_h_));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::release() {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
|
||||||
|
if (low_res_rt_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(low_res_rt_texture_);
|
||||||
|
low_res_rt_texture_ = INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accumulated_rt_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(accumulated_rt_texture_);
|
||||||
|
accumulated_rt_texture_ = INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upscaled_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(upscaled_texture_);
|
||||||
|
upscaled_texture_ = INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
ARE_LOG_INFO("SuperResolution released");
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureHandle SuperResolution::upscale() {
|
||||||
|
if (!initialized_ || !compute_shader_) {
|
||||||
|
return INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_shader_->use();
|
||||||
|
glBindImageTexture(1, accumulated_rt_texture_, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||||
|
glBindImageTexture(2, upscaled_texture_, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
|
||||||
|
|
||||||
|
uint gx = (full_width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
||||||
|
uint gy = (full_height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
||||||
|
glDispatchCompute(gx, gy, 1);
|
||||||
|
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||||
|
return upscaled_texture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::advance_jitter_frame() {
|
||||||
|
current_jitter_frame_ = (current_jitter_frame_ + 1) % config_.scaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::reset_accumulation() {
|
||||||
|
current_jitter_frame_ = 0;
|
||||||
|
clear_accumulation_texture_();
|
||||||
|
ARE_LOG_DEBUG("SuperResolution accumulation reset (frame " + std::to_string(current_jitter_frame_) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::resize(uint full_width, uint full_height) {
|
||||||
|
if (full_width == full_width_ && full_height == full_height_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
full_width_ = full_width;
|
||||||
|
full_height_ = full_height;
|
||||||
|
uint block = compute_block_size_();
|
||||||
|
low_res_w_ = full_width_ / block;
|
||||||
|
low_res_h_ = full_height_ / block;
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
if (low_res_rt_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(low_res_rt_texture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accumulated_rt_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(accumulated_rt_texture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upscaled_texture_ != INVALID_HANDLE) {
|
||||||
|
rm.destroy_texture(upscaled_texture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
create_textures_();
|
||||||
|
ARE_LOG_INFO("SuperResolution resized to " + std::to_string(full_width) + "x" + std::to_string(full_height));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::create_textures_() {
|
||||||
|
ResourceManager &rm = ResourceManager::instance();
|
||||||
|
low_res_rt_texture_ = rm.create_texture(low_res_w_, low_res_h_, TextureFormat::RGBA32F);
|
||||||
|
accumulated_rt_texture_ = rm.create_texture(full_width_, full_height_, TextureFormat::RGBA32F);
|
||||||
|
upscaled_texture_ = rm.create_texture(full_width_, full_height_, TextureFormat::RGBA32F);
|
||||||
|
reset_accumulation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuperResolution::clear_accumulation_texture_() const {
|
||||||
|
// FBO-based clear — far more efficient than a full compute dispatch.
|
||||||
|
// The texture is attached as a colour attachment and cleared to (0,0,0,0).
|
||||||
|
GLuint fbo;
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, accumulated_rt_texture_, 0);
|
||||||
|
const GLfloat clear_color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
||||||
|
glClearBufferfv(GL_COLOR, 0, clear_color);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glDeleteFramebuffers(1, &fbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
Loading…
Reference in New Issue