Last commit today

master
ternaryop8479 2026-02-08 22:46:24 +08:00
parent c1c062180d
commit 8ce33436d5
11 changed files with 607 additions and 723 deletions

View File

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

View File

@ -0,0 +1,7 @@
add_are_example(phase5_test
main.cpp
)
set_target_properties(phase5_test PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

View File

@ -0,0 +1,361 @@
/**
* @file main.cpp
* @brief Phase 5 verification program - CPU ray tracing test (RGBA16F output)
*/
#include <are/core/config.h>
#include <are/core/logger.h>
#include <are/core/profiler.h>
#include <are/platform/gl_context.h>
#include <are/platform/window.h>
#include <are/rasterizer/gbuffer.h>
#include <are/rasterizer/rasterizer.h>
#include <are/rasterizer/shader_program.h>
#include <are/scene/camera.h>
#include <are/scene/directional_light.h>
#include <are/scene/material.h>
#include <are/scene/mesh.h>
#include <are/scene/point_light.h>
#include <are/scene/scene_manager.h>
#include <are/acceleration/bvh.h>
#include <are/geometry/triangle.h>
#include <are/geometry/vertex.h>
#include <are/raytracer/cpu_raytracer.h>
#include "../lib/glad/glad/glad.h"
#include <glm/glm.hpp>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
using namespace are;
namespace {
/**
* @brief Create a fullscreen quad VAO.
* @return VAO handle
*/
uint32_t create_fullscreen_quad_vao() {
float vertices[] = {
// pos // uv
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f
};
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<Triangle> mesh_to_triangles(const Mesh &mesh, MaterialHandle material) {
std::vector<Triangle> tris;
if (mesh.is_empty()) {
return tris;
}
const auto &v = mesh.get_vertices();
const auto &idx = mesh.get_indices();
if (idx.size() % 3 != 0) {
ARE_LOG_ERROR("phase5_test: mesh indices not multiple of 3");
return tris;
}
tris.reserve(idx.size() / 3);
for (size_t i = 0; i < idx.size(); i += 3) {
uint32_t i0 = idx[i + 0];
uint32_t i1 = idx[i + 1];
uint32_t i2 = idx[i + 2];
if (i0 >= v.size() || i1 >= v.size() || i2 >= v.size()) {
continue;
}
Triangle t(v[i0], v[i1], v[i2], material);
tris.push_back(t);
}
return tris;
}
} // 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<Vertex> ground_v = {
Vertex(Vec3(-4, 0, -4), Vec3(0, 1, 0), Vec2(0, 0)),
Vertex(Vec3(4, 0, -4), Vec3(0, 1, 0), Vec2(1, 0)),
Vertex(Vec3(4, 0, 4), Vec3(0, 1, 0), Vec2(1, 1)),
Vertex(Vec3(-4, 0, 4), Vec3(0, 1, 0), Vec2(0, 1)),
};
std::vector<uint32_t> ground_i = { 0, 1, 2, 2, 3, 0 };
Mesh ground(ground_v, ground_i, mat_h);
ground.compute_tangents();
MeshHandle ground_mh = scene.add_mesh(ground);
(void)ground_mh;
// A tilted triangle
std::vector<Vertex> tri_v = {
Vertex(Vec3(-0.8f, 0.2f, 0.0f), Vec3(0, 0.8f, 0.6f), Vec2(0, 0)),
Vertex(Vec3(0.8f, 0.2f, 0.0f), Vec3(0, 0.8f, 0.6f), Vec2(1, 0)),
Vertex(Vec3(0.0f, 1.2f, 0.3f), Vec3(0, 0.8f, 0.6f), Vec2(0.5f, 1)),
};
std::vector<uint32_t> tri_i = { 0, 1, 2 };
Mesh tri_mesh(tri_v, tri_i, mat_h);
tri_mesh.compute_tangents();
scene.add_mesh(tri_mesh);
// Lights
auto sun = std::make_shared<DirectionalLight>(Vec3(-1, -1, -0.5f), Vec3(1.0f), 2.0f);
scene.add_light(sun);
auto point = std::make_shared<PointLight>(Vec3(0, 2.5f, 1.5f), Vec3(1.0f, 0.95f, 0.8f), 10.0f, 10.0f);
point->set_cast_shadows(true);
scene.add_light(point);
// Camera
Camera camera(Vec3(0.0f, 1.8f, 4.5f), Vec3(0.0f, 0.6f, 0.0f));
camera.set_perspective(45.0f, static_cast<Real>(fb_w) / static_cast<Real>(fb_h), 0.1f, 200.0f);
// Build BVH from scene meshes
std::vector<Triangle> triangles;
for (const auto &m : scene.get_all_meshes()) {
auto tris = mesh_to_triangles(m, m.get_material());
triangles.insert(triangles.end(), tris.begin(), tris.end());
}
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;
}

View File

@ -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> rasterizer_; ///< Rasterization pipeline
std::unique_ptr<RayTracer> raytracer_; ///< Ray tracing pipeline
std::unique_ptr<TextureManager> texture_manager_; ///< Texture management
std::unique_ptr<BVH> bvh_; ///< BVH acceleration structure
Camera camera_; ///< Active camera
RenderStats stats_; ///< Rendering statistics

View File

@ -1,11 +1,10 @@
/**
* @file cpu_raytracer.cpp
* @brief CPU ray tracing implementation
* @brief Implementation of CPURayTracer
*/
#include <are/raytracer/cpu_raytracer.h>
#include <are/raytracer/ray.h>
#include <are/raytracer/hit_record.h>
#include <are/acceleration/bvh.h>
#include <are/scene/scene_manager.h>
#include <are/scene/camera.h>
@ -15,6 +14,7 @@
#include <are/scene/point_light.h>
#include <are/scene/spot_light.h>
#include <are/rasterizer/gbuffer.h>
#include <are/utils/random.h>
#include <are/utils/math_utils.h>
#include <are/core/logger.h>
@ -22,33 +22,55 @@
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <algorithm>
#include <cmath>
#ifdef ARE_USE_OPENMP
#include <omp.h>
#endif
#include <algorithm>
#include <limits>
#include <stdexcept>
// #ifdef ARE_USE_OPENMP
// #include <omp.h>
// #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,
@ -58,249 +80,242 @@ void CPURayTracer::render(const SceneManager& scene,
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_));
}
const int spp = std::max(1, config_.spp);
const int max_depth = std::max(1, config_.max_depth);
// Render using ray tracing
ARE_LOG_INFO("Starting CPU ray tracing (" + std::to_string(config_.spp) + " spp)");
framebuffer_.assign(static_cast<size_t>(width_ * height_), Vec4(0.0f));
const int spp = config_.spp;
const Real inv_spp = 1.0f / static_cast<Real>(spp);
#ifdef ARE_USE_OPENMP
#pragma omp parallel for schedule(dynamic, 16)
#endif
// #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);
Vec3 hdr(0.0f);
// Multi-sampling
for (int s = 0; s < spp; ++s) {
// Jittered sampling
Real u = (x + random_float()) / static_cast<Real>(width_);
Real v = (y + random_float()) / static_cast<Real>(height_);
Real u = (static_cast<Real>(x) + rng.random_float()) / static_cast<Real>(width_);
Real v = (static_cast<Real>(y) + rng.random_float()) / static_cast<Real>(height_);
// Generate ray
Vec3 ray_origin, ray_direction;
camera.generate_ray(u, v, ray_origin, ray_direction);
Vec3 origin;
Vec3 direction;
camera.generate_ray(u, v, origin, direction);
Ray ray(ray_origin, ray_direction);
// Trace ray
color += trace_ray(ray, 0);
Ray ray(origin, direction, are_epsilon, 1e30f);
hdr += trace_ray(ray, max_depth);
}
// Average samples
color *= inv_spp;
hdr /= static_cast<Real>(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<int>(progress)) + "%");
// Phase 5: tonemap in tracer for standalone output
Vec3 ldr = tonemap_reinhard(hdr, 1.0f);
framebuffer_[static_cast<size_t>(y * width_ + x)] = Vec4(ldr, 1.0f);
}
}
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;
Vec3 result = direct * 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_);
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);
// 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 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;
Vec3 L(0.0f);
Real max_distance = 1e30f;
Real attenuation = 1.0f;
// Compute light direction based on type
if (light->get_type() == LightType::ARE_LIGHT_DIRECTIONAL) {
auto* dir_light = static_cast<const DirectionalLight*>(light.get());
light_dir = -dir_light->get_direction();
light_distance = 1e30f; // Infinite distance
const LightType type = light_ptr->get_type();
if (type == LightType::ARE_LIGHT_DIRECTIONAL) {
const auto* dl = static_cast<const DirectionalLight*>(light_ptr.get());
L = -glm::normalize(dl->get_direction());
} else if (type == LightType::ARE_LIGHT_POINT) {
const auto* pl = static_cast<const PointLight*>(light_ptr.get());
Vec3 to_light = pl->get_position() - hit.position_;
Real dist = glm::length(to_light);
if (dist < are_epsilon) {
continue;
}
else if (light->get_type() == LightType::ARE_LIGHT_POINT) {
auto* point_light = static_cast<const PointLight*>(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;
if (!pl->affects_point(hit.position_)) {
continue;
}
else if (light->get_type() == LightType::ARE_LIGHT_SPOT) {
auto* spot_light = static_cast<const SpotLight*>(light.get());
Vec3 to_light = spot_light->get_position() - hit.position_;
light_distance = glm::length(to_light);
light_dir = to_light / light_distance;
L = to_light / dist;
max_distance = dist;
attenuation = pl->calculate_attenuation(dist);
} else if (type == LightType::ARE_LIGHT_SPOT) {
const auto* sl = static_cast<const SpotLight*>(light_ptr.get());
Vec3 to_light = sl->get_position() - hit.position_;
Real dist = glm::length(to_light);
if (dist < are_epsilon) {
continue;
}
if (!sl->affects_point(hit.position_)) {
continue;
}
L = to_light / dist;
max_distance = dist;
// Apply spotlight factor
Real spot_factor = spot_light->calculate_spot_factor(-light_dir);
if (spot_factor <= 0.0f) {
continue; // Outside spotlight cone
Vec3 light_to_point = glm::normalize(hit.position_ - sl->get_position());
Real spot = sl->calculate_spot_factor(light_to_point);
attenuation *= spot;
} else {
continue;
}
light_color *= spot_factor;
}
// 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 (!in_shadow) {
// Lambertian diffuse
Real n_dot_l = glm::max(glm::dot(hit.normal_, light_dir), 0.0f);
lighting += light_color * n_dot_l;
if (light_ptr->get_cast_shadows()) {
if (is_in_shadow(offset_ray_origin(hit.position_, hit.normal_), L, max_distance)) {
continue;
}
}
// Add ambient term
lighting += Vec3(0.03f);
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;
}
return lighting;
}
Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
// Random real generator
thread_local RandomGenerator generator;
ARE_PROFILE_FUNCTION();
const int num_samples = config_.ao_samples;
const Real radius = config_.ao_radius;
if (!bvh_) {
return 1.0f;
}
int occluded_count = 0;
const int ao_samples = std::max(1, config_.ao_samples);
const Real radius = std::max(are_epsilon, config_.ao_radius);
for (int i = 0; i < num_samples; ++i) {
// Generate random direction in hemisphere
Vec3 sample_dir = generator.random_in_hemisphere(hit.normal_);
RandomGenerator& rng = get_thread_random();
// Cast AO ray
Ray ao_ray(hit.position_ + hit.normal_ * are_epsilon, sample_dir,
are_epsilon, radius);
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);
// 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<Real>(occluded_count) / num_samples);
return ao;
Real occ = static_cast<Real>(occluded) / static_cast<Real>(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

View File

@ -1,10 +1,11 @@
/**
* @file hit_record.cpp
* @brief Implementation of hit record
* @brief Implementation of HitRecord structure
*/
#include <are/raytracer/hit_record.h>
#include <glm/glm.hpp>
#include <limits>
namespace are {
@ -13,22 +14,19 @@ HitRecord::HitRecord()
, normal_(0.0f, 1.0f, 0.0f)
, texcoord_(0.0f)
, tangent_(1.0f, 0.0f, 0.0f)
, t_(-1.0f)
, t_(std::numeric_limits<Real>::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;
}
bool HitRecord::is_valid() const {
return t_ >= 0.0f && material_ != are_invalid_handle;
return t_ > 0.0f && t_ < std::numeric_limits<Real>::max();
}
} // namespace are

View File

@ -1,22 +1,18 @@
/**
* @file raytracer.cpp
* @brief Implementation of RayTracer base class
* @brief Implementation of RayTracer interface
*/
#include <are/raytracer/raytracer.h>
#include <are/core/logger.h>
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

View File

@ -1,61 +0,0 @@
/**
* @file compute_raytracer.cpp
* @brief Compute shader ray tracing implementation (placeholder)
*/
#include <are/raytracer/compute_raytracer.h>
#include <are/core/logger.h>
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

View File

@ -1,35 +0,0 @@
/**
* @file sampler.cpp
* @brief Texture sampling utilities implementation
*/
#include <are/texture/texture.h>
#include <are/texture/sampler.h>
#include <are/core/logger.h>
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

View File

@ -1,205 +0,0 @@
/**
* @file texture.cpp
* @brief Implementation of texture class
*/
#include <are/texture/texture.h>
#include <are/utils/image_io.h>
#include <are/core/logger.h>
#include <glad/glad.h>
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

View File

@ -1,191 +0,0 @@
/**
* @file texture_manager.cpp
* @brief Implementation of texture manager
*/
#include <are/texture/texture_manager.h>
#include <are/core/logger.h>
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<Texture>();
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<Texture>();
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<size_t>(texture_size * 1.33);
total += texture_size;
}
}
return total;
}
} // namespace are