这个架构最后一次commit

master
ternaryop8479 2026-02-09 17:47:21 +08:00
parent 01dd5bd91e
commit 96ffcd4edc
15 changed files with 889 additions and 885 deletions

View File

@ -1,373 +1,282 @@
/**
* @file main.cpp
* @brief Phase 5 verification program - CPU ray tracing test (RGBA16F output)
* @brief Phase 5 test: Hybrid GBuffer-driven CPU raytracing with primitive-id
*/
#include <iostream>
#include <are/core/config.h>
#include <are/core/logger.h>
#include <are/core/profiler.h>
#include <are/platform/gl_context.h>
#include <are/platform/window.h>
#include <are/platform/gl_context.h>
#include <are/rasterizer/gbuffer.h>
#include <are/rasterizer/rasterizer.h>
#include <are/rasterizer/gbuffer.h>
#include <are/rasterizer/shader_program.h>
#include <are/scene/camera.h>
#include <are/scene/directional_light.h>
#include <are/scene/material.h>
#include <are/scene/mesh.h>
#include <are/scene/point_light.h>
#include <are/scene/scene_manager.h>
#include <are/renderer/geometry_cache.h>
#include <are/acceleration/bvh.h>
#include <are/geometry/triangle.h>
#include <are/geometry/vertex.h>
#include <are/scene/scene_manager.h>
#include <are/scene/camera.h>
#include <are/scene/mesh.h>
#include <are/scene/material.h>
#include <are/scene/directional_light.h>
#include <are/scene/point_light.h>
#include <are/raytracer/cpu_raytracer.h>
#include "../lib/glad/glad/glad.h"
#include <glm/glm.hpp>
#include <vector>
#include <string>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
using namespace are;
namespace {
/**
* @brief Create a fullscreen quad VAO.
* @return VAO handle
*/
uint32_t create_fullscreen_quad_vao() {
float vertices[] = {
// pos // uv
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f
};
float vertices[] = {
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f
};
uint32_t indices[] = { 0, 1, 2, 2, 3, 0 };
uint32_t indices[] = { 0, 1, 2, 2, 3, 0 };
uint32_t vao = 0, vbo = 0, ebo = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float)));
glBindVertexArray(0);
// Intentionally leak vbo/ebo for this small test (or store & delete if you prefer).
return vao;
glBindVertexArray(0);
return vao;
}
/**
* @brief Create RGBA16F output texture.
* @param width Texture width
* @param height Texture height
* @return Texture ID
*/
uint32_t create_output_texture_rgba16f(int width, int height) {
uint32_t tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
uint32_t tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
return tex;
}
/**
* @brief Build triangles from a mesh (positions only, identity transform).
* @param mesh Mesh
* @param material Material handle
* @return Triangle list
*/
std::vector<Triangle> mesh_to_triangles(const Mesh &mesh, MaterialHandle material) {
std::vector<Triangle> tris;
if (mesh.is_empty()) {
return tris;
}
const auto &v = mesh.get_vertices();
const auto &idx = mesh.get_indices();
if (idx.size() % 3 != 0) {
ARE_LOG_ERROR("phase5_test: mesh indices not multiple of 3");
return tris;
}
tris.reserve(idx.size() / 3);
for (size_t i = 0; i < idx.size(); i += 3) {
uint32_t i0 = idx[i + 0];
uint32_t i1 = idx[i + 1];
uint32_t i2 = idx[i + 2];
if (i0 >= v.size() || i1 >= v.size() || i2 >= v.size()) {
continue;
}
Triangle t(v[i0], v[i1], v[i2], material);
tris.push_back(t);
}
return tris;
glBindTexture(GL_TEXTURE_2D, 0);
return tex;
}
} // namespace
int main() {
Logger::init(LogLevel::ARE_LOG_INFO);
Profiler::init();
try {
Logger::init(LogLevel::ARE_LOG_INFO);
Profiler::init();
WindowConfig wc;
wc.width = 960;
wc.height = 540;
wc.title = "ARE Phase 5 - CPU Ray Tracing (RGBA16F)";
wc.vsync = true;
WindowConfig wc;
wc.width = 960;
wc.height = 540;
wc.title = "ARE Phase5 - Hybrid + PrimitiveID";
wc.vsync = true;
Window window(wc);
Window window(wc);
if (!GLContext::initialize()) {
ARE_LOG_CRITICAL("phase5_test: GLContext::initialize failed");
return -1;
}
GLContext::print_info();
if (!GLContext::initialize()) {
ARE_LOG_CRITICAL("GLContext initialize failed");
return -1;
}
int fb_w = 0;
int fb_h = 0;
window.get_framebuffer_size(fb_w, fb_h);
if (fb_w <= 0 || fb_h <= 0) {
ARE_LOG_CRITICAL("phase5_test: framebuffer size is invalid");
return -1;
}
int fb_w = 0, fb_h = 0;
for (int i = 0; i < 240; ++i) {
window.poll_events();
window.get_framebuffer_size(fb_w, fb_h);
if (fb_w > 0 && fb_h > 0) break;
window.swap_buffers();
}
if (fb_w <= 0 || fb_h <= 0) {
ARE_LOG_CRITICAL("Invalid framebuffer size");
return -1;
}
// GBuffer only used for resolution / future hybrid path
Rasterizer rasterizer(fb_w, fb_h);
GBuffer &gbuffer = rasterizer.get_gbuffer();
Rasterizer rasterizer(fb_w, fb_h);
rasterizer.initialize_shaders("shaders/");
// Output texture (RGBA16F)
uint32_t output_tex = create_output_texture_rgba16f(fb_w, fb_h);
uint32_t output_tex = create_output_texture_rgba16f(fb_w, fb_h);
uint32_t quad_vao = create_fullscreen_quad_vao();
// Simple display shader
const char *fsq_vs = R"(
// Display shader (simple)
const char* vs = R"(
#version 430 core
layout(location = 0) in vec2 a_pos;
layout(location = 1) in vec2 a_uv;
out vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos, 0.0, 1.0);
}
void main(){ v_uv=a_uv; gl_Position=vec4(a_pos,0,1); }
)";
const char *fsq_fs = R"(
const char* fs = R"(
#version 430 core
in vec2 v_uv;
out vec4 frag_color;
uniform sampler2D u_tex;
void main() {
frag_color = texture(u_tex, v_uv);
}
void main(){ frag_color = texture(u_tex, v_uv); }
)";
ShaderProgram display;
if (!display.compile_shader(ShaderType::ARE_SHADER_VERTEX, fsq_vs) || !display.compile_shader(ShaderType::ARE_SHADER_FRAGMENT, fsq_fs) || !display.link()) {
ARE_LOG_CRITICAL("phase5_test: failed to compile display shader");
return -1;
}
ShaderProgram display;
if (!display.compile_shader(ShaderType::ARE_SHADER_VERTEX, vs) ||
!display.compile_shader(ShaderType::ARE_SHADER_FRAGMENT, fs) ||
!display.link()) {
ARE_LOG_CRITICAL("Display shader compile failed");
return -1;
}
uint32_t quad_vao = create_fullscreen_quad_vao();
// Scene setup
SceneManager scene;
// Scene setup
SceneManager scene;
Material mat;
mat.set_albedo(Vec3(0.8f, 0.2f, 0.2f));
mat.set_roughness(0.6f);
MaterialHandle mat_h = scene.add_material(mat);
Material mat;
mat.set_albedo(Vec3(0.8f, 0.2f, 0.2f));
mat.set_roughness(0.6f);
MaterialHandle mat_h = scene.add_material(mat);
std::vector<Vertex> ground_v = {
Vertex(Vec3(-4, 0, -4), Vec3(0, 1, 0), Vec2(0, 0)),
Vertex(Vec3( 4, 0, -4), Vec3(0, 1, 0), Vec2(1, 0)),
Vertex(Vec3( 4, 0, 4), Vec3(0, 1, 0), Vec2(1, 1)),
Vertex(Vec3(-4, 0, 4), Vec3(0, 1, 0), Vec2(0, 1)),
};
std::vector<uint32_t> ground_i = { 0, 1, 2, 2, 3, 0 };
Mesh ground(ground_v, ground_i, mat_h);
ground.compute_tangents();
// A ground plane (two triangles)
std::vector<Vertex> ground_v = {
Vertex(Vec3(-4, 0, -4), Vec3(0, 1, 0), Vec2(0, 0)),
Vertex(Vec3(4, 0, -4), Vec3(0, 1, 0), Vec2(1, 0)),
Vertex(Vec3(4, 0, 4), Vec3(0, 1, 0), Vec2(1, 1)),
Vertex(Vec3(-4, 0, 4), Vec3(0, 1, 0), Vec2(0, 1)),
};
std::vector<uint32_t> ground_i = { 0, 1, 2, 2, 3, 0 };
Mesh ground(ground_v, ground_i, mat_h);
ground.compute_tangents();
MeshHandle ground_mh = scene.add_mesh(ground);
(void)ground_mh;
std::vector<Vertex> tri_v = {
Vertex(Vec3(-0.8f, 0.2f, 0.0f), Vec3(0, 1, 0), Vec2(0, 0)),
Vertex(Vec3( 0.8f, 0.2f, 0.0f), Vec3(0, 1, 0), Vec2(1, 0)),
Vertex(Vec3( 0.0f, 1.2f, 0.3f), Vec3(0, 1, 0), Vec2(0.5f, 1)),
};
std::vector<uint32_t> tri_i = { 0, 1, 2 };
Mesh tri_mesh(tri_v, tri_i, mat_h);
tri_mesh.compute_tangents();
// A tilted triangle
std::vector<Vertex> tri_v = {
Vertex(Vec3(-0.8f, 0.2f, 0.0f), Vec3(0, 0.8f, 0.6f), Vec2(0, 0)),
Vertex(Vec3(0.8f, 0.2f, 0.0f), Vec3(0, 0.8f, 0.6f), Vec2(1, 0)),
Vertex(Vec3(0.0f, 1.2f, 0.3f), Vec3(0, 0.8f, 0.6f), Vec2(0.5f, 1)),
};
std::vector<uint32_t> tri_i = { 0, 1, 2 };
Mesh tri_mesh(tri_v, tri_i, mat_h);
tri_mesh.compute_tangents();
scene.add_mesh(tri_mesh);
// Upload meshes BEFORE adding (SceneManager stores copy)
rasterizer.upload_mesh(ground);
scene.add_mesh(ground);
// Lights
auto sun = std::make_shared<DirectionalLight>(Vec3(-1, -1, -0.5f), Vec3(1.0f), 2.0f);
scene.add_light(sun);
rasterizer.upload_mesh(tri_mesh);
scene.add_mesh(tri_mesh);
auto point = std::make_shared<PointLight>(Vec3(0, 2.5f, 1.5f), Vec3(1.0f, 0.95f, 0.8f), 10.0f, 10.0f);
point->set_cast_shadows(true);
scene.add_light(point);
auto sun = std::make_shared<DirectionalLight>(Vec3(-1, -1, -0.5f), Vec3(1.0f), 2.0f);
sun->set_cast_shadows(true);
scene.add_light(sun);
// Camera
Camera camera(Vec3(0.0f, 1.8f, 4.5f), Vec3(0.0f, 0.6f, 0.0f));
camera.set_perspective(45.0f, static_cast<Real>(fb_w) / static_cast<Real>(fb_h), 0.1f, 200.0f);
auto point = std::make_shared<PointLight>(Vec3(0, 2.5f, 1.5f), Vec3(1.0f, 0.95f, 0.8f), 10.0f, 10.0f);
point->set_cast_shadows(true);
scene.add_light(point);
// Build BVH from scene meshes
std::vector<Triangle> triangles;
for (const auto &m : scene.get_all_meshes()) {
auto tris = mesh_to_triangles(m, m.get_material());
triangles.insert(triangles.end(), tris.begin(), tris.end());
}
Camera camera(Vec3(0.0f, 1.8f, 4.5f), Vec3(0.0f, 0.6f, 0.0f));
camera.set_perspective(45.0f, static_cast<Real>(fb_w) / static_cast<Real>(fb_h), 0.1f, 200.0f);
BVH bvh;
if (!bvh.build(triangles)) {
ARE_LOG_CRITICAL("phase5_test: BVH build failed");
return -1;
}
// Geometry cache: single source of truth
GeometryCache geom;
if (!geom.build_from_scene(scene)) {
ARE_LOG_CRITICAL("GeometryCache build failed");
return -1;
}
// Ray tracer config
RayTracingConfig rtc;
rtc.backend = RayTracingBackend::ARE_RT_BACKEND_CPU;
rtc.spp = 1;
rtc.max_depth = 4;
rtc.enable_gi = false;
rtc.enable_ao = false;
rtc.ao_samples = 1;
rtc.ao_radius = 1.0f;
// Provide triangle base offsets to rasterizer for primitive id output
rasterizer.set_triangle_base_provider([&](size_t mesh_index) {
return geom.get_mesh_triangle_base(mesh_index);
});
CPURayTracer tracer(rtc);
tracer.update_bvh(bvh);
// CPU ray tracer uses the same BVH (same triangle layout)
RayTracingConfig rtc;
rtc.backend = RayTracingBackend::ARE_RT_BACKEND_CPU;
rtc.spp = 1;
rtc.max_depth = 3;
rtc.enable_gi = false;
rtc.enable_ao = false;
rtc.ao_samples = 4;
rtc.ao_radius = 1.0f;
ARE_LOG_INFO("Controls:");
ARE_LOG_INFO(" 1: spp = 1");
ARE_LOG_INFO(" 2: spp = 16");
ARE_LOG_INFO(" A: toggle AO");
ARE_LOG_INFO(" G: toggle GI");
ARE_LOG_INFO(" ESC: exit");
CPURayTracer tracer(rtc);
tracer.update_bvh(geom.get_bvh());
bool request_render = true;
bool request_render = true;
while (!window.should_close()) {
std::cout << "RENDERING" << std::endl;
window.poll_events();
while (!window.should_close()) {
window.poll_events();
if (window.is_key_pressed(256)) {
window.set_should_close(true);
}
if (window.is_key_pressed(256)) {
window.set_should_close(true);
}
if (window.is_key_pressed(49)) { // 1
rtc.spp = 1;
tracer.set_config(rtc);
request_render = true;
}
if (window.is_key_pressed(50)) { // 2
rtc.spp = 16;
tracer.set_config(rtc);
request_render = true;
}
if (window.is_key_pressed(65)) { // A
rtc.enable_ao = !rtc.enable_ao;
tracer.set_config(rtc);
request_render = true;
}
if (window.is_key_pressed(71)) { // G
rtc.enable_gi = !rtc.enable_gi;
tracer.set_config(rtc);
request_render = true;
}
int new_fb_w = 0, new_fb_h = 0;
window.get_framebuffer_size(new_fb_w, new_fb_h);
if (new_fb_w <= 0 || new_fb_h <= 0) {
window.swap_buffers();
continue;
}
// Handle resize
int new_fb_w = 0;
int new_fb_h = 0;
window.get_framebuffer_size(new_fb_w, new_fb_h);
if (new_fb_w != fb_w || new_fb_h != fb_h) {
fb_w = new_fb_w;
fb_h = new_fb_h;
// If minimized / invalid size: keep window responsive, skip rendering
if (new_fb_w <= 0 || new_fb_h <= 0) {
window.swap_buffers();
continue;
}
rasterizer.resize(fb_w, fb_h);
glDeleteTextures(1, &output_tex);
output_tex = create_output_texture_rgba16f(fb_w, fb_h);
camera.set_aspect_ratio(static_cast<Real>(fb_w) / static_cast<Real>(fb_h));
if (new_fb_w != fb_w || new_fb_h != fb_h) {
fb_w = new_fb_w;
fb_h = new_fb_h;
request_render = true;
}
rasterizer.resize(fb_w, fb_h);
if (request_render) {
rasterizer.render_gbuffer(scene, camera);
tracer.render(scene, camera, &rasterizer.get_gbuffer(), output_tex);
request_render = false;
}
glDeleteTextures(1, &output_tex);
output_tex = create_output_texture_rgba16f(fb_w, fb_h);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, fb_w, fb_h);
glDisable(GL_DEPTH_TEST);
camera.set_aspect_ratio(static_cast<Real>(fb_w) / static_cast<Real>(fb_h));
display.use();
display.set_uniform("u_tex", 0);
request_render = true;
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, output_tex);
if (request_render) {
ARE_PROFILE_SCOPE("CPU Ray Trace");
tracer.render(scene, camera, &gbuffer, output_tex);
request_render = false;
}
glBindVertexArray(quad_vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
// Present
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, fb_w, fb_h);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, 0);
window.swap_buffers();
}
display.use();
display.set_uniform("u_tex", 0);
glDeleteTextures(1, &output_tex);
glDeleteVertexArrays(1, &quad_vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, output_tex);
Profiler::shutdown();
Logger::shutdown();
return 0;
glBindVertexArray(quad_vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
window.swap_buffers();
}
glDeleteTextures(1, &output_tex);
glDeleteVertexArrays(1, &quad_vao);
Profiler::print_results();
Profiler::shutdown();
Logger::shutdown();
return 0;
} catch (const std::exception& e) {
Logger::init(LogLevel::ARE_LOG_INFO);
ARE_LOG_CRITICAL(std::string("Unhandled exception: ") + e.what());
Logger::shutdown();
return -1;
}
}

View File

@ -6,12 +6,12 @@
#ifndef ARE_INCLUDE_ACCELERATION_BVH_H
#define ARE_INCLUDE_ACCELERATION_BVH_H
#include <are/core/types.h>
#include <are/acceleration/bvh_node.h>
#include <are/acceleration/bvh_builder.h>
#include <are/acceleration/bvh_node.h>
#include <are/core/types.h>
#include <are/geometry/triangle.h>
#include <are/raytracer/ray.h>
#include <are/raytracer/hit_record.h>
#include <are/raytracer/ray.h>
#include <vector>
namespace are {
@ -22,98 +22,112 @@ namespace are {
*/
class BVH {
public:
/**
* @brief Constructor
*/
BVH();
/**
* @brief Constructor
*/
BVH();
/**
* @brief Destructor
*/
~BVH();
/**
* @brief Destructor
*/
~BVH();
/**
* @brief Build BVH from triangle list
* @param triangles Triangle list
* @param config Build configuration
* @return true if build succeeded
*/
bool build(const std::vector<Triangle>& triangles,
const BVHBuildConfig& config = BVHBuildConfig());
/**
* @brief Build BVH from triangle list
* @param triangles Triangle list
* @param config Build configuration
* @return true if build succeeded
*/
bool build(const std::vector<Triangle> &triangles,
const BVHBuildConfig &config = BVHBuildConfig());
/**
* @brief Traverse BVH and find closest intersection
* @param ray Ray to trace
* @param hit Output hit record
* @return true if intersection found
*/
bool intersect(const Ray& ray, HitRecord& hit) const;
/**
* @brief Traverse BVH and find closest intersection
* @param ray Ray to trace
* @param hit Output hit record
* @return true if intersection found
*/
bool intersect(const Ray &ray, HitRecord &hit) const;
/**
* @brief Fast occlusion test (any hit)
* @param ray Ray to trace
* @param t_max Maximum t value
* @return true if any intersection found
*/
bool intersect_any(const Ray& ray, Real t_max) const;
/**
* @brief Fast occlusion test (any hit)
* @param ray Ray to trace
* @param t_max Maximum t value
* @return true if any intersection found
*/
bool intersect_any(const Ray &ray, Real t_max) const;
/**
* @brief Fast occlusion test (any hit), with ignored triangle id
* @param ray Ray to trace
* @param t_max Maximum t value
* @param ignore_triangle_index Triangle index to ignore (e.g. self primitive id)
* @return true if any intersection found (excluding ignored triangle)
*/
bool intersect_any(const Ray &ray, Real t_max, uint32_t ignore_triangle_index) const;
/**
* @brief Check if BVH is built
* @return true if built
*/
bool is_built() const { return !nodes_.empty(); }
/**
* @brief Check if BVH is built
* @return true if built
*/
bool is_built() const {
return !nodes_.empty();
}
/**
* @brief Get BVH nodes (for GPU upload)
* @return Node array
*/
const std::vector<BVHNode>& get_nodes() const { return nodes_; }
/**
* @brief Get BVH nodes (for GPU upload)
* @return Node array
*/
const std::vector<BVHNode> &get_nodes() const {
return nodes_;
}
/**
* @brief Get primitive indices
* @return Index array
*/
const std::vector<uint32_t>& get_primitive_indices() const {
return primitive_indices_;
}
/**
* @brief Get primitive indices
* @return Index array
*/
const std::vector<uint32_t> &get_primitive_indices() const {
return primitive_indices_;
}
/**
* @brief Get triangles
* @return Triangle array
*/
const std::vector<Triangle>& get_triangles() const { return triangles_; }
/**
* @brief Get triangles
* @return Triangle array
*/
const std::vector<Triangle> &get_triangles() const {
return triangles_;
}
/**
* @brief Get memory usage in bytes
* @return Memory usage
*/
size_t get_memory_usage() const;
/**
* @brief Get memory usage in bytes
* @return Memory usage
*/
size_t get_memory_usage() const;
/**
* @brief Clear BVH data
*/
void clear();
/**
* @brief Clear BVH data
*/
void clear();
private:
// Recursive traversal (kept for reference)
bool intersect_recursive(uint32_t node_index, const Ray& ray, HitRecord& hit) const;
bool intersect_any_recursive(uint32_t node_index, const Ray& ray, Real t_max) const;
// Optimized iterative traversal
bool intersect_iterative(const Ray& ray, HitRecord& hit) const;
bool intersect_any_iterative(const Ray& ray, Real t_max) const;
// Fast intersection helpers
inline bool intersect_aabb_fast(const AABB& bounds, const Ray& ray,
const Vec3& inv_dir, Real t_max,
Real& t_min_out, Real& t_max_out) const;
inline bool intersect_triangle_fast(const Triangle& triangle, const Ray& ray,
Real t_max, HitRecord& hit) const;
// Recursive traversal (kept for reference)
bool intersect_recursive(uint32_t node_index, const Ray &ray, HitRecord &hit) const;
bool intersect_any_recursive(uint32_t node_index, const Ray &ray, Real t_max) const;
std::vector<BVHNode> nodes_; ///< BVH nodes
std::vector<uint32_t> primitive_indices_; ///< Primitive index array
std::vector<Triangle> triangles_; ///< Triangle data
uint32_t root_index_; ///< Root node index
// Optimized iterative traversal
bool intersect_iterative(const Ray &ray, HitRecord &hit) const;
bool intersect_any_iterative(const Ray &ray, Real t_max) const;
// Fast intersection helpers
inline bool intersect_aabb_fast(const AABB &bounds, const Ray &ray,
const Vec3 &inv_dir, Real t_max,
Real &t_min_out, Real &t_max_out) const;
inline bool intersect_triangle_fast(const Triangle &triangle, const Ray &ray,
Real t_max, HitRecord &hit) const;
std::vector<BVHNode> nodes_; ///< BVH nodes
std::vector<uint32_t> primitive_indices_; ///< Primitive index array
std::vector<Triangle> triangles_; ///< Triangle data
uint32_t root_index_; ///< Root node index
};
} // namespace are

View File

@ -14,8 +14,14 @@ namespace are {
/**
* @class GBuffer
* @brief G-Buffer for deferred rendering
*
* Contains multiple render targets for position, normal, albedo, etc.
*
* Attachment layout:
* 0: position (RGB16F)
* 1: normal (RGB16F)
* 2: albedo+metallic (RGBA8)
* 3: roughness+ao (RG8)
* 4: primitive id (R32UI)
* Depth: depth texture (DEPTH_COMPONENT24)
*/
class GBuffer {
public:
@ -55,7 +61,7 @@ public:
/**
* @brief Bind texture for reading
* @param index Texture index (0=position, 1=normal, 2=albedo, etc.)
* @param index Texture index
* @param texture_unit Texture unit to bind to
*/
void bind_texture(int index, int texture_unit);
@ -66,6 +72,7 @@ public:
uint32_t get_albedo_texture() const { return albedo_texture_; }
uint32_t get_material_texture() const { return material_texture_; }
uint32_t get_depth_texture() const { return depth_texture_; }
uint32_t get_primitive_id_texture() const { return primitive_id_texture_; }
// Dimensions
int get_width() const { return width_; }
@ -73,6 +80,15 @@ public:
/**
* @brief Read pixel data from G-Buffer
*
* Index mapping:
* - 0: position (RGB16F) -> GL_RGB/GL_FLOAT
* - 1: normal (RGB16F) -> GL_RGB/GL_FLOAT
* - 2: albedo_metallic (RGBA8) -> GL_RGBA/GL_UNSIGNED_BYTE
* - 3: material (RG8) -> GL_RG/GL_UNSIGNED_BYTE
* - 4: depth (DEPTH_COMPONENT24) -> GL_DEPTH_COMPONENT/GL_FLOAT
* - 5: primitive id (R32UI) -> GL_RED_INTEGER/GL_UNSIGNED_INT
*
* @param index Buffer index
* @param data Output data pointer
*/
@ -84,17 +100,17 @@ private:
void create_framebuffer();
uint32_t fbo_; ///< Framebuffer object
uint32_t rbo_depth_; ///< Depth renderbuffer
uint32_t rbo_depth_; ///< Legacy depth renderbuffer (unused)
// G-Buffer textures
uint32_t position_texture_; ///< World position (RGB16F)
uint32_t normal_texture_; ///< World normal (RGB16F)
uint32_t albedo_texture_; ///< Albedo + Metallic (RGBA8)
uint32_t material_texture_; ///< Roughness + AO (RG8)
uint32_t depth_texture_; ///< Depth (R32F)
uint32_t position_texture_;
uint32_t normal_texture_;
uint32_t albedo_texture_;
uint32_t material_texture_;
uint32_t depth_texture_;
uint32_t primitive_id_texture_;
int width_; ///< Buffer width
int height_; ///< Buffer height
int width_;
int height_;
};
} // namespace are

View File

@ -7,12 +7,12 @@
#define ARE_INCLUDE_RASTERIZER_RASTERIZER_H
#include <are/core/types.h>
#include <are/core/config.h>
#include <memory>
#include <functional>
#include <string>
namespace are {
// Forward declarations
class GBuffer;
class ShaderProgram;
class SceneManager;
@ -20,67 +20,52 @@ class Camera;
class Mesh;
/**
* @class Rasterizer
* @brief OpenGL rasterization pipeline
*
* Renders scene geometry to G-Buffer using traditional rasterization.
* @struct RasterizerState
* @brief Rasterizer fixed-function state (configurable)
*/
struct RasterizerState {
bool enable_depth_test = true;
bool enable_cull_face = false;
uint32_t cull_face_mode = 0x0405; // GL_BACK
uint32_t front_face = 0x0901; // GL_CCW
};
class Rasterizer {
public:
/**
* @brief Constructor
* @param width Framebuffer width
* @param height Framebuffer height
*/
Rasterizer(int width, int height);
/**
* @brief Destructor
*/
~Rasterizer();
/**
* @brief Resize framebuffer
* @param width New width
* @param height New height
*/
void resize(int width, int height);
/**
* @brief Render scene to G-Buffer
* @param scene Scene manager
* @param camera Camera
*/
void render_gbuffer(const SceneManager& scene, const Camera& camera);
/**
* @brief Get G-Buffer
* @return G-Buffer reference
*/
GBuffer& get_gbuffer();
const GBuffer& get_gbuffer() const;
/**
* @brief Upload mesh data to GPU
* @param mesh Mesh to upload
*/
void upload_mesh(Mesh& mesh);
/**
* @brief Delete mesh GPU resources
* @param mesh Mesh to delete
*/
void delete_mesh(Mesh& mesh);
private:
void initialize_shaders(const std::string& shader_dir);
void set_triangle_base_provider(std::function<uint32_t(size_t)> provider);
/**
* @brief Set rasterizer fixed-function state
* @param state State
*/
void set_state(const RasterizerState& state);
private:
void setup_mesh_buffers(Mesh& mesh);
std::unique_ptr<GBuffer> gbuffer_; ///< G-Buffer
std::unique_ptr<ShaderProgram> gbuffer_shader_; ///< G-Buffer shader
int width_; ///< Framebuffer width
int height_; ///< Framebuffer height
std::unique_ptr<GBuffer> gbuffer_;
std::unique_ptr<ShaderProgram> gbuffer_shader_;
std::function<uint32_t(size_t)> triangle_base_provider_;
RasterizerState state_;
int width_;
int height_;
};
} // namespace are

View File

@ -12,73 +12,28 @@
namespace are {
/**
* @enum ShaderType
* @brief Shader stage types
*/
enum class ShaderType {
ARE_SHADER_VERTEX,
ARE_SHADER_FRAGMENT,
ARE_SHADER_COMPUTE
};
/**
* @class ShaderProgram
* @brief OpenGL shader program management
*/
class ShaderProgram {
public:
/**
* @brief Constructor
*/
ShaderProgram();
/**
* @brief Destructor
*/
~ShaderProgram();
/**
* @brief Load and compile shader from file
* @param type Shader type
* @param filepath Shader file path
* @return true if compilation succeeded
*/
bool load_shader(ShaderType type, const std::string& filepath);
/**
* @brief Compile shader from source string
* @param type Shader type
* @param source Shader source code
* @return true if compilation succeeded
*/
bool compile_shader(ShaderType type, const std::string& source);
/**
* @brief Link shader program
* @return true if linking succeeded
*/
bool link();
/**
* @brief Use this shader program
*/
void use() const;
/**
* @brief Check if program is valid
* @return true if valid
*/
bool is_valid() const { return program_ != 0 && linked_; }
/**
* @brief Get OpenGL program ID
* @return Program ID
*/
uint32_t get_program() const { return program_; }
// Uniform setters
void set_uniform(const std::string& name, int value);
void set_uniform(const std::string& name, uint32_t value); ///< NEW
void set_uniform(const std::string& name, float value);
void set_uniform(const std::string& name, const Vec2& value);
void set_uniform(const std::string& name, const Vec3& value);
@ -86,24 +41,18 @@ public:
void set_uniform(const std::string& name, const Mat3& value);
void set_uniform(const std::string& name, const Mat4& value);
/**
* @brief Get uniform location (cached)
* @param name Uniform name
* @return Uniform location (-1 if not found)
*/
int get_uniform_location(const std::string& name);
private:
bool check_compile_errors(uint32_t shader, ShaderType type);
bool check_link_errors();
uint32_t program_; ///< OpenGL program ID
uint32_t vertex_shader_; ///< Vertex shader ID
uint32_t fragment_shader_; ///< Fragment shader ID
uint32_t compute_shader_; ///< Compute shader ID
bool linked_; ///< Link status
std::unordered_map<std::string, int> uniform_cache_; ///< Uniform location cache
uint32_t program_;
uint32_t vertex_shader_;
uint32_t fragment_shader_;
uint32_t compute_shader_;
bool linked_;
std::unordered_map<std::string, int> uniform_cache_;
};
} // namespace are

View File

@ -89,7 +89,7 @@ private:
* @param max_distance Maximum distance
* @return true if in shadow
*/
bool is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance);
bool is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance, uint32_t ignore_triangle);
const BVH* bvh_; ///< BVH reference
const SceneManager* scene_; ///< Scene reference

View File

@ -0,0 +1,63 @@
/**
* @file geometry_cache.h
* @brief Frame-level geometry cache for consistent BVH and GBuffer primitive ids
*/
#ifndef ARE_INCLUDE_RENDERER_GEOMETRY_CACHE_H
#define ARE_INCLUDE_RENDERER_GEOMETRY_CACHE_H
#include <are/core/types.h>
#include <are/geometry/triangle.h>
#include <are/acceleration/bvh.h>
#include <are/scene/scene_manager.h>
#include <vector>
namespace are {
/**
* @class GeometryCache
* @brief Builds a global triangle list and BVH from a SceneManager snapshot.
*
* Provides a single source of truth for:
* - Global triangle array layout (used by BVH and RayTracer)
* - Mesh -> triangle base mapping (used by Rasterizer for primitive id output)
*/
class GeometryCache {
public:
/**
* @brief Build cache from scene
* @param scene Scene manager
* @param bvh_config BVH build config
* @return true if succeeded
*/
bool build_from_scene(const SceneManager& scene,
const BVHBuildConfig& bvh_config = BVHBuildConfig());
/**
* @brief Get BVH reference
* @return BVH
*/
const BVH& get_bvh() const { return bvh_; }
/**
* @brief Get global triangles
* @return Triangle array
*/
const std::vector<Triangle>& get_triangles() const { return triangles_; }
/**
* @brief Get triangle base for mesh by index into scene.get_all_meshes()
* @param mesh_index Mesh index in scene.get_all_meshes()
* @return Base triangle id
*/
uint32_t get_mesh_triangle_base(size_t mesh_index) const;
private:
std::vector<Triangle> triangles_;
std::vector<uint32_t> mesh_triangle_base_;
BVH bvh_;
};
} // namespace are
#endif // ARE_INCLUDE_RENDERER_GEOMETRY_CACHE_H

View File

@ -1,33 +1,26 @@
#version 430 core
// Inputs from vertex shader
in vec3 v_world_position;
in vec3 v_world_normal;
in vec2 v_texcoord;
in vec3 v_world_tangent;
flat in uint v_triangle_id_base;
// Material uniforms
uniform vec3 u_albedo;
uniform float u_metallic;
uniform float u_roughness;
// G-Buffer outputs
layout(location = 0) out vec3 g_position;
layout(location = 1) out vec3 g_normal;
layout(location = 2) out vec4 g_albedo_metallic;
layout(location = 3) out vec2 g_roughness_ao;
layout(location = 4) out uint g_primitive_id;
void main() {
// Output world position
g_position = v_world_position;
// Output normalized world normal
g_normal = normalize(v_world_normal);
// Output albedo (RGB) and metallic (A)
g_albedo_metallic = vec4(u_albedo, u_metallic);
// Output roughness (R) and ambient occlusion (G)
// AO is set to 1.0 by default (no occlusion)
g_roughness_ao = vec2(u_roughness, 1.0);
// Global primitive ID = mesh base + primitive id within draw call
g_primitive_id = v_triangle_id_base + uint(gl_PrimitiveID);
}

View File

@ -1,35 +1,29 @@
#version 430 core
// Vertex attributes
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec3 a_normal;
layout(location = 2) in vec2 a_texcoord;
layout(location = 3) in vec3 a_tangent;
// Uniforms
uniform mat4 u_model;
uniform mat4 u_view;
uniform mat4 u_projection;
uniform mat3 u_normal_matrix;
// Outputs to fragment shader
// NOTE: We store it as int uniform in C++ for simplicity; GLSL expects uint.
uniform uint u_triangle_id_base;
out vec3 v_world_position;
out vec3 v_world_normal;
out vec2 v_texcoord;
out vec3 v_world_tangent;
flat out uint v_triangle_id_base;
void main() {
// Transform position to world space
vec4 world_pos = u_model * vec4(a_position, 1.0);
v_world_position = world_pos.xyz;
// Transform normal and tangent to world space
v_world_normal = normalize(u_normal_matrix * a_normal);
v_world_tangent = normalize(u_normal_matrix * a_tangent);
// Pass through texture coordinates
v_texcoord = a_texcoord;
// Transform to clip space
v_triangle_id_base = u_triangle_id_base;
gl_Position = u_projection * u_view * world_pos;
}

View File

@ -3,6 +3,7 @@
* @brief Implementation of BVH class (optimized version)
*/
#include <stack>
#include <algorithm>
#include <are/acceleration/bvh.h>
#include <are/core/logger.h>
@ -72,6 +73,53 @@ bool BVH::intersect_any(const Ray &ray, Real t_max) const {
return intersect_any_iterative(ray, t_max);
}
bool BVH::intersect_any(const Ray& ray, Real t_max, uint32_t ignore_triangle_index) const {
ARE_PROFILE_FUNCTION();
if (!is_built()) {
return false;
}
// iterative traversal is safer than recursion for deep trees, but keep style consistent
std::stack<uint32_t> stack;
stack.push(root_index_);
while (!stack.empty()) {
uint32_t node_index = stack.top();
stack.pop();
const BVHNode& node = nodes_[node_index];
Real t0, t1;
if (!node.bounds_.intersect_ray(ray, t0, t1)) {
continue;
}
if (t0 > t_max) {
continue;
}
if (node.is_leaf()) {
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
if (prim_idx == ignore_triangle_index) {
continue;
}
if (prim_idx >= triangles_.size()) {
continue;
}
if (triangles_[prim_idx].intersect_fast(ray, t_max)) {
return true;
}
}
} else {
stack.push(node.left_child_);
stack.push(node.right_child_);
}
}
return false;
}
size_t BVH::get_memory_usage() const {
size_t total = 0;
total += nodes_.size() * sizeof(BVHNode);

View File

@ -18,47 +18,52 @@ GBuffer::GBuffer(int width, int height)
, albedo_texture_(0)
, material_texture_(0)
, depth_texture_(0)
, primitive_id_texture_(0)
, width_(width)
, height_(height) {
create_textures();
create_framebuffer();
ARE_LOG_INFO("GBuffer: Created " + std::to_string(width) + "x" + std::to_string(height));
}
GBuffer::~GBuffer() {
delete_textures();
if (rbo_depth_ != 0) {
glDeleteRenderbuffers(1, &rbo_depth_);
rbo_depth_ = 0;
}
if (fbo_ != 0) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = 0;
}
}
void GBuffer::resize(int width, int height) {
ARE_PROFILE_FUNCTION();
if (width == width_ && height == height_) {
return;
}
width_ = width;
height_ = height;
// Recreate textures and framebuffer
delete_textures();
if (rbo_depth_ != 0) {
glDeleteRenderbuffers(1, &rbo_depth_);
rbo_depth_ = 0;
}
if (fbo_ != 0) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = 0;
}
create_textures();
create_framebuffer();
ARE_LOG_INFO("GBuffer: Resized to " + std::to_string(width) + "x" + std::to_string(height));
}
void GBuffer::bind() {
@ -78,13 +83,14 @@ void GBuffer::clear() {
void GBuffer::bind_texture(int index, int texture_unit) {
glActiveTexture(GL_TEXTURE0 + texture_unit);
switch (index) {
case 0: glBindTexture(GL_TEXTURE_2D, position_texture_); break;
case 1: glBindTexture(GL_TEXTURE_2D, normal_texture_); break;
case 2: glBindTexture(GL_TEXTURE_2D, albedo_texture_); break;
case 3: glBindTexture(GL_TEXTURE_2D, material_texture_); break;
case 4: glBindTexture(GL_TEXTURE_2D, depth_texture_); break;
case 5: glBindTexture(GL_TEXTURE_2D, primitive_id_texture_); break;
default:
ARE_LOG_WARN("GBuffer: Invalid texture index " + std::to_string(index));
break;
@ -93,55 +99,61 @@ void GBuffer::bind_texture(int index, int texture_unit) {
void GBuffer::read_pixels(int index, void* data) {
ARE_PROFILE_FUNCTION();
bind();
GLenum attachment;
GLenum format;
GLenum type;
// Robust: read from texture object (not from FBO read buffer)
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
uint32_t tex = 0;
GLenum format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
switch (index) {
case 0: // Position
attachment = GL_COLOR_ATTACHMENT0;
case 0:
tex = position_texture_;
format = GL_RGB;
type = GL_FLOAT;
break;
case 1: // Normal
attachment = GL_COLOR_ATTACHMENT1;
case 1:
tex = normal_texture_;
format = GL_RGB;
type = GL_FLOAT;
break;
case 2: // Albedo
attachment = GL_COLOR_ATTACHMENT2;
case 2:
tex = albedo_texture_;
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
break;
case 3: // Material
attachment = GL_COLOR_ATTACHMENT3;
case 3:
tex = material_texture_;
format = GL_RG;
type = GL_UNSIGNED_BYTE;
break;
case 4: // Depth
attachment = GL_DEPTH_ATTACHMENT;
case 4:
tex = depth_texture_;
format = GL_DEPTH_COMPONENT;
type = GL_FLOAT;
break;
case 5:
tex = primitive_id_texture_;
format = GL_RED_INTEGER;
type = GL_UNSIGNED_INT;
break;
default:
ARE_LOG_ERROR("GBuffer: Invalid buffer index for read_pixels");
unbind();
return;
}
glReadBuffer(attachment);
glReadPixels(0, 0, width_, height_, format, type, data);
unbind();
glBindTexture(GL_TEXTURE_2D, tex);
glGetTexImage(GL_TEXTURE_2D, 0, format, type, data);
glBindTexture(GL_TEXTURE_2D, 0);
}
void GBuffer::create_textures() {
ARE_PROFILE_FUNCTION();
// Position texture (RGB16F)
glGenTextures(1, &position_texture_);
glBindTexture(GL_TEXTURE_2D, position_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width_, height_, 0, GL_RGB, GL_FLOAT, nullptr);
@ -149,8 +161,7 @@ void GBuffer::create_textures() {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Normal texture (RGB16F)
glGenTextures(1, &normal_texture_);
glBindTexture(GL_TEXTURE_2D, normal_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width_, height_, 0, GL_RGB, GL_FLOAT, nullptr);
@ -158,8 +169,7 @@ void GBuffer::create_textures() {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Albedo + Metallic texture (RGBA8)
glGenTextures(1, &albedo_texture_);
glBindTexture(GL_TEXTURE_2D, albedo_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width_, height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
@ -167,8 +177,7 @@ void GBuffer::create_textures() {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Roughness + AO texture (RG8)
glGenTextures(1, &material_texture_);
glBindTexture(GL_TEXTURE_2D, material_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width_, height_, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
@ -176,76 +185,70 @@ void GBuffer::create_textures() {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Depth texture (R32F)
glGenTextures(1, &depth_texture_);
glBindTexture(GL_TEXTURE_2D, depth_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width_, height_, 0, GL_RED, GL_FLOAT, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width_, height_, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glGenTextures(1, &primitive_id_texture_);
glBindTexture(GL_TEXTURE_2D, primitive_id_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32UI, width_, height_, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
}
void GBuffer::delete_textures() {
if (position_texture_ != 0) {
glDeleteTextures(1, &position_texture_);
position_texture_ = 0;
}
if (normal_texture_ != 0) {
glDeleteTextures(1, &normal_texture_);
normal_texture_ = 0;
}
if (albedo_texture_ != 0) {
glDeleteTextures(1, &albedo_texture_);
albedo_texture_ = 0;
}
if (material_texture_ != 0) {
glDeleteTextures(1, &material_texture_);
material_texture_ = 0;
}
if (depth_texture_ != 0) {
glDeleteTextures(1, &depth_texture_);
depth_texture_ = 0;
}
if (position_texture_ != 0) glDeleteTextures(1, &position_texture_);
if (normal_texture_ != 0) glDeleteTextures(1, &normal_texture_);
if (albedo_texture_ != 0) glDeleteTextures(1, &albedo_texture_);
if (material_texture_ != 0) glDeleteTextures(1, &material_texture_);
if (depth_texture_ != 0) glDeleteTextures(1, &depth_texture_);
if (primitive_id_texture_ != 0) glDeleteTextures(1, &primitive_id_texture_);
position_texture_ = 0;
normal_texture_ = 0;
albedo_texture_ = 0;
material_texture_ = 0;
depth_texture_ = 0;
primitive_id_texture_ = 0;
}
void GBuffer::create_framebuffer() {
ARE_PROFILE_FUNCTION();
// Create framebuffer
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Attach textures
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, position_texture_, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, normal_texture_, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, albedo_texture_, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, material_texture_, 0);
// Specify draw buffers
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4, GL_TEXTURE_2D, primitive_id_texture_, 0);
GLenum draw_buffers[] = {
GL_COLOR_ATTACHMENT0,
GL_COLOR_ATTACHMENT1,
GL_COLOR_ATTACHMENT2,
GL_COLOR_ATTACHMENT3
GL_COLOR_ATTACHMENT3,
GL_COLOR_ATTACHMENT4
};
glDrawBuffers(4, draw_buffers);
// Create depth renderbuffer
glGenRenderbuffers(1, &rbo_depth_);
glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth_);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_, height_);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth_);
// Check framebuffer completeness
glDrawBuffers(5, draw_buffers);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_texture_, 0);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
ARE_LOG_ERROR("GBuffer: Framebuffer is not complete! Status: " + std::to_string(status));
ARE_LOG_ERROR("GBuffer: Framebuffer incomplete. Status=" + std::to_string(status));
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

View File

@ -14,172 +14,131 @@
#include <are/core/logger.h>
#include <are/core/profiler.h>
#include <are/platform/gl_context.h>
#include <glad/glad.h>
#include <glm/gtc/matrix_inverse.hpp>
namespace are {
Rasterizer::Rasterizer(int width, int height)
: width_(width)
: gbuffer_(std::make_unique<GBuffer>(width, height))
, gbuffer_shader_(std::make_unique<ShaderProgram>())
, triangle_base_provider_(nullptr)
, state_()
, width_(width)
, height_(height) {
ARE_PROFILE_FUNCTION();
// Create G-Buffer
gbuffer_ = std::make_unique<GBuffer>(width, height);
// Create shader program
gbuffer_shader_ = std::make_unique<ShaderProgram>();
ARE_LOG_INFO("Rasterizer: Created " + std::to_string(width) + "x" + std::to_string(height));
}
Rasterizer::~Rasterizer() {
ARE_LOG_INFO("Rasterizer: Destroyed");
Rasterizer::~Rasterizer() = default;
void Rasterizer::set_state(const RasterizerState& state) {
state_ = state;
}
void Rasterizer::set_triangle_base_provider(std::function<uint32_t(size_t)> provider) {
triangle_base_provider_ = std::move(provider);
}
void Rasterizer::resize(int width, int height) {
ARE_PROFILE_FUNCTION();
if (width == width_ && height == height_) {
return;
}
if (width == width_ && height == height_) return;
width_ = width;
height_ = height;
if (gbuffer_) {
gbuffer_->resize(width, height);
}
ARE_LOG_INFO("Rasterizer: Resized to " + std::to_string(width) + "x" + std::to_string(height));
gbuffer_->resize(width_, height_);
}
void Rasterizer::render_gbuffer(const SceneManager& scene, const Camera& camera) {
ARE_PROFILE_FUNCTION();
if (!gbuffer_shader_ || !gbuffer_shader_->is_valid()) {
ARE_LOG_ERROR("Rasterizer: G-Buffer shader is not valid");
ARE_LOG_ERROR("Rasterizer: gbuffer shader not ready");
return;
}
// Bind G-Buffer for rendering
gbuffer_->bind();
// Clear buffers
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Enable depth testing
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Enable face culling
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
// Use G-Buffer shader
if (state_.enable_depth_test) {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
} else {
glDisable(GL_DEPTH_TEST);
}
if (state_.enable_cull_face) {
glEnable(GL_CULL_FACE);
glCullFace(static_cast<GLenum>(state_.cull_face_mode));
glFrontFace(static_cast<GLenum>(state_.front_face));
} else {
glDisable(GL_CULL_FACE);
}
gbuffer_shader_->use();
// Set view and projection matrices
gbuffer_shader_->set_uniform("u_view", camera.get_view_matrix());
gbuffer_shader_->set_uniform("u_projection", camera.get_projection_matrix());
// Render all meshes
const auto& meshes = scene.get_all_meshes();
const auto& materials = scene.get_all_materials();
for (const auto& mesh : meshes) {
if (mesh.is_empty() || !mesh.has_gpu_resources()) {
continue;
}
// Set model matrix (identity for now, can be extended with Transform)
Mat4 model_matrix = Mat4(1.0f);
gbuffer_shader_->set_uniform("u_model", model_matrix);
// Calculate normal matrix
Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(model_matrix)));
for (size_t mi = 0; mi < meshes.size(); ++mi) {
const auto& mesh = meshes[mi];
if (mesh.is_empty() || !mesh.has_gpu_resources()) continue;
Mat4 model = Mat4(1.0f);
gbuffer_shader_->set_uniform("u_model", model);
Mat3 normal_matrix = glm::inverseTranspose(Mat3(model));
gbuffer_shader_->set_uniform("u_normal_matrix", normal_matrix);
// Set material properties
MaterialHandle mat_handle = mesh.get_material();
if (mat_handle != are_invalid_handle && mat_handle <= materials.size()) {
const Material& material = materials[mat_handle - 1]; // Handle is 1-based
gbuffer_shader_->set_uniform("u_albedo", material.get_albedo());
gbuffer_shader_->set_uniform("u_metallic", material.get_metallic());
gbuffer_shader_->set_uniform("u_roughness", material.get_roughness());
uint32_t tri_base = triangle_base_provider_ ? triangle_base_provider_(mi) : 0u;
// IMPORTANT: u_triangle_id_base is uint in GLSL, must use glUniform1ui
gbuffer_shader_->set_uniform("u_triangle_id_base", tri_base);
const Material* mat = scene.get_material(mesh.get_material());
if (mat) {
gbuffer_shader_->set_uniform("u_albedo", mat->get_albedo());
gbuffer_shader_->set_uniform("u_metallic", mat->get_metallic());
gbuffer_shader_->set_uniform("u_roughness", mat->get_roughness());
} else {
// Default material
gbuffer_shader_->set_uniform("u_albedo", Vec3(0.8f, 0.8f, 0.8f));
gbuffer_shader_->set_uniform("u_albedo", Vec3(0.8f));
gbuffer_shader_->set_uniform("u_metallic", 0.0f);
gbuffer_shader_->set_uniform("u_roughness", 0.5f);
}
// Draw mesh
glBindVertexArray(mesh.get_vao());
glDrawElements(GL_TRIANGLES,
static_cast<GLsizei>(mesh.get_index_count()),
GL_UNSIGNED_INT,
nullptr);
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.get_index_count()), GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
}
// Disable states
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
// Unbind G-Buffer
gbuffer_->unbind();
ARE_GL_CHECK();
}
GBuffer& Rasterizer::get_gbuffer() {
return *gbuffer_;
}
const GBuffer& Rasterizer::get_gbuffer() const {
return *gbuffer_;
}
GBuffer& Rasterizer::get_gbuffer() { return *gbuffer_; }
const GBuffer& Rasterizer::get_gbuffer() const { return *gbuffer_; }
void Rasterizer::upload_mesh(Mesh& mesh) {
ARE_PROFILE_FUNCTION();
if (mesh.is_empty()) {
ARE_LOG_WARN("Rasterizer: Attempting to upload empty mesh");
ARE_LOG_WARN("Rasterizer: upload_mesh on empty mesh");
return;
}
// Delete existing GPU resources if any
if (mesh.has_gpu_resources()) {
delete_mesh(mesh);
}
if (mesh.has_gpu_resources()) delete_mesh(mesh);
setup_mesh_buffers(mesh);
ARE_LOG_DEBUG("Rasterizer: Uploaded mesh with " +
std::to_string(mesh.get_vertex_count()) + " vertices, " +
std::to_string(mesh.get_triangle_count()) + " triangles");
}
void Rasterizer::delete_mesh(Mesh& mesh) {
ARE_PROFILE_FUNCTION();
uint32_t vao = mesh.get_vao();
uint32_t vbo = mesh.get_vbo();
uint32_t ebo = mesh.get_ebo();
if (vao != 0) {
glDeleteVertexArrays(1, &vao);
}
if (vbo != 0) {
glDeleteBuffers(1, &vbo);
}
if (ebo != 0) {
glDeleteBuffers(1, &ebo);
}
if (vao) glDeleteVertexArrays(1, &vao);
if (vbo) glDeleteBuffers(1, &vbo);
if (ebo) glDeleteBuffers(1, &ebo);
mesh.set_vao(0);
mesh.set_vbo(0);
mesh.set_ebo(0);
@ -187,94 +146,51 @@ void Rasterizer::delete_mesh(Mesh& mesh) {
void Rasterizer::initialize_shaders(const std::string& shader_dir) {
ARE_PROFILE_FUNCTION();
if (!gbuffer_shader_) {
gbuffer_shader_ = std::make_unique<ShaderProgram>();
}
std::string vert_path = shader_dir + "gbuffer/gbuffer.vert";
std::string frag_path = shader_dir + "gbuffer/gbuffer.frag";
bool success = true;
if (!gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_VERTEX, vert_path)) {
ARE_LOG_ERROR("Rasterizer: Failed to load vertex shader: " + vert_path);
success = false;
}
if (!gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_FRAGMENT, frag_path)) {
ARE_LOG_ERROR("Rasterizer: Failed to load fragment shader: " + frag_path);
success = false;
}
if (success && !gbuffer_shader_->link()) {
ARE_LOG_ERROR("Rasterizer: Failed to link G-Buffer shader program");
success = false;
}
if (success) {
ARE_LOG_INFO("Rasterizer: Shaders initialized successfully");
bool ok = true;
ok &= gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_VERTEX, shader_dir + "gbuffer/gbuffer.vert");
ok &= gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_FRAGMENT, shader_dir + "gbuffer/gbuffer.frag");
ok &= gbuffer_shader_->link();
if (!ok) {
ARE_LOG_ERROR("Rasterizer: Failed to init gbuffer shaders");
}
}
void Rasterizer::setup_mesh_buffers(Mesh& mesh) {
ARE_PROFILE_FUNCTION();
uint32_t vao, vbo, ebo;
// Create VAO
uint32_t vao = 0, vbo = 0, ebo = 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Create VBO
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER,
mesh.get_vertex_count() * sizeof(Vertex),
mesh.get_vertices().data(),
GL_STATIC_DRAW);
// Create EBO
glBufferData(GL_ARRAY_BUFFER, mesh.get_vertex_count() * sizeof(Vertex), mesh.get_vertices().data(), GL_STATIC_DRAW);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
mesh.get_index_count() * sizeof(uint32_t),
mesh.get_indices().data(),
GL_STATIC_DRAW);
// Setup vertex attributes
// Position (location = 0)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.get_index_count() * sizeof(uint32_t), mesh.get_indices().data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<void*>(get_position_offset()));
// Normal (location = 1)
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<void*>(get_normal_offset()));
// Texcoord (location = 2)
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<void*>(get_texcoord_offset()));
// Tangent (location = 3)
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE,
sizeof(Vertex),
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<void*>(get_tangent_offset()));
// Unbind VAO
glBindVertexArray(0);
// Store handles in mesh
mesh.set_vao(vao);
mesh.set_vbo(vbo);
mesh.set_ebo(ebo);
ARE_GL_CHECK();
}

View File

@ -155,8 +155,12 @@ void ShaderProgram::use() const {
}
}
void ShaderProgram::set_uniform(const std::string& name, uint32_t value) {
glUniform1ui(get_uniform_location(name), value);
}
void ShaderProgram::set_uniform(const std::string& name, int value) {
glUniform1i(get_uniform_location(name), value);
glUniform1ui(get_uniform_location(name), value);
}
void ShaderProgram::set_uniform(const std::string& name, float value) {

View File

@ -1,14 +1,12 @@
/**
* @file cpu_raytracer.cpp
* @brief Implementation of CPURayTracer
* @brief CPU hybrid ray tracer (GBuffer-driven) with geometric normal offset
*/
#include <are/raytracer/cpu_raytracer.h>
#include <are/acceleration/bvh.h>
#include <are/scene/scene_manager.h>
#include <are/scene/camera.h>
#include <are/scene/material.h>
#include <are/scene/light.h>
#include <are/scene/directional_light.h>
#include <are/scene/point_light.h>
@ -26,34 +24,37 @@
#include <algorithm>
#include <limits>
#include <stdexcept>
// #ifdef ARE_USE_OPENMP
// #include <omp.h>
// #endif
#include <vector>
namespace are {
namespace {
/**
* @brief Apply simple Reinhard tonemapping.
* @param c HDR color
* @param exposure Exposure value
* @return LDR color in [0,1]
*/
inline Real compute_ray_epsilon(const Vec3& p) {
Real s = std::max({std::abs(p.x), std::abs(p.y), std::abs(p.z), 1.0f});
return 1e-4f * s;
}
inline Vec3 offset_ray_origin(const Vec3& p, const Vec3& ng) {
Real eps = compute_ray_epsilon(p);
return p + ng * (eps * 4.0f);
}
inline Vec3 tonemap_reinhard(const Vec3& c, Real exposure) {
Vec3 x = c * exposure;
return x / (Vec3(1.0f) + x);
}
/**
* @brief Offset ray origin to reduce self-intersection.
* @param p Hit position
* @param n Shading normal
* @return Offset position
*/
inline Vec3 offset_ray_origin(const Vec3& p, const Vec3& n) {
return p + n * (are_epsilon * 10.0f);
inline Vec3 decode_albedo_from_rgba8(uint8_t r, uint8_t g, uint8_t b) {
return Vec3(r, g, b) / 255.0f;
}
inline Real decode_01_from_u8(uint8_t v) {
return static_cast<Real>(v) / 255.0f;
}
inline bool finite_vec3(const Vec3& v) {
return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z);
}
} // namespace
@ -78,6 +79,7 @@ void CPURayTracer::render(const SceneManager& scene,
const GBuffer* gbuffer,
uint32_t output_texture) {
ARE_PROFILE_FUNCTION();
(void)camera;
if (!bvh_ || !bvh_->is_built()) {
ARE_LOG_ERROR("CPURayTracer: BVH is null or not built");
@ -85,8 +87,8 @@ void CPURayTracer::render(const SceneManager& scene,
}
if (!gbuffer) {
ARE_LOG_CRITICAL("CPURayTracer: GBuffer is null, cannot infer render resolution");
throw std::runtime_error("CPURayTracer requires a valid GBuffer for resolution");
ARE_LOG_CRITICAL("CPURayTracer: GBuffer is null (hybrid requires it)");
throw std::runtime_error("CPURayTracer requires GBuffer in hybrid mode");
}
if (output_texture == 0) {
@ -99,104 +101,142 @@ void CPURayTracer::render(const SceneManager& scene,
height_ = gbuffer->get_height();
if (width_ <= 0 || height_ <= 0) {
ARE_LOG_ERROR("CPURayTracer: Invalid render resolution");
ARE_LOG_ERROR("CPURayTracer: Invalid resolution");
return;
}
std::vector<Vec3> pos(static_cast<size_t>(width_ * height_));
std::vector<Vec3> nrm(static_cast<size_t>(width_ * height_));
std::vector<uint8_t> albedo_metallic(static_cast<size_t>(width_ * height_ * 4));
std::vector<uint8_t> rough_ao(static_cast<size_t>(width_ * height_ * 2));
std::vector<Real> depth(static_cast<size_t>(width_ * height_));
std::vector<uint32_t> prim_id(static_cast<size_t>(width_ * height_));
const_cast<GBuffer *>(gbuffer)->read_pixels(0, pos.data());
const_cast<GBuffer *>(gbuffer)->read_pixels(1, nrm.data());
const_cast<GBuffer *>(gbuffer)->read_pixels(2, albedo_metallic.data());
const_cast<GBuffer *>(gbuffer)->read_pixels(3, rough_ao.data());
const_cast<GBuffer *>(gbuffer)->read_pixels(4, depth.data());
const_cast<GBuffer *>(gbuffer)->read_pixels(5, prim_id.data());
framebuffer_.assign(static_cast<size_t>(width_ * height_), Vec3(0.0f));
const int spp = std::max(1, config_.spp);
const int max_depth = std::max(1, config_.max_depth);
const auto& triangles = bvh_->get_triangles();
framebuffer_.assign(static_cast<size_t>(width_ * height_), Vec4(0.0f));
// #ifdef ARE_USE_OPENMP
// #pragma omp parallel for schedule(dynamic, 1)
// #endif
for (int y = 0; y < height_; ++y) {
RandomGenerator& rng = get_thread_random();
for (int x = 0; x < width_; ++x) {
Vec3 hdr(0.0f);
const size_t idx = static_cast<size_t>(y * width_ + x);
for (int s = 0; s < spp; ++s) {
Real u = (static_cast<Real>(x) + rng.random_float()) / static_cast<Real>(width_);
Real v = (static_cast<Real>(y) + rng.random_float()) / static_cast<Real>(height_);
Vec3 origin;
Vec3 direction;
camera.generate_ray(u, v, origin, direction);
Ray ray(origin, direction, are_epsilon, 1e30f);
hdr += trace_ray(ray, max_depth);
// Depth validity
if (!(depth[idx] > 0.0f && depth[idx] < 0.999999f)) {
framebuffer_[idx] = Vec3(0.0f);
continue;
}
hdr /= static_cast<Real>(spp);
Vec3 P = pos[idx];
Vec3 Ns = glm::normalize(nrm[idx]);
// Phase 5: tonemap in tracer for standalone output
Vec3 ldr = tonemap_reinhard(hdr, 1.0f);
framebuffer_[static_cast<size_t>(y * width_ + x)] = Vec4(ldr, 1.0f);
if (!finite_vec3(P) || !finite_vec3(Ns) || glm::length(Ns) < 0.1f) {
framebuffer_[idx] = Vec3(0.0f);
continue;
}
// Geometric normal from primitive id
Vec3 Ng = Ns;
uint32_t pid = prim_id[idx];
if (pid < triangles.size()) {
Ng = triangles[pid].normal();
}
const uint8_t* am = &albedo_metallic[idx * 4];
Vec3 albedo = decode_albedo_from_rgba8(am[0], am[1], am[2]);
(void)decode_01_from_u8(am[3]);
const uint8_t* ra = &rough_ao[idx * 2];
(void)decode_01_from_u8(ra[0]);
Real ao_gbuffer = decode_01_from_u8(ra[1]);
Vec3 accum(0.0f);
for (int s = 0; s < spp; ++s) {
HitRecord surf;
surf.position_ = P;
surf.normal_ = Ns;
surf.t_ = 1.0f;
surf.material_ = are_invalid_handle;
// Direct lighting (shadow uses robust epsilon)
Vec3 direct = compute_direct_lighting(surf);
// AO
Real ao = 1.0f;
if (config_.enable_ao) {
ao = compute_ambient_occlusion(surf);
}
ao *= ao_gbuffer;
// GI (simplified)
Vec3 gi(0.0f);
if (config_.enable_gi && max_depth > 1) {
Vec3 bounce_dir = rng.random_cosine_direction(Ns);
Vec3 origin = offset_ray_origin(P, Ng);
Real eps = compute_ray_epsilon(P);
Ray bounce(origin, bounce_dir, eps * 4.0f, 1e30f);
gi = trace_ray(bounce, max_depth - 1);
}
Vec3 c = albedo * direct * ao + albedo * gi;
accum += c;
}
Vec3 hdr = accum / static_cast<Real>(spp);
framebuffer_[idx] = tonemap_reinhard(hdr, 1.0f);
}
}
// Upload to output texture (recommended internal format: GL_RGBA16F)
std::vector<Vec4> rgba(static_cast<size_t>(width_ * height_));
for (size_t i = 0; i < rgba.size(); ++i) {
rgba[i] = Vec4(framebuffer_[i], 1.0f);
}
glBindTexture(GL_TEXTURE_2D, output_texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_FLOAT, framebuffer_.data());
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_FLOAT, rgba.data());
glBindTexture(GL_TEXTURE_2D, 0);
}
Vec3 CPURayTracer::trace_ray(const Ray& ray, int depth) {
ARE_PROFILE_FUNCTION();
if (depth <= 0) {
return Vec3(0.0f);
}
HitRecord hit;
if (!bvh_->intersect(ray, hit)) {
// Simple sky
Vec3 unit_dir = glm::normalize(ray.direction_);
Real t = 0.5f * (unit_dir.y + 1.0f);
return lerp(Vec3(1.0f), Vec3(0.5f, 0.7f, 1.0f), t);
return Vec3(0.0f);
}
return shade(hit, ray, depth);
}
Vec3 CPURayTracer::shade(const HitRecord& hit, const Ray& ray, int depth) {
Vec3 albedo(0.8f);
if (scene_) {
const Material* mat = scene_->get_material(hit.material_);
if (mat) {
albedo = mat->get_albedo();
}
Vec3 Ng = hit.normal_;
if (hit.triangle_index_ < bvh_->get_triangles().size()) {
Ng = bvh_->get_triangles()[hit.triangle_index_].normal();
}
Vec3 direct = compute_direct_lighting(hit);
Real ao = 1.0f;
if (config_.enable_ao) {
ao = compute_ambient_occlusion(hit);
}
RandomGenerator& rng = get_thread_random();
Vec3 dir = rng.random_cosine_direction(hit.normal_);
Vec3 origin = offset_ray_origin(hit.position_, Ng);
Real eps = compute_ray_epsilon(hit.position_);
// Direct term (Lambert)
Vec3 Lo = albedo * direct * ao;
// Diffuse GI (cosine-weighted)
if (config_.enable_gi && depth > 1) {
RandomGenerator& rng = get_thread_random();
Vec3 bounce_dir = rng.random_cosine_direction(hit.normal_);
Ray bounce_ray(offset_ray_origin(hit.position_, hit.normal_), bounce_dir, are_epsilon, 1e30f);
Vec3 Li = trace_ray(bounce_ray, depth - 1);
Lo += albedo * Li;
}
(void)ray;
return Lo;
Ray bounce(origin, dir, eps * 4.0f, 1e30f);
return trace_ray(bounce, depth - 1);
}
Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
ARE_PROFILE_FUNCTION();
Vec3 lighting(0.0f);
if (!scene_) {
return lighting;
@ -204,15 +244,13 @@ Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
const auto& lights = scene_->get_all_lights();
for (const auto& light_ptr : lights) {
if (!light_ptr) {
continue;
}
if (!light_ptr) continue;
Vec3 L(0.0f);
Real max_distance = 1e30f;
Real attenuation = 1.0f;
const LightType type = light_ptr->get_type();
LightType type = light_ptr->get_type();
if (type == LightType::ARE_LIGHT_DIRECTIONAL) {
const auto* dl = static_cast<const DirectionalLight*>(light_ptr.get());
@ -221,12 +259,8 @@ Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
const auto* pl = static_cast<const PointLight*>(light_ptr.get());
Vec3 to_light = pl->get_position() - hit.position_;
Real dist = glm::length(to_light);
if (dist < are_epsilon) {
continue;
}
if (!pl->affects_point(hit.position_)) {
continue;
}
if (dist < are_epsilon) continue;
if (!pl->affects_point(hit.position_)) continue;
L = to_light / dist;
max_distance = dist;
attenuation = pl->calculate_attenuation(dist);
@ -234,32 +268,27 @@ Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
const auto* sl = static_cast<const SpotLight*>(light_ptr.get());
Vec3 to_light = sl->get_position() - hit.position_;
Real dist = glm::length(to_light);
if (dist < are_epsilon) {
continue;
}
if (!sl->affects_point(hit.position_)) {
continue;
}
if (dist < are_epsilon) continue;
if (!sl->affects_point(hit.position_)) continue;
L = to_light / dist;
max_distance = dist;
Vec3 light_to_point = glm::normalize(hit.position_ - sl->get_position());
Real spot = sl->calculate_spot_factor(light_to_point);
attenuation *= spot;
attenuation *= sl->calculate_spot_factor(light_to_point);
} else {
continue;
}
if (light_ptr->get_cast_shadows()) {
if (is_in_shadow(offset_ray_origin(hit.position_, hit.normal_), L, max_distance)) {
Real eps = compute_ray_epsilon(hit.position_);
Vec3 origin = hit.position_ + hit.normal_ * (eps * 4.0f);
Ray shadow(origin, L, eps * 4.0f, max_distance);
if (bvh_ && bvh_->intersect_any(shadow, max_distance)) {
continue;
}
}
Real n_dot_l = std::max(0.0f, glm::dot(hit.normal_, L));
if (n_dot_l <= 0.0f) {
continue;
}
if (n_dot_l <= 0.0f) continue;
Vec3 radiance = light_ptr->get_color() * light_ptr->get_intensity();
lighting += radiance * n_dot_l * attenuation;
@ -269,8 +298,6 @@ Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
}
Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
ARE_PROFILE_FUNCTION();
if (!bvh_) {
return 1.0f;
}
@ -283,8 +310,9 @@ Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
int occluded = 0;
for (int i = 0; i < ao_samples; ++i) {
Vec3 dir = rng.random_in_hemisphere(hit.normal_);
Ray ao_ray(offset_ray_origin(hit.position_, hit.normal_), dir, are_epsilon, radius);
Real eps = compute_ray_epsilon(hit.position_);
Vec3 origin = hit.position_ + hit.normal_ * (eps * 4.0f);
Ray ao_ray(origin, dir, eps * 4.0f, radius);
if (bvh_->intersect_any(ao_ray, radius)) {
occluded++;
}
@ -294,16 +322,13 @@ Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
return 1.0f - occ;
}
bool CPURayTracer::is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance) {
ARE_PROFILE_FUNCTION();
if (!bvh_) {
return false;
}
bool CPURayTracer::is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance, uint32_t ignore_triangle) {
if (!bvh_) return false;
Real t_max = (max_distance > 0.0f) ? max_distance : 1e30f;
Ray shadow_ray(origin, direction, are_epsilon, t_max);
return bvh_->intersect_any(shadow_ray, t_max);
Real eps = compute_ray_epsilon(origin);
Ray shadow(origin, direction, eps * 4.0f, t_max);
return bvh_->intersect_any(shadow, t_max, ignore_triangle);
}
} // namespace are

View File

@ -0,0 +1,85 @@
/**
* @file geometry_cache.cpp
* @brief Implementation of GeometryCache
*/
#include <are/renderer/geometry_cache.h>
#include <are/core/logger.h>
#include <are/core/profiler.h>
namespace are {
static std::vector<Triangle> mesh_to_triangles(const Mesh& mesh) {
std::vector<Triangle> tris;
if (mesh.is_empty()) {
return tris;
}
const auto& v = mesh.get_vertices();
const auto& idx = mesh.get_indices();
if (idx.size() % 3 != 0) {
ARE_LOG_ERROR("GeometryCache: Mesh index count is not multiple of 3");
return tris;
}
MaterialHandle material = mesh.get_material();
tris.reserve(idx.size() / 3);
for (size_t i = 0; i < idx.size(); i += 3) {
uint32_t i0 = idx[i + 0];
uint32_t i1 = idx[i + 1];
uint32_t i2 = idx[i + 2];
if (i0 >= v.size() || i1 >= v.size() || i2 >= v.size()) {
continue;
}
tris.emplace_back(v[i0], v[i1], v[i2], material);
}
return tris;
}
bool GeometryCache::build_from_scene(const SceneManager& scene, const BVHBuildConfig& bvh_config) {
ARE_PROFILE_FUNCTION();
triangles_.clear();
mesh_triangle_base_.clear();
const auto& meshes = scene.get_all_meshes();
mesh_triangle_base_.reserve(meshes.size());
uint32_t base = 0;
for (size_t mi = 0; mi < meshes.size(); ++mi) {
mesh_triangle_base_.push_back(base);
auto tris = mesh_to_triangles(meshes[mi]);
base += static_cast<uint32_t>(tris.size());
triangles_.insert(triangles_.end(), tris.begin(), tris.end());
}
if (triangles_.empty()) {
ARE_LOG_WARN("GeometryCache: No triangles in scene");
return false;
}
if (!bvh_.build(triangles_, bvh_config)) {
ARE_LOG_ERROR("GeometryCache: BVH build failed");
return false;
}
ARE_LOG_INFO("GeometryCache: Built triangles=" + std::to_string(triangles_.size()) +
", meshes=" + std::to_string(meshes.size()));
return true;
}
uint32_t GeometryCache::get_mesh_triangle_base(size_t mesh_index) const {
if (mesh_index >= mesh_triangle_base_.size()) {
return 0;
}
return mesh_triangle_base_[mesh_index];
}
} // namespace are