Compare commits
4 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5bb7d461a4 | |
|
|
9259bb9c6f | |
|
|
25547365e8 | |
|
|
adfe38a453 |
|
|
@ -37,3 +37,7 @@ Makefile
|
|||
# OS files
|
||||
.DS_Store
|
||||
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);
|
||||
|
||||
// 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();
|
||||
g_scene->add_mesh(short_box);
|
||||
|
||||
|
|
@ -554,6 +554,9 @@ int main() {
|
|||
config.rt_config.enable_accumulation = true;
|
||||
config.enable_denoising = false;
|
||||
|
||||
config.sr_config.enabled = true;
|
||||
config.sr_config.scaling = 4;
|
||||
|
||||
g_renderer = std::make_unique<Renderer>(config);
|
||||
if (!g_renderer->initialize()) {
|
||||
ARE_LOG_ERROR("Failed to initialize renderer");
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -536,6 +536,10 @@ int main() {
|
|||
config.rt_config.enable_accumulation = true;
|
||||
config.enable_denoising = false;
|
||||
|
||||
|
||||
config.sr_config.enabled = true;
|
||||
config.sr_config.scaling = 4.0;
|
||||
|
||||
g_renderer = std::make_unique<Renderer>(config);
|
||||
if (!g_renderer->initialize()) {
|
||||
ARE_LOG_ERROR("Failed to initialize renderer");
|
||||
|
|
|
|||
|
|
@ -16,114 +16,62 @@ namespace are {
|
|||
// Compute shader based ray tracer
|
||||
class RayTracer {
|
||||
public:
|
||||
/*
|
||||
* @brief Constructor
|
||||
* @param width Output width
|
||||
* @param height Output height
|
||||
* @param config Ray tracer configuration
|
||||
*/
|
||||
RayTracer(uint width, uint height, const RayTracerConfig &config);
|
||||
|
||||
// Destructor
|
||||
~RayTracer();
|
||||
|
||||
/*
|
||||
* @brief Initialize ray tracer
|
||||
* @return True if initialization succeeded
|
||||
*/
|
||||
bool initialize(const std::shared_ptr<Shader> &shader);
|
||||
|
||||
// Release resources
|
||||
void release();
|
||||
|
||||
/*
|
||||
* @brief Trace rays using G-Buffer as input
|
||||
* @param scene Scene data
|
||||
* @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);
|
||||
|
||||
// Reset accumulation buffer
|
||||
void reset_accumulation();
|
||||
|
||||
/*
|
||||
* @brief Get current configuration
|
||||
* @return Current configuration
|
||||
*/
|
||||
const RayTracerConfig &get_config() const {
|
||||
return config_;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Update configuration
|
||||
* @param config New configuration
|
||||
*/
|
||||
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);
|
||||
|
||||
private:
|
||||
uint width_;
|
||||
uint height_;
|
||||
uint width_, height_;
|
||||
RayTracerConfig config_;
|
||||
|
||||
// Scene data hash for change detection
|
||||
uint materials_hash_;
|
||||
uint lights_hash_;
|
||||
|
||||
// Texture arrays for PBR materials
|
||||
GLuint texture_arrays_[6]; // albedo, normal, metallic, roughness, ao, emission
|
||||
uint texture_array_sizes_[6]; // Number of textures in each array
|
||||
|
||||
// Texture array caching (content hash based)
|
||||
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
|
||||
GLuint texture_arrays_[6];
|
||||
uint texture_array_sizes_[6];
|
||||
uint texture_config_hash_;
|
||||
uint texture_slot_hashes_[6];
|
||||
bool texture_arrays_dirty_;
|
||||
|
||||
std::shared_ptr<Shader> compute_shader_;
|
||||
TextureHandle accumulation_texture_;
|
||||
BufferHandle material_buffer_;
|
||||
BufferHandle light_buffer_;
|
||||
|
||||
// BVH related
|
||||
std::unique_ptr<BVH> bvh_;
|
||||
Buffer bvh_node_buffer_;
|
||||
Buffer bvh_triangle_buffer_; ///< Compact triangle data (intersection only)
|
||||
Buffer bvh_attr_buffer_; ///< Triangle attributes (fetched on hit)
|
||||
Buffer bvh_triangle_buffer_;
|
||||
Buffer bvh_attr_buffer_;
|
||||
bool bvh_built_;
|
||||
|
||||
uint frame_count_;
|
||||
bool initialized_;
|
||||
|
||||
/*
|
||||
* @brief Upload scene data to GPU buffers
|
||||
* @param scene Scene to upload
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* @brief Build texture arrays from scene materials
|
||||
* @param scene Scene containing materials
|
||||
*/
|
||||
void build_texture_arrays_(const Scene &scene);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "core/raytracer.h"
|
||||
#include "core/screen_blit.h"
|
||||
#include "core/shader_manager.h"
|
||||
#include "core/super_resolution.h"
|
||||
#include "scene/scene.h"
|
||||
#include "utils/config.h"
|
||||
#include <memory>
|
||||
|
|
@ -73,11 +74,11 @@ private:
|
|||
std::unique_ptr<ShaderManager> shader_manager_;
|
||||
std::unique_ptr<ScreenBlit> screen_blit_;
|
||||
std::unique_ptr<Denoiser> denoiser_;
|
||||
std::unique_ptr<SuperResolution> super_resolution_;
|
||||
|
||||
TextureHandle rt_output_texture_;
|
||||
|
||||
bool initialized_;
|
||||
uint frame_count_;
|
||||
};
|
||||
|
||||
} // namespace are
|
||||
|
|
|
|||
|
|
@ -86,12 +86,21 @@ public:
|
|||
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:
|
||||
std::unordered_map<std::string, std::shared_ptr<Shader>> shader_cache_;
|
||||
std::shared_ptr<Shader> gbuffer_shader_;
|
||||
std::shared_ptr<Shader> raytracing_shader_;
|
||||
std::shared_ptr<Shader> denoise_shader_;
|
||||
std::shared_ptr<Shader> screen_blit_shader_;
|
||||
std::shared_ptr<Shader> super_resolution_shader_;
|
||||
|
||||
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
|
||||
#define ARE_INCLUDE_UTILS_CONFIG_H
|
||||
|
||||
#include "basic/types.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "basic/types.h"
|
||||
|
||||
namespace are {
|
||||
|
||||
|
|
@ -17,6 +17,12 @@ struct RayTracerConfig {
|
|||
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
|
||||
struct RendererConfig {
|
||||
|
|
@ -24,8 +30,7 @@ struct RendererConfig {
|
|||
uint output_height;
|
||||
RayTracerConfig rt_config;
|
||||
bool enable_denoising;
|
||||
bool enable_sr; // Enable the super resolution mode
|
||||
double sr_scaling; // The magnification of super-resolution
|
||||
SuperResolutionConfig sr_config;
|
||||
};
|
||||
|
||||
} // 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)
|
||||
// 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 e1 = tri.e1.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;
|
||||
|
||||
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
|
||||
TriangleAttrGpu attr = bvh_attrs[gl_GlobalInvocationID.x];
|
||||
// We need the triangle index, not invocation ID. Use a different approach.
|
||||
// Check if triangle is hit (for shadow rays - no barycentric needed)
|
||||
bool intersect_triangle_any(Ray ray, TriangleCompactGpu tri, float t_max) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -131,28 +151,9 @@ HitInfo trace_ray_bvh(Ray ray) {
|
|||
for (uint i = 0u; i < count; ++i) {
|
||||
uint tri_idx = left_first + i;
|
||||
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 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 >= hit.t) continue;
|
||||
|
||||
// Record hit but defer attribute fetch
|
||||
float t, u, v;
|
||||
if (intersect_triangle_compact(ray, tri, hit.t, t, u, v)) {
|
||||
hit.hit = true;
|
||||
hit.t = t;
|
||||
hit.position = ray.origin + t * ray.direction;
|
||||
|
|
@ -161,6 +162,7 @@ HitInfo trace_ray_bvh(Ray ray) {
|
|||
hit_u = u;
|
||||
hit_v = v;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint left = left_first;
|
||||
uint right = left_first + 1u;
|
||||
|
|
@ -239,29 +241,10 @@ bool trace_any_bvh(Ray ray, float t_max) {
|
|||
if (count > 0u) {
|
||||
for (uint i = 0u; i < count; ++i) {
|
||||
TriangleCompactGpu tri = bvh_tris[left_first + i];
|
||||
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) 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;
|
||||
|
||||
if (intersect_triangle_any(ray, tri, t_max)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint left = left_first;
|
||||
uint right = left_first + 1u;
|
||||
|
|
|
|||
|
|
@ -83,11 +83,12 @@ ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint
|
|||
r.scattered = true;
|
||||
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;
|
||||
|
||||
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||
r.scattered_ray.direction = normalize(dir);
|
||||
r.scattered_ray.direction = dir;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +128,7 @@ ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint se
|
|||
|
||||
r.scattered = true;
|
||||
r.scattered_ray.origin = hit.position + N * EPSILON;
|
||||
r.scattered_ray.direction = normalize(L);
|
||||
r.scattered_ray.direction = L;
|
||||
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.direction = normalize(dir);
|
||||
r.scattered_ray.direction = dir;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,15 @@ uint init_seed(ivec2 pixel_coords, ivec2 image_size, uint frame_count) {
|
|||
return pcg_hash(spatial + temporal);
|
||||
}
|
||||
|
||||
// High-quality random float using 53-bit precision (double-like)
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
vec3 random_vec3_32(inout uint seed) {
|
||||
return vec3(random_float_32(seed), random_float_32(seed), random_float_32(seed));
|
||||
}
|
||||
|
||||
#endif // RNG_GLSL
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef SAMPLING_GLSL
|
||||
#define SAMPLING_GLSL
|
||||
|
||||
// Cosine-weighted hemisphere sampling (avoids infinite loop)
|
||||
// Uniform sphere sampling
|
||||
vec3 random_in_unit_sphere(inout uint seed) {
|
||||
float z = 1.0 - 2.0 * random_float(seed);
|
||||
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));
|
||||
}
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
|
@ -32,7 +49,6 @@ vec3 sample_ggx_half_vector(float roughness, vec3 N, inout uint seed) {
|
|||
float u1 = random_float(seed);
|
||||
float u2 = random_float(seed);
|
||||
|
||||
// Clamp to avoid numerical issues at boundaries
|
||||
u1 = clamp(u1, 0.001, 0.999);
|
||||
|
||||
// Spherical coordinates from GGX distribution
|
||||
|
|
|
|||
|
|
@ -104,6 +104,102 @@ const uint sobol_dirs_7[32] = uint[32](
|
|||
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
|
||||
uint sobol_direction(uint dimension, uint 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 == 6u) return sobol_dirs_6[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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
// Include shared modules
|
||||
#include "../include/common.glsl"
|
||||
#include "../include/structs.glsl"
|
||||
#include "../include/math.glsl"
|
||||
#include "../include/rng.glsl"
|
||||
#include "../include/sobol.glsl"
|
||||
#include "../include/sampling.glsl"
|
||||
#include "../include/tonemap.glsl"
|
||||
|
||||
// Workgroup size
|
||||
layout(local_size_x = 16, local_size_y = 16) in;
|
||||
|
||||
// G-Buffer inputs
|
||||
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 = 6, r32ui) uniform readonly uimage2D g_material_id;
|
||||
layout(binding = 7, rgba32f) uniform readonly image2D g_texcoord;
|
||||
layout(binding = 8, rgba32f) uniform readonly image2D g_tangent;
|
||||
layout(binding = 2, rgba32f) uniform readonly image2D g_texcoord;
|
||||
layout(binding = 7, rgba32f) uniform readonly image2D g_tangent;
|
||||
|
||||
// Output
|
||||
layout(binding = 3, rgba32f) uniform image2D output_image;
|
||||
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
|
||||
|
||||
// SSBO bindings
|
||||
layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; };
|
||||
layout(std430, binding = 1) readonly buffer LightBuffer { Light lights[]; };
|
||||
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[]; };
|
||||
layout(std430, binding = 0) readonly buffer MaterialBuffer {
|
||||
Material materials[];
|
||||
};
|
||||
layout(std430, binding = 1) readonly buffer LightBuffer {
|
||||
Light lights[];
|
||||
};
|
||||
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_samples_per_pixel;
|
||||
uniform uint u_max_depth;
|
||||
|
|
@ -41,7 +46,13 @@ uniform bool u_use_bvh;
|
|||
uniform uint u_bvh_node_count;
|
||||
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 = 11) uniform sampler2DArray u_texture_normal_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 = 15) uniform sampler2DArray u_texture_emission_array;
|
||||
|
||||
// Include material, BVH, and lighting modules (needs uniform declarations above)
|
||||
#include "../include/material.glsl"
|
||||
#include "../include/bvh.glsl"
|
||||
#include "../include/lighting.glsl"
|
||||
|
||||
// Sobol sampling state
|
||||
struct SobolState {
|
||||
uint sample_index; // Which sample (0, 1, 2, ...)
|
||||
uint dimension; // Current dimension being used
|
||||
uint scramble; // Seed for Owen scrambling
|
||||
uint sample_index;
|
||||
uint dimension;
|
||||
uint scramble;
|
||||
};
|
||||
|
||||
// Initialize Sobol state
|
||||
SobolState init_sobol(uint pixel_index, uint frame, uint sample_idx) {
|
||||
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.dimension = 0u;
|
||||
// Use pixel index for per-pixel Owen scrambling
|
||||
state.scramble = pcg_hash(pixel_index + frame * 668265263u);
|
||||
return state;
|
||||
}
|
||||
|
||||
// Get next Sobol float in [0, 1)
|
||||
float sobol_next(inout SobolState state) {
|
||||
float value;
|
||||
if (state.dimension < 8u) {
|
||||
// Use Sobol for first 8 dimensions
|
||||
if (state.dimension < 16u) {
|
||||
value = sobol_get(state.sample_index, state.dimension, state.scramble);
|
||||
} else {
|
||||
// Fall back to PCG for higher dimensions
|
||||
uint rng_state = pcg_hash(state.scramble + state.dimension * 2654435761u);
|
||||
value = float(rng_state) / 4294967296.0;
|
||||
}
|
||||
|
|
@ -89,218 +90,166 @@ float sobol_next(inout SobolState state) {
|
|||
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) {
|
||||
float a = roughness * roughness;
|
||||
float a2 = a * a;
|
||||
|
||||
float u1 = clamp(sobol_next(state), 0.001, 0.999);
|
||||
float u2 = sobol_next(state);
|
||||
|
||||
float cos_theta = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0));
|
||||
cos_theta = clamp(cos_theta, 0.0, 1.0);
|
||||
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
||||
float phi = 2.0 * PI * u2;
|
||||
|
||||
vec3 H_tangent = vec3(sin_theta * cos(phi), sin_theta * sin(phi), cos_theta);
|
||||
mat3 onb = build_onb(N);
|
||||
return normalize(onb * H_tangent);
|
||||
float ct = sqrt((1.0 - u1) / ((a2 - 1.0) * u1 + 1.0));
|
||||
ct = clamp(ct, 0.0, 1.0);
|
||||
float st = sqrt(max(0.0, 1.0 - ct * ct));
|
||||
float ph = 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 T = normalize(cross(up, N));
|
||||
vec3 B = cross(N, T);
|
||||
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 r;
|
||||
r.scattered = true;
|
||||
r.attenuation = mat.albedo;
|
||||
|
||||
vec3 dir = hit.normal + sobol_unit_vector(state);
|
||||
float rs = sqrt(sobol_next(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;
|
||||
|
||||
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||
r.scattered_ray.direction = normalize(dir);
|
||||
r.scattered_ray.direction = dir;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Sobol-based metal scattering (GGX)
|
||||
ScatterResult scatter_metal_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||
ScatterResult r;
|
||||
|
||||
vec3 V = normalize(-ray_in.direction);
|
||||
vec3 N = hit.normal;
|
||||
float roughness = clamp(mat.roughness, 0.04, 1.0);
|
||||
|
||||
vec3 H = sobol_ggx_half_vector(roughness, N, state);
|
||||
if (dot(H, N) < 0.0) H = -H;
|
||||
|
||||
vec3 H = sobol_ggx_half_vector(roughness, hit.normal, state);
|
||||
if (dot(H, hit.normal) < 0.0) H = -H;
|
||||
vec3 L = reflect(-V, H);
|
||||
|
||||
float NdotL = dot(N, L);
|
||||
if (NdotL <= 0.0) {
|
||||
if (dot(hit.normal, L) <= 0.0) {
|
||||
r.scattered = false;
|
||||
r.attenuation = vec3(0.0);
|
||||
return r;
|
||||
}
|
||||
|
||||
float HdotV = max(dot(H, V), 0.001);
|
||||
vec3 F = fresnel_schlick(HdotV, mat.albedo);
|
||||
|
||||
r.attenuation = clamp(F, vec3(0.0), vec3(1.0));
|
||||
r.scattered = true;
|
||||
r.scattered_ray.origin = hit.position + N * EPSILON;
|
||||
r.scattered_ray.direction = normalize(L);
|
||||
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
|
||||
r.scattered_ray.direction = L;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Sobol-based dielectric scattering
|
||||
ScatterResult scatter_dielectric_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
|
||||
ScatterResult r;
|
||||
r.scattered = true;
|
||||
r.attenuation = vec3(1.0);
|
||||
|
||||
vec3 unit_dir = normalize(ray_in.direction);
|
||||
float cos_theta = dot(-unit_dir, hit.normal);
|
||||
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
|
||||
|
||||
bool entering = cos_theta > 0.0;
|
||||
float eta = entering ? (1.0 / mat.ior) : mat.ior;
|
||||
vec3 normal = entering ? hit.normal : -hit.normal;
|
||||
|
||||
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 ud = normalize(ray_in.direction);
|
||||
float ct = dot(-ud, hit.normal);
|
||||
float st = sqrt(max(0.0, 1.0 - ct * ct));
|
||||
bool ent = ct > 0.0;
|
||||
float eta = ent ? (1.0 / mat.ior) : mat.ior;
|
||||
vec3 N = ent ? hit.normal : -hit.normal;
|
||||
float st_t = eta * st;
|
||||
float f = fresnel_dielectric(ct, mat.ior);
|
||||
vec3 dir;
|
||||
if (total_internal_reflection || sobol_next(state) < f) {
|
||||
dir = reflect_vector(unit_dir, normal);
|
||||
} else {
|
||||
dir = refract_vector(unit_dir, normal, eta);
|
||||
}
|
||||
|
||||
if (st_t >= 1.0 || sobol_next(state) < f)
|
||||
dir = reflect_vector(ud, N);
|
||||
else
|
||||
dir = refract_vector(ud, N, eta);
|
||||
r.scattered_ray.origin = hit.position + dir * EPSILON;
|
||||
r.scattered_ray.direction = normalize(dir);
|
||||
r.scattered_ray.direction = dir;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Sobol-based scatter dispatcher
|
||||
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_METAL) return scatter_metal_sobol(ray_in, hit, mat, state);
|
||||
if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric_sobol(ray_in, hit, mat, state);
|
||||
|
||||
ScatterResult r;
|
||||
r.scattered = false;
|
||||
r.attenuation = vec3(0.0);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Generate camera ray (center pixel, no jitter)
|
||||
Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size) {
|
||||
vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size);
|
||||
vec2 ndc = uv * 2.0 - 1.0;
|
||||
|
||||
vec4 p_near = u_inv_view_projection * vec4(ndc, 0.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;
|
||||
|
||||
vec4 pn = u_inv_view_projection * vec4(ndc, 0.0, 1.0);
|
||||
vec4 pf = u_inv_view_projection * vec4(ndc, 1.0, 1.0);
|
||||
Ray r;
|
||||
r.origin = near_ws;
|
||||
r.direction = normalize(far_ws - near_ws);
|
||||
r.origin = pn.xyz / pn.w;
|
||||
r.direction = normalize(pf.xyz / pf.w - r.origin);
|
||||
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) {
|
||||
Ray ray = generate_camera_ray(pixel_coords, image_size);
|
||||
|
||||
vec3 radiance = vec3(0.0);
|
||||
vec3 throughput = vec3(1.0);
|
||||
|
||||
// Depth 0: try G-Buffer hit first
|
||||
HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords);
|
||||
if (hit0.hit) {
|
||||
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);
|
||||
|
||||
radiance += throughput * mat0.emission;
|
||||
|
||||
ScatterResult sc0 = scatter_ray_sobol(ray, hit0, mat0, sobol);
|
||||
if (!sc0.scattered) return radiance;
|
||||
|
||||
throughput *= sc0.attenuation;
|
||||
ray = sc0.scattered_ray;
|
||||
}
|
||||
|
||||
// Subsequent bounces: BVH
|
||||
for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) {
|
||||
HitInfo hit = trace_ray_bvh(ray);
|
||||
if (!hit.hit) {
|
||||
radiance += throughput * environment_color(ray.direction);
|
||||
break;
|
||||
}
|
||||
|
||||
Material mat = fetch_material(hit.material_id);
|
||||
apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent);
|
||||
|
||||
radiance += throughput * mat.emission;
|
||||
|
||||
ScatterResult sc = scatter_ray_sobol(ray, hit, mat, sobol);
|
||||
if (!sc.scattered) break;
|
||||
|
||||
throughput *= sc.attenuation;
|
||||
|
||||
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);
|
||||
if (p < RR_THRESHOLD || sobol_next(sobol) > p) break;
|
||||
throughput /= p;
|
||||
}
|
||||
|
||||
ray = sc.scattered_ray;
|
||||
|
||||
if (all(lessThan(throughput, vec3(EPSILON)))) break;
|
||||
}
|
||||
|
||||
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() {
|
||||
ivec2 rt_coord = ivec2(gl_GlobalInvocationID.xy);
|
||||
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;
|
||||
}
|
||||
|
||||
void main() {
|
||||
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 image_size = imageSize(output_image);
|
||||
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 seed = init_seed(pixel_coords, image_size, u_frame_count);
|
||||
|
||||
vec3 color = vec3(0.0);
|
||||
uint spp = max(u_samples_per_pixel, 1u);
|
||||
|
|
@ -310,19 +259,26 @@ void main() {
|
|||
color += trace_path_sobol(pixel_coords, image_size, sobol);
|
||||
}
|
||||
color /= float(spp);
|
||||
|
||||
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) {
|
||||
vec4 old = imageLoad(accumulation_image, pixel_coords);
|
||||
uint cyc = u_frame_count / u_sr_scaling;
|
||||
float w = 1.0 / float(cyc + 1u);
|
||||
vec3 acc = mix(old.rgb, color, w);
|
||||
imageStore(accumulation_image, pixel_coords, vec4(acc, 1.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Standard non‑SR accumulation ─────────────────────────────────────
|
||||
vec3 acc_color = color;
|
||||
if (u_enable_accumulation && u_frame_count > 0u) {
|
||||
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
|
||||
vec3 old = imageLoad(accumulation_image, rt_coord).rgb;
|
||||
float w = 1.0 / float(u_frame_count + 1u);
|
||||
accumulation_color = mix(accumulated, color, w);
|
||||
acc_color = mix(old, color, w);
|
||||
}
|
||||
|
||||
vec3 output_color = aces_tonemap(accumulation_color);
|
||||
|
||||
imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0));
|
||||
imageStore(output_image, pixel_coords, vec4(output_color, 1.0));
|
||||
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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ Denoiser::~Denoiser() {
|
|||
}
|
||||
|
||||
bool Denoiser::initialize(const std::shared_ptr<Shader> &shader) {
|
||||
if (initialized_) return true;
|
||||
if (initialized_)
|
||||
return true;
|
||||
|
||||
if (!shader || !shader->is_valid()) {
|
||||
ARE_LOG_ERROR("Invalid denoise shader");
|
||||
|
|
@ -36,7 +37,8 @@ bool Denoiser::initialize(const std::shared_ptr<Shader>& shader) {
|
|||
}
|
||||
|
||||
void Denoiser::release() {
|
||||
if (!initialized_) return;
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
shader_.reset();
|
||||
|
||||
|
|
@ -55,11 +57,13 @@ void Denoiser::release() {
|
|||
}
|
||||
|
||||
void Denoiser::resize(uint width, uint height) {
|
||||
if (width == width_ && height == height_) return;
|
||||
if (width == width_ && height == height_)
|
||||
return;
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
|
||||
if (!initialized_) return;
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
|
||||
|
|
@ -78,7 +82,8 @@ void Denoiser::resize(uint width, uint height) {
|
|||
}
|
||||
|
||||
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;
|
||||
temporal_weight = (temporal_weight < 0.0f) ? 0.0f : ((temporal_weight > 1.0f) ? 1.0f : temporal_weight);
|
||||
|
|
@ -100,7 +105,6 @@ TextureHandle Denoiser::denoise(TextureHandle input_texture, int radius, float t
|
|||
uint groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
||||
uint groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
||||
glDispatchCompute(groups_x, groups_y, 1);
|
||||
|
||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
// Copy output to history for next frame (if temporal accumulation is enabled)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "basic/constants.h"
|
||||
#include "resource/resource_manager.h"
|
||||
#include "utils/logger.h"
|
||||
#include <cmath>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace are {
|
||||
|
|
@ -17,39 +18,22 @@ namespace {
|
|||
return h;
|
||||
}
|
||||
|
||||
// Compute hash of texture pointers for a specific slot
|
||||
uint compute_slot_texture_hash(const std::vector<std::shared_ptr<Texture>> &textures) {
|
||||
if (textures.empty())
|
||||
return 0u;
|
||||
// Hash the raw pointers to detect texture set changes
|
||||
std::vector<const void *> ptrs;
|
||||
ptrs.reserve(textures.size());
|
||||
for (const auto &t : textures) {
|
||||
for (const auto &t : textures)
|
||||
ptrs.push_back(t.get());
|
||||
}
|
||||
return fnv1a_hash_bytes(ptrs.data(), ptrs.size() * sizeof(void *));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
RayTracer::RayTracer(uint width, uint height, const RayTracerConfig &config)
|
||||
: 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) {
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
: 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) {
|
||||
for (int i = 0; i < 6; ++i)
|
||||
texture_slot_hashes_[i] = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
RayTracer::~RayTracer() {
|
||||
release();
|
||||
|
|
@ -60,15 +44,9 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &shader) {
|
|||
ARE_LOG_WARN("RayTracer already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
|
||||
compute_shader_ = shader;
|
||||
|
||||
// Create accumulation texture
|
||||
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
||||
|
||||
// Create shader storage buffers
|
||||
BufferDescription ssbo_desc;
|
||||
ssbo_desc.type = BufferType::SHADER_STORAGE_BUFFER;
|
||||
ssbo_desc.usage = BufferUsage::DYNAMIC_DRAW;
|
||||
|
|
@ -76,108 +54,85 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &shader) {
|
|||
ssbo_desc.data = nullptr;
|
||||
material_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++) {
|
||||
texture_arrays_[i] = 0;
|
||||
texture_array_sizes_[i] = 0;
|
||||
}
|
||||
|
||||
// Initialize BVH if enabled
|
||||
if (config_.use_bvh) {
|
||||
if (config_.use_bvh)
|
||||
bvh_ = std::make_unique<BVH>();
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
ARE_LOG_INFO("RayTracer initialized successfully");
|
||||
ARE_LOG_INFO("RayTracer initialized (" + std::to_string(width_) + "x" + std::to_string(height_) + ")");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RayTracer::release() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
|
||||
if (accumulation_texture_ != INVALID_HANDLE) {
|
||||
rm.destroy_texture(accumulation_texture_);
|
||||
accumulation_texture_ = INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Release texture arrays
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (texture_arrays_[i] != 0) {
|
||||
rm.destroy_texture_array(texture_arrays_[i]);
|
||||
texture_arrays_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (material_buffer_ != INVALID_HANDLE) {
|
||||
rm.destroy_buffer(material_buffer_);
|
||||
material_buffer_ = INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (light_buffer_ != INVALID_HANDLE) {
|
||||
rm.destroy_buffer(light_buffer_);
|
||||
light_buffer_ = INVALID_HANDLE;
|
||||
}
|
||||
|
||||
bvh_node_buffer_.release();
|
||||
bvh_triangle_buffer_.release();
|
||||
bvh_attr_buffer_.release();
|
||||
|
||||
bvh_.reset();
|
||||
bvh_built_ = false;
|
||||
|
||||
initialized_ = false;
|
||||
ARE_LOG_INFO("RayTracer released");
|
||||
}
|
||||
|
||||
bool RayTracer::rebuild_bvh(const Scene &scene) {
|
||||
if (!config_.use_bvh) {
|
||||
ARE_LOG_WARN("BVH is disabled in configuration");
|
||||
if (!config_.use_bvh)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bvh_) {
|
||||
if (!bvh_)
|
||||
bvh_ = std::make_unique<BVH>();
|
||||
}
|
||||
|
||||
ARE_LOG_INFO("Building BVH for ray tracing...");
|
||||
|
||||
if (!bvh_->build(scene.get_meshes())) {
|
||||
ARE_LOG_ERROR("Failed to build BVH");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bvh_->upload_to_gpu(bvh_node_buffer_, bvh_triangle_buffer_, bvh_attr_buffer_)) {
|
||||
ARE_LOG_ERROR("Failed to upload BVH to GPU");
|
||||
return false;
|
||||
}
|
||||
|
||||
bvh_built_ = true;
|
||||
reset_accumulation();
|
||||
ARE_LOG_INFO("BVH built and uploaded successfully");
|
||||
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_) {
|
||||
ARE_LOG_ERROR("RayTracer not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!compute_shader_->is_valid()) {
|
||||
ARE_LOG_ERROR("Ray tracing compute shader not loaded");
|
||||
ARE_LOG_ERROR("Compute shader not loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build BVH if enabled and not built yet
|
||||
if (config_.use_bvh && !bvh_built_) {
|
||||
if (config_.use_bvh && !bvh_built_)
|
||||
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();
|
||||
bool has_textures = false;
|
||||
for (const auto &mat : materials) {
|
||||
|
|
@ -188,40 +143,28 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
|
|||
}
|
||||
if (has_textures) {
|
||||
build_texture_arrays_(scene);
|
||||
|
||||
// Bind texture arrays
|
||||
glActiveTexture(GL_TEXTURE10);
|
||||
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]);
|
||||
for (int slot = 0; slot < 6; slot++) {
|
||||
glActiveTexture(GL_TEXTURE10 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload scene data (materials now have correct texture indices)
|
||||
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();
|
||||
|
||||
// Bind G-Buffer textures
|
||||
// ── G‑buffer images ────────────────────────────────────────────────────
|
||||
bind_gbuffer_(gbuffer);
|
||||
|
||||
// Bind output and accumulation textures
|
||||
glBindImageTexture(3, output_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
|
||||
glBindImageTexture(4, accumulation_texture_, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
|
||||
// ── output image (binding 3) ──────────────────────────────────────────
|
||||
glBindImageTexture(3, output_image, 0, GL_FALSE, 0, GL_WRITE_ONLY, 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_) {
|
||||
bvh_node_buffer_.bind_base(2);
|
||||
bvh_triangle_buffer_.bind_base(3);
|
||||
|
|
@ -232,56 +175,48 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
|
|||
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_samples_per_pixel", config_.samples_per_pixel);
|
||||
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_bool("u_enable_accumulation", config_.enable_accumulation);
|
||||
|
||||
// Enable/disable textures based on material usage
|
||||
compute_shader_->set_bool("u_enable_accumulation", sr_enabled ? false : config_.enable_accumulation);
|
||||
compute_shader_->set_bool("u_enable_textures", has_textures);
|
||||
|
||||
// Set camera data
|
||||
const Camera &camera = scene.get_camera();
|
||||
Mat4 inv_vp = glm::inverse(camera.get_view_projection_matrix());
|
||||
compute_shader_->set_mat4("u_inv_view_projection", inv_vp);
|
||||
compute_shader_->set_mat4("u_inv_view_projection", glm::inverse(camera.get_view_projection_matrix()));
|
||||
|
||||
// Dispatch compute shader
|
||||
uint num_groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X;
|
||||
uint num_groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y;
|
||||
compute_shader_->set_uint("u_sr_enabled", sr_enabled);
|
||||
compute_shader_->set_uint("u_sr_scaling", sr_scaling);
|
||||
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);
|
||||
|
||||
// Memory barrier
|
||||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
// Increment frame count for accumulation
|
||||
if (config_.enable_accumulation) {
|
||||
if (config_.enable_accumulation || sr_enabled)
|
||||
frame_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
void RayTracer::resize(uint width, uint height) {
|
||||
if (width == width_ && height == height_)
|
||||
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;
|
||||
height_ = height;
|
||||
|
||||
if (initialized_) {
|
||||
if (!initialized_)
|
||||
return;
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
|
||||
// Recreate accumulation texture
|
||||
if (accumulation_texture_ != INVALID_HANDLE) {
|
||||
if (accumulation_texture_ != INVALID_HANDLE)
|
||||
rm.destroy_texture(accumulation_texture_);
|
||||
}
|
||||
|
||||
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
|
||||
|
||||
reset_accumulation();
|
||||
}
|
||||
}
|
||||
|
||||
void RayTracer::reset_accumulation() {
|
||||
frame_count_ = 0;
|
||||
|
|
@ -289,10 +224,8 @@ void RayTracer::reset_accumulation() {
|
|||
|
||||
void RayTracer::set_config(const RayTracerConfig &config) {
|
||||
bool bvh_changed = (config.use_bvh != config_.use_bvh);
|
||||
|
||||
config_ = config;
|
||||
reset_accumulation();
|
||||
|
||||
if (bvh_changed) {
|
||||
if (config_.use_bvh && !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) {
|
||||
// Upload materials (on change only)
|
||||
const auto &materials = scene.get_materials();
|
||||
if (!materials.empty()) {
|
||||
// Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes)
|
||||
struct MaterialData {
|
||||
alignas(16) Vec3 albedo;
|
||||
alignas(16) Vec3 emission;
|
||||
float metallic;
|
||||
float roughness;
|
||||
float metallic, roughness;
|
||||
int type;
|
||||
float ior;
|
||||
float ao;
|
||||
float padding1;
|
||||
float ior, ao, padding1;
|
||||
uint texture_handles[6];
|
||||
};
|
||||
|
||||
std::vector<MaterialData> material_data;
|
||||
material_data.reserve(materials.size());
|
||||
|
||||
std::vector<MaterialData> md;
|
||||
md.reserve(materials.size());
|
||||
for (const auto &mat : materials) {
|
||||
MaterialData data {};
|
||||
data.albedo = mat->get_albedo();
|
||||
data.metallic = mat->get_metallic();
|
||||
data.emission = mat->get_emission();
|
||||
data.roughness = mat->get_roughness();
|
||||
data.type = static_cast<int>(mat->get_type());
|
||||
data.ior = mat->get_ior();
|
||||
data.ao = 1.0f; // default: no AO
|
||||
|
||||
// Texture array indices (0 = no texture, 1+ = index into array)
|
||||
data.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO);
|
||||
data.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL);
|
||||
data.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC);
|
||||
data.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS);
|
||||
data.texture_handles[4] = mat->get_texture_index(TextureSlot::AO);
|
||||
data.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION);
|
||||
|
||||
material_data.push_back(data);
|
||||
MaterialData d {};
|
||||
d.albedo = mat->get_albedo();
|
||||
d.metallic = mat->get_metallic();
|
||||
d.emission = mat->get_emission();
|
||||
d.roughness = mat->get_roughness();
|
||||
d.type = static_cast<int>(mat->get_type());
|
||||
d.ior = mat->get_ior();
|
||||
d.ao = 1.0f;
|
||||
d.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO);
|
||||
d.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL);
|
||||
d.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC);
|
||||
d.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS);
|
||||
d.texture_handles[4] = mat->get_texture_index(TextureSlot::AO);
|
||||
d.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION);
|
||||
md.push_back(d);
|
||||
}
|
||||
|
||||
uint h = fnv1a_hash_bytes(material_data.data(), material_data.size() * sizeof(MaterialData));
|
||||
uint h = fnv1a_hash_bytes(md.data(), md.size() * sizeof(MaterialData));
|
||||
if (h != materials_hash_) {
|
||||
materials_hash_ = h;
|
||||
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER,
|
||||
material_data.size() * sizeof(MaterialData),
|
||||
material_data.data(), GL_DYNAMIC_DRAW);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, md.size() * sizeof(MaterialData), md.data(), GL_DYNAMIC_DRAW);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
||||
|
||||
reset_accumulation(); // materials changed => invalidate accumulation
|
||||
reset_accumulation();
|
||||
} else {
|
||||
// Still ensure bound (in case other code changed bindings)
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_);
|
||||
}
|
||||
} else {
|
||||
materials_hash_ = 0u;
|
||||
}
|
||||
|
||||
// Upload lights (on change only)
|
||||
const auto &lights = scene.get_lights();
|
||||
if (!lights.empty()) {
|
||||
struct LightData {
|
||||
|
|
@ -377,33 +293,26 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
|
|||
Vec2 spot_angles;
|
||||
Vec2 padding;
|
||||
};
|
||||
|
||||
std::vector<LightData> light_data;
|
||||
light_data.reserve(lights.size());
|
||||
|
||||
for (const auto &light : lights) {
|
||||
LightData data {};
|
||||
data.position = light->get_position();
|
||||
data.type = static_cast<int>(light->get_type());
|
||||
data.direction = light->get_direction();
|
||||
data.intensity = light->get_intensity();
|
||||
data.color = light->get_color();
|
||||
data.range = light->get_range();
|
||||
data.spot_angles = Vec2(light->get_inner_angle(), light->get_outer_angle());
|
||||
light_data.push_back(data);
|
||||
std::vector<LightData> ld;
|
||||
ld.reserve(lights.size());
|
||||
for (const auto &l : lights) {
|
||||
LightData d {};
|
||||
d.position = l->get_position();
|
||||
d.type = static_cast<int>(l->get_type());
|
||||
d.direction = l->get_direction();
|
||||
d.intensity = l->get_intensity();
|
||||
d.color = l->get_color();
|
||||
d.range = l->get_range();
|
||||
d.spot_angles = Vec2(l->get_inner_angle(), l->get_outer_angle());
|
||||
ld.push_back(d);
|
||||
}
|
||||
|
||||
uint h = fnv1a_hash_bytes(light_data.data(), light_data.size() * sizeof(LightData));
|
||||
uint h = fnv1a_hash_bytes(ld.data(), ld.size() * sizeof(LightData));
|
||||
if (h != lights_hash_) {
|
||||
lights_hash_ = h;
|
||||
|
||||
glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_buffer_);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER,
|
||||
light_data.size() * sizeof(LightData),
|
||||
light_data.data(), GL_DYNAMIC_DRAW);
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, ld.size() * sizeof(LightData), ld.data(), GL_DYNAMIC_DRAW);
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_);
|
||||
|
||||
reset_accumulation(); // lights changed => invalidate accumulation
|
||||
reset_accumulation();
|
||||
} else {
|
||||
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) {
|
||||
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(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
|
||||
|
||||
// Texcoord
|
||||
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);
|
||||
glBindImageTexture(2, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||
glBindImageTexture(7, gbuffer.get_texture(GBUFFER_TANGENT), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
|
||||
}
|
||||
|
||||
void RayTracer::build_texture_arrays_(const Scene &scene) {
|
||||
const auto &materials = scene.get_materials();
|
||||
|
||||
// Collect all textures for each slot
|
||||
std::vector<std::shared_ptr<Texture>> textures[6];
|
||||
|
||||
for (const auto &mat : materials) {
|
||||
for (int slot = 0; slot < 6; slot++) {
|
||||
auto tex = mat->get_texture(static_cast<TextureSlot>(slot));
|
||||
if (tex && tex->is_valid()) {
|
||||
// Check if texture already added (use set for O(1) lookup)
|
||||
bool found = false;
|
||||
for (const auto &t : textures[slot]) {
|
||||
if (t.get() == tex.get()) {
|
||||
|
|
@ -444,92 +344,62 @@ void RayTracer::build_texture_arrays_(const Scene &scene) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (!found)
|
||||
textures[slot].push_back(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute hash for each slot and check if rebuild is needed
|
||||
bool any_slot_dirty = false;
|
||||
uint new_slot_hashes[6];
|
||||
for (int slot = 0; slot < 6; 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;
|
||||
}
|
||||
}
|
||||
|
||||
// If no slots changed, skip entire rebuild
|
||||
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) {
|
||||
glActiveTexture(GL_TEXTURE10 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
|
||||
// Build arrays only for dirty slots
|
||||
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_) {
|
||||
// Bind existing array
|
||||
if (texture_arrays_[slot] != 0) {
|
||||
glActiveTexture(GL_TEXTURE10 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Destroy previous texture array if exists
|
||||
if (texture_arrays_[slot] != 0) {
|
||||
rm.destroy_texture_array(texture_arrays_[slot]);
|
||||
texture_arrays_[slot] = 0;
|
||||
}
|
||||
|
||||
if (textures[slot].empty()) {
|
||||
texture_array_sizes_[slot] = 0;
|
||||
texture_slot_hashes_[slot] = 0u;
|
||||
continue;
|
||||
}
|
||||
|
||||
texture_array_sizes_[slot] = static_cast<uint>(textures[slot].size());
|
||||
|
||||
// Create texture array using ResourceManager
|
||||
TextureArrayDescription desc;
|
||||
desc.textures = textures[slot];
|
||||
desc.filter = TextureFilter::LINEAR;
|
||||
desc.wrap = TextureWrap::REPEAT;
|
||||
|
||||
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++) {
|
||||
// Index is i+1 because 0 means "no texture" in the shader
|
||||
uint32_t array_index = static_cast<uint32_t>(i) + 1;
|
||||
for (const auto &mat : materials) {
|
||||
if (mat->get_texture(static_cast<TextureSlot>(slot)).get() == textures[slot][i].get()) {
|
||||
for (const auto &mat : materials)
|
||||
if (mat->get_texture(static_cast<TextureSlot>(slot)).get() == textures[slot][i].get())
|
||||
mat->set_texture_index(static_cast<TextureSlot>(slot), array_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update slot hash
|
||||
texture_slot_hashes_[slot] = new_slot_hashes[slot];
|
||||
|
||||
// Bind the newly created array
|
||||
if (texture_arrays_[slot] != 0) {
|
||||
glActiveTexture(GL_TEXTURE10 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]);
|
||||
}
|
||||
}
|
||||
|
||||
texture_arrays_dirty_ = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "core/renderer.h"
|
||||
#include "resource/resource_manager.h"
|
||||
#include "utils/logger.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <glad/glad.h>
|
||||
|
||||
|
|
@ -9,8 +10,7 @@ namespace are {
|
|||
Renderer::Renderer(const RendererConfig &config)
|
||||
: config_(config)
|
||||
, rt_output_texture_(INVALID_HANDLE)
|
||||
, initialized_(false)
|
||||
, frame_count_(0) {
|
||||
, initialized_(false) {
|
||||
}
|
||||
|
||||
Renderer::~Renderer() {
|
||||
|
|
@ -25,6 +25,27 @@ bool Renderer::initialize() {
|
|||
|
||||
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
|
||||
shader_manager_ = std::make_unique<ShaderManager>();
|
||||
if (!shader_manager_->initialize()) {
|
||||
|
|
@ -40,10 +61,7 @@ bool Renderer::initialize() {
|
|||
}
|
||||
|
||||
// Initialize ray tracer
|
||||
RayTracerConfig rt_config = config_.rt_config;
|
||||
|
||||
// Initialize ray tracer
|
||||
raytracer_ = std::make_unique<RayTracer>(config_.output_width, config_.output_height, rt_config);
|
||||
raytracer_ = std::make_unique<RayTracer>(config_.output_width, config_.output_height, config_.rt_config);
|
||||
const auto &rt_shader = shader_manager_->get_raytracing_shader();
|
||||
if (!raytracer_->initialize(rt_shader)) {
|
||||
ARE_LOG_ERROR("Failed to initialize ray tracer");
|
||||
|
|
@ -65,6 +83,16 @@ bool Renderer::initialize() {
|
|||
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)
|
||||
ResourceManager &rm = ResourceManager::instance();
|
||||
rt_output_texture_ = rm.create_texture(config_.output_width, config_.output_height, TextureFormat::RGBA32F);
|
||||
|
|
@ -92,6 +120,7 @@ void Renderer::shutdown() {
|
|||
gbuffer_.reset();
|
||||
shader_manager_.reset();
|
||||
denoiser_.reset();
|
||||
super_resolution_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
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
|
||||
auto raytrace_start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Use output texture if provided, otherwise use internal texture
|
||||
TextureHandle rt_output = (output_texture != 0) ? output_texture : rt_output_texture_;
|
||||
TextureHandle rt_output;
|
||||
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,
|
||||
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();
|
||||
stats.raytrace_time_ms_ = std::chrono::duration<float, std::milli>(raytrace_end - raytrace_start).count();
|
||||
|
||||
// Phase 3: Denoise texture
|
||||
TextureHandle final_output = rt_output;
|
||||
|
||||
// Phase 3: Post-processing and output
|
||||
TextureHandle final_output;
|
||||
if (config_.sr_config.enabled && super_resolution_) {
|
||||
// Denoising intentionally skipped — cross‑cycle accumulation provides temporal smoothing
|
||||
auto &sr = *super_resolution_;
|
||||
final_output = sr.upscale();
|
||||
sr.advance_jitter_frame();
|
||||
} else {
|
||||
final_output = rt_output;
|
||||
if (config_.enable_denoising && denoiser_) {
|
||||
// Use temporal accumulation with weight 0.1 (10% blend of new frame)
|
||||
float temporal_weight = 0.1f;
|
||||
final_output = denoiser_->denoise(rt_output, 1, temporal_weight);
|
||||
final_output = denoiser_->denoise(final_output, 1, temporal_weight);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -184,6 +223,10 @@ void Renderer::resize(uint width, uint height) {
|
|||
raytracer_->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));
|
||||
}
|
||||
}
|
||||
|
|
@ -200,6 +243,15 @@ void Renderer::set_config(const RendererConfig &config) {
|
|||
|
||||
// Update ray tracer 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_) {
|
||||
denoiser_->reset_history();
|
||||
}
|
||||
|
||||
// Reset super resolution accumulation on scene change
|
||||
if (super_resolution_) {
|
||||
super_resolution_->reset_accumulation();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace are
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ void ShaderManager::release() {
|
|||
screen_blit_shader_.reset();
|
||||
raytracing_shader_.reset();
|
||||
denoise_shader_.reset();
|
||||
super_resolution_shader_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
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_() {
|
||||
// Load G-buffer shader
|
||||
ARE_LOG_INFO("Loading G-buffer shaders..");
|
||||
ARE_LOG_INFO("Loading G-buffer shaders...");
|
||||
gbuffer_shader_ = std::make_shared<Shader>();
|
||||
if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) {
|
||||
ARE_LOG_ERROR("Failed to load G-Buffer shader");
|
||||
|
|
@ -132,6 +133,16 @@ bool ShaderManager::load_builtin_shaders_() {
|
|||
shader_cache_["denoise"] = denoise_shader_;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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