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_visual_test)
|
||||||
add_subdirectory(examples/02_phase3_test)
|
add_subdirectory(examples/02_phase3_test)
|
||||||
add_subdirectory(examples/03_phase4_test)
|
add_subdirectory(examples/03_phase4_test)
|
||||||
|
add_subdirectory(examples/04_phase5_test)
|
||||||
|
|
||||||
message(STATUS "Examples will be built")
|
message(STATUS "Examples will be built")
|
||||||
endif()
|
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 Rasterizer;
|
||||||
class RayTracer;
|
class RayTracer;
|
||||||
class TextureManager;
|
class TextureManager;
|
||||||
class BVH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Renderer
|
* @class Renderer
|
||||||
|
|
@ -110,7 +109,6 @@ private:
|
||||||
std::unique_ptr<Rasterizer> rasterizer_; ///< Rasterization pipeline
|
std::unique_ptr<Rasterizer> rasterizer_; ///< Rasterization pipeline
|
||||||
std::unique_ptr<RayTracer> raytracer_; ///< Ray tracing pipeline
|
std::unique_ptr<RayTracer> raytracer_; ///< Ray tracing pipeline
|
||||||
std::unique_ptr<TextureManager> texture_manager_; ///< Texture management
|
std::unique_ptr<TextureManager> texture_manager_; ///< Texture management
|
||||||
std::unique_ptr<BVH> bvh_; ///< BVH acceleration structure
|
|
||||||
|
|
||||||
Camera camera_; ///< Active camera
|
Camera camera_; ///< Active camera
|
||||||
RenderStats stats_; ///< Rendering statistics
|
RenderStats stats_; ///< Rendering statistics
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* @file cpu_raytracer.cpp
|
* @file cpu_raytracer.cpp
|
||||||
* @brief CPU ray tracing implementation
|
* @brief Implementation of CPURayTracer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/raytracer/cpu_raytracer.h>
|
#include <are/raytracer/cpu_raytracer.h>
|
||||||
#include <are/raytracer/ray.h>
|
|
||||||
#include <are/raytracer/hit_record.h>
|
|
||||||
#include <are/acceleration/bvh.h>
|
#include <are/acceleration/bvh.h>
|
||||||
#include <are/scene/scene_manager.h>
|
#include <are/scene/scene_manager.h>
|
||||||
#include <are/scene/camera.h>
|
#include <are/scene/camera.h>
|
||||||
|
|
@ -15,6 +14,7 @@
|
||||||
#include <are/scene/point_light.h>
|
#include <are/scene/point_light.h>
|
||||||
#include <are/scene/spot_light.h>
|
#include <are/scene/spot_light.h>
|
||||||
#include <are/rasterizer/gbuffer.h>
|
#include <are/rasterizer/gbuffer.h>
|
||||||
|
|
||||||
#include <are/utils/random.h>
|
#include <are/utils/random.h>
|
||||||
#include <are/utils/math_utils.h>
|
#include <are/utils/math_utils.h>
|
||||||
#include <are/core/logger.h>
|
#include <are/core/logger.h>
|
||||||
|
|
@ -22,285 +22,300 @@
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#ifdef ARE_USE_OPENMP
|
#include <algorithm>
|
||||||
#include <omp.h>
|
#include <limits>
|
||||||
#endif
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// #ifdef ARE_USE_OPENMP
|
||||||
|
// #include <omp.h>
|
||||||
|
// #endif
|
||||||
|
|
||||||
namespace are {
|
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)
|
CPURayTracer::CPURayTracer(const RayTracingConfig& config)
|
||||||
: RayTracer(config)
|
: RayTracer(config)
|
||||||
, bvh_(nullptr)
|
, bvh_(nullptr)
|
||||||
, scene_(nullptr)
|
, scene_(nullptr)
|
||||||
|
, framebuffer_()
|
||||||
, width_(0)
|
, width_(0)
|
||||||
, height_(0)
|
, height_(0) {
|
||||||
{
|
|
||||||
ARE_LOG_INFO("CPU ray tracer initialized");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CPURayTracer::~CPURayTracer() {
|
CPURayTracer::~CPURayTracer() = default;
|
||||||
ARE_LOG_INFO("CPU ray tracer destroyed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CPURayTracer::update_bvh(const BVH& bvh) {
|
void CPURayTracer::update_bvh(const BVH& bvh) {
|
||||||
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,
|
void CPURayTracer::render(const SceneManager& scene,
|
||||||
const Camera& camera,
|
const Camera& camera,
|
||||||
const GBuffer* gbuffer,
|
const GBuffer* gbuffer,
|
||||||
uint32_t output_texture) {
|
uint32_t output_texture) {
|
||||||
ARE_PROFILE_FUNCTION();
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
if (!bvh_ || !bvh_->is_built()) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scene_ = &scene;
|
scene_ = &scene;
|
||||||
|
width_ = gbuffer->get_width();
|
||||||
// Get framebuffer size from output texture
|
height_ = gbuffer->get_height();
|
||||||
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);
|
|
||||||
|
|
||||||
if (width_ <= 0 || height_ <= 0) {
|
if (width_ <= 0 || height_ <= 0) {
|
||||||
ARE_LOG_ERROR("Invalid output texture dimensions");
|
ARE_LOG_ERROR("CPURayTracer: Invalid render resolution");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize framebuffer if needed
|
const int spp = std::max(1, config_.spp);
|
||||||
size_t pixel_count = width_ * height_;
|
const int max_depth = std::max(1, config_.max_depth);
|
||||||
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
|
framebuffer_.assign(static_cast<size_t>(width_ * height_), Vec4(0.0f));
|
||||||
ARE_LOG_INFO("Starting CPU ray tracing (" + std::to_string(config_.spp) + " spp)");
|
|
||||||
|
|
||||||
const int spp = config_.spp;
|
// #ifdef ARE_USE_OPENMP
|
||||||
const Real inv_spp = 1.0f / static_cast<Real>(spp);
|
// #pragma omp parallel for schedule(dynamic, 1)
|
||||||
|
// #endif
|
||||||
#ifdef ARE_USE_OPENMP
|
|
||||||
#pragma omp parallel for schedule(dynamic, 16)
|
|
||||||
#endif
|
|
||||||
for (int y = 0; y < height_; ++y) {
|
for (int y = 0; y < height_; ++y) {
|
||||||
|
RandomGenerator& rng = get_thread_random();
|
||||||
|
|
||||||
for (int x = 0; x < width_; ++x) {
|
for (int x = 0; x < width_; ++x) {
|
||||||
Vec3 color(0.0f);
|
Vec3 hdr(0.0f);
|
||||||
|
|
||||||
// Multi-sampling
|
|
||||||
for (int s = 0; s < spp; ++s) {
|
for (int s = 0; s < spp; ++s) {
|
||||||
// Jittered sampling
|
Real u = (static_cast<Real>(x) + rng.random_float()) / static_cast<Real>(width_);
|
||||||
Real u = (x + random_float()) / static_cast<Real>(width_);
|
Real v = (static_cast<Real>(y) + rng.random_float()) / static_cast<Real>(height_);
|
||||||
Real v = (y + random_float()) / static_cast<Real>(height_);
|
|
||||||
|
|
||||||
// Generate ray
|
Vec3 origin;
|
||||||
Vec3 ray_origin, ray_direction;
|
Vec3 direction;
|
||||||
camera.generate_ray(u, v, ray_origin, ray_direction);
|
camera.generate_ray(u, v, origin, direction);
|
||||||
|
|
||||||
Ray ray(ray_origin, ray_direction);
|
Ray ray(origin, direction, are_epsilon, 1e30f);
|
||||||
|
hdr += trace_ray(ray, max_depth);
|
||||||
// Trace ray
|
|
||||||
color += trace_ray(ray, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Average samples
|
hdr /= static_cast<Real>(spp);
|
||||||
color *= inv_spp;
|
|
||||||
|
|
||||||
// Store in framebuffer
|
// Phase 5: tonemap in tracer for standalone output
|
||||||
size_t index = y * width_ + x;
|
Vec3 ldr = tonemap_reinhard(hdr, 1.0f);
|
||||||
framebuffer_[index] = color;
|
framebuffer_[static_cast<size_t>(y * width_ + x)] = Vec4(ldr, 1.0f);
|
||||||
}
|
|
||||||
|
|
||||||
// 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)) + "%");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ARE_LOG_INFO("Ray tracing complete, uploading to GPU");
|
// Upload to output texture (recommended internal format: GL_RGBA16F)
|
||||||
|
|
||||||
// Upload to GPU texture
|
|
||||||
glBindTexture(GL_TEXTURE_2D, output_texture);
|
glBindTexture(GL_TEXTURE_2D, output_texture);
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
GL_RGB, GL_FLOAT, framebuffer_.data());
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_FLOAT, framebuffer_.data());
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 CPURayTracer::trace_ray(const Ray& ray, int depth) {
|
Vec3 CPURayTracer::trace_ray(const Ray& ray, int depth) {
|
||||||
// Russian roulette termination
|
ARE_PROFILE_FUNCTION();
|
||||||
if (depth >= config_.max_depth) {
|
|
||||||
|
if (depth <= 0) {
|
||||||
return Vec3(0.0f);
|
return Vec3(0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersect with scene
|
|
||||||
HitRecord hit;
|
HitRecord hit;
|
||||||
if (!bvh_->intersect(ray, hit)) {
|
if (!bvh_->intersect(ray, hit)) {
|
||||||
// Sky color (simple gradient)
|
// Simple sky
|
||||||
Real t = 0.5f * (ray.direction_.y + 1.0f);
|
Vec3 unit_dir = glm::normalize(ray.direction_);
|
||||||
return glm::mix(Vec3(1.0f), Vec3(0.5f, 0.7f, 1.0f), t);
|
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);
|
return shade(hit, ray, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 CPURayTracer::shade(const HitRecord& hit, const Ray& ray, int depth) {
|
Vec3 CPURayTracer::shade(const HitRecord& hit, const Ray& ray, int depth) {
|
||||||
// Random real generator
|
ARE_PROFILE_FUNCTION();
|
||||||
thread_local RandomGenerator generator;
|
|
||||||
|
|
||||||
// Get material
|
Vec3 albedo(0.8f);
|
||||||
const Material* material = scene_->get_material(hit.material_);
|
Real metallic = 0.0f;
|
||||||
if (!material) {
|
Real roughness = 0.5f;
|
||||||
return Vec3(1.0f, 0.0f, 1.0f); // Magenta for missing material
|
|
||||||
|
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 direct = compute_direct_lighting(hit);
|
||||||
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
|
|
||||||
Real ao = 1.0f;
|
Real ao = 1.0f;
|
||||||
|
|
||||||
if (config_.enable_ao) {
|
if (config_.enable_ao) {
|
||||||
ao = compute_ambient_occlusion(hit);
|
ao = compute_ambient_occlusion(hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple diffuse shading
|
Vec3 result = direct * ao;
|
||||||
Vec3 color = albedo * direct_lighting * ao;
|
|
||||||
|
|
||||||
// Global illumination (indirect lighting)
|
if (config_.enable_gi && depth > 1) {
|
||||||
if (config_.enable_gi && depth < config_.max_depth - 1) {
|
RandomGenerator& rng = get_thread_random();
|
||||||
// Generate random direction in hemisphere
|
Vec3 bounce_dir = rng.random_cosine_direction(hit.normal_);
|
||||||
Vec3 scatter_direction = generator.random_cosine_direction(hit.normal_);
|
Ray bounce_ray(offset_ray_origin(hit.position_, hit.normal_), bounce_dir, are_epsilon, 1e30f);
|
||||||
|
|
||||||
// Trace secondary ray
|
Vec3 bounced = trace_ray(bounce_ray, depth - 1);
|
||||||
Ray scatter_ray(hit.position_ + hit.normal_ * are_epsilon, scatter_direction);
|
result += albedo * bounced * 0.5f;
|
||||||
Vec3 indirect = trace_ray(scatter_ray, depth + 1);
|
|
||||||
|
|
||||||
// Add indirect lighting (weighted by albedo)
|
|
||||||
color += albedo * indirect * 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) {
|
Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
Vec3 lighting(0.0f);
|
Vec3 lighting(0.0f);
|
||||||
|
if (!scene_) {
|
||||||
|
return lighting;
|
||||||
|
}
|
||||||
|
|
||||||
const auto& lights = scene_->get_all_lights();
|
const auto& lights = scene_->get_all_lights();
|
||||||
|
for (const auto& light_ptr : lights) {
|
||||||
for (const auto& light : lights) {
|
if (!light_ptr) {
|
||||||
if (!light) continue;
|
|
||||||
|
|
||||||
// Check if light affects this point
|
|
||||||
if (!light->affects_point(hit.position_)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 light_dir;
|
Vec3 L(0.0f);
|
||||||
Vec3 light_color = light->get_color() * light->get_intensity();
|
Real max_distance = 1e30f;
|
||||||
Real light_distance = 1e30f;
|
Real attenuation = 1.0f;
|
||||||
|
|
||||||
// Compute light direction based on type
|
const LightType type = light_ptr->get_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
|
|
||||||
}
|
|
||||||
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
|
if (type == LightType::ARE_LIGHT_DIRECTIONAL) {
|
||||||
Real attenuation = point_light->calculate_attenuation(light_distance);
|
const auto* dl = static_cast<const DirectionalLight*>(light_ptr.get());
|
||||||
light_color *= attenuation;
|
L = -glm::normalize(dl->get_direction());
|
||||||
}
|
} else if (type == LightType::ARE_LIGHT_POINT) {
|
||||||
else if (light->get_type() == LightType::ARE_LIGHT_SPOT) {
|
const auto* pl = static_cast<const PointLight*>(light_ptr.get());
|
||||||
auto* spot_light = static_cast<const SpotLight*>(light.get());
|
Vec3 to_light = pl->get_position() - hit.position_;
|
||||||
Vec3 to_light = spot_light->get_position() - hit.position_;
|
Real dist = glm::length(to_light);
|
||||||
light_distance = glm::length(to_light);
|
if (dist < are_epsilon) {
|
||||||
light_dir = to_light / light_distance;
|
continue;
|
||||||
|
|
||||||
// Apply spotlight factor
|
|
||||||
Real spot_factor = spot_light->calculate_spot_factor(-light_dir);
|
|
||||||
if (spot_factor <= 0.0f) {
|
|
||||||
continue; // Outside spotlight cone
|
|
||||||
}
|
}
|
||||||
|
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<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;
|
||||||
|
|
||||||
light_color *= spot_factor;
|
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
|
if (light_ptr->get_cast_shadows()) {
|
||||||
bool in_shadow = false;
|
if (is_in_shadow(offset_ray_origin(hit.position_, hit.normal_), L, max_distance)) {
|
||||||
if (light->get_cast_shadows()) {
|
continue;
|
||||||
in_shadow = is_in_shadow(hit.position_ + hit.normal_ * are_epsilon,
|
}
|
||||||
light_dir, light_distance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_shadow) {
|
Real n_dot_l = std::max(0.0f, glm::dot(hit.normal_, L));
|
||||||
// Lambertian diffuse
|
if (n_dot_l <= 0.0f) {
|
||||||
Real n_dot_l = glm::max(glm::dot(hit.normal_, light_dir), 0.0f);
|
continue;
|
||||||
lighting += light_color * n_dot_l;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return lighting;
|
||||||
}
|
}
|
||||||
|
|
||||||
Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
|
Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
|
||||||
// Random real generator
|
ARE_PROFILE_FUNCTION();
|
||||||
thread_local RandomGenerator generator;
|
|
||||||
|
|
||||||
const int num_samples = config_.ao_samples;
|
if (!bvh_) {
|
||||||
const Real radius = config_.ao_radius;
|
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) {
|
RandomGenerator& rng = get_thread_random();
|
||||||
// Generate random direction in hemisphere
|
|
||||||
Vec3 sample_dir = generator.random_in_hemisphere(hit.normal_);
|
|
||||||
|
|
||||||
// Cast AO ray
|
int occluded = 0;
|
||||||
Ray ao_ray(hit.position_ + hit.normal_ * are_epsilon, sample_dir,
|
for (int i = 0; i < ao_samples; ++i) {
|
||||||
are_epsilon, radius);
|
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)) {
|
if (bvh_->intersect_any(ao_ray, radius)) {
|
||||||
occluded_count++;
|
occluded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AO factor (1.0 = no occlusion, 0.0 = full occlusion)
|
Real occ = static_cast<Real>(occluded) / static_cast<Real>(ao_samples);
|
||||||
Real ao = 1.0f - (static_cast<Real>(occluded_count) / num_samples);
|
return 1.0f - occ;
|
||||||
return ao;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CPURayTracer::is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance) {
|
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);
|
ARE_PROFILE_FUNCTION();
|
||||||
return bvh_->intersect_any(shadow_ray, max_distance);
|
|
||||||
|
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
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,32 @@
|
||||||
/**
|
/**
|
||||||
* @file hit_record.cpp
|
* @file hit_record.cpp
|
||||||
* @brief Implementation of hit record
|
* @brief Implementation of HitRecord structure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/raytracer/hit_record.h>
|
#include <are/raytracer/hit_record.h>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
HitRecord::HitRecord()
|
HitRecord::HitRecord()
|
||||||
: position_(0.0f)
|
: position_(0.0f)
|
||||||
, normal_(0.0f, 1.0f, 0.0f)
|
, normal_(0.0f, 1.0f, 0.0f)
|
||||||
, texcoord_(0.0f)
|
, texcoord_(0.0f)
|
||||||
, tangent_(1.0f, 0.0f, 0.0f)
|
, tangent_(1.0f, 0.0f, 0.0f)
|
||||||
, t_(-1.0f)
|
, t_(std::numeric_limits<Real>::max())
|
||||||
, material_(are_invalid_handle)
|
, material_(are_invalid_handle)
|
||||||
, triangle_index_(0)
|
, triangle_index_(0)
|
||||||
, front_face_(true) {
|
, front_face_(true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HitRecord::set_face_normal(const Vec3 &ray_direction, const Vec3 &outward_normal) {
|
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;
|
||||||
front_face_ = glm::dot(ray_direction, outward_normal) < 0.0f;
|
normal_ = front_face_ ? outward_normal : -outward_normal;
|
||||||
|
|
||||||
// Normal always points against ray direction
|
|
||||||
normal_ = front_face_ ? outward_normal : -outward_normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HitRecord::is_valid() const {
|
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
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
/**
|
/**
|
||||||
* @file raytracer.cpp
|
* @file raytracer.cpp
|
||||||
* @brief Implementation of RayTracer base class
|
* @brief Implementation of RayTracer interface
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/raytracer/raytracer.h>
|
#include <are/raytracer/raytracer.h>
|
||||||
#include <are/core/logger.h>
|
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
RayTracer::RayTracer(const RayTracingConfig& config)
|
RayTracer::RayTracer(const RayTracingConfig& config)
|
||||||
: config_(config) {
|
: config_(config) {
|
||||||
ARE_LOG_INFO("RayTracer: Created with max depth " +
|
|
||||||
std::to_string(config_.max_depth));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RayTracer::set_config(const RayTracingConfig& config) {
|
void RayTracer::set_config(const RayTracingConfig& config) {
|
||||||
config_ = config;
|
config_ = config;
|
||||||
ARE_LOG_INFO("RayTracer: Configuration updated");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace are
|
} // 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