feat: 添加超分辨率模块框架,实现光线追踪低倍率渲染

- 实现超分辨率模块基本框架(super_resolution.h/.cpp/.comp)
- 实现非完整倍率光线追踪采样(支持整数倍采样缩放倍率,super_resolution.comp&raytracing.comp&tonemap.glsl)
main
ternaryop8479 2026-05-22 23:26:29 +08:00
parent 25547365e8
commit 9259bb9c6f
18 changed files with 1196 additions and 1051 deletions

4
.gitignore vendored
View File

@ -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.

View File

@ -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.

View File

@ -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");

View File

@ -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 scene Scene data
* @param gbuffer G-Buffer containing geometry information
* @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);
};

View File

@ -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

View File

@ -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_;

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -6,9 +6,9 @@ layout(binding = 0, rgba32f) uniform readonly image2D u_input;
layout(binding = 1, rgba32f) uniform writeonly image2D u_output;
layout(binding = 2, rgba32f) uniform readonly image2D u_history;
uniform int u_radius; // 1 => 3x3, 2 => 5x5
uniform float u_temporal_weight; // 0 = no temporal, 1 = full history
uniform bool u_has_history; // Whether history texture is valid
uniform int u_radius; // 1 => 3x3, 2 => 5x5
uniform float u_temporal_weight; // 0 = no temporal, 1 = full history
uniform bool u_has_history; // Whether history texture is valid
// Gaussian weight based on distance
float gaussian_weight(float dist, float sigma) {
@ -24,8 +24,8 @@ void main() {
vec3 center_color = imageLoad(u_input, p).rgb;
// Sigma values for bilateral filter
float sigma_space = float(u_radius); // spatial sigma
float sigma_color = 0.3; // color sigma (adjust for more/less smoothing)
float sigma_space = float(u_radius); // spatial sigma
float sigma_color = 0.3; // color sigma (adjust for more/less smoothing)
vec3 sum = vec3(0.0);
float weight_sum = 0.0;

View File

@ -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));
}

View File

@ -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 = 6, r32ui) uniform readonly uimage2D g_material_id;
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,32 +60,24 @@ 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 < 16u) {
@ -87,219 +90,166 @@ float sobol_next(inout SobolState state) {
return value;
}
// 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);
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);
mat3 onb = mat3(T, B, N);
return onb * H_tangent;
return mat3(T, B, N) * Ht;
}
// Sobol-based diffuse scattering with cosine-weighted importance sampling
ScatterResult scatter_diffuse_sobol(Ray ray_in, HitInfo hit, Material mat, inout SobolState state) {
ScatterResult r;
r.scattered = true;
r.attenuation = mat.albedo;
// Cosine-weighted importance sampling for Lambertian BRDF
float r_sqrt = sqrt(sobol_next(state));
float phi = 2.0 * PI * sobol_next(state);
float x = r_sqrt * cos(phi);
float y = r_sqrt * sin(phi);
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);
mat3 onb = mat3(T, B, hit.normal);
vec3 dir = onb * vec3(x, y, z);
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 = 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.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 f = fresnel_dielectric(cos_theta, mat.ior);
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 = 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 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 image_size = imageSize(output_image);
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;
}
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);
@ -309,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;
if (u_enable_accumulation && u_frame_count > 0u) {
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
float w = 1.0 / float(u_frame_count + 1u);
accumulation_color = mix(accumulated, color, w);
// ── Super resolution: percycle 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;
}
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));
// ── Standard nonSR accumulation ─────────────────────────────────────
vec3 acc_color = color;
if (u_enable_accumulation && u_frame_count > 0u) {
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));
}

View File

@ -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,38 +18,21 @@ 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() {
@ -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
// ── Gbuffer 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 fullres accumulation texture
// nonSR: use raytracer'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,55 +175,47 @@ 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_) {
ResourceManager &rm = ResourceManager::instance();
// Recreate accumulation texture
if (accumulation_texture_ != INVALID_HANDLE) {
rm.destroy_texture(accumulation_texture_);
}
accumulation_texture_ = rm.create_texture(width_, height_, TextureFormat::RGBA32F);
reset_accumulation();
}
if (!initialized_)
return;
ResourceManager &rm = ResourceManager::instance();
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() {
@ -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(2, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
// Tangent
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;
}

View File

@ -9,8 +9,7 @@ namespace are {
Renderer::Renderer(const RendererConfig &config)
: config_(config)
, rt_output_texture_(INVALID_HANDLE)
, initialized_(false)
, frame_count_(0) {
, initialized_(false) {
}
Renderer::~Renderer() {
@ -40,10 +39,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 +61,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 +98,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 +131,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);
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;
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);
// Phase 3: Post-processing and output
TextureHandle final_output;
if (config_.sr_config.enabled && super_resolution_) {
// Denoising intentionally skipped — crosscycle 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_) {
float temporal_weight = 0.1f;
final_output = denoiser_->denoise(final_output, 1, temporal_weight);
}
}
// Phase 4: Blit to screen if output is default framebuffer
@ -156,11 +178,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 +201,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 +221,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 +241,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

View File

@ -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;
}

View File

@ -0,0 +1,133 @@
#include "core/super_resolution.h"
#include "basic/constants.h"
#include "resource/resource_manager.h"
#include "utils/logger.h"
#include <cmath>
#include <glad/glad.h>
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("SuperResolution 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("SuperResolution 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