diff --git a/examples/04_phase5_test/main.cpp b/examples/04_phase5_test/main.cpp index 452de16..31202c2 100644 --- a/examples/04_phase5_test/main.cpp +++ b/examples/04_phase5_test/main.cpp @@ -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 -#include #include #include -#include #include +#include -#include #include +#include #include -#include -#include -#include -#include -#include -#include +#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include "../lib/glad/glad/glad.h" -#include +#include +#include #include #include -#include -#include 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 mesh_to_triangles(const Mesh &mesh, MaterialHandle material) { - std::vector 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 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 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 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 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 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 tri_i = { 0, 1, 2 }; + Mesh tri_mesh(tri_v, tri_i, mat_h); + tri_mesh.compute_tangents(); - // A tilted triangle - std::vector 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 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(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(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(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(fb_w) / static_cast(fb_h), 0.1f, 200.0f); + auto point = std::make_shared(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 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(fb_w) / static_cast(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(fb_w) / static_cast(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(fb_w) / static_cast(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; + } } diff --git a/include/are/acceleration/bvh.h b/include/are/acceleration/bvh.h index e1d2f27..f5b45bd 100644 --- a/include/are/acceleration/bvh.h +++ b/include/are/acceleration/bvh.h @@ -6,12 +6,12 @@ #ifndef ARE_INCLUDE_ACCELERATION_BVH_H #define ARE_INCLUDE_ACCELERATION_BVH_H -#include -#include #include +#include +#include #include -#include #include +#include #include 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& 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 &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& get_nodes() const { return nodes_; } + /** + * @brief Get BVH nodes (for GPU upload) + * @return Node array + */ + const std::vector &get_nodes() const { + return nodes_; + } - /** - * @brief Get primitive indices - * @return Index array - */ - const std::vector& get_primitive_indices() const { - return primitive_indices_; - } + /** + * @brief Get primitive indices + * @return Index array + */ + const std::vector &get_primitive_indices() const { + return primitive_indices_; + } - /** - * @brief Get triangles - * @return Triangle array - */ - const std::vector& get_triangles() const { return triangles_; } + /** + * @brief Get triangles + * @return Triangle array + */ + const std::vector &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 nodes_; ///< BVH nodes - std::vector primitive_indices_; ///< Primitive index array - std::vector 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 nodes_; ///< BVH nodes + std::vector primitive_indices_; ///< Primitive index array + std::vector triangles_; ///< Triangle data + uint32_t root_index_; ///< Root node index }; } // namespace are diff --git a/include/are/rasterizer/gbuffer.h b/include/are/rasterizer/gbuffer.h index f99553f..ac5081f 100644 --- a/include/are/rasterizer/gbuffer.h +++ b/include/are/rasterizer/gbuffer.h @@ -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 diff --git a/include/are/rasterizer/rasterizer.h b/include/are/rasterizer/rasterizer.h index 1cdf5e0..8117fa3 100644 --- a/include/are/rasterizer/rasterizer.h +++ b/include/are/rasterizer/rasterizer.h @@ -7,12 +7,12 @@ #define ARE_INCLUDE_RASTERIZER_RASTERIZER_H #include -#include #include +#include +#include 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 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_; ///< G-Buffer - std::unique_ptr gbuffer_shader_; ///< G-Buffer shader - - int width_; ///< Framebuffer width - int height_; ///< Framebuffer height + std::unique_ptr gbuffer_; + std::unique_ptr gbuffer_shader_; + + std::function triangle_base_provider_; + RasterizerState state_; + + int width_; + int height_; }; } // namespace are diff --git a/include/are/rasterizer/shader_program.h b/include/are/rasterizer/shader_program.h index 4ca4bf9..9e5f118 100644 --- a/include/are/rasterizer/shader_program.h +++ b/include/are/rasterizer/shader_program.h @@ -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 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 uniform_cache_; }; } // namespace are diff --git a/include/are/raytracer/cpu_raytracer.h b/include/are/raytracer/cpu_raytracer.h index 86dd6b1..883333a 100644 --- a/include/are/raytracer/cpu_raytracer.h +++ b/include/are/raytracer/cpu_raytracer.h @@ -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 diff --git a/include/are/renderer/geometry_cache.h b/include/are/renderer/geometry_cache.h new file mode 100644 index 0000000..1a0c941 --- /dev/null +++ b/include/are/renderer/geometry_cache.h @@ -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 +#include +#include +#include +#include + +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& 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 triangles_; + std::vector mesh_triangle_base_; + BVH bvh_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_RENDERER_GEOMETRY_CACHE_H diff --git a/shaders/gbuffer/gbuffer.frag b/shaders/gbuffer/gbuffer.frag index f8dbe90..93dad07 100644 --- a/shaders/gbuffer/gbuffer.frag +++ b/shaders/gbuffer/gbuffer.frag @@ -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); } diff --git a/shaders/gbuffer/gbuffer.vert b/shaders/gbuffer/gbuffer.vert index a280ef5..edb994a 100644 --- a/shaders/gbuffer/gbuffer.vert +++ b/shaders/gbuffer/gbuffer.vert @@ -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; } diff --git a/src/acceleration/bvh.cpp b/src/acceleration/bvh.cpp index 59c3a68..243e6f7 100644 --- a/src/acceleration/bvh.cpp +++ b/src/acceleration/bvh.cpp @@ -3,6 +3,7 @@ * @brief Implementation of BVH class (optimized version) */ +#include #include #include #include @@ -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 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); diff --git a/src/rasterizer/gbuffer.cpp b/src/rasterizer/gbuffer.cpp index 5d989a9..acabdd6 100644 --- a/src/rasterizer/gbuffer.cpp +++ b/src/rasterizer/gbuffer.cpp @@ -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); } diff --git a/src/rasterizer/rasterizer.cpp b/src/rasterizer/rasterizer.cpp index 3407bad..92782e5 100644 --- a/src/rasterizer/rasterizer.cpp +++ b/src/rasterizer/rasterizer.cpp @@ -14,172 +14,131 @@ #include #include #include + #include #include namespace are { Rasterizer::Rasterizer(int width, int height) - : width_(width) + : gbuffer_(std::make_unique(width, height)) + , gbuffer_shader_(std::make_unique()) + , triangle_base_provider_(nullptr) + , state_() + , width_(width) , height_(height) { - ARE_PROFILE_FUNCTION(); - - // Create G-Buffer - gbuffer_ = std::make_unique(width, height); - - // Create shader program - gbuffer_shader_ = std::make_unique(); - - 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 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(state_.cull_face_mode)); + glFrontFace(static_cast(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(mesh.get_index_count()), - GL_UNSIGNED_INT, - nullptr); + glDrawElements(GL_TRIANGLES, static_cast(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(); - } - - 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(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(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(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(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(); } diff --git a/src/rasterizer/shader_program.cpp b/src/rasterizer/shader_program.cpp index 612ed75..04194b9 100644 --- a/src/rasterizer/shader_program.cpp +++ b/src/rasterizer/shader_program.cpp @@ -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) { diff --git a/src/raytracer/cpu_raytracer.cpp b/src/raytracer/cpu_raytracer.cpp index 3de3b79..f6bdc18 100644 --- a/src/raytracer/cpu_raytracer.cpp +++ b/src/raytracer/cpu_raytracer.cpp @@ -1,14 +1,12 @@ /** * @file cpu_raytracer.cpp - * @brief Implementation of CPURayTracer + * @brief CPU hybrid ray tracer (GBuffer-driven) with geometric normal offset */ #include #include #include -#include -#include #include #include #include @@ -26,34 +24,37 @@ #include #include #include - -// #ifdef ARE_USE_OPENMP -// #include -// #endif +#include 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(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 pos(static_cast(width_ * height_)); + std::vector nrm(static_cast(width_ * height_)); + std::vector albedo_metallic(static_cast(width_ * height_ * 4)); + std::vector rough_ao(static_cast(width_ * height_ * 2)); + std::vector depth(static_cast(width_ * height_)); + std::vector prim_id(static_cast(width_ * height_)); + + const_cast(gbuffer)->read_pixels(0, pos.data()); + const_cast(gbuffer)->read_pixels(1, nrm.data()); + const_cast(gbuffer)->read_pixels(2, albedo_metallic.data()); + const_cast(gbuffer)->read_pixels(3, rough_ao.data()); + const_cast(gbuffer)->read_pixels(4, depth.data()); + const_cast(gbuffer)->read_pixels(5, prim_id.data()); + + framebuffer_.assign(static_cast(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(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(y * width_ + x); - for (int s = 0; s < spp; ++s) { - Real u = (static_cast(x) + rng.random_float()) / static_cast(width_); - Real v = (static_cast(y) + rng.random_float()) / static_cast(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(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(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(spp); + framebuffer_[idx] = tonemap_reinhard(hdr, 1.0f); } } - // Upload to output texture (recommended internal format: GL_RGBA16F) + std::vector rgba(static_cast(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(light_ptr.get()); @@ -221,12 +259,8 @@ Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) { const auto* pl = static_cast(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(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 diff --git a/src/renderer/geometry_cache.cpp b/src/renderer/geometry_cache.cpp new file mode 100644 index 0000000..a2a1d45 --- /dev/null +++ b/src/renderer/geometry_cache.cpp @@ -0,0 +1,85 @@ +/** + * @file geometry_cache.cpp + * @brief Implementation of GeometryCache + */ + +#include +#include +#include + +namespace are { + +static std::vector mesh_to_triangles(const Mesh& mesh) { + std::vector 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(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