diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ed5c3f..d2600df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,6 +185,7 @@ if(ARE_BUILD_EXAMPLES) add_subdirectory(examples/02_visual_test) add_subdirectory(examples/02_phase3_test) add_subdirectory(examples/03_phase4_test) + add_subdirectory(examples/04_phase5_test) message(STATUS "Examples will be built") endif() diff --git a/examples/04_phase5_test/CMakeLists.txt b/examples/04_phase5_test/CMakeLists.txt new file mode 100644 index 0000000..758345d --- /dev/null +++ b/examples/04_phase5_test/CMakeLists.txt @@ -0,0 +1,7 @@ +add_are_example(phase5_test + main.cpp +) + +set_target_properties(phase5_test PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) diff --git a/examples/04_phase5_test/main.cpp b/examples/04_phase5_test/main.cpp new file mode 100644 index 0000000..d21c52c --- /dev/null +++ b/examples/04_phase5_test/main.cpp @@ -0,0 +1,361 @@ +/** + * @file main.cpp + * @brief Phase 5 verification program - CPU ray tracing test (RGBA16F output) + */ + +#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 + +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 + }; + + uint32_t indices[] = { 0, 1, 2, 2, 3, 0 }; + + uint32_t vao = 0; + uint32_t vbo = 0; + uint32_t ebo = 0; + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + + 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; +} + +/** + * @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); + + 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_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; +} + +} // namespace + +int main() { + 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; + + Window window(wc); + + if (!GLContext::initialize()) { + ARE_LOG_CRITICAL("phase5_test: GLContext::initialize failed"); + return -1; + } + GLContext::print_info(); + + 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; + } + + // GBuffer only used for resolution / future hybrid path + Rasterizer rasterizer(fb_w, fb_h); + GBuffer &gbuffer = rasterizer.get_gbuffer(); + + // Output texture (RGBA16F) + uint32_t output_tex = create_output_texture_rgba16f(fb_w, fb_h); + + // Simple display shader + const char *fsq_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); +} +)"; + + const char *fsq_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); +} +)"; + + 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; + } + + uint32_t quad_vao = create_fullscreen_quad_vao(); + + // 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); + + // 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; + + // 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); + + // Lights + auto sun = std::make_shared(Vec3(-1, -1, -0.5f), Vec3(1.0f), 2.0f); + scene.add_light(sun); + + 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); + + // 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); + + // 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()); + } + + BVH bvh; + if (!bvh.build(triangles)) { + ARE_LOG_CRITICAL("phase5_test: BVH 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 = true; + rtc.enable_ao = true; + rtc.ao_samples = 8; + rtc.ao_radius = 1.0f; + + CPURayTracer tracer(rtc); + tracer.update_bvh(bvh); + + 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"); + + bool request_render = true; + + while (!window.should_close()) { + window.poll_events(); + + 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; + } + + // 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; + rasterizer.resize(fb_w, fb_h); + + glDeleteTextures(1, &output_tex); + output_tex = create_output_texture_rgba16f(fb_w, fb_h); + + request_render = true; + } + + if (request_render) { + ARE_PROFILE_SCOPE("CPU Ray Trace"); + tracer.render(scene, camera, &gbuffer, output_tex); + request_render = false; + } + + // Present + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, fb_w, fb_h); + glDisable(GL_DEPTH_TEST); + + display.use(); + display.set_uniform("u_tex", 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, output_tex); + + 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; +} diff --git a/include/are/renderer/renderer.h b/include/are/renderer/renderer.h index 435108f..77bd5eb 100644 --- a/include/are/renderer/renderer.h +++ b/include/are/renderer/renderer.h @@ -24,7 +24,6 @@ class SceneManager; class Rasterizer; class RayTracer; class TextureManager; -class BVH; /** * @class Renderer @@ -110,7 +109,6 @@ private: std::unique_ptr rasterizer_; ///< Rasterization pipeline std::unique_ptr raytracer_; ///< Ray tracing pipeline std::unique_ptr texture_manager_; ///< Texture management - std::unique_ptr bvh_; ///< BVH acceleration structure Camera camera_; ///< Active camera RenderStats stats_; ///< Rendering statistics diff --git a/src/raytracer/cpu_raytracer.cpp b/src/raytracer/cpu_raytracer.cpp index dda2622..45f3f80 100644 --- a/src/raytracer/cpu_raytracer.cpp +++ b/src/raytracer/cpu_raytracer.cpp @@ -1,11 +1,10 @@ /** * @file cpu_raytracer.cpp - * @brief CPU ray tracing implementation + * @brief Implementation of CPURayTracer */ #include -#include -#include + #include #include #include @@ -15,6 +14,7 @@ #include #include #include + #include #include #include @@ -22,285 +22,300 @@ #include #include -#include -#include -#ifdef ARE_USE_OPENMP -#include -#endif +#include +#include +#include + +// #ifdef ARE_USE_OPENMP +// #include +// #endif namespace are { +namespace { + +/** + * @brief Apply simple Reinhard tonemapping. + * @param c HDR color + * @param exposure Exposure value + * @return LDR color in [0,1] + */ +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); +} + +} // namespace + CPURayTracer::CPURayTracer(const RayTracingConfig& config) : RayTracer(config) , bvh_(nullptr) , scene_(nullptr) + , framebuffer_() , width_(0) - , height_(0) -{ - ARE_LOG_INFO("CPU ray tracer initialized"); + , height_(0) { } -CPURayTracer::~CPURayTracer() { - ARE_LOG_INFO("CPU ray tracer destroyed"); -} +CPURayTracer::~CPURayTracer() = default; void CPURayTracer::update_bvh(const BVH& bvh) { bvh_ = &bvh; - ARE_LOG_INFO("BVH updated for CPU ray tracer (" + - std::to_string(bvh.get_nodes().size()) + " nodes)"); } -void CPURayTracer::render(const SceneManager& scene, - const Camera& camera, - const GBuffer* gbuffer, - uint32_t output_texture) { +void CPURayTracer::render(const SceneManager& scene, + const Camera& camera, + const GBuffer* gbuffer, + uint32_t output_texture) { ARE_PROFILE_FUNCTION(); - + if (!bvh_ || !bvh_->is_built()) { - ARE_LOG_ERROR("BVH not built, cannot render"); + ARE_LOG_ERROR("CPURayTracer: BVH is null or not built"); return; } - + + if (!gbuffer) { + ARE_LOG_CRITICAL("CPURayTracer: GBuffer is null, cannot infer render resolution"); + throw std::runtime_error("CPURayTracer requires a valid GBuffer for resolution"); + } + + if (output_texture == 0) { + ARE_LOG_ERROR("CPURayTracer: output_texture is 0"); + return; + } + scene_ = &scene; - - // Get framebuffer size from output texture - glBindTexture(GL_TEXTURE_2D, output_texture); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width_); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height_); - glBindTexture(GL_TEXTURE_2D, 0); - + width_ = gbuffer->get_width(); + height_ = gbuffer->get_height(); + if (width_ <= 0 || height_ <= 0) { - ARE_LOG_ERROR("Invalid output texture dimensions"); + ARE_LOG_ERROR("CPURayTracer: Invalid render resolution"); return; } - - // Resize framebuffer if needed - size_t pixel_count = width_ * height_; - if (framebuffer_.size() != pixel_count) { - framebuffer_.resize(pixel_count); - ARE_LOG_INFO("Framebuffer resized to " + std::to_string(width_) + "x" + std::to_string(height_)); - } - - // Render using ray tracing - ARE_LOG_INFO("Starting CPU ray tracing (" + std::to_string(config_.spp) + " spp)"); - - const int spp = config_.spp; - const Real inv_spp = 1.0f / static_cast(spp); - - #ifdef ARE_USE_OPENMP - #pragma omp parallel for schedule(dynamic, 16) - #endif + + const int spp = std::max(1, config_.spp); + const int max_depth = std::max(1, config_.max_depth); + + 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 color(0.0f); - - // Multi-sampling + Vec3 hdr(0.0f); + for (int s = 0; s < spp; ++s) { - // Jittered sampling - Real u = (x + random_float()) / static_cast(width_); - Real v = (y + random_float()) / static_cast(height_); - - // Generate ray - Vec3 ray_origin, ray_direction; - camera.generate_ray(u, v, ray_origin, ray_direction); - - Ray ray(ray_origin, ray_direction); - - // Trace ray - color += trace_ray(ray, 0); + 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); } - - // Average samples - color *= inv_spp; - - // Store in framebuffer - size_t index = y * width_ + x; - framebuffer_[index] = color; - } - - // Progress logging (every 10%) - if (y % (height_ / 10) == 0) { - Real progress = 100.0f * y / height_; - ARE_LOG_INFO("Ray tracing progress: " + std::to_string(static_cast(progress)) + "%"); + + hdr /= static_cast(spp); + + // 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); } } - - ARE_LOG_INFO("Ray tracing complete, uploading to GPU"); - - // Upload to GPU texture + + // Upload to output texture (recommended internal format: GL_RGBA16F) glBindTexture(GL_TEXTURE_2D, output_texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, - GL_RGB, GL_FLOAT, framebuffer_.data()); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_FLOAT, framebuffer_.data()); glBindTexture(GL_TEXTURE_2D, 0); } Vec3 CPURayTracer::trace_ray(const Ray& ray, int depth) { - // Russian roulette termination - if (depth >= config_.max_depth) { + ARE_PROFILE_FUNCTION(); + + if (depth <= 0) { return Vec3(0.0f); } - - // Intersect with scene + HitRecord hit; if (!bvh_->intersect(ray, hit)) { - // Sky color (simple gradient) - Real t = 0.5f * (ray.direction_.y + 1.0f); - return glm::mix(Vec3(1.0f), Vec3(0.5f, 0.7f, 1.0f), t); + // 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); } - - // Shade hit point + return shade(hit, ray, depth); } Vec3 CPURayTracer::shade(const HitRecord& hit, const Ray& ray, int depth) { - // Random real generator - thread_local RandomGenerator generator; + ARE_PROFILE_FUNCTION(); - // Get material - const Material* material = scene_->get_material(hit.material_); - if (!material) { - return Vec3(1.0f, 0.0f, 1.0f); // Magenta for missing material + Vec3 albedo(0.8f); + Real metallic = 0.0f; + Real roughness = 0.5f; + + if (scene_) { + const Material* mat = scene_->get_material(hit.material_); + if (mat) { + albedo = mat->get_albedo(); + metallic = mat->get_metallic(); + roughness = mat->get_roughness(); + } } - - // Get material properties - Vec3 albedo = material->get_albedo(); - Real metallic = material->get_metallic(); - Real roughness = material->get_roughness(); - Vec3 emissive = material->get_emissive(); - - // Emissive materials - if (material->is_emissive()) { - return emissive; - } - - // Compute direct lighting - Vec3 direct_lighting = compute_direct_lighting(hit); - - // Compute ambient occlusion + + Vec3 direct = compute_direct_lighting(hit); Real ao = 1.0f; + if (config_.enable_ao) { ao = compute_ambient_occlusion(hit); } - - // Simple diffuse shading - Vec3 color = albedo * direct_lighting * ao; - - // Global illumination (indirect lighting) - if (config_.enable_gi && depth < config_.max_depth - 1) { - // Generate random direction in hemisphere - Vec3 scatter_direction = generator.random_cosine_direction(hit.normal_); - - // Trace secondary ray - Ray scatter_ray(hit.position_ + hit.normal_ * are_epsilon, scatter_direction); - Vec3 indirect = trace_ray(scatter_ray, depth + 1); - - // Add indirect lighting (weighted by albedo) - color += albedo * indirect * 0.5f; + + Vec3 result = direct * ao; + + 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 bounced = trace_ray(bounce_ray, depth - 1); + result += albedo * bounced * 0.5f; } - - return color; + + // Phase 5: Lambert base + result *= albedo; + + (void)ray; + (void)metallic; + (void)roughness; + + return result; } Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) { + ARE_PROFILE_FUNCTION(); + Vec3 lighting(0.0f); - + if (!scene_) { + return lighting; + } + const auto& lights = scene_->get_all_lights(); - - for (const auto& light : lights) { - if (!light) continue; - - // Check if light affects this point - if (!light->affects_point(hit.position_)) { + for (const auto& light_ptr : lights) { + if (!light_ptr) { continue; } - - Vec3 light_dir; - Vec3 light_color = light->get_color() * light->get_intensity(); - Real light_distance = 1e30f; - - // Compute light direction based on type - if (light->get_type() == LightType::ARE_LIGHT_DIRECTIONAL) { - auto* dir_light = static_cast(light.get()); - light_dir = -dir_light->get_direction(); - light_distance = 1e30f; // Infinite distance - } - else if (light->get_type() == LightType::ARE_LIGHT_POINT) { - auto* point_light = static_cast(light.get()); - Vec3 to_light = point_light->get_position() - hit.position_; - light_distance = glm::length(to_light); - light_dir = to_light / light_distance; - - // Apply attenuation - Real attenuation = point_light->calculate_attenuation(light_distance); - light_color *= attenuation; - } - else if (light->get_type() == LightType::ARE_LIGHT_SPOT) { - auto* spot_light = static_cast(light.get()); - Vec3 to_light = spot_light->get_position() - hit.position_; - light_distance = glm::length(to_light); - light_dir = to_light / light_distance; - - // Apply spotlight factor - Real spot_factor = spot_light->calculate_spot_factor(-light_dir); - if (spot_factor <= 0.0f) { - continue; // Outside spotlight cone + + Vec3 L(0.0f); + Real max_distance = 1e30f; + Real attenuation = 1.0f; + + const LightType type = light_ptr->get_type(); + + if (type == LightType::ARE_LIGHT_DIRECTIONAL) { + const auto* dl = static_cast(light_ptr.get()); + L = -glm::normalize(dl->get_direction()); + } else if (type == LightType::ARE_LIGHT_POINT) { + 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; } - - light_color *= spot_factor; + if (!pl->affects_point(hit.position_)) { + continue; + } + L = to_light / dist; + max_distance = dist; + attenuation = pl->calculate_attenuation(dist); + } else if (type == LightType::ARE_LIGHT_SPOT) { + 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; + } + 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; + } else { + continue; } - - // Shadow test - bool in_shadow = false; - if (light->get_cast_shadows()) { - in_shadow = is_in_shadow(hit.position_ + hit.normal_ * are_epsilon, - light_dir, light_distance); + + if (light_ptr->get_cast_shadows()) { + if (is_in_shadow(offset_ray_origin(hit.position_, hit.normal_), L, max_distance)) { + continue; + } } - - if (!in_shadow) { - // Lambertian diffuse - Real n_dot_l = glm::max(glm::dot(hit.normal_, light_dir), 0.0f); - lighting += light_color * n_dot_l; + + Real n_dot_l = std::max(0.0f, glm::dot(hit.normal_, L)); + if (n_dot_l <= 0.0f) { + continue; } + + Vec3 radiance = light_ptr->get_color() * light_ptr->get_intensity(); + lighting += radiance * n_dot_l * attenuation; } - - // Add ambient term - lighting += Vec3(0.03f); - + return lighting; } Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) { - // Random real generator - thread_local RandomGenerator generator; + ARE_PROFILE_FUNCTION(); + + if (!bvh_) { + return 1.0f; + } + + const int ao_samples = std::max(1, config_.ao_samples); + const Real radius = std::max(are_epsilon, config_.ao_radius); + + RandomGenerator& rng = get_thread_random(); + + 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); - const int num_samples = config_.ao_samples; - const Real radius = config_.ao_radius; - - int occluded_count = 0; - - for (int i = 0; i < num_samples; ++i) { - // Generate random direction in hemisphere - Vec3 sample_dir = generator.random_in_hemisphere(hit.normal_); - - // Cast AO ray - Ray ao_ray(hit.position_ + hit.normal_ * are_epsilon, sample_dir, - are_epsilon, radius); - - // Check if ray hits anything within radius if (bvh_->intersect_any(ao_ray, radius)) { - occluded_count++; + occluded++; } } - - // AO factor (1.0 = no occlusion, 0.0 = full occlusion) - Real ao = 1.0f - (static_cast(occluded_count) / num_samples); - return ao; + + Real occ = static_cast(occluded) / static_cast(ao_samples); + return 1.0f - occ; } bool CPURayTracer::is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance) { - Ray shadow_ray(origin, direction, are_epsilon, max_distance - are_epsilon); - return bvh_->intersect_any(shadow_ray, max_distance); + ARE_PROFILE_FUNCTION(); + + 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); } } // namespace are diff --git a/src/raytracer/hit_record.cpp b/src/raytracer/hit_record.cpp index dabd6ce..2952a9f 100644 --- a/src/raytracer/hit_record.cpp +++ b/src/raytracer/hit_record.cpp @@ -1,34 +1,32 @@ /** * @file hit_record.cpp - * @brief Implementation of hit record + * @brief Implementation of HitRecord structure */ #include #include +#include namespace are { HitRecord::HitRecord() - : position_(0.0f) - , normal_(0.0f, 1.0f, 0.0f) - , texcoord_(0.0f) - , tangent_(1.0f, 0.0f, 0.0f) - , t_(-1.0f) - , material_(are_invalid_handle) - , triangle_index_(0) - , front_face_(true) { + : position_(0.0f) + , normal_(0.0f, 1.0f, 0.0f) + , texcoord_(0.0f) + , tangent_(1.0f, 0.0f, 0.0f) + , t_(std::numeric_limits::max()) + , material_(are_invalid_handle) + , triangle_index_(0) + , front_face_(true) { } -void HitRecord::set_face_normal(const Vec3 &ray_direction, const Vec3 &outward_normal) { - // Determine if ray hit front face or back face - front_face_ = glm::dot(ray_direction, outward_normal) < 0.0f; - - // Normal always points against ray direction - normal_ = front_face_ ? outward_normal : -outward_normal; +void HitRecord::set_face_normal(const Vec3& ray_direction, const Vec3& outward_normal) { + front_face_ = glm::dot(ray_direction, outward_normal) < 0.0f; + normal_ = front_face_ ? outward_normal : -outward_normal; } bool HitRecord::is_valid() const { - return t_ >= 0.0f && material_ != are_invalid_handle; + return t_ > 0.0f && t_ < std::numeric_limits::max(); } } // namespace are diff --git a/src/raytracer/raytracer.cpp b/src/raytracer/raytracer.cpp index 926e472..e002b5d 100644 --- a/src/raytracer/raytracer.cpp +++ b/src/raytracer/raytracer.cpp @@ -1,22 +1,18 @@ /** * @file raytracer.cpp - * @brief Implementation of RayTracer base class + * @brief Implementation of RayTracer interface */ #include -#include namespace are { RayTracer::RayTracer(const RayTracingConfig& config) : config_(config) { - ARE_LOG_INFO("RayTracer: Created with max depth " + - std::to_string(config_.max_depth)); } void RayTracer::set_config(const RayTracingConfig& config) { config_ = config; - ARE_LOG_INFO("RayTracer: Configuration updated"); } } // namespace are diff --git a/src/texture/compute_texture.cpp b/src/texture/compute_texture.cpp deleted file mode 100644 index fd6063a..0000000 --- a/src/texture/compute_texture.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file compute_raytracer.cpp - * @brief Compute shader ray tracing implementation (placeholder) - */ - -#include -#include - -namespace are { - -ComputeRayTracer::ComputeRayTracer(const RayTracingConfig& config) - : RayTracer(config) - , bvh_buffer_(0) - , triangle_buffer_(0) - , material_buffer_(0) - , light_buffer_(0) - , buffers_initialized_(false) -{ - ARE_LOG_WARN("Compute shader ray tracer is not yet implemented"); - ARE_LOG_INFO("Compute ray tracer initialized (placeholder)"); -} - -ComputeRayTracer::~ComputeRayTracer() { - // TODO: Clean up GPU buffers - ARE_LOG_INFO("Compute ray tracer destroyed"); -} - -void ComputeRayTracer::render(const SceneManager& scene, - const Camera& camera, - const GBuffer* gbuffer, - uint32_t output_texture) { - ARE_LOG_WARN("Compute shader ray tracing not implemented yet, skipping render"); - - // TODO: Implement compute shader ray tracing - // For now, just do nothing -} - -void ComputeRayTracer::update_bvh(const BVH& bvh) { - ARE_LOG_INFO("BVH update for compute ray tracer (not implemented)"); - - // TODO: Upload BVH to GPU -} - -void ComputeRayTracer::initialize_compute_shader(const std::string& shader_dir) { - // TODO: Load and compile compute shader - ARE_LOG_WARN("Compute shader initialization not implemented"); -} - -void ComputeRayTracer::upload_scene_data(const SceneManager& scene) { - // TODO: Upload scene data to GPU -} - -void ComputeRayTracer::upload_bvh_data(const BVH& bvh) { - // TODO: Upload BVH to GPU -} - -void ComputeRayTracer::upload_camera_data(const Camera& camera) { - // TODO: Upload camera data to GPU -} - -} // namespace are diff --git a/src/texture/sampler.cpp b/src/texture/sampler.cpp deleted file mode 100644 index 945c407..0000000 --- a/src/texture/sampler.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @file sampler.cpp - * @brief Texture sampling utilities implementation - */ - -#include -#include -#include - -namespace are { - -Vec4 Sampler::sample(const Texture& texture, const Vec2& uv) { - // Default to bilinear sampling - return sample_bilinear(texture, uv); -} - -Vec4 Sampler::sample_bilinear(const Texture& texture, const Vec2& uv) { - // TODO: Implement CPU-side bilinear sampling - // For now, return white - ARE_LOG_WARN("CPU texture sampling not implemented"); - return Vec4(1.0f); -} - -Vec4 Sampler::sample_nearest(const Texture& texture, const Vec2& uv) { - // TODO: Implement CPU-side nearest sampling - ARE_LOG_WARN("CPU texture sampling not implemented"); - return Vec4(1.0f); -} - -Vec2 Sampler::apply_wrap(const Vec2& uv, TextureWrap wrap) { - // TODO: Implement wrapping modes - return uv; -} - -} // namespace are diff --git a/src/texture/texture.cpp b/src/texture/texture.cpp deleted file mode 100644 index 12b4eb2..0000000 --- a/src/texture/texture.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/** - * @file texture.cpp - * @brief Implementation of texture class - */ - -#include -#include -#include -#include - -namespace are { - -// Helper function to convert TextureFormat to OpenGL format -static GLenum get_gl_internal_format(TextureFormat format) { - switch (format) { - case TextureFormat::ARE_TEXTURE_R8: return GL_R8; - case TextureFormat::ARE_TEXTURE_RG8: return GL_RG8; - case TextureFormat::ARE_TEXTURE_RGB8: return GL_RGB8; - case TextureFormat::ARE_TEXTURE_RGBA8: return GL_RGBA8; - case TextureFormat::ARE_TEXTURE_R16F: return GL_R16F; - case TextureFormat::ARE_TEXTURE_RG16F: return GL_RG16F; - case TextureFormat::ARE_TEXTURE_RGB16F: return GL_RGB16F; - case TextureFormat::ARE_TEXTURE_RGBA16F: return GL_RGBA16F; - case TextureFormat::ARE_TEXTURE_R32F: return GL_R32F; - case TextureFormat::ARE_TEXTURE_RG32F: return GL_RG32F; - case TextureFormat::ARE_TEXTURE_RGB32F: return GL_RGB32F; - case TextureFormat::ARE_TEXTURE_RGBA32F: return GL_RGBA32F; - default: return GL_RGBA8; - } -} - -static GLenum get_gl_format(int channels) { - switch (channels) { - case 1: return GL_RED; - case 2: return GL_RG; - case 3: return GL_RGB; - case 4: return GL_RGBA; - default: return GL_RGBA; - } -} - -static GLenum get_gl_filter(TextureFilter filter) { - switch (filter) { - case TextureFilter::ARE_TEXTURE_FILTER_NEAREST: - return GL_NEAREST; - case TextureFilter::ARE_TEXTURE_FILTER_LINEAR: - return GL_LINEAR; - case TextureFilter::ARE_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: - return GL_NEAREST_MIPMAP_NEAREST; - case TextureFilter::ARE_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: - return GL_LINEAR_MIPMAP_NEAREST; - case TextureFilter::ARE_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: - return GL_NEAREST_MIPMAP_LINEAR; - case TextureFilter::ARE_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: - return GL_LINEAR_MIPMAP_LINEAR; - default: - return GL_LINEAR; - } -} - -static GLenum get_gl_wrap(TextureWrap wrap) { - switch (wrap) { - case TextureWrap::ARE_TEXTURE_WRAP_REPEAT: - return GL_REPEAT; - case TextureWrap::ARE_TEXTURE_WRAP_CLAMP_TO_EDGE: - return GL_CLAMP_TO_EDGE; - case TextureWrap::ARE_TEXTURE_WRAP_CLAMP_TO_BORDER: - return GL_CLAMP_TO_BORDER; - case TextureWrap::ARE_TEXTURE_WRAP_MIRRORED_REPEAT: - return GL_MIRRORED_REPEAT; - default: - return GL_REPEAT; - } -} - -Texture::Texture() - : texture_id_(0) - , width_(0) - , height_(0) - , format_(TextureFormat::ARE_TEXTURE_RGBA8) -{ -} - -Texture::~Texture() { - destroy(); -} - -bool Texture::load_from_file(const std::string& filepath, - TextureFormat format, - bool generate_mipmaps) { - // Load image data - ImageData image = load_image(filepath, true); // Flip vertically for OpenGL - - if (!image.is_valid()) { - ARE_LOG_ERROR("Failed to load image: " + filepath); - return false; - } - - return create_from_data(image.width_, image.height_, format, - image.data_.data(), generate_mipmaps); -} - -bool Texture::create_from_data(int width, int height, - TextureFormat format, - const void* data, - bool generate_mipmaps) { - if (width <= 0 || height <= 0) { - ARE_LOG_ERROR("Invalid texture dimensions"); - return false; - } - - // Delete old texture if exists - if (texture_id_ != 0) { - destroy(); - } - - width_ = width; - height_ = height; - format_ = format; - - // Create OpenGL texture - glGenTextures(1, &texture_id_); - glBindTexture(GL_TEXTURE_2D, texture_id_); - - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - generate_mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Upload texture data - GLenum internal_format = get_gl_internal_format(format); - GLenum data_format = GL_RGBA; // Assume RGBA input - - // Determine data format from input (assume 4 channels for now) - if (data) { - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, - data_format, GL_UNSIGNED_BYTE, data); - } else { - // Create empty texture - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, - data_format, GL_UNSIGNED_BYTE, nullptr); - } - - // Generate mipmaps - if (generate_mipmaps) { - glGenerateMipmap(GL_TEXTURE_2D); - } - - glBindTexture(GL_TEXTURE_2D, 0); - - return true; -} - -void Texture::bind(int unit) const { - if (texture_id_ == 0) { - ARE_LOG_WARN("Attempting to bind invalid texture"); - return; - } - - glActiveTexture(GL_TEXTURE0 + unit); - glBindTexture(GL_TEXTURE_2D, texture_id_); -} - -void Texture::unbind() const { - glBindTexture(GL_TEXTURE_2D, 0); -} - -void Texture::set_filter(TextureFilter min_filter, TextureFilter mag_filter) { - if (texture_id_ == 0) return; - - glBindTexture(GL_TEXTURE_2D, texture_id_); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, get_gl_filter(min_filter)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, get_gl_filter(mag_filter)); - glBindTexture(GL_TEXTURE_2D, 0); -} - -void Texture::set_wrap(TextureWrap wrap_s, TextureWrap wrap_t) { - if (texture_id_ == 0) return; - - glBindTexture(GL_TEXTURE_2D, texture_id_); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, get_gl_wrap(wrap_s)); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, get_gl_wrap(wrap_t)); - glBindTexture(GL_TEXTURE_2D, 0); -} - -void Texture::generate_mipmaps() { - if (texture_id_ == 0) return; - - glBindTexture(GL_TEXTURE_2D, texture_id_); - glGenerateMipmap(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, 0); -} - -void Texture::destroy() { - if (texture_id_ != 0) { - glDeleteTextures(1, &texture_id_); - texture_id_ = 0; - width_ = 0; - height_ = 0; - } -} - -} // namespace are diff --git a/src/texture/texture_manager.cpp b/src/texture/texture_manager.cpp deleted file mode 100644 index a05319a..0000000 --- a/src/texture/texture_manager.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/** - * @file texture_manager.cpp - * @brief Implementation of texture manager - */ - -#include -#include - -namespace are { - -TextureManager::TextureManager() - : next_handle_(1) -{ - ARE_LOG_INFO("Texture manager initialized"); -} - -TextureManager::~TextureManager() { - clear(); - ARE_LOG_INFO("Texture manager destroyed"); -} - -TextureHandle TextureManager::load_texture(const std::string& filepath, - TextureFormat format, - bool generate_mipmaps) { - // Check if texture already loaded - auto it = path_to_handle_.find(filepath); - if (it != path_to_handle_.end()) { - ARE_LOG_INFO("Texture already loaded: " + filepath); - return it->second; - } - - // Create new texture - auto texture = std::make_unique(); - - if (!texture->load_from_file(filepath, format, generate_mipmaps)) { - ARE_LOG_ERROR("Failed to load texture: " + filepath); - return are_invalid_handle; - } - - // Assign handle - TextureHandle handle = next_handle_++; - - // Store texture - textures_[handle] = std::move(texture); - path_to_handle_[filepath] = handle; - - ARE_LOG_INFO("Texture loaded: " + filepath + " (handle: " + std::to_string(handle) + ")"); - - return handle; -} - -TextureHandle TextureManager::create_texture(const std::string& name, - int width, int height, - TextureFormat format, - const void* data, - bool generate_mipmaps) { - // Check if texture with this name already exists - auto it = path_to_handle_.find(name); - if (it != path_to_handle_.end()) { - ARE_LOG_WARN("Texture with name already exists: " + name); - return it->second; - } - - // Create new texture - auto texture = std::make_unique(); - - if (!texture->create_from_data(width, height, format, data, generate_mipmaps)) { - ARE_LOG_ERROR("Failed to create texture: " + name); - return are_invalid_handle; - } - - // Assign handle - TextureHandle handle = next_handle_++; - - // Store texture - textures_[handle] = std::move(texture); - path_to_handle_[name] = handle; - - ARE_LOG_INFO("Texture created: " + name + " (handle: " + std::to_string(handle) + ")"); - - return handle; -} - -Texture* TextureManager::get_texture(TextureHandle handle) { - auto it = textures_.find(handle); - if (it != textures_.end()) { - return it->second.get(); - } - return nullptr; -} - -const Texture* TextureManager::get_texture(TextureHandle handle) const { - auto it = textures_.find(handle); - if (it != textures_.end()) { - return it->second.get(); - } - return nullptr; -} - -void TextureManager::unload_texture(TextureHandle handle) { - auto it = textures_.find(handle); - if (it == textures_.end()) { - return; - } - - // Remove from path map - for (auto path_it = path_to_handle_.begin(); path_it != path_to_handle_.end(); ++path_it) { - if (path_it->second == handle) { - path_to_handle_.erase(path_it); - break; - } - } - - // Remove texture - textures_.erase(it); - - ARE_LOG_INFO("Texture unloaded (handle: " + std::to_string(handle) + ")"); -} - -void TextureManager::clear() { - textures_.clear(); - path_to_handle_.clear(); - next_handle_ = 1; - - ARE_LOG_INFO("All textures cleared"); -} - -size_t TextureManager::get_memory_usage() const { - size_t total = 0; - - for (const auto& [handle, texture] : textures_) { - if (texture && texture->is_valid()) { - // Estimate memory usage (width * height * bytes_per_pixel) - int width = texture->get_width(); - int height = texture->get_height(); - - // Estimate bytes per pixel based on format - int bytes_per_pixel = 4; // Default RGBA8 - - switch (texture->get_format()) { - case TextureFormat::ARE_TEXTURE_R8: - bytes_per_pixel = 1; - break; - case TextureFormat::ARE_TEXTURE_RG8: - bytes_per_pixel = 2; - break; - case TextureFormat::ARE_TEXTURE_RGB8: - bytes_per_pixel = 3; - break; - case TextureFormat::ARE_TEXTURE_RGBA8: - bytes_per_pixel = 4; - break; - case TextureFormat::ARE_TEXTURE_R16F: - bytes_per_pixel = 2; - break; - case TextureFormat::ARE_TEXTURE_RG16F: - bytes_per_pixel = 4; - break; - case TextureFormat::ARE_TEXTURE_RGB16F: - bytes_per_pixel = 6; - break; - case TextureFormat::ARE_TEXTURE_RGBA16F: - bytes_per_pixel = 8; - break; - case TextureFormat::ARE_TEXTURE_R32F: - bytes_per_pixel = 4; - break; - case TextureFormat::ARE_TEXTURE_RG32F: - bytes_per_pixel = 8; - break; - case TextureFormat::ARE_TEXTURE_RGB32F: - bytes_per_pixel = 12; - break; - case TextureFormat::ARE_TEXTURE_RGBA32F: - bytes_per_pixel = 16; - break; - } - - size_t texture_size = width * height * bytes_per_pixel; - - // Account for mipmaps (approximately 1.33x base size) - texture_size = static_cast(texture_size * 1.33); - - total += texture_size; - } - } - - return total; -} - -} // namespace are