Last commit today
parent
c1c062180d
commit
8ce33436d5
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
add_are_example(phase5_test
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(phase5_test PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||
)
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue