完成光栅化渲染G-Buffer
parent
0107df50cb
commit
c1c062180d
|
|
@ -181,6 +181,10 @@ if(ARE_BUILD_EXAMPLES)
|
||||||
|
|
||||||
# Add example subdirectories
|
# Add example subdirectories
|
||||||
add_subdirectory(examples/00_phase1_test)
|
add_subdirectory(examples/00_phase1_test)
|
||||||
|
add_subdirectory(examples/01_phase2_test)
|
||||||
|
add_subdirectory(examples/02_visual_test)
|
||||||
|
add_subdirectory(examples/02_phase3_test)
|
||||||
|
add_subdirectory(examples/03_phase4_test)
|
||||||
|
|
||||||
message(STATUS "Examples will be built")
|
message(STATUS "Examples will be built")
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Phase 2 verification example
|
||||||
|
|
||||||
|
add_are_example(phase2_test
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy to bin directory for easy execution
|
||||||
|
set_target_properties(phase2_test PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,326 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Phase 2 verification program
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <are/geometry/vertex.h>
|
||||||
|
#include <are/geometry/triangle.h>
|
||||||
|
#include <are/geometry/aabb.h>
|
||||||
|
#include <are/geometry/transform.h>
|
||||||
|
#include <are/scene/camera.h>
|
||||||
|
#include <are/scene/mesh.h>
|
||||||
|
#include <are/scene/material.h>
|
||||||
|
#include <are/scene/directional_light.h>
|
||||||
|
#include <are/scene/point_light.h>
|
||||||
|
#include <are/scene/spot_light.h>
|
||||||
|
#include <are/scene/scene_manager.h>
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace are;
|
||||||
|
|
||||||
|
// Test result tracking
|
||||||
|
struct TestResult {
|
||||||
|
std::string name;
|
||||||
|
bool passed;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TestResult> test_results;
|
||||||
|
|
||||||
|
void report_test(const std::string& name, bool passed, const std::string& message = "") {
|
||||||
|
test_results.push_back({name, passed, message});
|
||||||
|
if (passed) {
|
||||||
|
ARE_LOG_INFO("✓ " + name);
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("✗ " + name + ": " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Vertex operations
|
||||||
|
void test_vertex() {
|
||||||
|
Vertex v1(Vec3(1, 2, 3));
|
||||||
|
Vertex v2(Vec3(4, 5, 6), Vec3(0, 1, 0));
|
||||||
|
Vertex v3 = Vertex::lerp(v1, v2, 0.5f);
|
||||||
|
|
||||||
|
bool passed = glm::length(v3.position_ - Vec3(2.5f, 3.5f, 4.5f)) < are_epsilon;
|
||||||
|
report_test("Vertex interpolation", passed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: AABB operations
|
||||||
|
void test_aabb() {
|
||||||
|
AABB aabb1(Vec3(-1, -1, -1), Vec3(1, 1, 1));
|
||||||
|
AABB aabb2(Vec3(0, 0, 0), Vec3(2, 2, 2));
|
||||||
|
|
||||||
|
bool test1 = aabb1.is_valid();
|
||||||
|
bool test2 = aabb1.contains(Vec3(0, 0, 0));
|
||||||
|
bool test3 = aabb1.intersects(aabb2);
|
||||||
|
bool test4 = aabb1.longest_axis() == 0; // All axes equal
|
||||||
|
|
||||||
|
AABB merged = AABB::merge(aabb1, aabb2);
|
||||||
|
bool test5 = merged.contains(Vec3(-1, -1, -1)) && merged.contains(Vec3(2, 2, 2));
|
||||||
|
|
||||||
|
report_test("AABB validity", test1);
|
||||||
|
report_test("AABB contains point", test2);
|
||||||
|
report_test("AABB intersection", test3);
|
||||||
|
report_test("AABB merge", test5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Triangle operations
|
||||||
|
void test_triangle() {
|
||||||
|
Vertex v0(Vec3(0, 0, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v1(Vec3(1, 0, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v2(Vec3(0, 1, 0), Vec3(0, 0, 1));
|
||||||
|
|
||||||
|
Triangle tri(v0, v1, v2);
|
||||||
|
|
||||||
|
Vec3 centroid = tri.centroid();
|
||||||
|
bool test1 = glm::length(centroid - Vec3(1.0f/3.0f, 1.0f/3.0f, 0.0f)) < are_epsilon;
|
||||||
|
|
||||||
|
Vec3 normal = tri.normal();
|
||||||
|
bool test2 = glm::length(normal - Vec3(0, 0, 1)) < are_epsilon;
|
||||||
|
|
||||||
|
Real area = tri.area();
|
||||||
|
bool test3 = std::abs(area - 0.5f) < are_epsilon;
|
||||||
|
|
||||||
|
AABB aabb = tri.compute_aabb();
|
||||||
|
bool test4 = aabb.contains(Vec3(0, 0, 0)) && aabb.contains(Vec3(1, 0, 0));
|
||||||
|
|
||||||
|
report_test("Triangle centroid", test1);
|
||||||
|
report_test("Triangle normal", test2);
|
||||||
|
report_test("Triangle area", test3);
|
||||||
|
report_test("Triangle AABB", test4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Ray-Triangle intersection
|
||||||
|
void test_ray_triangle_intersection() {
|
||||||
|
Vertex v0(Vec3(0, 0, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v1(Vec3(1, 0, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v2(Vec3(0, 1, 0), Vec3(0, 0, 1));
|
||||||
|
|
||||||
|
Triangle tri(v0, v1, v2);
|
||||||
|
|
||||||
|
// Ray hitting the triangle
|
||||||
|
Ray ray1(Vec3(0.25f, 0.25f, -1.0f), Vec3(0, 0, 1));
|
||||||
|
HitRecord hit1;
|
||||||
|
bool test1 = tri.intersect(ray1, hit1);
|
||||||
|
|
||||||
|
// Ray missing the triangle
|
||||||
|
Ray ray2(Vec3(2, 2, -1), Vec3(0, 0, 1));
|
||||||
|
HitRecord hit2;
|
||||||
|
bool test2 = !tri.intersect(ray2, hit2);
|
||||||
|
|
||||||
|
report_test("Ray-Triangle hit", test1);
|
||||||
|
report_test("Ray-Triangle miss", test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Transform operations
|
||||||
|
void test_transform() {
|
||||||
|
Transform t1 = Transform::translate(Vec3(1, 2, 3));
|
||||||
|
Transform t2 = Transform::rotate(Vec3(0, are_pi / 2, 0));
|
||||||
|
Transform t3 = Transform::scale(Vec3(2, 2, 2));
|
||||||
|
|
||||||
|
Vec3 point = Vec3(1, 0, 0);
|
||||||
|
Vec3 transformed = t1.transform_point(point);
|
||||||
|
bool test1 = glm::length(transformed - Vec3(2, 2, 3)) < are_epsilon;
|
||||||
|
|
||||||
|
Vec3 scaled = t3.transform_point(point);
|
||||||
|
bool test2 = glm::length(scaled - Vec3(2, 0, 0)) < are_epsilon;
|
||||||
|
|
||||||
|
report_test("Transform translation", test1);
|
||||||
|
report_test("Transform scale", test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Camera operations
|
||||||
|
void test_camera() {
|
||||||
|
Camera camera(Vec3(0, 0, 5), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, 16.0f / 9.0f, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
Vec3 forward = camera.get_forward();
|
||||||
|
bool test1 = glm::length(forward - Vec3(0, 0, -1)) < are_epsilon;
|
||||||
|
|
||||||
|
Vec3 origin, direction;
|
||||||
|
camera.generate_ray(0.5f, 0.5f, origin, direction);
|
||||||
|
bool test2 = glm::length(origin - Vec3(0, 0, 5)) < are_epsilon;
|
||||||
|
bool test3 = glm::length(direction - Vec3(0, 0, -1)) < 0.1f; // Approximate
|
||||||
|
|
||||||
|
report_test("Camera forward vector", test1);
|
||||||
|
report_test("Camera ray generation origin", test2);
|
||||||
|
report_test("Camera ray generation direction", test3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Mesh operations
|
||||||
|
void test_mesh() {
|
||||||
|
std::vector<Vertex> vertices = {
|
||||||
|
Vertex(Vec3(0, 0, 0), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(1, 0, 0), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(0, 1, 0), Vec3(0, 0, 1))
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices = {0, 1, 2};
|
||||||
|
|
||||||
|
Mesh mesh(vertices, indices);
|
||||||
|
|
||||||
|
bool test1 = mesh.get_vertex_count() == 3;
|
||||||
|
bool test2 = mesh.get_triangle_count() == 1;
|
||||||
|
bool test3 = mesh.get_aabb().is_valid();
|
||||||
|
|
||||||
|
Vertex v0, v1, v2;
|
||||||
|
bool test4 = mesh.get_triangle(0, v0, v1, v2);
|
||||||
|
|
||||||
|
report_test("Mesh vertex count", test1);
|
||||||
|
report_test("Mesh triangle count", test2);
|
||||||
|
report_test("Mesh AABB", test3);
|
||||||
|
report_test("Mesh get triangle", test4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Material operations
|
||||||
|
void test_material() {
|
||||||
|
Material mat;
|
||||||
|
mat.set_albedo(Vec3(0.8f, 0.2f, 0.1f));
|
||||||
|
mat.set_metallic(0.5f);
|
||||||
|
mat.set_roughness(0.3f);
|
||||||
|
mat.set_emissive(Vec3(1.0f, 0.5f, 0.0f));
|
||||||
|
|
||||||
|
bool test1 = glm::length(mat.get_albedo() - Vec3(0.8f, 0.2f, 0.1f)) < are_epsilon;
|
||||||
|
bool test2 = std::abs(mat.get_metallic() - 0.5f) < are_epsilon;
|
||||||
|
bool test3 = mat.is_emissive();
|
||||||
|
|
||||||
|
mat.set_albedo_map("textures/albedo.png");
|
||||||
|
bool test4 = mat.has_albedo_map();
|
||||||
|
|
||||||
|
report_test("Material albedo", test1);
|
||||||
|
report_test("Material metallic", test2);
|
||||||
|
report_test("Material emissive", test3);
|
||||||
|
report_test("Material texture map", test4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: Light operations
|
||||||
|
void test_lights() {
|
||||||
|
// Directional light
|
||||||
|
DirectionalLight dir_light(Vec3(0, -1, 0), Vec3(1, 1, 1), 1.0f);
|
||||||
|
bool test1 = dir_light.affects_point(Vec3(100, 100, 100));
|
||||||
|
|
||||||
|
// Point light
|
||||||
|
PointLight point_light(Vec3(0, 0, 0), Vec3(1, 1, 1), 1.0f, 10.0f);
|
||||||
|
bool test2 = point_light.affects_point(Vec3(5, 0, 0));
|
||||||
|
bool test3 = !point_light.affects_point(Vec3(20, 0, 0));
|
||||||
|
|
||||||
|
// Spot light
|
||||||
|
SpotLight spot_light(Vec3(0, 0, 0), Vec3(0, 0, -1), 30.0f, 45.0f);
|
||||||
|
bool test4 = spot_light.affects_point(Vec3(0, 0, -5));
|
||||||
|
|
||||||
|
report_test("Directional light affects all points", test1);
|
||||||
|
report_test("Point light range (inside)", test2);
|
||||||
|
report_test("Point light range (outside)", test3);
|
||||||
|
report_test("Spot light cone", test4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 10: SceneManager operations
|
||||||
|
void test_scene_manager() {
|
||||||
|
SceneManager scene;
|
||||||
|
|
||||||
|
// Add mesh
|
||||||
|
std::vector<Vertex> vertices = {
|
||||||
|
Vertex(Vec3(0, 0, 0)),
|
||||||
|
Vertex(Vec3(1, 0, 0)),
|
||||||
|
Vertex(Vec3(0, 1, 0))
|
||||||
|
};
|
||||||
|
std::vector<uint32_t> indices = {0, 1, 2};
|
||||||
|
Mesh mesh(vertices, indices);
|
||||||
|
|
||||||
|
MeshHandle mesh_handle = scene.add_mesh(mesh);
|
||||||
|
bool test1 = mesh_handle != are_invalid_handle;
|
||||||
|
bool test2 = scene.get_mesh_count() == 1;
|
||||||
|
|
||||||
|
// Add material
|
||||||
|
Material mat;
|
||||||
|
MaterialHandle mat_handle = scene.add_material(mat);
|
||||||
|
bool test3 = mat_handle != are_invalid_handle;
|
||||||
|
bool test4 = scene.get_material_count() == 1;
|
||||||
|
|
||||||
|
// Add light
|
||||||
|
auto light = std::make_shared<DirectionalLight>();
|
||||||
|
LightHandle light_handle = scene.add_light(light);
|
||||||
|
bool test5 = light_handle != are_invalid_handle;
|
||||||
|
bool test6 = scene.get_light_count() == 1;
|
||||||
|
|
||||||
|
// Test dirty flag
|
||||||
|
bool test7 = scene.is_dirty();
|
||||||
|
scene.clear_dirty();
|
||||||
|
bool test8 = !scene.is_dirty();
|
||||||
|
|
||||||
|
// Remove mesh
|
||||||
|
scene.remove_mesh(mesh_handle);
|
||||||
|
bool test9 = scene.get_mesh_count() == 0;
|
||||||
|
|
||||||
|
report_test("SceneManager add mesh", test1);
|
||||||
|
report_test("SceneManager mesh count", test2);
|
||||||
|
report_test("SceneManager add material", test3);
|
||||||
|
report_test("SceneManager material count", test4);
|
||||||
|
report_test("SceneManager add light", test5);
|
||||||
|
report_test("SceneManager light count", test6);
|
||||||
|
report_test("SceneManager dirty flag (set)", test7);
|
||||||
|
report_test("SceneManager dirty flag (clear)", test8);
|
||||||
|
report_test("SceneManager remove mesh", test9);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize logger
|
||||||
|
Logger::init(LogLevel::ARE_LOG_INFO);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Phase 2 Verification Program");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
test_vertex();
|
||||||
|
test_aabb();
|
||||||
|
test_triangle();
|
||||||
|
test_ray_triangle_intersection();
|
||||||
|
test_transform();
|
||||||
|
test_camera();
|
||||||
|
test_mesh();
|
||||||
|
test_material();
|
||||||
|
test_lights();
|
||||||
|
test_scene_manager();
|
||||||
|
|
||||||
|
// Print summary
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Test Summary");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
int passed = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
for (const auto& result : test_results) {
|
||||||
|
if (result.passed) {
|
||||||
|
++passed;
|
||||||
|
} else {
|
||||||
|
++failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Total tests: " + std::to_string(test_results.size()));
|
||||||
|
ARE_LOG_INFO("Passed: " + std::to_string(passed));
|
||||||
|
ARE_LOG_INFO("Failed: " + std::to_string(failed));
|
||||||
|
|
||||||
|
if (failed == 0) {
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("✓ All Phase 2 tests passed!");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("========================================");
|
||||||
|
ARE_LOG_ERROR("✗ Some tests failed. Please review.");
|
||||||
|
ARE_LOG_ERROR("========================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::shutdown();
|
||||||
|
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Phase 3 verification example
|
||||||
|
|
||||||
|
add_are_example(phase3_test
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy to bin directory for easy execution
|
||||||
|
set_target_properties(phase3_test PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy shaders to build directory
|
||||||
|
add_custom_command(TARGET phase3_test POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
${CMAKE_SOURCE_DIR}/shaders
|
||||||
|
${CMAKE_BINARY_DIR}/bin/shaders
|
||||||
|
COMMENT "Copying shaders to build directory"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,478 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Phase 3 verification program - G-Buffer rendering test
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/geometry/vertex.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/material.h>
|
||||||
|
#include <are/scene/mesh.h>
|
||||||
|
#include <are/scene/scene_manager.h>
|
||||||
|
#include <are/utils/file_utils.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include "../lib/glad/glad/glad.h"
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
using namespace are;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a simple cube mesh
|
||||||
|
*/
|
||||||
|
Mesh create_cube_mesh() {
|
||||||
|
std::vector<Vertex> vertices = {
|
||||||
|
// Front face
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, 0.5f), Vec3(0, 0, 1), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, 0.5f), Vec3(0, 0, 1), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, 0.5f), Vec3(0, 0, 1), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, 0.5f), Vec3(0, 0, 1), Vec2(0, 1)),
|
||||||
|
|
||||||
|
// Back face
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, -0.5f), Vec3(0, 0, -1), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, -0.5f), Vec3(0, 0, -1), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, -0.5f), Vec3(0, 0, -1), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, -0.5f), Vec3(0, 0, -1), Vec2(0, 1)),
|
||||||
|
|
||||||
|
// Top face
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, 0.5f), Vec3(0, 1, 0), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, 0.5f), Vec3(0, 1, 0), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, -0.5f), Vec3(0, 1, 0), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, -0.5f), Vec3(0, 1, 0), Vec2(0, 1)),
|
||||||
|
|
||||||
|
// Bottom face
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, -0.5f), Vec3(0, -1, 0), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, -0.5f), Vec3(0, -1, 0), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, 0.5f), Vec3(0, -1, 0), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, 0.5f), Vec3(0, -1, 0), Vec2(0, 1)),
|
||||||
|
|
||||||
|
// Right face
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, 0.5f), Vec3(1, 0, 0), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(0.5f, -0.5f, -0.5f), Vec3(1, 0, 0), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, -0.5f), Vec3(1, 0, 0), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(0.5f, 0.5f, 0.5f), Vec3(1, 0, 0), Vec2(0, 1)),
|
||||||
|
|
||||||
|
// Left face
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, -0.5f), Vec3(-1, 0, 0), Vec2(0, 0)),
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, 0.5f), Vec3(-1, 0, 0), Vec2(1, 0)),
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, 0.5f), Vec3(-1, 0, 0), Vec2(1, 1)),
|
||||||
|
Vertex(Vec3(-0.5f, 0.5f, -0.5f), Vec3(-1, 0, 0), Vec2(0, 1))
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices = {
|
||||||
|
// Front
|
||||||
|
0, 1, 2, 2, 3, 0,
|
||||||
|
// Back
|
||||||
|
4, 5, 6, 6, 7, 4,
|
||||||
|
// Top
|
||||||
|
8, 9, 10, 10, 11, 8,
|
||||||
|
// Bottom
|
||||||
|
12, 13, 14, 14, 15, 12,
|
||||||
|
// Right
|
||||||
|
16, 17, 18, 18, 19, 16,
|
||||||
|
// Left
|
||||||
|
20, 21, 22, 22, 23, 20
|
||||||
|
};
|
||||||
|
|
||||||
|
return Mesh(vertices, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a simple triangle mesh (positioned in front of cube)
|
||||||
|
*/
|
||||||
|
Mesh create_triangle_mesh() {
|
||||||
|
std::vector<Vertex> vertices = {
|
||||||
|
Vertex(Vec3(-0.5f, -0.5f, 1.0f), Vec3(0, 0, 1), Vec2(0, 0)), // Z = 1.0
|
||||||
|
Vertex(Vec3( 0.5f, -0.5f, 1.0f), Vec3(0, 0, 1), Vec2(1, 0)), // Z = 1.0
|
||||||
|
Vertex(Vec3( 0.0f, 0.5f, 1.0f), Vec3(0, 0, 1), Vec2(0.5f, 1)) // Z = 1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices = {0, 1, 2};
|
||||||
|
|
||||||
|
return Mesh(vertices, indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fullscreen quad shader for G-Buffer visualization
|
||||||
|
*/
|
||||||
|
const char *fullscreen_vert_source = R"(
|
||||||
|
#version 430 core
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texcoord;
|
||||||
|
out vec2 v_texcoord;
|
||||||
|
void main() {
|
||||||
|
v_texcoord = a_texcoord;
|
||||||
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char *visualize_frag_source = R"(
|
||||||
|
#version 430 core
|
||||||
|
in vec2 v_texcoord;
|
||||||
|
out vec4 frag_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform int u_mode; // 0=position, 1=normal, 2=albedo, 3=material
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 value = texture(u_texture, v_texcoord);
|
||||||
|
|
||||||
|
if (u_mode == 0) {
|
||||||
|
// Position: normalize to [0,1] range for visualization
|
||||||
|
frag_color = vec4(value.xyz * 0.5 + 0.5, 1.0);
|
||||||
|
} else if (u_mode == 1) {
|
||||||
|
// Normal: normalize to [0,1] range
|
||||||
|
frag_color = vec4(value.xyz * 0.5 + 0.5, 1.0);
|
||||||
|
} else if (u_mode == 2) {
|
||||||
|
// Albedo: direct output
|
||||||
|
frag_color = vec4(value.rgb, 1.0);
|
||||||
|
} else if (u_mode == 3) {
|
||||||
|
// Material: roughness in R, AO in G
|
||||||
|
frag_color = vec4(value.r, value.g, 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
frag_color = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create fullscreen quad VAO
|
||||||
|
*/
|
||||||
|
uint32_t create_fullscreen_quad() {
|
||||||
|
float vertices[] = {
|
||||||
|
// Position // Texcoord
|
||||||
|
-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, vbo, ebo;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return vao;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize logger
|
||||||
|
Logger::init(LogLevel::ARE_LOG_DEBUG);
|
||||||
|
Profiler::init();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Phase 3 Verification Program");
|
||||||
|
ARE_LOG_INFO("G-Buffer Rendering Test");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
// Create window configuration
|
||||||
|
WindowConfig window_config;
|
||||||
|
window_config.width = 800;
|
||||||
|
window_config.height = 600;
|
||||||
|
window_config.title = "Phase 3 - G-Buffer Test";
|
||||||
|
window_config.vsync = true;
|
||||||
|
|
||||||
|
// Create window
|
||||||
|
Window window(window_config);
|
||||||
|
|
||||||
|
// Initialize OpenGL
|
||||||
|
if (!GLContext::initialize()) {
|
||||||
|
ARE_LOG_CRITICAL("Failed to initialize OpenGL context");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLContext::print_info();
|
||||||
|
|
||||||
|
// Get shader directory (relative to executable)
|
||||||
|
std::string shader_dir = "shaders/";
|
||||||
|
|
||||||
|
// Create rasterizer
|
||||||
|
int fb_width, fb_height;
|
||||||
|
window.get_framebuffer_size(fb_width, fb_height);
|
||||||
|
Rasterizer rasterizer(fb_width, fb_height);
|
||||||
|
|
||||||
|
// Initialize shaders
|
||||||
|
// First, create G-Buffer shader manually for testing
|
||||||
|
ShaderProgram gbuffer_shader;
|
||||||
|
|
||||||
|
// Try to load from file first
|
||||||
|
bool shader_loaded = false;
|
||||||
|
if (file_exists(shader_dir + "gbuffer/gbuffer.vert") && file_exists(shader_dir + "gbuffer/gbuffer.frag")) {
|
||||||
|
if (gbuffer_shader.load_shader(ShaderType::ARE_SHADER_VERTEX, shader_dir + "gbuffer/gbuffer.vert") && gbuffer_shader.load_shader(ShaderType::ARE_SHADER_FRAGMENT, shader_dir + "gbuffer/gbuffer.frag") && gbuffer_shader.link()) {
|
||||||
|
shader_loaded = true;
|
||||||
|
ARE_LOG_INFO("Loaded G-Buffer shaders from files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to embedded shaders
|
||||||
|
if (!shader_loaded) {
|
||||||
|
ARE_LOG_WARN("Shader files not found, using embedded shaders");
|
||||||
|
|
||||||
|
const char *gbuffer_vert = R"(
|
||||||
|
#version 430 core
|
||||||
|
layout(location = 0) in vec3 a_position;
|
||||||
|
layout(location = 1) in vec3 a_normal;
|
||||||
|
layout(location = 2) in vec2 a_texcoord;
|
||||||
|
layout(location = 3) in vec3 a_tangent;
|
||||||
|
|
||||||
|
uniform mat4 u_model;
|
||||||
|
uniform mat4 u_view;
|
||||||
|
uniform mat4 u_projection;
|
||||||
|
uniform mat3 u_normal_matrix;
|
||||||
|
|
||||||
|
out vec3 v_world_position;
|
||||||
|
out vec3 v_world_normal;
|
||||||
|
out vec2 v_texcoord;
|
||||||
|
out vec3 v_world_tangent;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 world_pos = u_model * vec4(a_position, 1.0);
|
||||||
|
v_world_position = world_pos.xyz;
|
||||||
|
v_world_normal = normalize(u_normal_matrix * a_normal);
|
||||||
|
v_world_tangent = normalize(u_normal_matrix * a_tangent);
|
||||||
|
v_texcoord = a_texcoord;
|
||||||
|
gl_Position = u_projection * u_view * world_pos;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char *gbuffer_frag = R"(
|
||||||
|
#version 430 core
|
||||||
|
in vec3 v_world_position;
|
||||||
|
in vec3 v_world_normal;
|
||||||
|
in vec2 v_texcoord;
|
||||||
|
in vec3 v_world_tangent;
|
||||||
|
|
||||||
|
uniform vec3 u_albedo;
|
||||||
|
uniform float u_metallic;
|
||||||
|
uniform float u_roughness;
|
||||||
|
|
||||||
|
layout(location = 0) out vec3 g_position;
|
||||||
|
layout(location = 1) out vec3 g_normal;
|
||||||
|
layout(location = 2) out vec4 g_albedo_metallic;
|
||||||
|
layout(location = 3) out vec2 g_roughness_ao;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
g_position = v_world_position;
|
||||||
|
g_normal = normalize(v_world_normal);
|
||||||
|
g_albedo_metallic = vec4(u_albedo, u_metallic);
|
||||||
|
g_roughness_ao = vec2(u_roughness, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!gbuffer_shader.compile_shader(ShaderType::ARE_SHADER_VERTEX, gbuffer_vert) || !gbuffer_shader.compile_shader(ShaderType::ARE_SHADER_FRAGMENT, gbuffer_frag) || !gbuffer_shader.link()) {
|
||||||
|
ARE_LOG_CRITICAL("Failed to compile embedded G-Buffer shaders");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create visualization shader
|
||||||
|
ShaderProgram vis_shader;
|
||||||
|
if (!vis_shader.compile_shader(ShaderType::ARE_SHADER_VERTEX, fullscreen_vert_source) || !vis_shader.compile_shader(ShaderType::ARE_SHADER_FRAGMENT, visualize_frag_source) || !vis_shader.link()) {
|
||||||
|
ARE_LOG_CRITICAL("Failed to compile visualization shaders");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create fullscreen quad
|
||||||
|
uint32_t fullscreen_quad_vao = create_fullscreen_quad();
|
||||||
|
|
||||||
|
// Create scene
|
||||||
|
SceneManager scene;
|
||||||
|
|
||||||
|
// Create materials
|
||||||
|
Material red_material;
|
||||||
|
red_material.set_albedo(Vec3(0.8f, 0.2f, 0.2f));
|
||||||
|
red_material.set_metallic(0.0f);
|
||||||
|
red_material.set_roughness(0.5f);
|
||||||
|
MaterialHandle red_mat_handle = scene.add_material(red_material);
|
||||||
|
|
||||||
|
Material green_material;
|
||||||
|
green_material.set_albedo(Vec3(0.2f, 0.8f, 0.2f));
|
||||||
|
green_material.set_metallic(0.5f);
|
||||||
|
green_material.set_roughness(0.3f);
|
||||||
|
MaterialHandle green_mat_handle = scene.add_material(green_material);
|
||||||
|
|
||||||
|
// Create meshes
|
||||||
|
Mesh cube = create_cube_mesh();
|
||||||
|
cube.set_material(red_mat_handle);
|
||||||
|
cube.compute_tangents();
|
||||||
|
|
||||||
|
Mesh triangle = create_triangle_mesh();
|
||||||
|
triangle.set_material(green_mat_handle);
|
||||||
|
triangle.compute_tangents();
|
||||||
|
|
||||||
|
// Upload meshes to GPU
|
||||||
|
rasterizer.upload_mesh(cube);
|
||||||
|
rasterizer.upload_mesh(triangle);
|
||||||
|
|
||||||
|
// Add meshes to scene
|
||||||
|
scene.add_mesh(cube);
|
||||||
|
scene.add_mesh(triangle);
|
||||||
|
|
||||||
|
// Create camera
|
||||||
|
Camera camera(Vec3(0, 0, 3), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, static_cast<float>(fb_width) / fb_height, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
// Visualization mode (0=position, 1=normal, 2=albedo, 3=material)
|
||||||
|
int vis_mode = 2; // Start with albedo
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Controls:");
|
||||||
|
ARE_LOG_INFO(" 1 - View Position buffer");
|
||||||
|
ARE_LOG_INFO(" 2 - View Normal buffer");
|
||||||
|
ARE_LOG_INFO(" 3 - View Albedo buffer");
|
||||||
|
ARE_LOG_INFO(" 4 - View Material buffer (Roughness/AO)");
|
||||||
|
ARE_LOG_INFO(" ESC - Exit");
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
float time = 0.0f;
|
||||||
|
while (!window.should_close()) {
|
||||||
|
ARE_PROFILE_SCOPE("Frame");
|
||||||
|
|
||||||
|
// Poll events
|
||||||
|
window.poll_events();
|
||||||
|
|
||||||
|
// Handle input
|
||||||
|
if (window.is_key_pressed(256)) { // ESC
|
||||||
|
window.set_should_close(true);
|
||||||
|
}
|
||||||
|
if (window.is_key_pressed(49))
|
||||||
|
vis_mode = 0; // 1 - Position
|
||||||
|
if (window.is_key_pressed(50))
|
||||||
|
vis_mode = 1; // 2 - Normal
|
||||||
|
if (window.is_key_pressed(51))
|
||||||
|
vis_mode = 2; // 3 - Albedo
|
||||||
|
if (window.is_key_pressed(52))
|
||||||
|
vis_mode = 3; // 4 - Material
|
||||||
|
|
||||||
|
// Update camera position (orbit around origin)
|
||||||
|
time += 0.016f;
|
||||||
|
float cam_x = std::sin(time * 0.5f) * 3.0f;
|
||||||
|
float cam_z = std::cos(time * 0.5f) * 3.0f;
|
||||||
|
camera.set_position(Vec3(cam_x, 1.5f, cam_z));
|
||||||
|
camera.set_target(Vec3(0, 0, 0));
|
||||||
|
|
||||||
|
// Render to G-Buffer
|
||||||
|
{
|
||||||
|
ARE_PROFILE_SCOPE("Render G-Buffer");
|
||||||
|
|
||||||
|
// Manually render since we're using our own shader
|
||||||
|
GBuffer &gbuffer = rasterizer.get_gbuffer();
|
||||||
|
gbuffer.bind();
|
||||||
|
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
|
||||||
|
gbuffer_shader.use();
|
||||||
|
gbuffer_shader.set_uniform("u_view", camera.get_view_matrix());
|
||||||
|
gbuffer_shader.set_uniform("u_projection", camera.get_projection_matrix());
|
||||||
|
|
||||||
|
// Render all meshes
|
||||||
|
const auto &meshes = scene.get_all_meshes();
|
||||||
|
const auto &materials = scene.get_all_materials();
|
||||||
|
|
||||||
|
for (const auto &mesh : meshes) {
|
||||||
|
if (!mesh.has_gpu_resources())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Mat4 model = Mat4(1.0f);
|
||||||
|
gbuffer_shader.set_uniform("u_model", model);
|
||||||
|
|
||||||
|
Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(model)));
|
||||||
|
gbuffer_shader.set_uniform("u_normal_matrix", normal_matrix);
|
||||||
|
|
||||||
|
MaterialHandle mat_handle = mesh.get_material();
|
||||||
|
if (mat_handle != are_invalid_handle && mat_handle <= materials.size()) {
|
||||||
|
const Material &mat = materials[mat_handle - 1];
|
||||||
|
gbuffer_shader.set_uniform("u_albedo", mat.get_albedo());
|
||||||
|
gbuffer_shader.set_uniform("u_metallic", mat.get_metallic());
|
||||||
|
gbuffer_shader.set_uniform("u_roughness", mat.get_roughness());
|
||||||
|
} else {
|
||||||
|
gbuffer_shader.set_uniform("u_albedo", Vec3(0.8f));
|
||||||
|
gbuffer_shader.set_uniform("u_metallic", 0.0f);
|
||||||
|
gbuffer_shader.set_uniform("u_roughness", 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(mesh.get_vao());
|
||||||
|
glDrawElements(GL_TRIANGLES,
|
||||||
|
static_cast<GLsizei>(mesh.get_index_count()),
|
||||||
|
GL_UNSIGNED_INT,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
gbuffer.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visualize G-Buffer
|
||||||
|
{
|
||||||
|
ARE_PROFILE_SCOPE("Visualize G-Buffer");
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glViewport(0, 0, fb_width, fb_height);
|
||||||
|
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
vis_shader.use();
|
||||||
|
vis_shader.set_uniform("u_mode", vis_mode);
|
||||||
|
vis_shader.set_uniform("u_texture", 0);
|
||||||
|
|
||||||
|
// Bind appropriate G-Buffer texture
|
||||||
|
GBuffer &gbuffer = rasterizer.get_gbuffer();
|
||||||
|
gbuffer.bind_texture(vis_mode, 0);
|
||||||
|
|
||||||
|
glBindVertexArray(fullscreen_quad_vao);
|
||||||
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap buffers
|
||||||
|
window.swap_buffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
glDeleteVertexArrays(1, &fullscreen_quad_vao);
|
||||||
|
|
||||||
|
// Print profiling results
|
||||||
|
Profiler::print_results();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Phase 3 test completed successfully!");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
Profiler::shutdown();
|
||||||
|
Logger::shutdown();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Phase 2 visual verification example
|
||||||
|
|
||||||
|
add_are_example(visual_test
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy to bin directory
|
||||||
|
set_target_properties(visual_test PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,329 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Visual verification using software rasterization
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/geometry/triangle.h>
|
||||||
|
#include <are/geometry/vertex.h>
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <are/scene/camera.h>
|
||||||
|
#include <are/scene/material.h>
|
||||||
|
#include <are/scene/mesh.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "../lib/stb/stb_image_write.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace are;
|
||||||
|
|
||||||
|
// Simple framebuffer
|
||||||
|
struct Framebuffer {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::vector<uint8_t> pixels; // RGB format
|
||||||
|
|
||||||
|
Framebuffer(int w, int h) : width(w), height(h) {
|
||||||
|
pixels.resize(w * h * 3, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||||
|
return;
|
||||||
|
int index = (y * width + x) * 3;
|
||||||
|
pixels[index + 0] = r;
|
||||||
|
pixels[index + 1] = g;
|
||||||
|
pixels[index + 2] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_pixel(int x, int y, const Vec3 &color) {
|
||||||
|
uint8_t r = static_cast<uint8_t>(std::min(color.x * 255.0f, 255.0f));
|
||||||
|
uint8_t g = static_cast<uint8_t>(std::min(color.y * 255.0f, 255.0f));
|
||||||
|
uint8_t b = static_cast<uint8_t>(std::min(color.z * 255.0f, 255.0f));
|
||||||
|
set_pixel(x, y, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save(const std::string &filename) {
|
||||||
|
return stbi_write_png(filename.c_str(), width, height, 3,
|
||||||
|
pixels.data(), width * 3)
|
||||||
|
!= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple shading function
|
||||||
|
Vec3 shade_hit(const HitRecord &hit, const Vec3 &light_dir) {
|
||||||
|
// Lambertian shading
|
||||||
|
float ndotl = std::max(0.0f, glm::dot(hit.normal_, light_dir));
|
||||||
|
|
||||||
|
// Base color based on normal (for visualization)
|
||||||
|
Vec3 base_color = (hit.normal_ + Vec3(1.0f)) * 0.5f;
|
||||||
|
|
||||||
|
// Apply lighting
|
||||||
|
Vec3 ambient = base_color * 0.2f;
|
||||||
|
Vec3 diffuse = base_color * ndotl * 0.8f;
|
||||||
|
|
||||||
|
return ambient + diffuse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a single triangle
|
||||||
|
void render_triangle(Framebuffer &fb, const Triangle &tri, Camera &camera) {
|
||||||
|
Vec3 light_dir = glm::normalize(Vec3(0.5f, 1.0f, 0.5f));
|
||||||
|
|
||||||
|
for (int y = 0; y < fb.height; ++y) {
|
||||||
|
for (int x = 0; x < fb.width; ++x) {
|
||||||
|
// Generate ray
|
||||||
|
float u = (x + 0.5f) / fb.width;
|
||||||
|
float v = (y + 0.5f) / fb.height;
|
||||||
|
|
||||||
|
Vec3 origin, direction;
|
||||||
|
camera.generate_ray(u, v, origin, direction);
|
||||||
|
Ray ray(origin, direction);
|
||||||
|
|
||||||
|
// Test intersection
|
||||||
|
HitRecord hit;
|
||||||
|
if (tri.intersect(ray, hit)) {
|
||||||
|
Vec3 color = shade_hit(hit, light_dir);
|
||||||
|
fb.set_pixel(x, y, color);
|
||||||
|
} else {
|
||||||
|
// Background gradient
|
||||||
|
Vec3 bg_color = Vec3(0.5f, 0.7f, 1.0f) * (1.0f - v) + Vec3(1.0f, 1.0f, 1.0f) * v;
|
||||||
|
fb.set_pixel(x, y, bg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render multiple triangles (mesh)
|
||||||
|
void render_mesh(Framebuffer &fb, const Mesh &mesh, Camera &camera) {
|
||||||
|
Vec3 light_dir = glm::normalize(Vec3(0.5f, 1.0f, 0.5f));
|
||||||
|
|
||||||
|
for (int y = 0; y < fb.height; ++y) {
|
||||||
|
for (int x = 0; x < fb.width; ++x) {
|
||||||
|
// Generate ray
|
||||||
|
float u = (x + 0.5f) / fb.width;
|
||||||
|
float v = (y + 0.5f) / fb.height;
|
||||||
|
|
||||||
|
Vec3 origin, direction;
|
||||||
|
camera.generate_ray(u, v, origin, direction);
|
||||||
|
Ray ray(origin, direction);
|
||||||
|
|
||||||
|
// Test intersection with all triangles
|
||||||
|
HitRecord closest_hit;
|
||||||
|
closest_hit.t_ = ray.t_max_;
|
||||||
|
bool hit_any = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mesh.get_triangle_count(); ++i) {
|
||||||
|
Vertex v0, v1, v2;
|
||||||
|
if (mesh.get_triangle(i, v0, v1, v2)) {
|
||||||
|
Triangle tri(v0, v1, v2);
|
||||||
|
HitRecord hit;
|
||||||
|
if (tri.intersect(ray, hit) && hit.t_ < closest_hit.t_) {
|
||||||
|
closest_hit = hit;
|
||||||
|
hit_any = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit_any) {
|
||||||
|
Vec3 color = shade_hit(closest_hit, light_dir);
|
||||||
|
fb.set_pixel(x, y, color);
|
||||||
|
} else {
|
||||||
|
// Background gradient
|
||||||
|
Vec3 bg_color = Vec3(0.5f, 0.7f, 1.0f) * (1.0f - v) + Vec3(1.0f, 1.0f, 1.0f) * v;
|
||||||
|
fb.set_pixel(x, y, bg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Logger::init(LogLevel::ARE_LOG_INFO);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Phase 2 Visual Verification");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
const int width = 800;
|
||||||
|
const int height = 600;
|
||||||
|
|
||||||
|
// Test 1: Single triangle
|
||||||
|
{
|
||||||
|
ARE_LOG_INFO("Rendering single triangle...");
|
||||||
|
|
||||||
|
Framebuffer fb(width, height);
|
||||||
|
|
||||||
|
// Create triangle
|
||||||
|
Vertex v0(Vec3(-1, -1, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v1(Vec3(1, -1, 0), Vec3(0, 0, 1));
|
||||||
|
Vertex v2(Vec3(0, 1, 0), Vec3(0, 0, 1));
|
||||||
|
Triangle tri(v0, v1, v2);
|
||||||
|
|
||||||
|
// Setup camera
|
||||||
|
Camera camera(Vec3(0, 0, 3), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, (float)width / height, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
// Render
|
||||||
|
render_triangle(fb, tri, camera);
|
||||||
|
|
||||||
|
// Save
|
||||||
|
if (fb.save("output_triangle.png")) {
|
||||||
|
ARE_LOG_INFO("✓ Saved: output_triangle.png");
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("✗ Failed to save output_triangle.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Colored triangle (using normals)
|
||||||
|
{
|
||||||
|
ARE_LOG_INFO("Rendering colored triangle...");
|
||||||
|
|
||||||
|
Framebuffer fb(width, height);
|
||||||
|
|
||||||
|
// Create triangle with different normals for each vertex
|
||||||
|
Vertex v0(Vec3(-1, -1, 0), Vec3(1, 0, 0)); // Red
|
||||||
|
Vertex v1(Vec3(1, -1, 0), Vec3(0, 1, 0)); // Green
|
||||||
|
Vertex v2(Vec3(0, 1, 0), Vec3(0, 0, 1)); // Blue
|
||||||
|
Triangle tri(v0, v1, v2);
|
||||||
|
|
||||||
|
Camera camera(Vec3(0, 0, 3), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, (float)width / height, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
render_triangle(fb, tri, camera);
|
||||||
|
|
||||||
|
if (fb.save("output_colored_triangle.png")) {
|
||||||
|
ARE_LOG_INFO("✓ Saved: output_colored_triangle.png");
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("✗ Failed to save output_colored_triangle.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Cube (mesh with multiple triangles)
|
||||||
|
{
|
||||||
|
ARE_LOG_INFO("Rendering cube...");
|
||||||
|
|
||||||
|
Framebuffer fb(width, height);
|
||||||
|
|
||||||
|
// Create cube vertices
|
||||||
|
std::vector<Vertex> vertices = {
|
||||||
|
// Front face
|
||||||
|
Vertex(Vec3(-1, -1, 1), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(1, -1, 1), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(1, 1, 1), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(-1, 1, 1), Vec3(0, 0, 1)),
|
||||||
|
// Back face
|
||||||
|
Vertex(Vec3(-1, -1, -1), Vec3(0, 0, -1)),
|
||||||
|
Vertex(Vec3(1, -1, -1), Vec3(0, 0, -1)),
|
||||||
|
Vertex(Vec3(1, 1, -1), Vec3(0, 0, -1)),
|
||||||
|
Vertex(Vec3(-1, 1, -1), Vec3(0, 0, -1)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create cube indices
|
||||||
|
std::vector<uint32_t> indices = {
|
||||||
|
// Front
|
||||||
|
0, 1, 2, 2, 3, 0,
|
||||||
|
// Right
|
||||||
|
1, 5, 6, 6, 2, 1,
|
||||||
|
// Back
|
||||||
|
5, 4, 7, 7, 6, 5,
|
||||||
|
// Left
|
||||||
|
4, 0, 3, 3, 7, 4,
|
||||||
|
// Top
|
||||||
|
3, 2, 6, 6, 7, 3,
|
||||||
|
// Bottom
|
||||||
|
4, 5, 1, 1, 0, 4
|
||||||
|
};
|
||||||
|
|
||||||
|
Mesh cube(vertices, indices);
|
||||||
|
|
||||||
|
// Setup camera (slightly angled view)
|
||||||
|
Camera camera(Vec3(3, 2, 4), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, (float)width / height, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
// Render
|
||||||
|
render_mesh(fb, cube, camera);
|
||||||
|
|
||||||
|
if (fb.save("output_cube.png")) {
|
||||||
|
ARE_LOG_INFO("✓ Saved: output_cube.png");
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("✗ Failed to save output_cube.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Cornell Box (corrected)
|
||||||
|
{
|
||||||
|
ARE_LOG_INFO("Rendering Cornell Box...");
|
||||||
|
|
||||||
|
Framebuffer fb(width, height);
|
||||||
|
|
||||||
|
std::vector<Vertex> vertices;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
|
||||||
|
// Helper function to add a quad
|
||||||
|
auto add_quad = [&](const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3, const Vec3 &normal) {
|
||||||
|
unsigned int base = vertices.size();
|
||||||
|
vertices.push_back(Vertex(v0, normal));
|
||||||
|
vertices.push_back(Vertex(v1, normal));
|
||||||
|
vertices.push_back(Vertex(v2, normal));
|
||||||
|
vertices.push_back(Vertex(v3, normal));
|
||||||
|
indices.insert(indices.end(), { base + 0, base + 1, base + 2, base + 2, base + 3, base + 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Floor (white)
|
||||||
|
add_quad(
|
||||||
|
Vec3(-2, -2, 2), Vec3(2, -2, 2),
|
||||||
|
Vec3(2, -2, -2), Vec3(-2, -2, -2),
|
||||||
|
Vec3(0, 1, 0));
|
||||||
|
|
||||||
|
// Ceiling (white)
|
||||||
|
add_quad(
|
||||||
|
Vec3(-2, 2, -2), Vec3(2, 2, -2),
|
||||||
|
Vec3(2, 2, 2), Vec3(-2, 2, 2),
|
||||||
|
Vec3(0, -1, 0));
|
||||||
|
|
||||||
|
// Back wall (white)
|
||||||
|
add_quad(
|
||||||
|
Vec3(-2, -2, -2), Vec3(2, -2, -2),
|
||||||
|
Vec3(2, 2, -2), Vec3(-2, 2, -2),
|
||||||
|
Vec3(0, 0, 1));
|
||||||
|
|
||||||
|
// Left wall (red)
|
||||||
|
add_quad(
|
||||||
|
Vec3(-2, -2, 2), Vec3(-2, -2, -2),
|
||||||
|
Vec3(-2, 2, -2), Vec3(-2, 2, 2),
|
||||||
|
Vec3(1, 0, 0));
|
||||||
|
|
||||||
|
// Right wall (green)
|
||||||
|
add_quad(
|
||||||
|
Vec3(2, -2, -2), Vec3(2, -2, 2),
|
||||||
|
Vec3(2, 2, 2), Vec3(2, 2, -2),
|
||||||
|
Vec3(-1, 0, 0));
|
||||||
|
|
||||||
|
Mesh cornell_box(vertices, indices);
|
||||||
|
|
||||||
|
Camera camera(Vec3(0, 0, 5), Vec3(0, 0, 0));
|
||||||
|
camera.set_perspective(45.0f, (float)width / height, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
render_mesh(fb, cornell_box, camera);
|
||||||
|
|
||||||
|
if (fb.save("output_cornell_box.png")) {
|
||||||
|
ARE_LOG_INFO("✓ Saved: output_cornell_box.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("✓ All images generated successfully!");
|
||||||
|
ARE_LOG_INFO("Check the following files:");
|
||||||
|
ARE_LOG_INFO(" - output_triangle.png");
|
||||||
|
ARE_LOG_INFO(" - output_colored_triangle.png");
|
||||||
|
ARE_LOG_INFO(" - output_cube.png");
|
||||||
|
ARE_LOG_INFO(" - output_cornell_box.png");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
Logger::shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Phase 4 verification example
|
||||||
|
|
||||||
|
add_are_example(phase4_test
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copy to bin directory for easy execution
|
||||||
|
set_target_properties(phase4_test PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief Phase 4 verification program - BVH construction and traversal test
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/acceleration/bvh.h>
|
||||||
|
#include <are/acceleration/bvh_builder.h>
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/geometry/triangle.h>
|
||||||
|
#include <are/geometry/vertex.h>
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace are;
|
||||||
|
|
||||||
|
// Test result tracking
|
||||||
|
struct TestResult {
|
||||||
|
std::string name;
|
||||||
|
bool passed;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TestResult> test_results;
|
||||||
|
|
||||||
|
void report_test(const std::string &name, bool passed, const std::string &message = "") {
|
||||||
|
test_results.push_back({ name, passed, message });
|
||||||
|
if (passed) {
|
||||||
|
ARE_LOG_INFO("✓ " + name);
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("✗ " + name + ": " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a simple scene with a few triangles
|
||||||
|
*/
|
||||||
|
std::vector<Triangle> create_simple_scene() {
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
|
||||||
|
// Ground plane (2 triangles)
|
||||||
|
Vertex v0(Vec3(-5, 0, -5), Vec3(0, 1, 0));
|
||||||
|
Vertex v1(Vec3(5, 0, -5), Vec3(0, 1, 0));
|
||||||
|
Vertex v2(Vec3(5, 0, 5), Vec3(0, 1, 0));
|
||||||
|
Vertex v3(Vec3(-5, 0, 5), Vec3(0, 1, 0));
|
||||||
|
|
||||||
|
triangles.emplace_back(v0, v1, v2);
|
||||||
|
triangles.emplace_back(v0, v2, v3);
|
||||||
|
|
||||||
|
// Cube (12 triangles)
|
||||||
|
Vec3 cube_min(-1, 1, -1);
|
||||||
|
Vec3 cube_max(1, 3, 1);
|
||||||
|
|
||||||
|
// Front face
|
||||||
|
triangles.emplace_back(
|
||||||
|
Vertex(Vec3(cube_min.x, cube_min.y, cube_max.z), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(cube_max.x, cube_min.y, cube_max.z), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(cube_max.x, cube_max.y, cube_max.z), Vec3(0, 0, 1)));
|
||||||
|
triangles.emplace_back(
|
||||||
|
Vertex(Vec3(cube_min.x, cube_min.y, cube_max.z), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(cube_max.x, cube_max.y, cube_max.z), Vec3(0, 0, 1)),
|
||||||
|
Vertex(Vec3(cube_min.x, cube_max.y, cube_max.z), Vec3(0, 0, 1)));
|
||||||
|
|
||||||
|
// Add more faces... (simplified for brevity)
|
||||||
|
|
||||||
|
return triangles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a complex scene with many triangles
|
||||||
|
*/
|
||||||
|
std::vector<Triangle> create_complex_scene(int num_triangles) {
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
triangles.reserve(num_triangles);
|
||||||
|
|
||||||
|
std::mt19937 rng(42);
|
||||||
|
std::uniform_real_distribution<float> dist(-10.0f, 10.0f);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_triangles; ++i) {
|
||||||
|
Vec3 p0(dist(rng), dist(rng), dist(rng));
|
||||||
|
Vec3 p1 = p0 + Vec3(dist(rng) * 0.5f, dist(rng) * 0.5f, dist(rng) * 0.5f);
|
||||||
|
Vec3 p2 = p0 + Vec3(dist(rng) * 0.5f, dist(rng) * 0.5f, dist(rng) * 0.5f);
|
||||||
|
|
||||||
|
Vec3 normal = glm::normalize(glm::cross(p1 - p0, p2 - p0));
|
||||||
|
|
||||||
|
triangles.emplace_back(
|
||||||
|
Vertex(p0, normal),
|
||||||
|
Vertex(p1, normal),
|
||||||
|
Vertex(p2, normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
return triangles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 1: BVH construction
|
||||||
|
*/
|
||||||
|
void test_bvh_construction() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
BVHBuildConfig config;
|
||||||
|
config.split_method_ = BVHSplitMethod::ARE_BVH_SPLIT_MIDDLE;
|
||||||
|
config.max_leaf_size_ = 4;
|
||||||
|
|
||||||
|
bool success = bvh.build(triangles, config);
|
||||||
|
|
||||||
|
report_test("BVH construction (simple scene)", success);
|
||||||
|
report_test("BVH is built", bvh.is_built());
|
||||||
|
report_test("BVH has nodes", !bvh.get_nodes().empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 2: BVH construction with SAH
|
||||||
|
*/
|
||||||
|
void test_bvh_construction_sah() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
BVHBuildConfig config;
|
||||||
|
config.split_method_ = BVHSplitMethod::ARE_BVH_SPLIT_SAH;
|
||||||
|
config.max_leaf_size_ = 2;
|
||||||
|
|
||||||
|
bool success = bvh.build(triangles, config);
|
||||||
|
|
||||||
|
report_test("BVH construction with SAH", success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 3: Ray-BVH intersection
|
||||||
|
*/
|
||||||
|
void test_ray_bvh_intersection() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
// Ray hitting the ground plane
|
||||||
|
Ray ray1(Vec3(0, 5, 0), Vec3(0, -1, 0));
|
||||||
|
HitRecord hit1;
|
||||||
|
bool test1 = bvh.intersect(ray1, hit1);
|
||||||
|
|
||||||
|
// Ray missing everything
|
||||||
|
Ray ray2(Vec3(100, 5, 100), Vec3(0, -1, 0));
|
||||||
|
HitRecord hit2;
|
||||||
|
bool test2 = !bvh.intersect(ray2, hit2);
|
||||||
|
|
||||||
|
report_test("Ray-BVH intersection (hit)", test1);
|
||||||
|
report_test("Ray-BVH intersection (miss)", test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 4: BVH occlusion test
|
||||||
|
*/
|
||||||
|
void test_bvh_occlusion() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
// Ray with occlusion
|
||||||
|
Ray ray1(Vec3(0, 5, 0), Vec3(0, -1, 0));
|
||||||
|
bool test1 = bvh.intersect_any(ray1, 10.0f);
|
||||||
|
|
||||||
|
// Ray without occlusion
|
||||||
|
Ray ray2(Vec3(100, 5, 100), Vec3(0, -1, 0));
|
||||||
|
bool test2 = !bvh.intersect_any(ray2, 10.0f);
|
||||||
|
|
||||||
|
report_test("BVH occlusion test (occluded)", test1);
|
||||||
|
report_test("BVH occlusion test (not occluded)", test2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 5: BVH performance with complex scene
|
||||||
|
*/
|
||||||
|
void test_bvh_performance() {
|
||||||
|
const int num_triangles = 10000;
|
||||||
|
auto triangles = create_complex_scene(num_triangles);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Building BVH for " + std::to_string(num_triangles) + " triangles...");
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
BVHBuildConfig config;
|
||||||
|
config.split_method_ = BVHSplitMethod::ARE_BVH_SPLIT_SAH;
|
||||||
|
|
||||||
|
auto start_build = std::chrono::high_resolution_clock::now();
|
||||||
|
bool success = bvh.build(triangles, config);
|
||||||
|
auto end_build = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
double build_time = std::chrono::duration<double, std::milli>(end_build - start_build).count();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("BVH build time: " + std::to_string(build_time) + " ms");
|
||||||
|
|
||||||
|
// Test ray tracing performance
|
||||||
|
const int num_rays = 10000;
|
||||||
|
std::mt19937 rng(42);
|
||||||
|
std::uniform_real_distribution<float> dist(-10.0f, 10.0f);
|
||||||
|
|
||||||
|
int hit_count = 0;
|
||||||
|
|
||||||
|
auto start_trace = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
for (int i = 0; i < num_rays; ++i) {
|
||||||
|
Vec3 origin(dist(rng), dist(rng), dist(rng));
|
||||||
|
Vec3 direction = glm::normalize(Vec3(dist(rng), dist(rng), dist(rng)));
|
||||||
|
|
||||||
|
Ray ray(origin, direction);
|
||||||
|
HitRecord hit;
|
||||||
|
|
||||||
|
if (bvh.intersect(ray, hit)) {
|
||||||
|
hit_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end_trace = std::chrono::high_resolution_clock::now();
|
||||||
|
double trace_time = std::chrono::duration<double, std::milli>(end_trace - start_trace).count();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Ray tracing time: " + std::to_string(trace_time) + " ms for " + std::to_string(num_rays) + " rays");
|
||||||
|
ARE_LOG_INFO("Hit rate: " + std::to_string(hit_count) + "/" + std::to_string(num_rays) + " (" + std::to_string(100.0 * hit_count / num_rays) + "%)");
|
||||||
|
ARE_LOG_INFO("Average time per ray: " + std::to_string(trace_time / num_rays) + " ms");
|
||||||
|
|
||||||
|
report_test("BVH performance test", success && build_time < 5000.0); // Should build in < 5 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 6: BVH memory usage
|
||||||
|
*/
|
||||||
|
void test_bvh_memory() {
|
||||||
|
auto triangles = create_complex_scene(1000);
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
size_t memory = bvh.get_memory_usage();
|
||||||
|
ARE_LOG_INFO("BVH memory usage: " + std::to_string(memory / 1024) + " KB");
|
||||||
|
|
||||||
|
report_test("BVH memory usage", memory > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 7: BVH clear and rebuild
|
||||||
|
*/
|
||||||
|
void test_bvh_clear_rebuild() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
bool test1 = bvh.is_built();
|
||||||
|
|
||||||
|
bvh.clear();
|
||||||
|
bool test2 = !bvh.is_built();
|
||||||
|
|
||||||
|
bvh.build(triangles);
|
||||||
|
bool test3 = bvh.is_built();
|
||||||
|
|
||||||
|
report_test("BVH clear and rebuild (initial build)", test1);
|
||||||
|
report_test("BVH clear and rebuild (after clear)", test2);
|
||||||
|
report_test("BVH clear and rebuild (rebuild)", test3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 8: BVH with empty scene
|
||||||
|
*/
|
||||||
|
void test_bvh_empty_scene() {
|
||||||
|
std::vector<Triangle> empty_triangles;
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bool success = bvh.build(empty_triangles);
|
||||||
|
|
||||||
|
report_test("BVH with empty scene", !success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 9: BVH node structure
|
||||||
|
*/
|
||||||
|
void test_bvh_node_structure() {
|
||||||
|
auto triangles = create_simple_scene();
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
const auto &nodes = bvh.get_nodes();
|
||||||
|
|
||||||
|
bool test1 = !nodes.empty();
|
||||||
|
|
||||||
|
// Check root node
|
||||||
|
bool test2 = nodes[0].bounds_.is_valid();
|
||||||
|
|
||||||
|
// Count leaf and internal nodes
|
||||||
|
int leaf_count = 0;
|
||||||
|
int internal_count = 0;
|
||||||
|
|
||||||
|
for (const auto &node : nodes) {
|
||||||
|
if (node.is_leaf()) {
|
||||||
|
leaf_count++;
|
||||||
|
} else {
|
||||||
|
internal_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test3 = leaf_count > 0;
|
||||||
|
bool test4 = internal_count >= 0;
|
||||||
|
|
||||||
|
ARE_LOG_INFO("BVH structure: " + std::to_string(nodes.size()) + " nodes (" + std::to_string(leaf_count) + " leaves, " + std::to_string(internal_count) + " internal)");
|
||||||
|
|
||||||
|
report_test("BVH node structure (has nodes)", test1);
|
||||||
|
report_test("BVH node structure (valid root)", test2);
|
||||||
|
report_test("BVH node structure (has leaves)", test3);
|
||||||
|
report_test("BVH node structure (node counts)", test4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test 10: BVH traversal correctness
|
||||||
|
*/
|
||||||
|
void test_bvh_traversal_correctness() {
|
||||||
|
// Create a simple scene with known geometry
|
||||||
|
std::vector<Triangle> triangles;
|
||||||
|
|
||||||
|
// Single triangle at origin
|
||||||
|
Vertex v0(Vec3(-1, 0, -1), Vec3(0, 1, 0));
|
||||||
|
Vertex v1(Vec3(1, 0, -1), Vec3(0, 1, 0));
|
||||||
|
Vertex v2(Vec3(0, 0, 1), Vec3(0, 1, 0));
|
||||||
|
|
||||||
|
triangles.emplace_back(v0, v1, v2);
|
||||||
|
|
||||||
|
BVH bvh;
|
||||||
|
bvh.build(triangles);
|
||||||
|
|
||||||
|
// Ray hitting the triangle from above
|
||||||
|
Ray ray(Vec3(0, 5, 0), Vec3(0, -1, 0));
|
||||||
|
HitRecord hit;
|
||||||
|
|
||||||
|
bool intersected = bvh.intersect(ray, hit);
|
||||||
|
|
||||||
|
bool test1 = intersected;
|
||||||
|
bool test2 = hit.t_ > 0.0f && hit.t_ < 10.0f;
|
||||||
|
bool test3 = glm::length(hit.normal_ - Vec3(0, 1, 0)) < 0.01f;
|
||||||
|
|
||||||
|
report_test("BVH traversal correctness (intersection)", test1);
|
||||||
|
report_test("BVH traversal correctness (t value)", test2);
|
||||||
|
report_test("BVH traversal correctness (normal)", test3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Initialize logger and profiler
|
||||||
|
Logger::init(LogLevel::ARE_LOG_INFO);
|
||||||
|
Profiler::init();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Phase 4 Verification Program");
|
||||||
|
ARE_LOG_INFO("BVH Construction and Traversal Test");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
test_bvh_construction();
|
||||||
|
test_bvh_construction_sah();
|
||||||
|
test_ray_bvh_intersection();
|
||||||
|
test_bvh_occlusion();
|
||||||
|
test_bvh_performance();
|
||||||
|
test_bvh_memory();
|
||||||
|
test_bvh_clear_rebuild();
|
||||||
|
test_bvh_empty_scene();
|
||||||
|
test_bvh_node_structure();
|
||||||
|
test_bvh_traversal_correctness();
|
||||||
|
|
||||||
|
// Print summary
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("Test Summary");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
|
||||||
|
int passed = 0;
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
for (const auto &result : test_results) {
|
||||||
|
if (result.passed) {
|
||||||
|
++passed;
|
||||||
|
} else {
|
||||||
|
++failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Total tests: " + std::to_string(test_results.size()));
|
||||||
|
ARE_LOG_INFO("Passed: " + std::to_string(passed));
|
||||||
|
ARE_LOG_INFO("Failed: " + std::to_string(failed));
|
||||||
|
|
||||||
|
if (failed == 0) {
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
ARE_LOG_INFO("✓ All Phase 4 tests passed!");
|
||||||
|
ARE_LOG_INFO("========================================");
|
||||||
|
} else {
|
||||||
|
ARE_LOG_ERROR("========================================");
|
||||||
|
ARE_LOG_ERROR("✗ Some tests failed. Please review.");
|
||||||
|
ARE_LOG_ERROR("========================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print profiling results
|
||||||
|
Profiler::print_results();
|
||||||
|
|
||||||
|
Profiler::shutdown();
|
||||||
|
Logger::shutdown();
|
||||||
|
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
@ -95,9 +95,21 @@ public:
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Recursive traversal (kept for reference)
|
||||||
bool intersect_recursive(uint32_t node_index, const Ray& ray, HitRecord& hit) const;
|
bool intersect_recursive(uint32_t node_index, const Ray& ray, HitRecord& hit) const;
|
||||||
bool intersect_any_recursive(uint32_t node_index, const Ray& ray, Real t_max) const;
|
bool intersect_any_recursive(uint32_t node_index, const Ray& ray, Real t_max) const;
|
||||||
|
|
||||||
|
// Optimized iterative traversal
|
||||||
|
bool intersect_iterative(const Ray& ray, HitRecord& hit) const;
|
||||||
|
bool intersect_any_iterative(const Ray& ray, Real t_max) const;
|
||||||
|
|
||||||
|
// Fast intersection helpers
|
||||||
|
inline bool intersect_aabb_fast(const AABB& bounds, const Ray& ray,
|
||||||
|
const Vec3& inv_dir, Real t_max,
|
||||||
|
Real& t_min_out, Real& t_max_out) const;
|
||||||
|
inline bool intersect_triangle_fast(const Triangle& triangle, const Ray& ray,
|
||||||
|
Real t_max, HitRecord& hit) const;
|
||||||
|
|
||||||
std::vector<BVHNode> nodes_; ///< BVH nodes
|
std::vector<BVHNode> nodes_; ///< BVH nodes
|
||||||
std::vector<uint32_t> primitive_indices_; ///< Primitive index array
|
std::vector<uint32_t> primitive_indices_; ///< Primitive index array
|
||||||
std::vector<Triangle> triangles_; ///< Triangle data
|
std::vector<Triangle> triangles_; ///< Triangle data
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@
|
||||||
#define ARE_INCLUDE_RAYTRACER_COMPUTE_RAYTRACER_H
|
#define ARE_INCLUDE_RAYTRACER_COMPUTE_RAYTRACER_H
|
||||||
|
|
||||||
#include <are/raytracer/raytracer.h>
|
#include <are/raytracer/raytracer.h>
|
||||||
|
#include <are/rasterizer/shader_program.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
// Forward declarations
|
|
||||||
class ShaderProgram;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class ComputeRayTracer
|
* @class ComputeRayTracer
|
||||||
* @brief GPU-based ray tracing using compute shaders
|
* @brief GPU-based ray tracing using compute shaders
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class SceneManager;
|
||||||
class Rasterizer;
|
class Rasterizer;
|
||||||
class RayTracer;
|
class RayTracer;
|
||||||
class TextureManager;
|
class TextureManager;
|
||||||
|
class BVH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Renderer
|
* @class Renderer
|
||||||
|
|
@ -109,6 +110,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,10 @@
|
||||||
#define ARE_INCLUDE_TEXTURE_SAMPLER_H
|
#define ARE_INCLUDE_TEXTURE_SAMPLER_H
|
||||||
|
|
||||||
#include <are/core/types.h>
|
#include <are/core/types.h>
|
||||||
|
#include <are/texture/texture.h>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
class Texture;
|
|
||||||
class TextureWrap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Sampler
|
* @class Sampler
|
||||||
* @brief Texture sampling utilities for CPU ray tracing
|
* @brief Texture sampling utilities for CPU ray tracing
|
||||||
|
|
|
||||||
|
|
@ -850,5 +850,5 @@ renderer.render();
|
||||||
- [ ] 光源能够打包数据
|
- [ ] 光源能够打包数据
|
||||||
- [ ] 场景管理器能够管理对象
|
- [ ] 场景管理器能够管理对象
|
||||||
|
|
||||||
对于目前的进度,我们已经实现了Phase 1,正在实现Phase 2,请你帮我们进行Phase 2的实现。
|
对于目前的进度,我们已经实现到了Phase 4,正在实现Phase 5,请你帮我们进行Phase 5以及后续代码的实现。
|
||||||
但是因为我还没有给你我们已有的代码,因此你需要哪些头文件的代码?请在下一轮向我询问,然后我们会开始Phase 2的开发。
|
但是因为我还没有给你我们已有的代码,因此你需要哪些头文件的代码?请在下一轮向我询问,然后我们会开始Phase 5的开发。
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,605 @@
|
||||||
|
### 文件:include/are/platform/window.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file window.h
|
||||||
|
* @brief Window management using GLFW
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_PLATFORM_WINDOW_H
|
||||||
|
#define ARE_INCLUDE_PLATFORM_WINDOW_H
|
||||||
|
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Forward declare GLFW types to avoid including GLFW in header
|
||||||
|
struct GLFWwindow;
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Window
|
||||||
|
* @brief GLFW window wrapper
|
||||||
|
*
|
||||||
|
* Manages window creation, input handling, and OpenGL context.
|
||||||
|
*/
|
||||||
|
class Window {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param config Window configuration
|
||||||
|
*/
|
||||||
|
explicit Window(const WindowConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~Window();
|
||||||
|
|
||||||
|
// Window control
|
||||||
|
bool should_close() const;
|
||||||
|
void set_should_close(bool should_close);
|
||||||
|
void swap_buffers();
|
||||||
|
void poll_events();
|
||||||
|
|
||||||
|
// Window properties
|
||||||
|
int get_width() const;
|
||||||
|
int get_height() const;
|
||||||
|
Real get_aspect_ratio() const;
|
||||||
|
const std::string& get_title() const;
|
||||||
|
|
||||||
|
void set_title(const std::string& title);
|
||||||
|
void set_size(int width, int height);
|
||||||
|
|
||||||
|
// Framebuffer size (may differ from window size on high-DPI displays)
|
||||||
|
void get_framebuffer_size(int& width, int& height) const;
|
||||||
|
|
||||||
|
// VSync control
|
||||||
|
void set_vsync(bool enabled);
|
||||||
|
bool get_vsync() const;
|
||||||
|
|
||||||
|
// Input queries (basic support)
|
||||||
|
bool is_key_pressed(int key) const;
|
||||||
|
bool is_mouse_button_pressed(int button) const;
|
||||||
|
void get_cursor_pos(double& x, double& y) const;
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
GLFWwindow* get_native_window() const { return window_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initialize_glfw();
|
||||||
|
void create_window();
|
||||||
|
void setup_callbacks();
|
||||||
|
|
||||||
|
static void framebuffer_size_callback(GLFWwindow* window, int width, int height);
|
||||||
|
static void error_callback(int error, const char* description);
|
||||||
|
|
||||||
|
GLFWwindow* window_; ///< GLFW window handle
|
||||||
|
WindowConfig config_; ///< Window configuration
|
||||||
|
bool vsync_enabled_; ///< VSync state
|
||||||
|
|
||||||
|
static int instance_count_; ///< Number of Window instances
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_PLATFORM_WINDOW_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/platform/gl_context.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file gl_context.h
|
||||||
|
* @brief OpenGL context management
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_PLATFORM_GL_CONTEXT_H
|
||||||
|
#define ARE_INCLUDE_PLATFORM_GL_CONTEXT_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class GLContext
|
||||||
|
* @brief OpenGL context initialization and management
|
||||||
|
*
|
||||||
|
* Handles GLAD initialization and provides OpenGL utility functions.
|
||||||
|
*/
|
||||||
|
class GLContext {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize OpenGL context (load function pointers)
|
||||||
|
* @return true if initialization succeeded
|
||||||
|
*/
|
||||||
|
static bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if context is initialized
|
||||||
|
* @return true if initialized
|
||||||
|
*/
|
||||||
|
static bool is_initialized();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get OpenGL version string
|
||||||
|
* @return Version string
|
||||||
|
*/
|
||||||
|
static std::string get_version();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get OpenGL renderer string
|
||||||
|
* @return Renderer string
|
||||||
|
*/
|
||||||
|
static std::string get_renderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get OpenGL vendor string
|
||||||
|
* @return Vendor string
|
||||||
|
*/
|
||||||
|
static std::string get_vendor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if OpenGL extension is supported
|
||||||
|
* @param extension Extension name
|
||||||
|
* @return true if supported
|
||||||
|
*/
|
||||||
|
static bool is_extension_supported(const std::string& extension);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print OpenGL information to console
|
||||||
|
*/
|
||||||
|
static void print_info();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check for OpenGL errors
|
||||||
|
* @param file Source file
|
||||||
|
* @param line Line number
|
||||||
|
* @return true if error occurred
|
||||||
|
*/
|
||||||
|
static bool check_error(const char* file, int line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all OpenGL errors
|
||||||
|
*/
|
||||||
|
static void clear_errors();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool initialized_; ///< Initialization flag
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
// OpenGL error checking macro
|
||||||
|
#ifdef ARE_ENABLE_DEBUG_VIS
|
||||||
|
#define ARE_GL_CHECK() are::GLContext::check_error(__FILE__, __LINE__)
|
||||||
|
#else
|
||||||
|
#define ARE_GL_CHECK() ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_PLATFORM_GL_CONTEXT_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/utils/file_utils.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file file_utils.h
|
||||||
|
* @brief File system utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_UTILS_FILE_UTILS_H
|
||||||
|
#define ARE_INCLUDE_UTILS_FILE_UTILS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read entire file into string
|
||||||
|
* @param filepath File path
|
||||||
|
* @return File contents (empty if failed)
|
||||||
|
*/
|
||||||
|
std::string read_file_to_string(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read entire file into byte array
|
||||||
|
* @param filepath File path
|
||||||
|
* @return File contents (empty if failed)
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> read_file_to_bytes(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write string to file
|
||||||
|
* @param filepath File path
|
||||||
|
* @param content Content to write
|
||||||
|
* @return true if write succeeded
|
||||||
|
*/
|
||||||
|
bool write_string_to_file(const std::string& filepath, const std::string& content);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write bytes to file
|
||||||
|
* @param filepath File path
|
||||||
|
* @param data Data pointer
|
||||||
|
* @param size Data size in bytes
|
||||||
|
* @return true if write succeeded
|
||||||
|
*/
|
||||||
|
bool write_bytes_to_file(const std::string& filepath, const void* data, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if file exists
|
||||||
|
* @param filepath File path
|
||||||
|
* @return true if file exists
|
||||||
|
*/
|
||||||
|
bool file_exists(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if path is directory
|
||||||
|
* @param path Directory path
|
||||||
|
* @return true if directory exists
|
||||||
|
*/
|
||||||
|
bool is_directory(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create directory (including parent directories)
|
||||||
|
* @param path Directory path
|
||||||
|
* @return true if creation succeeded
|
||||||
|
*/
|
||||||
|
bool create_directory(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get file extension
|
||||||
|
* @param filepath File path
|
||||||
|
* @return Extension (lowercase, without dot)
|
||||||
|
*/
|
||||||
|
std::string get_file_extension(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get filename from path
|
||||||
|
* @param filepath File path
|
||||||
|
* @return Filename (without directory)
|
||||||
|
*/
|
||||||
|
std::string get_filename(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get directory from path
|
||||||
|
* @param filepath File path
|
||||||
|
* @return Directory path
|
||||||
|
*/
|
||||||
|
std::string get_directory(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Join path components
|
||||||
|
* @param parts Path components
|
||||||
|
* @return Joined path
|
||||||
|
*/
|
||||||
|
std::string join_path(const std::vector<std::string>& parts);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Normalize path (resolve .. and .)
|
||||||
|
* @param path Path to normalize
|
||||||
|
* @return Normalized path
|
||||||
|
*/
|
||||||
|
std::string normalize_path(const std::string& path);
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_UTILS_FILE_UTILS_H
|
||||||
|
```
|
||||||
|
|
||||||
|
这是你所要求的三个头文件,如果还需要更多头文件的代码,请随时向我提出。同时,平台层着三个函数的代码我也已经实现,你只需要专心考虑渲染管线即可。此外,渲染管线的头文件我也实现了,你只需要负责实现渲染管线的shader以及代码实现即可。
|
||||||
|
### 文件:include/are/rasterizer/shader_program.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file shader_program.h
|
||||||
|
* @brief OpenGL shader program wrapper
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RASTERIZER_SHADER_PROGRAM_H
|
||||||
|
#define ARE_INCLUDE_RASTERIZER_SHADER_PROGRAM_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum ShaderType
|
||||||
|
* @brief Shader stage types
|
||||||
|
*/
|
||||||
|
enum class ShaderType {
|
||||||
|
ARE_SHADER_VERTEX,
|
||||||
|
ARE_SHADER_FRAGMENT,
|
||||||
|
ARE_SHADER_COMPUTE
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ShaderProgram
|
||||||
|
* @brief OpenGL shader program management
|
||||||
|
*/
|
||||||
|
class ShaderProgram {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
*/
|
||||||
|
ShaderProgram();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~ShaderProgram();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load and compile shader from file
|
||||||
|
* @param type Shader type
|
||||||
|
* @param filepath Shader file path
|
||||||
|
* @return true if compilation succeeded
|
||||||
|
*/
|
||||||
|
bool load_shader(ShaderType type, const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compile shader from source string
|
||||||
|
* @param type Shader type
|
||||||
|
* @param source Shader source code
|
||||||
|
* @return true if compilation succeeded
|
||||||
|
*/
|
||||||
|
bool compile_shader(ShaderType type, const std::string& source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Link shader program
|
||||||
|
* @return true if linking succeeded
|
||||||
|
*/
|
||||||
|
bool link();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Use this shader program
|
||||||
|
*/
|
||||||
|
void use() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if program is valid
|
||||||
|
* @return true if valid
|
||||||
|
*/
|
||||||
|
bool is_valid() const { return program_ != 0 && linked_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get OpenGL program ID
|
||||||
|
* @return Program ID
|
||||||
|
*/
|
||||||
|
uint32_t get_program() const { return program_; }
|
||||||
|
|
||||||
|
// Uniform setters
|
||||||
|
void set_uniform(const std::string& name, int value);
|
||||||
|
void set_uniform(const std::string& name, float value);
|
||||||
|
void set_uniform(const std::string& name, const Vec2& value);
|
||||||
|
void set_uniform(const std::string& name, const Vec3& value);
|
||||||
|
void set_uniform(const std::string& name, const Vec4& value);
|
||||||
|
void set_uniform(const std::string& name, const Mat3& value);
|
||||||
|
void set_uniform(const std::string& name, const Mat4& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get uniform location (cached)
|
||||||
|
* @param name Uniform name
|
||||||
|
* @return Uniform location (-1 if not found)
|
||||||
|
*/
|
||||||
|
int get_uniform_location(const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool check_compile_errors(uint32_t shader, ShaderType type);
|
||||||
|
bool check_link_errors();
|
||||||
|
|
||||||
|
uint32_t program_; ///< OpenGL program ID
|
||||||
|
uint32_t vertex_shader_; ///< Vertex shader ID
|
||||||
|
uint32_t fragment_shader_; ///< Fragment shader ID
|
||||||
|
uint32_t compute_shader_; ///< Compute shader ID
|
||||||
|
|
||||||
|
bool linked_; ///< Link status
|
||||||
|
std::unordered_map<std::string, int> uniform_cache_; ///< Uniform location cache
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RASTERIZER_SHADER_PROGRAM_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/rasterizer/gbuffer.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file gbuffer.h
|
||||||
|
* @brief G-Buffer management for deferred rendering
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RASTERIZER_GBUFFER_H
|
||||||
|
#define ARE_INCLUDE_RASTERIZER_GBUFFER_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class GBuffer
|
||||||
|
* @brief G-Buffer for deferred rendering
|
||||||
|
*
|
||||||
|
* Contains multiple render targets for position, normal, albedo, etc.
|
||||||
|
*/
|
||||||
|
class GBuffer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param width Buffer width
|
||||||
|
* @param height Buffer height
|
||||||
|
*/
|
||||||
|
GBuffer(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~GBuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resize G-Buffer
|
||||||
|
* @param width New width
|
||||||
|
* @param height New height
|
||||||
|
*/
|
||||||
|
void resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bind G-Buffer for rendering
|
||||||
|
*/
|
||||||
|
void bind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unbind G-Buffer
|
||||||
|
*/
|
||||||
|
void unbind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all buffers
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bind texture for reading
|
||||||
|
* @param index Texture index (0=position, 1=normal, 2=albedo, etc.)
|
||||||
|
* @param texture_unit Texture unit to bind to
|
||||||
|
*/
|
||||||
|
void bind_texture(int index, int texture_unit);
|
||||||
|
|
||||||
|
// Texture getters
|
||||||
|
uint32_t get_position_texture() const { return position_texture_; }
|
||||||
|
uint32_t get_normal_texture() const { return normal_texture_; }
|
||||||
|
uint32_t get_albedo_texture() const { return albedo_texture_; }
|
||||||
|
uint32_t get_material_texture() const { return material_texture_; }
|
||||||
|
uint32_t get_depth_texture() const { return depth_texture_; }
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
int get_width() const { return width_; }
|
||||||
|
int get_height() const { return height_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read pixel data from G-Buffer
|
||||||
|
* @param index Buffer index
|
||||||
|
* @param data Output data pointer
|
||||||
|
*/
|
||||||
|
void read_pixels(int index, void* data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create_textures();
|
||||||
|
void delete_textures();
|
||||||
|
void create_framebuffer();
|
||||||
|
|
||||||
|
uint32_t fbo_; ///< Framebuffer object
|
||||||
|
uint32_t rbo_depth_; ///< Depth renderbuffer
|
||||||
|
|
||||||
|
// G-Buffer textures
|
||||||
|
uint32_t position_texture_; ///< World position (RGB16F)
|
||||||
|
uint32_t normal_texture_; ///< World normal (RGB16F)
|
||||||
|
uint32_t albedo_texture_; ///< Albedo + Metallic (RGBA8)
|
||||||
|
uint32_t material_texture_; ///< Roughness + AO (RG8)
|
||||||
|
uint32_t depth_texture_; ///< Depth (R32F)
|
||||||
|
|
||||||
|
int width_; ///< Buffer width
|
||||||
|
int height_; ///< Buffer height
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RASTERIZER_GBUFFER_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/rasterizer/rasterizer.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file rasterizer.h
|
||||||
|
* @brief Rasterization pipeline for G-Buffer generation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RASTERIZER_RASTERIZER_H
|
||||||
|
#define ARE_INCLUDE_RASTERIZER_RASTERIZER_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <are/core/config.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class GBuffer;
|
||||||
|
class ShaderProgram;
|
||||||
|
class SceneManager;
|
||||||
|
class Camera;
|
||||||
|
class Mesh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Rasterizer
|
||||||
|
* @brief OpenGL rasterization pipeline
|
||||||
|
*
|
||||||
|
* Renders scene geometry to G-Buffer using traditional rasterization.
|
||||||
|
*/
|
||||||
|
class Rasterizer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param width Framebuffer width
|
||||||
|
* @param height Framebuffer height
|
||||||
|
*/
|
||||||
|
Rasterizer(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~Rasterizer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resize framebuffer
|
||||||
|
* @param width New width
|
||||||
|
* @param height New height
|
||||||
|
*/
|
||||||
|
void resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render scene to G-Buffer
|
||||||
|
* @param scene Scene manager
|
||||||
|
* @param camera Camera
|
||||||
|
*/
|
||||||
|
void render_gbuffer(const SceneManager& scene, const Camera& camera);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get G-Buffer
|
||||||
|
* @return G-Buffer reference
|
||||||
|
*/
|
||||||
|
GBuffer& get_gbuffer();
|
||||||
|
const GBuffer& get_gbuffer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Upload mesh data to GPU
|
||||||
|
* @param mesh Mesh to upload
|
||||||
|
*/
|
||||||
|
void upload_mesh(Mesh& mesh);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete mesh GPU resources
|
||||||
|
* @param mesh Mesh to delete
|
||||||
|
*/
|
||||||
|
void delete_mesh(Mesh& mesh);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initialize_shaders(const std::string& shader_dir);
|
||||||
|
void setup_mesh_buffers(Mesh& mesh);
|
||||||
|
|
||||||
|
std::unique_ptr<GBuffer> gbuffer_; ///< G-Buffer
|
||||||
|
std::unique_ptr<ShaderProgram> gbuffer_shader_; ///< G-Buffer shader
|
||||||
|
|
||||||
|
int width_; ///< Framebuffer width
|
||||||
|
int height_; ///< Framebuffer height
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RASTERIZER_RASTERIZER_H
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有还需要我给出的内容的话,你就可以开始实现了。
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
很好!让我们来实现Phase 4吧!
|
||||||
|
如下是我已经实现的Phase 4头文件:
|
||||||
|
### 文件:include/are/acceleration/bvh_node.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file bvh_node.h
|
||||||
|
* @brief BVH node structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_ACCELERATION_BVH_NODE_H
|
||||||
|
#define ARE_INCLUDE_ACCELERATION_BVH_NODE_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <are/geometry/aabb.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct BVHNode
|
||||||
|
* @brief Node in Bounding Volume Hierarchy
|
||||||
|
*
|
||||||
|
* Uses a compact representation for efficient GPU transfer.
|
||||||
|
*/
|
||||||
|
struct BVHNode {
|
||||||
|
AABB bounds_; ///< Node bounding box
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint32_t left_child_; ///< Left child index (internal node)
|
||||||
|
uint32_t first_primitive_; ///< First primitive index (leaf node)
|
||||||
|
};
|
||||||
|
|
||||||
|
union {
|
||||||
|
uint32_t right_child_; ///< Right child index (internal node)
|
||||||
|
uint32_t primitive_count_; ///< Number of primitives (leaf node)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if node is a leaf
|
||||||
|
* @return true if leaf node
|
||||||
|
*/
|
||||||
|
bool is_leaf() const {
|
||||||
|
return primitive_count_ > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get node surface area (for SAH)
|
||||||
|
* @return Surface area
|
||||||
|
*/
|
||||||
|
Real surface_area() const {
|
||||||
|
return bounds_.surface_area();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_ACCELERATION_BVH_NODE_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/acceleration/bvh_builder.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file bvh_builder.h
|
||||||
|
* @brief BVH construction algorithms
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_ACCELERATION_BVH_BUILDER_H
|
||||||
|
#define ARE_INCLUDE_ACCELERATION_BVH_BUILDER_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <are/acceleration/bvh_node.h>
|
||||||
|
#include <are/geometry/triangle.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum BVHSplitMethod
|
||||||
|
* @brief BVH splitting strategies
|
||||||
|
*/
|
||||||
|
enum class BVHSplitMethod {
|
||||||
|
ARE_BVH_SPLIT_MIDDLE, ///< Split at midpoint
|
||||||
|
ARE_BVH_SPLIT_SAH ///< Surface Area Heuristic
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct BVHBuildConfig
|
||||||
|
* @brief Configuration for BVH construction
|
||||||
|
*/
|
||||||
|
struct BVHBuildConfig {
|
||||||
|
BVHSplitMethod split_method_ = BVHSplitMethod::ARE_BVH_SPLIT_SAH;
|
||||||
|
int max_leaf_size_ = 4; ///< Maximum triangles per leaf
|
||||||
|
int max_depth_ = 64; ///< Maximum tree depth
|
||||||
|
bool use_multithreading_ = true; ///< Use parallel construction
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class BVHBuilder
|
||||||
|
* @brief Constructs BVH from triangle list
|
||||||
|
*/
|
||||||
|
class BVHBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param config Build configuration
|
||||||
|
*/
|
||||||
|
explicit BVHBuilder(const BVHBuildConfig& config = BVHBuildConfig());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build BVH from triangles
|
||||||
|
* @param triangles Triangle list
|
||||||
|
* @param nodes Output node list
|
||||||
|
* @param primitive_indices Output primitive index list
|
||||||
|
* @return Root node index
|
||||||
|
*/
|
||||||
|
uint32_t build(const std::vector<Triangle>& triangles,
|
||||||
|
std::vector<BVHNode>& nodes,
|
||||||
|
std::vector<uint32_t>& primitive_indices);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get build statistics
|
||||||
|
* @param node_count Output node count
|
||||||
|
* @param leaf_count Output leaf count
|
||||||
|
* @param max_depth Output maximum depth reached
|
||||||
|
*/
|
||||||
|
void get_stats(size_t& node_count, size_t& leaf_count, int& max_depth) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct BuildEntry {
|
||||||
|
uint32_t parent_;
|
||||||
|
uint32_t start_;
|
||||||
|
uint32_t end_;
|
||||||
|
int depth_;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t build_recursive(const std::vector<Triangle>& triangles,
|
||||||
|
std::vector<BVHNode>& nodes,
|
||||||
|
std::vector<uint32_t>& primitive_indices,
|
||||||
|
uint32_t start, uint32_t end, int depth);
|
||||||
|
|
||||||
|
int find_best_split_axis(const std::vector<Triangle>& triangles,
|
||||||
|
const std::vector<uint32_t>& indices,
|
||||||
|
uint32_t start, uint32_t end);
|
||||||
|
|
||||||
|
Real compute_sah_cost(const AABB& bounds, uint32_t count);
|
||||||
|
|
||||||
|
BVHBuildConfig config_;
|
||||||
|
size_t node_count_;
|
||||||
|
size_t leaf_count_;
|
||||||
|
int max_depth_reached_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_ACCELERATION_BVH_BUILDER_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/acceleration/bvh.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file bvh.h
|
||||||
|
* @brief BVH interface and traversal
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_ACCELERATION_BVH_H
|
||||||
|
#define ARE_INCLUDE_ACCELERATION_BVH_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <are/acceleration/bvh_node.h>
|
||||||
|
#include <are/acceleration/bvh_builder.h>
|
||||||
|
#include <are/geometry/triangle.h>
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class BVH
|
||||||
|
* @brief Bounding Volume Hierarchy for ray tracing acceleration
|
||||||
|
*/
|
||||||
|
class BVH {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
*/
|
||||||
|
BVH();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~BVH();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build BVH from triangle list
|
||||||
|
* @param triangles Triangle list
|
||||||
|
* @param config Build configuration
|
||||||
|
* @return true if build succeeded
|
||||||
|
*/
|
||||||
|
bool build(const std::vector<Triangle>& triangles,
|
||||||
|
const BVHBuildConfig& config = BVHBuildConfig());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Traverse BVH and find closest intersection
|
||||||
|
* @param ray Ray to trace
|
||||||
|
* @param hit Output hit record
|
||||||
|
* @return true if intersection found
|
||||||
|
*/
|
||||||
|
bool intersect(const Ray& ray, HitRecord& hit) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fast occlusion test (any hit)
|
||||||
|
* @param ray Ray to trace
|
||||||
|
* @param t_max Maximum t value
|
||||||
|
* @return true if any intersection found
|
||||||
|
*/
|
||||||
|
bool intersect_any(const Ray& ray, Real t_max) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if BVH is built
|
||||||
|
* @return true if built
|
||||||
|
*/
|
||||||
|
bool is_built() const { return !nodes_.empty(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get BVH nodes (for GPU upload)
|
||||||
|
* @return Node array
|
||||||
|
*/
|
||||||
|
const std::vector<BVHNode>& get_nodes() const { return nodes_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get primitive indices
|
||||||
|
* @return Index array
|
||||||
|
*/
|
||||||
|
const std::vector<uint32_t>& get_primitive_indices() const {
|
||||||
|
return primitive_indices_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get triangles
|
||||||
|
* @return Triangle array
|
||||||
|
*/
|
||||||
|
const std::vector<Triangle>& get_triangles() const { return triangles_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get memory usage in bytes
|
||||||
|
* @return Memory usage
|
||||||
|
*/
|
||||||
|
size_t get_memory_usage() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear BVH data
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool intersect_recursive(uint32_t node_index, const Ray& ray, HitRecord& hit) const;
|
||||||
|
bool intersect_any_recursive(uint32_t node_index, const Ray& ray, Real t_max) const;
|
||||||
|
|
||||||
|
std::vector<BVHNode> nodes_; ///< BVH nodes
|
||||||
|
std::vector<uint32_t> primitive_indices_; ///< Primitive index array
|
||||||
|
std::vector<Triangle> triangles_; ///< Triangle data
|
||||||
|
uint32_t root_index_; ///< Root node index
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_ACCELERATION_BVH_H
|
||||||
|
```
|
||||||
|
|
||||||
|
如果有依赖或者需要给你的头文件或实现文件我还没有给你,欢迎提出!
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
很好!现在让我们开始Phase 5的实现吧!如下是Phase 5我已经写好的头文件与依赖文件(注:ray.h/cpp&hit_record.h/cpp在前面的代码中你已经实现了,现在只需要再检查一遍之前的实现是否正确即可):
|
||||||
|
### 文件:include/are/raytracer/cpu_raytracer.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file cpu_raytracer.h
|
||||||
|
* @brief CPU-based ray tracing implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RAYTRACER_CPU_RAYTRACER_H
|
||||||
|
#define ARE_INCLUDE_RAYTRACER_CPU_RAYTRACER_H
|
||||||
|
|
||||||
|
#include <are/raytracer/raytracer.h>
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CPURayTracer
|
||||||
|
* @brief CPU-based ray tracing implementation
|
||||||
|
*
|
||||||
|
* Uses multithreading for parallel ray tracing on CPU.
|
||||||
|
*/
|
||||||
|
class CPURayTracer : public RayTracer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param config Ray tracing configuration
|
||||||
|
*/
|
||||||
|
explicit CPURayTracer(const RayTracingConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor
|
||||||
|
*/
|
||||||
|
~CPURayTracer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render scene using CPU ray tracing
|
||||||
|
* @param scene Scene manager
|
||||||
|
* @param camera Camera
|
||||||
|
* @param gbuffer G-Buffer (optional)
|
||||||
|
* @param output Output texture ID
|
||||||
|
*/
|
||||||
|
void render(const SceneManager& scene,
|
||||||
|
const Camera& camera,
|
||||||
|
const GBuffer* gbuffer,
|
||||||
|
uint32_t output_texture) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update BVH
|
||||||
|
* @param bvh BVH reference
|
||||||
|
*/
|
||||||
|
void update_bvh(const BVH& bvh) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Trace a single ray
|
||||||
|
* @param ray Ray to trace
|
||||||
|
* @param depth Current recursion depth
|
||||||
|
* @return Ray color
|
||||||
|
*/
|
||||||
|
Vec3 trace_ray(const Ray& ray, int depth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shade hit point
|
||||||
|
* @param hit Hit record
|
||||||
|
* @param ray Incident ray
|
||||||
|
* @param depth Current recursion depth
|
||||||
|
* @return Shaded color
|
||||||
|
*/
|
||||||
|
Vec3 shade(const HitRecord& hit, const Ray& ray, int depth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute direct lighting
|
||||||
|
* @param hit Hit record
|
||||||
|
* @return Direct lighting contribution
|
||||||
|
*/
|
||||||
|
Vec3 compute_direct_lighting(const HitRecord& hit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute ambient occlusion
|
||||||
|
* @param hit Hit record
|
||||||
|
* @return AO factor [0, 1]
|
||||||
|
*/
|
||||||
|
Real compute_ambient_occlusion(const HitRecord& hit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check shadow ray
|
||||||
|
* @param origin Shadow ray origin
|
||||||
|
* @param direction Shadow ray direction
|
||||||
|
* @param max_distance Maximum distance
|
||||||
|
* @return true if in shadow
|
||||||
|
*/
|
||||||
|
bool is_in_shadow(const Vec3& origin, const Vec3& direction, Real max_distance);
|
||||||
|
|
||||||
|
const BVH* bvh_; ///< BVH reference
|
||||||
|
const SceneManager* scene_; ///< Scene reference
|
||||||
|
std::vector<Vec3> framebuffer_; ///< CPU framebuffer (HDR)
|
||||||
|
int width_; ///< Framebuffer width
|
||||||
|
int height_; ///< Framebuffer height
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RAYTRACER_CPU_RAYTRACER_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/raytracer/raytracer.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file raytracer.h
|
||||||
|
* @brief Ray tracing interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RAYTRACER_RAYTRACER_H
|
||||||
|
#define ARE_INCLUDE_RAYTRACER_RAYTRACER_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <are/core/config.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class SceneManager;
|
||||||
|
class Camera;
|
||||||
|
class GBuffer;
|
||||||
|
class BVH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class RayTracer
|
||||||
|
* @brief Abstract ray tracing interface
|
||||||
|
*
|
||||||
|
* Base class for CPU and GPU ray tracing implementations.
|
||||||
|
*/
|
||||||
|
class RayTracer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param config Ray tracing configuration
|
||||||
|
*/
|
||||||
|
explicit RayTracer(const RayTracingConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Virtual destructor
|
||||||
|
*/
|
||||||
|
virtual ~RayTracer() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render scene using ray tracing
|
||||||
|
* @param scene Scene manager
|
||||||
|
* @param camera Camera
|
||||||
|
* @param gbuffer G-Buffer (optional, for hybrid rendering)
|
||||||
|
* @param output Output texture ID
|
||||||
|
*/
|
||||||
|
virtual void render(const SceneManager& scene,
|
||||||
|
const Camera& camera,
|
||||||
|
const GBuffer* gbuffer,
|
||||||
|
uint32_t output_texture) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update BVH
|
||||||
|
* @param bvh BVH reference
|
||||||
|
*/
|
||||||
|
virtual void update_bvh(const BVH& bvh) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set configuration
|
||||||
|
* @param config New configuration
|
||||||
|
*/
|
||||||
|
virtual void set_config(const RayTracingConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get configuration
|
||||||
|
* @return Current configuration
|
||||||
|
*/
|
||||||
|
const RayTracingConfig& get_config() const { return config_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
RayTracingConfig config_; ///< Ray tracing configuration
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
#endif // ARE_INCLUDE_RAYTRACER_RAYTRACER_H
|
||||||
|
```
|
||||||
|
此外,你可能需要随机数模块。
|
||||||
|
### 文件:include/utils/random.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file random.h
|
||||||
|
* @brief Random number generation utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_UTILS_RANDOM_H
|
||||||
|
#define ARE_INCLUDE_UTILS_RANDOM_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class RandomGenerator
|
||||||
|
* @brief Thread-safe random number generator
|
||||||
|
*
|
||||||
|
* Uses PCG (Permuted Congruential Generator) for high-quality random numbers.
|
||||||
|
*/
|
||||||
|
class RandomGenerator {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor with optional seed
|
||||||
|
* @param seed Random seed (0 = use random device)
|
||||||
|
*/
|
||||||
|
explicit RandomGenerator(uint64_t seed = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random float in [0, 1)
|
||||||
|
* @return Random float
|
||||||
|
*/
|
||||||
|
Real random_float();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random float in [min, max)
|
||||||
|
* @param min Minimum value
|
||||||
|
* @param max Maximum value
|
||||||
|
* @return Random float
|
||||||
|
*/
|
||||||
|
Real random_float(Real min, Real max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random integer in [min, max]
|
||||||
|
* @param min Minimum value
|
||||||
|
* @param max Maximum value
|
||||||
|
* @return Random integer
|
||||||
|
*/
|
||||||
|
int random_int(int min, int max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random point in unit disk
|
||||||
|
* @return Random point (z = 0)
|
||||||
|
*/
|
||||||
|
Vec3 random_in_unit_disk();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random point in unit sphere
|
||||||
|
* @return Random point
|
||||||
|
*/
|
||||||
|
Vec3 random_in_unit_sphere();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random unit vector
|
||||||
|
* @return Random unit vector
|
||||||
|
*/
|
||||||
|
Vec3 random_unit_vector();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random vector in hemisphere
|
||||||
|
* @param normal Hemisphere normal
|
||||||
|
* @return Random vector in hemisphere
|
||||||
|
*/
|
||||||
|
Vec3 random_in_hemisphere(const Vec3 &normal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random cosine-weighted direction
|
||||||
|
* @param normal Surface normal
|
||||||
|
* @return Random direction (cosine-weighted)
|
||||||
|
*/
|
||||||
|
Vec3 random_cosine_direction(const Vec3 &normal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set seed for reproducible results
|
||||||
|
* @param seed Random seed
|
||||||
|
*/
|
||||||
|
void set_seed(uint64_t seed);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mt19937_64 rng_; ///< Random number generator
|
||||||
|
std::uniform_real_distribution<Real> dist_; ///< Uniform distribution [0, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get thread-local random generator
|
||||||
|
* @return Reference to thread-local generator
|
||||||
|
*/
|
||||||
|
RandomGenerator &get_thread_random();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random float in [0, 1) using thread-local generator
|
||||||
|
* @return Random float
|
||||||
|
*/
|
||||||
|
inline Real random_float() {
|
||||||
|
return get_thread_random().random_float();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate random float in [min, max) using thread-local generator
|
||||||
|
* @param min Minimum value
|
||||||
|
* @param max Maximum value
|
||||||
|
* @return Random float
|
||||||
|
*/
|
||||||
|
inline Real random_float(Real min, Real max) {
|
||||||
|
return get_thread_random().random_float(min, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_UTILS_RANDOM_H
|
||||||
|
```
|
||||||
|
|
||||||
|
同样地,如果有依赖或者需要给你的头文件/实现文件还没有给你,欢迎提出;如果已有的数学或者随机数模块需要补充,请分别给出函数声明与实现。
|
||||||
|
|
@ -0,0 +1,322 @@
|
||||||
|
你对代码的观察非常敏锐!我对你的实现计划表示认同,下面是你要求到的和可能用到的头文件:
|
||||||
|
### 文件:include/are/raytracer/ray.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file ray.h
|
||||||
|
* @brief Ray structure for ray tracing
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RAYTRACER_RAY_H
|
||||||
|
#define ARE_INCLUDE_RAYTRACER_RAY_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Ray
|
||||||
|
* @brief Ray representation for ray tracing
|
||||||
|
*/
|
||||||
|
struct Ray {
|
||||||
|
Vec3 origin_; ///< Ray origin
|
||||||
|
Vec3 direction_; ///< Ray direction (normalized)
|
||||||
|
Real t_min_; ///< Minimum t value
|
||||||
|
Real t_max_; ///< Maximum t value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
|
Ray();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct ray with origin and direction
|
||||||
|
* @param origin Ray origin
|
||||||
|
* @param direction Ray direction (will be normalized)
|
||||||
|
* @param t_min Minimum t value
|
||||||
|
* @param t_max Maximum t value
|
||||||
|
*/
|
||||||
|
Ray(const Vec3& origin, const Vec3& direction,
|
||||||
|
Real t_min = are_epsilon, Real t_max = 1e30f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Evaluate ray at parameter t
|
||||||
|
* @param t Parameter value
|
||||||
|
* @return Point on ray
|
||||||
|
*/
|
||||||
|
Vec3 at(Real t) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if t is within valid range
|
||||||
|
* @param t Parameter value
|
||||||
|
* @return true if t is valid
|
||||||
|
*/
|
||||||
|
bool is_valid_t(Real t) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RAYTRACER_RAY_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/raytracer/hit_record.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file hit_record.h
|
||||||
|
* @brief Ray-surface intersection record
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_RAYTRACER_HIT_RECORD_H
|
||||||
|
#define ARE_INCLUDE_RAYTRACER_HIT_RECORD_H
|
||||||
|
|
||||||
|
#include <are/core/types.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct HitRecord
|
||||||
|
* @brief Information about ray-surface intersection
|
||||||
|
*/
|
||||||
|
struct HitRecord {
|
||||||
|
Vec3 position_; ///< Hit position in world space
|
||||||
|
Vec3 normal_; ///< Surface normal at hit point
|
||||||
|
Vec2 texcoord_; ///< Texture coordinates at hit point
|
||||||
|
Vec3 tangent_; ///< Tangent vector at hit point
|
||||||
|
Real t_; ///< Ray parameter at hit point
|
||||||
|
MaterialHandle material_; ///< Material at hit point
|
||||||
|
uint32_t triangle_index_; ///< Triangle index that was hit
|
||||||
|
bool front_face_; ///< Whether ray hit front face
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
|
HitRecord();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set face normal based on ray direction
|
||||||
|
* @param ray_direction Ray direction
|
||||||
|
* @param outward_normal Outward-facing normal
|
||||||
|
*/
|
||||||
|
void set_face_normal(const Vec3& ray_direction, const Vec3& outward_normal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if hit record is valid
|
||||||
|
* @return true if hit occurred
|
||||||
|
*/
|
||||||
|
bool is_valid() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_RAYTRACER_HIT_RECORD_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/scene/directional_light.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file directional_light.h
|
||||||
|
* @brief Directional light implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_SCENE_DIRECTIONAL_LIGHT_H
|
||||||
|
#define ARE_INCLUDE_SCENE_DIRECTIONAL_LIGHT_H
|
||||||
|
|
||||||
|
#include <are/scene/light.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DirectionalLight
|
||||||
|
* @brief Directional light source (sun-like)
|
||||||
|
*
|
||||||
|
* Represents an infinitely distant light source with parallel rays.
|
||||||
|
*/
|
||||||
|
class DirectionalLight : public Light {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
|
DirectionalLight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct with direction and color
|
||||||
|
* @param direction Light direction (will be normalized)
|
||||||
|
* @param color Light color
|
||||||
|
* @param intensity Light intensity
|
||||||
|
*/
|
||||||
|
DirectionalLight(const Vec3& direction, const Vec3& color = Vec3(1.0f),
|
||||||
|
Real intensity = 1.0f);
|
||||||
|
|
||||||
|
// Direction
|
||||||
|
void set_direction(const Vec3& direction);
|
||||||
|
const Vec3& get_direction() const { return direction_; }
|
||||||
|
|
||||||
|
// Light interface
|
||||||
|
LightData pack() const override;
|
||||||
|
bool affects_point(const Vec3& point) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec3 direction_; ///< Light direction (normalized)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_SCENE_DIRECTIONAL_LIGHT_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/scene/point_light.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file point_light.h
|
||||||
|
* @brief Point light implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_SCENE_POINT_LIGHT_H
|
||||||
|
#define ARE_INCLUDE_SCENE_POINT_LIGHT_H
|
||||||
|
|
||||||
|
#include <are/scene/light.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PointLight
|
||||||
|
* @brief Point light source
|
||||||
|
*
|
||||||
|
* Emits light equally in all directions from a single point.
|
||||||
|
*/
|
||||||
|
class PointLight : public Light {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
|
PointLight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct with position and color
|
||||||
|
* @param position Light position
|
||||||
|
* @param color Light color
|
||||||
|
* @param intensity Light intensity
|
||||||
|
* @param range Light range (attenuation distance)
|
||||||
|
*/
|
||||||
|
PointLight(const Vec3& position, const Vec3& color = Vec3(1.0f),
|
||||||
|
Real intensity = 1.0f, Real range = 10.0f);
|
||||||
|
|
||||||
|
// Position
|
||||||
|
void set_position(const Vec3& position);
|
||||||
|
const Vec3& get_position() const { return position_; }
|
||||||
|
|
||||||
|
// Range (attenuation)
|
||||||
|
void set_range(Real range);
|
||||||
|
Real get_range() const { return range_; }
|
||||||
|
|
||||||
|
// Attenuation parameters
|
||||||
|
void set_attenuation(Real constant, Real linear, Real quadratic);
|
||||||
|
Real get_constant_attenuation() const { return attenuation_constant_; }
|
||||||
|
Real get_linear_attenuation() const { return attenuation_linear_; }
|
||||||
|
Real get_quadratic_attenuation() const { return attenuation_quadratic_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate attenuation at given distance
|
||||||
|
* @param distance Distance from light
|
||||||
|
* @return Attenuation factor [0, 1]
|
||||||
|
*/
|
||||||
|
Real calculate_attenuation(Real distance) const;
|
||||||
|
|
||||||
|
// Light interface
|
||||||
|
LightData pack() const override;
|
||||||
|
bool affects_point(const Vec3& point) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec3 position_; ///< Light position
|
||||||
|
Real range_; ///< Light range
|
||||||
|
Real attenuation_constant_; ///< Constant attenuation factor
|
||||||
|
Real attenuation_linear_; ///< Linear attenuation factor
|
||||||
|
Real attenuation_quadratic_; ///< Quadratic attenuation factor
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_SCENE_POINT_LIGHT_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件:include/are/scene/spot_light.h
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* @file spot_light.h
|
||||||
|
* @brief Spot light implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ARE_INCLUDE_SCENE_SPOT_LIGHT_H
|
||||||
|
#define ARE_INCLUDE_SCENE_SPOT_LIGHT_H
|
||||||
|
|
||||||
|
#include <are/scene/light.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpotLight
|
||||||
|
* @brief Spot light source
|
||||||
|
*
|
||||||
|
* Emits light in a cone from a single point.
|
||||||
|
*/
|
||||||
|
class SpotLight : public Light {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor
|
||||||
|
*/
|
||||||
|
SpotLight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct with position, direction, and angles
|
||||||
|
* @param position Light position
|
||||||
|
* @param direction Light direction
|
||||||
|
* @param inner_angle Inner cone angle in degrees
|
||||||
|
* @param outer_angle Outer cone angle in degrees
|
||||||
|
* @param color Light color
|
||||||
|
* @param intensity Light intensity
|
||||||
|
*/
|
||||||
|
SpotLight(const Vec3& position, const Vec3& direction,Real inner_angle, Real outer_angle,
|
||||||
|
const Vec3& color = Vec3(1.0f), Real intensity = 1.0f);
|
||||||
|
|
||||||
|
// Position and direction
|
||||||
|
void set_position(const Vec3& position);
|
||||||
|
void set_direction(const Vec3& direction);
|
||||||
|
const Vec3& get_position() const { return position_; }
|
||||||
|
const Vec3& get_direction() const { return direction_; }
|
||||||
|
|
||||||
|
// Cone angles (in degrees)
|
||||||
|
void set_inner_angle(Real angle);
|
||||||
|
void set_outer_angle(Real angle);
|
||||||
|
Real get_inner_angle() const { return inner_angle_; }
|
||||||
|
Real get_outer_angle() const { return outer_angle_; }
|
||||||
|
|
||||||
|
// Range
|
||||||
|
void set_range(Real range);
|
||||||
|
Real get_range() const { return range_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate spotlight intensity at given direction
|
||||||
|
* @param to_point Direction from light to point (normalized)
|
||||||
|
* @return Spotlight factor [0, 1]
|
||||||
|
*/
|
||||||
|
Real calculate_spot_factor(const Vec3& to_point) const;
|
||||||
|
|
||||||
|
// Light interface
|
||||||
|
LightData pack() const override;
|
||||||
|
bool affects_point(const Vec3& point) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec3 position_; ///< Light position
|
||||||
|
Vec3 direction_; ///< Light direction (normalized)
|
||||||
|
Real inner_angle_; ///< Inner cone angle (degrees)
|
||||||
|
Real outer_angle_; ///< Outer cone angle (degrees)
|
||||||
|
Real range_; ///< Light range
|
||||||
|
Real cos_inner_; ///< Cosine of inner angle (cache
|
||||||
|
Real cos_outer_; ///< Cosine of outer angle (cached)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
||||||
|
#endif // ARE_INCLUDE_SCENE_SPOT_LIGHT_H
|
||||||
|
```
|
||||||
|
|
||||||
|
还有缺失的头文件需要补充吗?如果没有的话,我们就可以开始实现Phase 2了。
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
#version 430 core
|
||||||
|
|
||||||
|
// Inputs from vertex shader
|
||||||
|
in vec3 v_world_position;
|
||||||
|
in vec3 v_world_normal;
|
||||||
|
in vec2 v_texcoord;
|
||||||
|
in vec3 v_world_tangent;
|
||||||
|
|
||||||
|
// Material uniforms
|
||||||
|
uniform vec3 u_albedo;
|
||||||
|
uniform float u_metallic;
|
||||||
|
uniform float u_roughness;
|
||||||
|
|
||||||
|
// G-Buffer outputs
|
||||||
|
layout(location = 0) out vec3 g_position;
|
||||||
|
layout(location = 1) out vec3 g_normal;
|
||||||
|
layout(location = 2) out vec4 g_albedo_metallic;
|
||||||
|
layout(location = 3) out vec2 g_roughness_ao;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Output world position
|
||||||
|
g_position = v_world_position;
|
||||||
|
|
||||||
|
// Output normalized world normal
|
||||||
|
g_normal = normalize(v_world_normal);
|
||||||
|
|
||||||
|
// Output albedo (RGB) and metallic (A)
|
||||||
|
g_albedo_metallic = vec4(u_albedo, u_metallic);
|
||||||
|
|
||||||
|
// Output roughness (R) and ambient occlusion (G)
|
||||||
|
// AO is set to 1.0 by default (no occlusion)
|
||||||
|
g_roughness_ao = vec2(u_roughness, 1.0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#version 430 core
|
||||||
|
|
||||||
|
// Vertex attributes
|
||||||
|
layout(location = 0) in vec3 a_position;
|
||||||
|
layout(location = 1) in vec3 a_normal;
|
||||||
|
layout(location = 2) in vec2 a_texcoord;
|
||||||
|
layout(location = 3) in vec3 a_tangent;
|
||||||
|
|
||||||
|
// Uniforms
|
||||||
|
uniform mat4 u_model;
|
||||||
|
uniform mat4 u_view;
|
||||||
|
uniform mat4 u_projection;
|
||||||
|
uniform mat3 u_normal_matrix;
|
||||||
|
|
||||||
|
// Outputs to fragment shader
|
||||||
|
out vec3 v_world_position;
|
||||||
|
out vec3 v_world_normal;
|
||||||
|
out vec2 v_texcoord;
|
||||||
|
out vec3 v_world_tangent;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Transform position to world space
|
||||||
|
vec4 world_pos = u_model * vec4(a_position, 1.0);
|
||||||
|
v_world_position = world_pos.xyz;
|
||||||
|
|
||||||
|
// Transform normal and tangent to world space
|
||||||
|
v_world_normal = normalize(u_normal_matrix * a_normal);
|
||||||
|
v_world_tangent = normalize(u_normal_matrix * a_tangent);
|
||||||
|
|
||||||
|
// Pass through texture coordinates
|
||||||
|
v_texcoord = a_texcoord;
|
||||||
|
|
||||||
|
// Transform to clip space
|
||||||
|
gl_Position = u_projection * u_view * world_pos;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,464 @@
|
||||||
|
/**
|
||||||
|
* @file bvh.cpp
|
||||||
|
* @brief Implementation of BVH class (optimized version)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <are/acceleration/bvh.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
BVH::BVH()
|
||||||
|
: root_index_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
BVH::~BVH() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::build(const std::vector<Triangle> &triangles, const BVHBuildConfig &config) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (triangles.empty()) {
|
||||||
|
ARE_LOG_WARN("BVH: Cannot build from empty triangle list");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing data
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// Copy triangles
|
||||||
|
triangles_ = triangles;
|
||||||
|
|
||||||
|
// Build BVH
|
||||||
|
BVHBuilder builder(config);
|
||||||
|
root_index_ = builder.build(triangles_, nodes_, primitive_indices_);
|
||||||
|
|
||||||
|
// Get statistics
|
||||||
|
size_t node_count, leaf_count;
|
||||||
|
int max_depth;
|
||||||
|
builder.get_stats(node_count, leaf_count, max_depth);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("BVH: Built successfully");
|
||||||
|
ARE_LOG_INFO(" Triangles: " + std::to_string(triangles_.size()));
|
||||||
|
ARE_LOG_INFO(" Nodes: " + std::to_string(node_count));
|
||||||
|
ARE_LOG_INFO(" Leaves: " + std::to_string(leaf_count));
|
||||||
|
ARE_LOG_INFO(" Max depth: " + std::to_string(max_depth));
|
||||||
|
ARE_LOG_INFO(" Memory: " + std::to_string(get_memory_usage() / 1024) + " KB");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::intersect(const Ray &ray, HitRecord &hit) const {
|
||||||
|
// Note: No profiling here - this is a hot path
|
||||||
|
|
||||||
|
if (!is_built()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use iterative traversal with stack for better performance
|
||||||
|
return intersect_iterative(ray, hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::intersect_any(const Ray &ray, Real t_max) const {
|
||||||
|
// Note: No profiling here - this is a hot path
|
||||||
|
|
||||||
|
if (!is_built()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return intersect_any_iterative(ray, t_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BVH::get_memory_usage() const {
|
||||||
|
size_t total = 0;
|
||||||
|
total += nodes_.size() * sizeof(BVHNode);
|
||||||
|
total += primitive_indices_.size() * sizeof(uint32_t);
|
||||||
|
total += triangles_.size() * sizeof(Triangle);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BVH::clear() {
|
||||||
|
nodes_.clear();
|
||||||
|
primitive_indices_.clear();
|
||||||
|
triangles_.clear();
|
||||||
|
root_index_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::intersect_iterative(const Ray &ray, HitRecord &hit) const {
|
||||||
|
// Precompute inverse direction for faster AABB tests
|
||||||
|
Vec3 inv_dir(
|
||||||
|
1.0f / ray.direction_.x,
|
||||||
|
1.0f / ray.direction_.y,
|
||||||
|
1.0f / ray.direction_.z);
|
||||||
|
|
||||||
|
// Stack-based traversal (64 levels is enough for most scenes)
|
||||||
|
uint32_t stack[64];
|
||||||
|
int stack_ptr = 0;
|
||||||
|
stack[stack_ptr++] = root_index_;
|
||||||
|
|
||||||
|
bool hit_anything = false;
|
||||||
|
Real closest_t = ray.t_max_;
|
||||||
|
|
||||||
|
while (stack_ptr > 0) {
|
||||||
|
uint32_t node_index = stack[--stack_ptr];
|
||||||
|
|
||||||
|
if (node_index >= nodes_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BVHNode &node = nodes_[node_index];
|
||||||
|
|
||||||
|
// Fast AABB test with precomputed inverse direction
|
||||||
|
Real t_min, t_max;
|
||||||
|
if (!intersect_aabb_fast(node.bounds_, ray, inv_dir, closest_t, t_min, t_max)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.is_leaf()) {
|
||||||
|
// Test all primitives in leaf
|
||||||
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
||||||
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
||||||
|
|
||||||
|
if (prim_idx >= triangles_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Triangle &triangle = triangles_[prim_idx];
|
||||||
|
|
||||||
|
HitRecord temp_hit;
|
||||||
|
if (intersect_triangle_fast(triangle, ray, closest_t, temp_hit)) {
|
||||||
|
closest_t = temp_hit.t_;
|
||||||
|
hit = temp_hit;
|
||||||
|
hit.triangle_index_ = prim_idx;
|
||||||
|
hit_anything = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Push children to stack (far child first, so near child is processed first)
|
||||||
|
if (node.left_child_ >= nodes_.size() || node.right_child_ >= nodes_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BVHNode &left = nodes_[node.left_child_];
|
||||||
|
const BVHNode &right = nodes_[node.right_child_];
|
||||||
|
|
||||||
|
Real t_left_min, t_left_max;
|
||||||
|
Real t_right_min, t_right_max;
|
||||||
|
|
||||||
|
bool hit_left = intersect_aabb_fast(left.bounds_, ray, inv_dir, closest_t,
|
||||||
|
t_left_min, t_left_max);
|
||||||
|
bool hit_right = intersect_aabb_fast(right.bounds_, ray, inv_dir, closest_t,
|
||||||
|
t_right_min, t_right_max);
|
||||||
|
|
||||||
|
if (hit_left && hit_right) {
|
||||||
|
// Push far child first (so near child is popped first)
|
||||||
|
if (t_left_min < t_right_min) {
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.right_child_;
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.left_child_;
|
||||||
|
} else {
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.left_child_;
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.right_child_;
|
||||||
|
}
|
||||||
|
} else if (hit_left) {
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.left_child_;
|
||||||
|
} else if (hit_right) {
|
||||||
|
if (stack_ptr < 64)
|
||||||
|
stack[stack_ptr++] = node.right_child_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hit_anything;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::intersect_any_iterative(const Ray &ray, Real t_max) const {
|
||||||
|
// Precompute inverse direction
|
||||||
|
Vec3 inv_dir(
|
||||||
|
1.0f / ray.direction_.x,
|
||||||
|
1.0f / ray.direction_.y,
|
||||||
|
1.0f / ray.direction_.z);
|
||||||
|
|
||||||
|
// Stack-based traversal
|
||||||
|
uint32_t stack[64];
|
||||||
|
int stack_ptr = 0;
|
||||||
|
stack[stack_ptr++] = root_index_;
|
||||||
|
|
||||||
|
while (stack_ptr > 0) {
|
||||||
|
uint32_t node_index = stack[--stack_ptr];
|
||||||
|
|
||||||
|
if (node_index >= nodes_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BVHNode &node = nodes_[node_index];
|
||||||
|
|
||||||
|
// Fast AABB test
|
||||||
|
Real t_min, t_max_box;
|
||||||
|
if (!intersect_aabb_fast(node.bounds_, ray, inv_dir, t_max, t_min, t_max_box)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.is_leaf()) {
|
||||||
|
// Test all primitives in leaf
|
||||||
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
||||||
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
||||||
|
|
||||||
|
if (prim_idx >= triangles_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Triangle &triangle = triangles_[prim_idx];
|
||||||
|
|
||||||
|
if (triangle.intersect_fast(ray, t_max)) {
|
||||||
|
return true; // Early exit on first hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Push both children
|
||||||
|
if (node.left_child_ < nodes_.size() && stack_ptr < 64) {
|
||||||
|
stack[stack_ptr++] = node.left_child_;
|
||||||
|
}
|
||||||
|
if (node.right_child_ < nodes_.size() && stack_ptr < 64) {
|
||||||
|
stack[stack_ptr++] = node.right_child_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool BVH::intersect_aabb_fast(const AABB &bounds, const Ray &ray,
|
||||||
|
const Vec3 &inv_dir, Real t_max,
|
||||||
|
Real &t_min_out, Real &t_max_out) const {
|
||||||
|
// Optimized slab method with precomputed inverse direction
|
||||||
|
Real t_min = ray.t_min_;
|
||||||
|
Real t_max_local = t_max;
|
||||||
|
|
||||||
|
// X axis
|
||||||
|
{
|
||||||
|
Real t0 = (bounds.min_.x - ray.origin_.x) * inv_dir.x;
|
||||||
|
Real t1 = (bounds.max_.x - ray.origin_.x) * inv_dir.x;
|
||||||
|
|
||||||
|
if (inv_dir.x < 0.0f) {
|
||||||
|
Real temp = t0;
|
||||||
|
t0 = t1;
|
||||||
|
t1 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min = std::max(t_min, t0);
|
||||||
|
t_max_local = std::min(t_max_local, t1);
|
||||||
|
|
||||||
|
if (t_max_local < t_min) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y axis
|
||||||
|
{
|
||||||
|
Real t0 = (bounds.min_.y - ray.origin_.y) * inv_dir.y;
|
||||||
|
Real t1 = (bounds.max_.y - ray.origin_.y) * inv_dir.y;
|
||||||
|
|
||||||
|
if (inv_dir.y < 0.0f) {
|
||||||
|
Real temp = t0;
|
||||||
|
t0 = t1;
|
||||||
|
t1 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min = std::max(t_min, t0);
|
||||||
|
t_max_local = std::min(t_max_local, t1);
|
||||||
|
|
||||||
|
if (t_max_local < t_min) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Z axis
|
||||||
|
{
|
||||||
|
Real t0 = (bounds.min_.z - ray.origin_.z) * inv_dir.z;
|
||||||
|
Real t1 = (bounds.max_.z - ray.origin_.z) * inv_dir.z;
|
||||||
|
|
||||||
|
if (inv_dir.z < 0.0f) {
|
||||||
|
Real temp = t0;
|
||||||
|
t0 = t1;
|
||||||
|
t1 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min = std::max(t_min, t0);
|
||||||
|
t_max_local = std::min(t_max_local, t1);
|
||||||
|
|
||||||
|
if (t_max_local < t_min) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min_out = t_min;
|
||||||
|
t_max_out = t_max_local;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool BVH::intersect_triangle_fast(const Triangle &triangle, const Ray &ray,
|
||||||
|
Real t_max, HitRecord &hit) const {
|
||||||
|
// Möller-Trumbore algorithm (inlined for performance)
|
||||||
|
const Vec3 &v0 = triangle.v0_.position_;
|
||||||
|
const Vec3 &v1 = triangle.v1_.position_;
|
||||||
|
const Vec3 &v2 = triangle.v2_.position_;
|
||||||
|
|
||||||
|
const Vec3 edge1 = v1 - v0;
|
||||||
|
const Vec3 edge2 = v2 - v0;
|
||||||
|
|
||||||
|
const Vec3 h = glm::cross(ray.direction_, edge2);
|
||||||
|
const Real a = glm::dot(edge1, h);
|
||||||
|
|
||||||
|
// Check if ray is parallel to triangle
|
||||||
|
if (a > -are_epsilon && a < are_epsilon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Real f = 1.0f / a;
|
||||||
|
const Vec3 s = ray.origin_ - v0;
|
||||||
|
const Real u = f * glm::dot(s, h);
|
||||||
|
|
||||||
|
if (u < 0.0f || u > 1.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vec3 q = glm::cross(s, edge1);
|
||||||
|
const Real v = f * glm::dot(ray.direction_, q);
|
||||||
|
|
||||||
|
if (v < 0.0f || u + v > 1.0f) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Real t = f * glm::dot(edge2, q);
|
||||||
|
|
||||||
|
if (t < ray.t_min_ || t >= t_max) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill hit record
|
||||||
|
const Real w = 1.0f - u - v;
|
||||||
|
|
||||||
|
hit.t_ = t;
|
||||||
|
hit.position_ = ray.origin_ + ray.direction_ * t;
|
||||||
|
hit.material_ = triangle.material_;
|
||||||
|
|
||||||
|
// Interpolate vertex attributes
|
||||||
|
hit.normal_ = glm::normalize(
|
||||||
|
w * triangle.v0_.normal_ + u * triangle.v1_.normal_ + v * triangle.v2_.normal_);
|
||||||
|
|
||||||
|
hit.texcoord_ = w * triangle.v0_.texcoord_ + u * triangle.v1_.texcoord_ + v * triangle.v2_.texcoord_;
|
||||||
|
|
||||||
|
hit.tangent_ = glm::normalize(
|
||||||
|
w * triangle.v0_.tangent_ + u * triangle.v1_.tangent_ + v * triangle.v2_.tangent_);
|
||||||
|
|
||||||
|
// Determine front face
|
||||||
|
hit.set_face_normal(ray.direction_, hit.normal_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep recursive versions for reference/debugging
|
||||||
|
bool BVH::intersect_recursive(uint32_t node_index, const Ray &ray, HitRecord &hit) const {
|
||||||
|
if (node_index >= nodes_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BVHNode &node = nodes_[node_index];
|
||||||
|
|
||||||
|
Real t_min, t_max;
|
||||||
|
if (!node.bounds_.intersect_ray(ray, t_min, t_max)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t_min > hit.t_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hit_anything = false;
|
||||||
|
|
||||||
|
if (node.is_leaf()) {
|
||||||
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
||||||
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
||||||
|
|
||||||
|
if (prim_idx >= triangles_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Triangle &triangle = triangles_[prim_idx];
|
||||||
|
HitRecord temp_hit;
|
||||||
|
|
||||||
|
if (triangle.intersect(ray, temp_hit) && temp_hit.t_ < hit.t_) {
|
||||||
|
hit = temp_hit;
|
||||||
|
hit.triangle_index_ = prim_idx;
|
||||||
|
hit_anything = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Real t_left_min, t_left_max;
|
||||||
|
Real t_right_min, t_right_max;
|
||||||
|
|
||||||
|
bool hit_left = nodes_[node.left_child_].bounds_.intersect_ray(ray, t_left_min, t_left_max);
|
||||||
|
bool hit_right = nodes_[node.right_child_].bounds_.intersect_ray(ray, t_right_min, t_right_max);
|
||||||
|
|
||||||
|
// Traverse closer child first
|
||||||
|
if (hit_left && hit_right) {
|
||||||
|
if (t_left_min < t_right_min) {
|
||||||
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
||||||
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
||||||
|
} else {
|
||||||
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
||||||
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
||||||
|
}
|
||||||
|
} else if (hit_left) {
|
||||||
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
||||||
|
} else if (hit_right) {
|
||||||
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hit_anything;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BVH::intersect_any_recursive(uint32_t node_index, const Ray &ray, Real t_max) const {
|
||||||
|
if (node_index >= nodes_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BVHNode &node = nodes_[node_index];
|
||||||
|
|
||||||
|
Real t_min, t_max_box;
|
||||||
|
if (!node.bounds_.intersect_ray(ray, t_min, t_max_box)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t_min > t_max) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.is_leaf()) {
|
||||||
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
||||||
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
||||||
|
|
||||||
|
if (prim_idx >= triangles_.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangles_[prim_idx].intersect_fast(ray, t_max)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return intersect_any_recursive(node.left_child_, ray, t_max) || intersect_any_recursive(node.right_child_, ray, t_max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
/**
|
||||||
|
* @file bvh_builder.cpp
|
||||||
|
* @brief Implementation of BVH construction algorithms
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/acceleration/bvh_builder.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#ifdef ARE_USE_OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
BVHBuilder::BVHBuilder(const BVHBuildConfig& config)
|
||||||
|
: config_(config)
|
||||||
|
, node_count_(0)
|
||||||
|
, leaf_count_(0)
|
||||||
|
, max_depth_reached_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BVHBuilder::build(const std::vector<Triangle>& triangles,
|
||||||
|
std::vector<BVHNode>& nodes,
|
||||||
|
std::vector<uint32_t>& primitive_indices) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (triangles.empty()) {
|
||||||
|
ARE_LOG_WARN("BVHBuilder: Cannot build BVH from empty triangle list");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("BVHBuilder: Building BVH for " + std::to_string(triangles.size()) + " triangles");
|
||||||
|
|
||||||
|
// Reset statistics
|
||||||
|
node_count_ = 0;
|
||||||
|
leaf_count_ = 0;
|
||||||
|
max_depth_reached_ = 0;
|
||||||
|
|
||||||
|
// Initialize primitive indices
|
||||||
|
primitive_indices.resize(triangles.size());
|
||||||
|
for (size_t i = 0; i < triangles.size(); ++i) {
|
||||||
|
primitive_indices[i] = static_cast<uint32_t>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve space for nodes (estimate: 2 * num_triangles)
|
||||||
|
nodes.clear();
|
||||||
|
nodes.reserve(triangles.size() * 2);
|
||||||
|
|
||||||
|
// Build BVH recursively
|
||||||
|
uint32_t root_index = build_recursive(triangles, nodes, primitive_indices,
|
||||||
|
0, static_cast<uint32_t>(triangles.size()), 0);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("BVHBuilder: Built BVH with " + std::to_string(node_count_) + " nodes, " +
|
||||||
|
std::to_string(leaf_count_) + " leaves, max depth " +
|
||||||
|
std::to_string(max_depth_reached_));
|
||||||
|
|
||||||
|
return root_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BVHBuilder::get_stats(size_t& node_count, size_t& leaf_count, int& max_depth) const {
|
||||||
|
node_count = node_count_;
|
||||||
|
leaf_count = leaf_count_;
|
||||||
|
max_depth = max_depth_reached_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BVHBuilder::build_recursive(const std::vector<Triangle>& triangles,
|
||||||
|
std::vector<BVHNode>& nodes,
|
||||||
|
std::vector<uint32_t>& primitive_indices,
|
||||||
|
uint32_t start, uint32_t end, int depth) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
max_depth_reached_ = std::max(max_depth_reached_, depth);
|
||||||
|
|
||||||
|
// Create new node
|
||||||
|
uint32_t node_index = static_cast<uint32_t>(nodes.size());
|
||||||
|
nodes.emplace_back();
|
||||||
|
BVHNode& node = nodes[node_index];
|
||||||
|
node_count_++;
|
||||||
|
|
||||||
|
// Compute bounding box for all primitives in range
|
||||||
|
node.bounds_ = AABB::invalid();
|
||||||
|
for (uint32_t i = start; i < end; ++i) {
|
||||||
|
uint32_t prim_idx = primitive_indices[i];
|
||||||
|
node.bounds_.expand(triangles[prim_idx].compute_aabb());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t count = end - start;
|
||||||
|
|
||||||
|
// Check if we should create a leaf
|
||||||
|
bool should_create_leaf = (count <= static_cast<uint32_t>(config_.max_leaf_size_)) ||
|
||||||
|
(depth >= config_.max_depth_);
|
||||||
|
|
||||||
|
if (should_create_leaf) {
|
||||||
|
// Create leaf node
|
||||||
|
node.first_primitive_ = start;
|
||||||
|
node.primitive_count_ = count;
|
||||||
|
leaf_count_++;
|
||||||
|
return node_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find best split axis
|
||||||
|
int split_axis = find_best_split_axis(triangles, primitive_indices, start, end);
|
||||||
|
|
||||||
|
// Sort primitives along split axis
|
||||||
|
std::sort(primitive_indices.begin() + start,
|
||||||
|
primitive_indices.begin() + end,
|
||||||
|
[&](uint32_t a, uint32_t b) {
|
||||||
|
return triangles[a].centroid()[split_axis] <
|
||||||
|
triangles[b].centroid()[split_axis];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find split position
|
||||||
|
uint32_t mid = start + count / 2;
|
||||||
|
|
||||||
|
// Use SAH if enabled
|
||||||
|
if (config_.split_method_ == BVHSplitMethod::ARE_BVH_SPLIT_SAH) {
|
||||||
|
Real best_cost = std::numeric_limits<Real>::max();
|
||||||
|
uint32_t best_split = mid;
|
||||||
|
|
||||||
|
// Try different split positions
|
||||||
|
const int num_buckets = 12;
|
||||||
|
for (int i = 1; i < num_buckets; ++i) {
|
||||||
|
uint32_t test_split = start + (count * i) / num_buckets;
|
||||||
|
|
||||||
|
// Compute bounding boxes for left and right
|
||||||
|
AABB left_bounds = AABB::invalid();
|
||||||
|
AABB right_bounds = AABB::invalid();
|
||||||
|
|
||||||
|
for (uint32_t j = start; j < test_split; ++j) {
|
||||||
|
left_bounds.expand(triangles[primitive_indices[j]].compute_aabb());
|
||||||
|
}
|
||||||
|
for (uint32_t j = test_split; j < end; ++j) {
|
||||||
|
right_bounds.expand(triangles[primitive_indices[j]].compute_aabb());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute SAH cost
|
||||||
|
Real left_cost = compute_sah_cost(left_bounds, test_split - start);
|
||||||
|
Real right_cost = compute_sah_cost(right_bounds, end - test_split);
|
||||||
|
Real cost = left_cost + right_cost;
|
||||||
|
|
||||||
|
if (cost < best_cost) {
|
||||||
|
best_cost = cost;
|
||||||
|
best_split = test_split;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mid = best_split;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we don't create empty children
|
||||||
|
if (mid == start || mid == end) {
|
||||||
|
mid = start + count / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create internal node
|
||||||
|
node.primitive_count_ = 0; // Mark as internal node
|
||||||
|
|
||||||
|
// Build left and right children
|
||||||
|
uint32_t left_child = build_recursive(triangles, nodes, primitive_indices,
|
||||||
|
start, mid, depth + 1);
|
||||||
|
uint32_t right_child = build_recursive(triangles, nodes, primitive_indices,
|
||||||
|
mid, end, depth + 1);
|
||||||
|
|
||||||
|
// Update node (it may have been reallocated)
|
||||||
|
nodes[node_index].left_child_ = left_child;
|
||||||
|
nodes[node_index].right_child_ = right_child;
|
||||||
|
|
||||||
|
return node_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BVHBuilder::find_best_split_axis(const std::vector<Triangle>& triangles,
|
||||||
|
const std::vector<uint32_t>& indices,
|
||||||
|
uint32_t start, uint32_t end) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Compute centroid bounds
|
||||||
|
AABB centroid_bounds = AABB::invalid();
|
||||||
|
for (uint32_t i = start; i < end; ++i) {
|
||||||
|
centroid_bounds.expand(triangles[indices[i]].centroid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return longest axis
|
||||||
|
return centroid_bounds.longest_axis();
|
||||||
|
}
|
||||||
|
|
||||||
|
Real BVHBuilder::compute_sah_cost(const AABB& bounds, uint32_t count) {
|
||||||
|
// SAH cost = surface_area * primitive_count
|
||||||
|
// This is a simplified version; full SAH includes traversal cost
|
||||||
|
return bounds.surface_area() * static_cast<Real>(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* @file bvh_node.cpp
|
||||||
|
* @brief Implementation of BVHNode structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/acceleration/bvh_node.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
// BVHNode is a POD structure, no implementation needed
|
||||||
|
// All methods are inline in the header
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
/**
|
/**
|
||||||
* @file aabb.cpp
|
* @file aabb.cpp
|
||||||
* @brief Implementation of axis-aligned bounding box
|
* @brief Implementation of Axis-Aligned Bounding Box
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/geometry/aabb.h>
|
#include <are/geometry/aabb.h>
|
||||||
#include <are/raytracer/ray.h>
|
#include <are/raytracer/ray.h>
|
||||||
#include <limits>
|
#include <are/core/logger.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
AABB::AABB()
|
AABB::AABB()
|
||||||
: min_(std::numeric_limits<Real>::max())
|
: min_(std::numeric_limits<float>::max())
|
||||||
, max_(std::numeric_limits<Real>::lowest()) {
|
, max_(std::numeric_limits<float>::lowest()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB::AABB(const Vec3& min, const Vec3& max)
|
AABB::AABB(const Vec3& min, const Vec3& max)
|
||||||
|
|
@ -49,7 +51,6 @@ Real AABB::volume() const {
|
||||||
|
|
||||||
int AABB::longest_axis() const {
|
int AABB::longest_axis() const {
|
||||||
Vec3 d = size();
|
Vec3 d = size();
|
||||||
|
|
||||||
if (d.x > d.y && d.x > d.z) {
|
if (d.x > d.y && d.x > d.z) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (d.y > d.z) {
|
} else if (d.y > d.z) {
|
||||||
|
|
@ -65,9 +66,11 @@ void AABB::expand(const Vec3& point) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AABB::expand(const AABB& other) {
|
void AABB::expand(const AABB& other) {
|
||||||
|
if (other.is_valid()) {
|
||||||
min_ = glm::min(min_, other.min_);
|
min_ = glm::min(min_, other.min_);
|
||||||
max_ = glm::max(max_, other.max_);
|
max_ = glm::max(max_, other.max_);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AABB::contains(const Vec3& point) const {
|
bool AABB::contains(const Vec3& point) const {
|
||||||
return point.x >= min_.x && point.x <= max_.x &&
|
return point.x >= min_.x && point.x <= max_.x &&
|
||||||
|
|
@ -76,27 +79,44 @@ bool AABB::contains(const Vec3& point) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AABB::intersects(const AABB& other) const {
|
bool AABB::intersects(const AABB& other) const {
|
||||||
return (min_.x <= other.max_.x && max_.x >= other.min_.x) &&
|
return min_.x <= other.max_.x && max_.x >= other.min_.x &&
|
||||||
(min_.y <= other.max_.y && max_.y >= other.min_.y) &&
|
min_.y <= other.max_.y && max_.y >= other.min_.y &&
|
||||||
(min_.z <= other.max_.z && max_.z >= other.min_.z);
|
min_.z <= other.max_.z && max_.z >= other.min_.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AABB::intersect_ray(const Ray& ray, Real& t_min, Real& t_max) const {
|
bool AABB::intersect_ray(const Ray& ray, Real& t_min_out, Real& t_max_out) const {
|
||||||
// Optimized ray-AABB intersection (slab method)
|
// Slab method for ray-AABB intersection
|
||||||
Vec3 inv_dir = 1.0f / ray.direction_;
|
// Reference: "An Efficient and Robust Ray-Box Intersection Algorithm" by Williams et al.
|
||||||
Vec3 t0 = (min_ - ray.origin_) * inv_dir;
|
|
||||||
Vec3 t1 = (max_ - ray.origin_) * inv_dir;
|
|
||||||
|
|
||||||
Vec3 t_near = glm::min(t0, t1);
|
Real t_min = ray.t_min_;
|
||||||
Vec3 t_far = glm::max(t0, t1);
|
Real t_max = ray.t_max_;
|
||||||
|
|
||||||
t_min = glm::max(glm::max(t_near.x, t_near.y), t_near.z);
|
for (int i = 0; i < 3; ++i) {
|
||||||
t_max = glm::min(glm::min(t_far.x, t_far.y), t_far.z);
|
Real inv_d = 1.0f / ray.direction_[i];
|
||||||
|
Real t0 = (min_[i] - ray.origin_[i]) * inv_d;
|
||||||
|
Real t1 = (max_[i] - ray.origin_[i]) * inv_d;
|
||||||
|
|
||||||
return t_max >= t_min && t_max >= ray.t_min_;
|
if (inv_d < 0.0f) {
|
||||||
|
std::swap(t0, t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min = t0 > t_min ? t0 : t_min;
|
||||||
|
t_max = t1 < t_max ? t1 : t_max;
|
||||||
|
|
||||||
|
if (t_max < t_min) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t_min_out = t_min;
|
||||||
|
t_max_out = t_max;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB AABB::merge(const AABB& a, const AABB& b) {
|
AABB AABB::merge(const AABB& a, const AABB& b) {
|
||||||
|
if (!a.is_valid()) return b;
|
||||||
|
if (!b.is_valid()) return a;
|
||||||
|
|
||||||
return AABB(
|
return AABB(
|
||||||
glm::min(a.min_, b.min_),
|
glm::min(a.min_, b.min_),
|
||||||
glm::max(a.max_, b.max_)
|
glm::max(a.max_, b.max_)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* @file transform.cpp
|
* @file transform.cpp
|
||||||
* @brief Implementation of transformation utilities
|
* @brief Implementation of Transform class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include <are/geometry/transform.h>
|
#include <are/geometry/transform.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <glm/gtx/euler_angles.hpp>
|
#include <glm/gtx/euler_angles.hpp>
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ Transform::Transform()
|
||||||
, scale_(1.0f)
|
, scale_(1.0f)
|
||||||
, matrix_(1.0f)
|
, matrix_(1.0f)
|
||||||
, inverse_matrix_(1.0f)
|
, inverse_matrix_(1.0f)
|
||||||
, dirty_(false) {
|
, dirty_(true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform::Transform(const Vec3& position, const Vec3& rotation, const Vec3& scale)
|
Transform::Transform(const Vec3& position, const Vec3& rotation, const Vec3& scale)
|
||||||
|
|
@ -66,27 +67,62 @@ Mat3 Transform::get_normal_matrix() const {
|
||||||
if (dirty_) {
|
if (dirty_) {
|
||||||
update_matrix();
|
update_matrix();
|
||||||
}
|
}
|
||||||
|
// Normal matrix is the transpose of the inverse of the upper-left 3x3
|
||||||
return glm::transpose(glm::inverse(Mat3(matrix_)));
|
return glm::transpose(glm::inverse(Mat3(matrix_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 Transform::transform_point(const Vec3& point) const {
|
Vec3 Transform::transform_point(const Vec3& point) const {
|
||||||
Vec4 result = get_matrix() * Vec4(point, 1.0f);
|
if (dirty_) {
|
||||||
return Vec3(result) / result.w;
|
update_matrix();
|
||||||
|
}
|
||||||
|
Vec4 result = matrix_ * Vec4(point, 1.0f);
|
||||||
|
return Vec3(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 Transform::transform_direction(const Vec3& direction) const {
|
Vec3 Transform::transform_direction(const Vec3& direction) const {
|
||||||
return Vec3(get_matrix() * Vec4(direction, 0.0f));
|
if (dirty_) {
|
||||||
|
update_matrix();
|
||||||
|
}
|
||||||
|
Vec4 result = matrix_ * Vec4(direction, 0.0f);
|
||||||
|
return Vec3(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 Transform::transform_normal(const Vec3& normal) const {
|
Vec3 Transform::transform_normal(const Vec3& normal) const {
|
||||||
return glm::normalize(get_normal_matrix() * normal);
|
Mat3 normal_matrix = get_normal_matrix();
|
||||||
|
return glm::normalize(normal_matrix * normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform Transform::operator*(const Transform& other) const {
|
Transform Transform::operator*(const Transform& other) const {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Combine transforms by multiplying matrices
|
||||||
|
// Note: This is an approximation; for exact results,
|
||||||
|
// we would need to decompose the combined matrix
|
||||||
Transform result;
|
Transform result;
|
||||||
result.matrix_ = get_matrix() * other.get_matrix();
|
|
||||||
result.inverse_matrix_ = other.get_inverse_matrix() * get_inverse_matrix();
|
Mat4 combined = get_matrix() * other.get_matrix();
|
||||||
result.dirty_ = false;
|
|
||||||
|
// Extract translation
|
||||||
|
result.position_ = Vec3(combined[3]);
|
||||||
|
|
||||||
|
// Extract scale (approximate)
|
||||||
|
result.scale_.x = glm::length(Vec3(combined[0]));
|
||||||
|
result.scale_.y = glm::length(Vec3(combined[1]));
|
||||||
|
result.scale_.z = glm::length(Vec3(combined[2]));
|
||||||
|
|
||||||
|
// Remove scale from matrix to extract rotation
|
||||||
|
Mat3 rotation_matrix;
|
||||||
|
rotation_matrix[0] = Vec3(combined[0]) / result.scale_.x;
|
||||||
|
rotation_matrix[1] = Vec3(combined[1]) / result.scale_.y;
|
||||||
|
rotation_matrix[2] = Vec3(combined[2]) / result.scale_.z;
|
||||||
|
|
||||||
|
// Extract Euler angles (approximate, may have gimbal lock issues)
|
||||||
|
result.rotation_.x = std::atan2(rotation_matrix[2][1], rotation_matrix[2][2]);
|
||||||
|
result.rotation_.y = std::atan2(-rotation_matrix[2][0],std::sqrt(rotation_matrix[2][1] * rotation_matrix[2][1] +
|
||||||
|
rotation_matrix[2][2] * rotation_matrix[2][2]));
|
||||||
|
result.rotation_.z = std::atan2(rotation_matrix[1][0], rotation_matrix[0][0]);
|
||||||
|
|
||||||
|
result.dirty_ = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,12 +147,22 @@ void Transform::mark_dirty() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transform::update_matrix() const {
|
void Transform::update_matrix() const {
|
||||||
// Build transformation matrix: T * R * S
|
ARE_PROFILE_FUNCTION();
|
||||||
Mat4 translation = glm::translate(Mat4(1.0f), position_);
|
|
||||||
Mat4 rotation = glm::eulerAngleYXZ(rotation_.y, rotation_.x, rotation_.z);
|
|
||||||
Mat4 scale = glm::scale(Mat4(1.0f), scale_);
|
|
||||||
|
|
||||||
matrix_ = translation * rotation * scale;
|
// Build transformation matrix: T * R * S
|
||||||
|
// Translation
|
||||||
|
Mat4 translation_matrix = glm::translate(Mat4(1.0f), position_);
|
||||||
|
|
||||||
|
// Rotation (using Euler angles: YXZ order for typical camera/object rotation)
|
||||||
|
Mat4 rotation_matrix = glm::eulerAngleYXZ(rotation_.y, rotation_.x, rotation_.z);
|
||||||
|
|
||||||
|
// Scale
|
||||||
|
Mat4 scale_matrix = glm::scale(Mat4(1.0f), scale_);
|
||||||
|
|
||||||
|
// Combine: T * R * S
|
||||||
|
matrix_ = translation_matrix * rotation_matrix * scale_matrix;
|
||||||
|
|
||||||
|
// Compute inverse
|
||||||
inverse_matrix_ = glm::inverse(matrix_);
|
inverse_matrix_ = glm::inverse(matrix_);
|
||||||
|
|
||||||
dirty_ = false;
|
dirty_ = false;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* @file triangle.cpp
|
* @file triangle.cpp
|
||||||
* @brief Implementation of triangle primitive
|
* @brief Implementation of Triangle primitive
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
#include <are/geometry/triangle.h>
|
#include <are/geometry/triangle.h>
|
||||||
#include <are/raytracer/ray.h>
|
|
||||||
#include <are/raytracer/hit_record.h>
|
#include <are/raytracer/hit_record.h>
|
||||||
#include <glm/geometric.hpp>
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
Triangle::Triangle()
|
Triangle::Triangle()
|
||||||
: v0_()
|
: material_(are_invalid_handle) {
|
||||||
, v1_()
|
|
||||||
, v2_()
|
|
||||||
, material_(are_invalid_handle) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Triangle::Triangle(const Vertex& v0, const Vertex& v1, const Vertex& v2,
|
Triangle::Triangle(const Vertex &v0, const Vertex &v1, const Vertex &v2, MaterialHandle material)
|
||||||
MaterialHandle material)
|
|
||||||
: v0_(v0)
|
: v0_(v0)
|
||||||
, v1_(v1)
|
, v1_(v1)
|
||||||
, v2_(v2)
|
, v2_(v2)
|
||||||
|
|
@ -38,104 +36,110 @@ Vec3 Triangle::normal() const {
|
||||||
Real Triangle::area() const {
|
Real Triangle::area() const {
|
||||||
Vec3 edge1 = v1_.position_ - v0_.position_;
|
Vec3 edge1 = v1_.position_ - v0_.position_;
|
||||||
Vec3 edge2 = v2_.position_ - v0_.position_;
|
Vec3 edge2 = v2_.position_ - v0_.position_;
|
||||||
return glm::length(glm::cross(edge1, edge2)) * 0.5f;
|
return 0.5f * glm::length(glm::cross(edge1, edge2));
|
||||||
}
|
}
|
||||||
|
|
||||||
AABB Triangle::compute_aabb() const {
|
AABB Triangle::compute_aabb() const {
|
||||||
AABB box(v0_.position_);
|
AABB aabb(v0_.position_);
|
||||||
box.expand(v1_.position_);
|
aabb.expand(v1_.position_);
|
||||||
box.expand(v2_.position_);
|
aabb.expand(v2_.position_);
|
||||||
return box;
|
return aabb;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Triangle::intersect(const Ray &ray, HitRecord &hit) const {
|
bool Triangle::intersect(const Ray &ray, HitRecord &hit) const {
|
||||||
// Möller-Trumbore ray-triangle intersection algorithm
|
ARE_PROFILE_FUNCTION();
|
||||||
const Vec3& v0 = v0_.position_;
|
|
||||||
const Vec3& v1 = v1_.position_;
|
|
||||||
const Vec3& v2 = v2_.position_;
|
|
||||||
|
|
||||||
Vec3 edge1 = v1 - v0;
|
// Möller-Trumbore algorithm
|
||||||
Vec3 edge2 = v2 - v0;
|
// Reference: "Fast, Minimum Storage Ray/Triangle Intersection"
|
||||||
|
|
||||||
Vec3 h = glm::cross(ray.direction_, edge2);
|
const Vec3 edge1 = v1_.position_ - v0_.position_;
|
||||||
Real a = glm::dot(edge1, h);
|
const Vec3 edge2 = v2_.position_ - v0_.position_;
|
||||||
|
|
||||||
|
const Vec3 h = glm::cross(ray.direction_, edge2);
|
||||||
|
const Real a = glm::dot(edge1, h);
|
||||||
|
|
||||||
// Check if ray is parallel to triangle
|
// Check if ray is parallel to triangle
|
||||||
if (std::abs(a) < are_epsilon) {
|
if (a > -are_epsilon && a < are_epsilon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Real f = 1.0f / a;
|
const Real f = 1.0f / a;
|
||||||
Vec3 s = ray.origin_ - v0;
|
const Vec3 s = ray.origin_ - v0_.position_;
|
||||||
Real u = f * glm::dot(s, h);
|
const Real u = f * glm::dot(s, h);
|
||||||
|
|
||||||
|
// Check barycentric coordinate u
|
||||||
if (u < 0.0f || u > 1.0f) {
|
if (u < 0.0f || u > 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 q = glm::cross(s, edge1);
|
const Vec3 q = glm::cross(s, edge1);
|
||||||
Real v = f * glm::dot(ray.direction_, q);
|
const Real v = f * glm::dot(ray.direction_, q);
|
||||||
|
|
||||||
|
// Check barycentric coordinate v
|
||||||
if (v < 0.0f || u + v > 1.0f) {
|
if (v < 0.0f || u + v > 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Real t = f * glm::dot(edge2, q);
|
// Calculate t parameter
|
||||||
|
const Real t = f * glm::dot(edge2, q);
|
||||||
|
|
||||||
if (!ray.is_valid_t(t) || t >= hit.t_) {
|
// Check if intersection is within ray bounds
|
||||||
|
if (!ray.is_valid_t(t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute hit record
|
// Fill hit record
|
||||||
|
const Real w = 1.0f - u - v;
|
||||||
|
|
||||||
hit.t_ = t;
|
hit.t_ = t;
|
||||||
hit.position_ = ray.at(t);
|
hit.position_ = ray.at(t);
|
||||||
|
hit.material_ = material_;
|
||||||
|
|
||||||
// Interpolate vertex attributes using barycentric coordinates
|
// Interpolate vertex attributes using barycentric coordinates
|
||||||
Real w = 1.0f - u - v;
|
hit.normal_ = glm::normalize(
|
||||||
hit.normal_ = glm::normalize(w * v0_.normal_ + u * v1_.normal_ + v * v2_.normal_);
|
w * v0_.normal_ + u * v1_.normal_ + v * v2_.normal_);
|
||||||
hit.texcoord_ = w * v0_.texcoord_ + u * v1_.texcoord_ + v * v2_.texcoord_;
|
hit.texcoord_ = w * v0_.texcoord_ + u * v1_.texcoord_ + v * v2_.texcoord_;
|
||||||
hit.tangent_ = glm::normalize(w * v0_.tangent_ + u * v1_.tangent_ + v * v2_.tangent_);
|
hit.tangent_ = glm::normalize(
|
||||||
|
w * v0_.tangent_ + u * v1_.tangent_ + v * v2_.tangent_);
|
||||||
|
|
||||||
hit.material_ = material_;
|
// Determine front face
|
||||||
hit.set_face_normal(ray.direction_, hit.normal_);
|
hit.set_face_normal(ray.direction_, hit.normal_);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Triangle::intersect_fast(const Ray &ray, Real t_max) const {
|
bool Triangle::intersect_fast(const Ray &ray, Real t_max) const {
|
||||||
// Fast ray-triangle intersection (no hit record)
|
ARE_PROFILE_FUNCTION();
|
||||||
const Vec3& v0 = v0_.position_;
|
|
||||||
const Vec3& v1 = v1_.position_;
|
|
||||||
const Vec3& v2 = v2_.position_;
|
|
||||||
|
|
||||||
Vec3 edge1 = v1 - v0;
|
// Simplified Möller-Trumbore without hit record computation
|
||||||
Vec3 edge2 = v2 - v0;
|
const Vec3 edge1 = v1_.position_ - v0_.position_;
|
||||||
|
const Vec3 edge2 = v2_.position_ - v0_.position_;
|
||||||
|
|
||||||
Vec3 h = glm::cross(ray.direction_, edge2);
|
const Vec3 h = glm::cross(ray.direction_, edge2);
|
||||||
Real a = glm::dot(edge1, h);
|
const Real a = glm::dot(edge1, h);
|
||||||
|
|
||||||
if (std::abs(a) < are_epsilon) {
|
if (a > -are_epsilon && a < are_epsilon) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Real f = 1.0f / a;
|
const Real f = 1.0f / a;
|
||||||
Vec3 s = ray.origin_ - v0;
|
const Vec3 s = ray.origin_ - v0_.position_;
|
||||||
Real u = f * glm::dot(s, h);
|
const Real u = f * glm::dot(s, h);
|
||||||
|
|
||||||
if (u < 0.0f || u > 1.0f) {
|
if (u < 0.0f || u > 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 q = glm::cross(s, edge1);
|
const Vec3 q = glm::cross(s, edge1);
|
||||||
Real v = f * glm::dot(ray.direction_, q);
|
const Real v = f * glm::dot(ray.direction_, q);
|
||||||
|
|
||||||
if (v < 0.0f || u + v > 1.0f) {
|
if (v < 0.0f || u + v > 1.0f) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Real t = f * glm::dot(edge2, q);
|
const Real t = f * glm::dot(edge2, q);
|
||||||
|
|
||||||
return ray.is_valid_t(t) && t < t_max;
|
return t > ray.t_min_ && t < t_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* @file vertex.cpp
|
* @file vertex.cpp
|
||||||
* @brief Implementation of vertex structure
|
* @brief Implementation of Vertex structure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/geometry/vertex.h>
|
#include <are/geometry/vertex.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
/**
|
||||||
|
* @file gbuffer.cpp
|
||||||
|
* @brief Implementation of GBuffer class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/rasterizer/gbuffer.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
GBuffer::GBuffer(int width, int height)
|
||||||
|
: fbo_(0)
|
||||||
|
, rbo_depth_(0)
|
||||||
|
, position_texture_(0)
|
||||||
|
, normal_texture_(0)
|
||||||
|
, albedo_texture_(0)
|
||||||
|
, material_texture_(0)
|
||||||
|
, depth_texture_(0)
|
||||||
|
, width_(width)
|
||||||
|
, height_(height) {
|
||||||
|
|
||||||
|
create_textures();
|
||||||
|
create_framebuffer();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("GBuffer: Created " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
GBuffer::~GBuffer() {
|
||||||
|
delete_textures();
|
||||||
|
|
||||||
|
if (rbo_depth_ != 0) {
|
||||||
|
glDeleteRenderbuffers(1, &rbo_depth_);
|
||||||
|
}
|
||||||
|
if (fbo_ != 0) {
|
||||||
|
glDeleteFramebuffers(1, &fbo_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::resize(int width, int height) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (width == width_ && height == height_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
|
// Recreate textures and framebuffer
|
||||||
|
delete_textures();
|
||||||
|
if (rbo_depth_ != 0) {
|
||||||
|
glDeleteRenderbuffers(1, &rbo_depth_);
|
||||||
|
rbo_depth_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_textures();
|
||||||
|
create_framebuffer();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("GBuffer: Resized to " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::bind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||||
|
glViewport(0, 0, width_, height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::unbind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::clear() {
|
||||||
|
bind();
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::bind_texture(int index, int texture_unit) {
|
||||||
|
glActiveTexture(GL_TEXTURE0 + texture_unit);
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 0: glBindTexture(GL_TEXTURE_2D, position_texture_); break;
|
||||||
|
case 1: glBindTexture(GL_TEXTURE_2D, normal_texture_); break;
|
||||||
|
case 2: glBindTexture(GL_TEXTURE_2D, albedo_texture_); break;
|
||||||
|
case 3: glBindTexture(GL_TEXTURE_2D, material_texture_); break;
|
||||||
|
case 4: glBindTexture(GL_TEXTURE_2D, depth_texture_); break;
|
||||||
|
default:
|
||||||
|
ARE_LOG_WARN("GBuffer: Invalid texture index " + std::to_string(index));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::read_pixels(int index, void* data) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
GLenum attachment;
|
||||||
|
GLenum format;
|
||||||
|
GLenum type;
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 0: // Position
|
||||||
|
attachment = GL_COLOR_ATTACHMENT0;
|
||||||
|
format = GL_RGB;
|
||||||
|
type = GL_FLOAT;
|
||||||
|
break;
|
||||||
|
case 1: // Normal
|
||||||
|
attachment = GL_COLOR_ATTACHMENT1;
|
||||||
|
format = GL_RGB;
|
||||||
|
type = GL_FLOAT;
|
||||||
|
break;
|
||||||
|
case 2: // Albedo
|
||||||
|
attachment = GL_COLOR_ATTACHMENT2;
|
||||||
|
format = GL_RGBA;
|
||||||
|
type = GL_UNSIGNED_BYTE;
|
||||||
|
break;
|
||||||
|
case 3: // Material
|
||||||
|
attachment = GL_COLOR_ATTACHMENT3;
|
||||||
|
format = GL_RG;
|
||||||
|
type = GL_UNSIGNED_BYTE;
|
||||||
|
break;
|
||||||
|
case 4: // Depth
|
||||||
|
attachment = GL_DEPTH_ATTACHMENT;
|
||||||
|
format = GL_DEPTH_COMPONENT;
|
||||||
|
type = GL_FLOAT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ARE_LOG_ERROR("GBuffer: Invalid buffer index for read_pixels");
|
||||||
|
unbind();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glReadBuffer(attachment);
|
||||||
|
glReadPixels(0, 0, width_, height_, format, type, data);
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::create_textures() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Position texture (RGB16F)
|
||||||
|
glGenTextures(1, &position_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, position_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width_, height_, 0, GL_RGB, GL_FLOAT, nullptr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Normal texture (RGB16F)
|
||||||
|
glGenTextures(1, &normal_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, normal_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, width_, height_, 0, GL_RGB, GL_FLOAT, nullptr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Albedo + Metallic texture (RGBA8)
|
||||||
|
glGenTextures(1, &albedo_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, albedo_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width_, height_, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Roughness + AO texture (RG8)
|
||||||
|
glGenTextures(1, &material_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, material_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width_, height_, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Depth texture (R32F)
|
||||||
|
glGenTextures(1, &depth_texture_);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, depth_texture_);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width_, height_, 0, GL_RED, GL_FLOAT, nullptr);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::delete_textures() {
|
||||||
|
if (position_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &position_texture_);
|
||||||
|
position_texture_ = 0;
|
||||||
|
}
|
||||||
|
if (normal_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &normal_texture_);
|
||||||
|
normal_texture_ = 0;
|
||||||
|
}
|
||||||
|
if (albedo_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &albedo_texture_);
|
||||||
|
albedo_texture_ = 0;
|
||||||
|
}
|
||||||
|
if (material_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &material_texture_);
|
||||||
|
material_texture_ = 0;
|
||||||
|
}
|
||||||
|
if (depth_texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &depth_texture_);
|
||||||
|
depth_texture_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBuffer::create_framebuffer() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Create framebuffer
|
||||||
|
glGenFramebuffers(1, &fbo_);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
||||||
|
|
||||||
|
// Attach textures
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, position_texture_, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, normal_texture_, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, albedo_texture_, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, material_texture_, 0);
|
||||||
|
|
||||||
|
// Specify draw buffers
|
||||||
|
GLenum draw_buffers[] = {
|
||||||
|
GL_COLOR_ATTACHMENT0,
|
||||||
|
GL_COLOR_ATTACHMENT1,
|
||||||
|
GL_COLOR_ATTACHMENT2,
|
||||||
|
GL_COLOR_ATTACHMENT3
|
||||||
|
};
|
||||||
|
glDrawBuffers(4, draw_buffers);
|
||||||
|
|
||||||
|
// Create depth renderbuffer
|
||||||
|
glGenRenderbuffers(1, &rbo_depth_);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth_);
|
||||||
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_, height_);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth_);
|
||||||
|
|
||||||
|
// Check framebuffer completeness
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
ARE_LOG_ERROR("GBuffer: Framebuffer is not complete! Status: " + std::to_string(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
/**
|
||||||
|
* @file rasterizer.cpp
|
||||||
|
* @brief Implementation of Rasterizer class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/rasterizer/rasterizer.h>
|
||||||
|
#include <are/rasterizer/gbuffer.h>
|
||||||
|
#include <are/rasterizer/shader_program.h>
|
||||||
|
#include <are/scene/scene_manager.h>
|
||||||
|
#include <are/scene/camera.h>
|
||||||
|
#include <are/scene/mesh.h>
|
||||||
|
#include <are/scene/material.h>
|
||||||
|
#include <are/geometry/vertex.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/platform/gl_context.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/gtc/matrix_inverse.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
Rasterizer::Rasterizer(int width, int height)
|
||||||
|
: width_(width)
|
||||||
|
, height_(height) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Create G-Buffer
|
||||||
|
gbuffer_ = std::make_unique<GBuffer>(width, height);
|
||||||
|
|
||||||
|
// Create shader program
|
||||||
|
gbuffer_shader_ = std::make_unique<ShaderProgram>();
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Rasterizer: Created " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
Rasterizer::~Rasterizer() {
|
||||||
|
ARE_LOG_INFO("Rasterizer: Destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::resize(int width, int height) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (width == width_ && height == height_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
|
if (gbuffer_) {
|
||||||
|
gbuffer_->resize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Rasterizer: Resized to " + std::to_string(width) + "x" + std::to_string(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::render_gbuffer(const SceneManager& scene, const Camera& camera) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (!gbuffer_shader_ || !gbuffer_shader_->is_valid()) {
|
||||||
|
ARE_LOG_ERROR("Rasterizer: G-Buffer shader is not valid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind G-Buffer for rendering
|
||||||
|
gbuffer_->bind();
|
||||||
|
|
||||||
|
// Clear buffers
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
// Enable depth testing
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
// Enable face culling
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(GL_BACK);
|
||||||
|
glFrontFace(GL_CCW);
|
||||||
|
|
||||||
|
// Use G-Buffer shader
|
||||||
|
gbuffer_shader_->use();
|
||||||
|
|
||||||
|
// Set view and projection matrices
|
||||||
|
gbuffer_shader_->set_uniform("u_view", camera.get_view_matrix());
|
||||||
|
gbuffer_shader_->set_uniform("u_projection", camera.get_projection_matrix());
|
||||||
|
|
||||||
|
// Render all meshes
|
||||||
|
const auto& meshes = scene.get_all_meshes();
|
||||||
|
const auto& materials = scene.get_all_materials();
|
||||||
|
|
||||||
|
for (const auto& mesh : meshes) {
|
||||||
|
if (mesh.is_empty() || !mesh.has_gpu_resources()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set model matrix (identity for now, can be extended with Transform)
|
||||||
|
Mat4 model_matrix = Mat4(1.0f);
|
||||||
|
gbuffer_shader_->set_uniform("u_model", model_matrix);
|
||||||
|
|
||||||
|
// Calculate normal matrix
|
||||||
|
Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(model_matrix)));
|
||||||
|
gbuffer_shader_->set_uniform("u_normal_matrix", normal_matrix);
|
||||||
|
|
||||||
|
// Set material properties
|
||||||
|
MaterialHandle mat_handle = mesh.get_material();
|
||||||
|
if (mat_handle != are_invalid_handle && mat_handle <= materials.size()) {
|
||||||
|
const Material& material = materials[mat_handle - 1]; // Handle is 1-based
|
||||||
|
gbuffer_shader_->set_uniform("u_albedo", material.get_albedo());
|
||||||
|
gbuffer_shader_->set_uniform("u_metallic", material.get_metallic());
|
||||||
|
gbuffer_shader_->set_uniform("u_roughness", material.get_roughness());
|
||||||
|
} else {
|
||||||
|
// Default material
|
||||||
|
gbuffer_shader_->set_uniform("u_albedo", Vec3(0.8f, 0.8f, 0.8f));
|
||||||
|
gbuffer_shader_->set_uniform("u_metallic", 0.0f);
|
||||||
|
gbuffer_shader_->set_uniform("u_roughness", 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw mesh
|
||||||
|
glBindVertexArray(mesh.get_vao());
|
||||||
|
glDrawElements(GL_TRIANGLES,
|
||||||
|
static_cast<GLsizei>(mesh.get_index_count()),
|
||||||
|
GL_UNSIGNED_INT,
|
||||||
|
nullptr);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable states
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
|
// Unbind G-Buffer
|
||||||
|
gbuffer_->unbind();
|
||||||
|
|
||||||
|
ARE_GL_CHECK();
|
||||||
|
}
|
||||||
|
|
||||||
|
GBuffer& Rasterizer::get_gbuffer() {
|
||||||
|
return *gbuffer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GBuffer& Rasterizer::get_gbuffer() const {
|
||||||
|
return *gbuffer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::upload_mesh(Mesh& mesh) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (mesh.is_empty()) {
|
||||||
|
ARE_LOG_WARN("Rasterizer: Attempting to upload empty mesh");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete existing GPU resources if any
|
||||||
|
if (mesh.has_gpu_resources()) {
|
||||||
|
delete_mesh(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_mesh_buffers(mesh);
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("Rasterizer: Uploaded mesh with " +
|
||||||
|
std::to_string(mesh.get_vertex_count()) + " vertices, " +
|
||||||
|
std::to_string(mesh.get_triangle_count()) + " triangles");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::delete_mesh(Mesh& mesh) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
uint32_t vao = mesh.get_vao();
|
||||||
|
uint32_t vbo = mesh.get_vbo();
|
||||||
|
uint32_t ebo = mesh.get_ebo();
|
||||||
|
|
||||||
|
if (vao != 0) {
|
||||||
|
glDeleteVertexArrays(1, &vao);
|
||||||
|
}
|
||||||
|
if (vbo != 0) {
|
||||||
|
glDeleteBuffers(1, &vbo);
|
||||||
|
}
|
||||||
|
if (ebo != 0) {
|
||||||
|
glDeleteBuffers(1, &ebo);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.set_vao(0);
|
||||||
|
mesh.set_vbo(0);
|
||||||
|
mesh.set_ebo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::initialize_shaders(const std::string& shader_dir) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (!gbuffer_shader_) {
|
||||||
|
gbuffer_shader_ = std::make_unique<ShaderProgram>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string vert_path = shader_dir + "gbuffer/gbuffer.vert";
|
||||||
|
std::string frag_path = shader_dir + "gbuffer/gbuffer.frag";
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (!gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_VERTEX, vert_path)) {
|
||||||
|
ARE_LOG_ERROR("Rasterizer: Failed to load vertex shader: " + vert_path);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gbuffer_shader_->load_shader(ShaderType::ARE_SHADER_FRAGMENT, frag_path)) {
|
||||||
|
ARE_LOG_ERROR("Rasterizer: Failed to load fragment shader: " + frag_path);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success && !gbuffer_shader_->link()) {
|
||||||
|
ARE_LOG_ERROR("Rasterizer: Failed to link G-Buffer shader program");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
ARE_LOG_INFO("Rasterizer: Shaders initialized successfully");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rasterizer::setup_mesh_buffers(Mesh& mesh) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
uint32_t vao, vbo, ebo;
|
||||||
|
|
||||||
|
// Create VAO
|
||||||
|
glGenVertexArrays(1, &vao);
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
|
||||||
|
// Create VBO
|
||||||
|
glGenBuffers(1, &vbo);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER,
|
||||||
|
mesh.get_vertex_count() * sizeof(Vertex),
|
||||||
|
mesh.get_vertices().data(),
|
||||||
|
GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
// Create EBO
|
||||||
|
glGenBuffers(1, &ebo);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
mesh.get_index_count() * sizeof(uint32_t),
|
||||||
|
mesh.get_indices().data(),
|
||||||
|
GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
// Setup vertex attributes
|
||||||
|
// Position (location = 0)
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
|
||||||
|
sizeof(Vertex),
|
||||||
|
reinterpret_cast<void*>(get_position_offset()));
|
||||||
|
|
||||||
|
// Normal (location = 1)
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
|
||||||
|
sizeof(Vertex),
|
||||||
|
reinterpret_cast<void*>(get_normal_offset()));
|
||||||
|
|
||||||
|
// Texcoord (location = 2)
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
|
||||||
|
sizeof(Vertex),
|
||||||
|
reinterpret_cast<void*>(get_texcoord_offset()));
|
||||||
|
|
||||||
|
// Tangent (location = 3)
|
||||||
|
glEnableVertexAttribArray(3);
|
||||||
|
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE,
|
||||||
|
sizeof(Vertex),
|
||||||
|
reinterpret_cast<void*>(get_tangent_offset()));
|
||||||
|
|
||||||
|
// Unbind VAO
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// Store handles in mesh
|
||||||
|
mesh.set_vao(vao);
|
||||||
|
mesh.set_vbo(vbo);
|
||||||
|
mesh.set_ebo(ebo);
|
||||||
|
|
||||||
|
ARE_GL_CHECK();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
/**
|
||||||
|
* @file shader_program.cpp
|
||||||
|
* @brief Implementation of ShaderProgram class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/rasterizer/shader_program.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/utils/file_utils.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
ShaderProgram::ShaderProgram()
|
||||||
|
: program_(0)
|
||||||
|
, vertex_shader_(0)
|
||||||
|
, fragment_shader_(0)
|
||||||
|
, compute_shader_(0)
|
||||||
|
, linked_(false) {
|
||||||
|
program_ = glCreateProgram();
|
||||||
|
if (program_ == 0) {
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Failed to create OpenGL program");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderProgram::~ShaderProgram() {
|
||||||
|
if (vertex_shader_ != 0) {
|
||||||
|
glDeleteShader(vertex_shader_);
|
||||||
|
}
|
||||||
|
if (fragment_shader_ != 0) {
|
||||||
|
glDeleteShader(fragment_shader_);
|
||||||
|
}
|
||||||
|
if (compute_shader_ != 0) {
|
||||||
|
glDeleteShader(compute_shader_);
|
||||||
|
}
|
||||||
|
if (program_ != 0) {
|
||||||
|
glDeleteProgram(program_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgram::load_shader(ShaderType type, const std::string& filepath) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Read shader source from file
|
||||||
|
std::string source = read_file_to_string(filepath);
|
||||||
|
if (source.empty()) {
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Failed to read shader file: " + filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("ShaderProgram: Loaded shader from " + filepath);
|
||||||
|
return compile_shader(type, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgram::compile_shader(ShaderType type, const std::string& source) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
GLenum gl_type;
|
||||||
|
uint32_t* shader_id;
|
||||||
|
std::string type_name;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ShaderType::ARE_SHADER_VERTEX:
|
||||||
|
gl_type = GL_VERTEX_SHADER;
|
||||||
|
shader_id = &vertex_shader_;
|
||||||
|
type_name = "vertex";
|
||||||
|
break;
|
||||||
|
case ShaderType::ARE_SHADER_FRAGMENT:
|
||||||
|
gl_type = GL_FRAGMENT_SHADER;
|
||||||
|
shader_id = &fragment_shader_;
|
||||||
|
type_name = "fragment";
|
||||||
|
break;
|
||||||
|
case ShaderType::ARE_SHADER_COMPUTE:
|
||||||
|
gl_type = GL_COMPUTE_SHADER;
|
||||||
|
shader_id = &compute_shader_;
|
||||||
|
type_name = "compute";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Unknown shader type");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete existing shader if any
|
||||||
|
if (*shader_id != 0) {
|
||||||
|
glDeleteShader(*shader_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and compile shader
|
||||||
|
*shader_id = glCreateShader(gl_type);
|
||||||
|
const char* source_cstr = source.c_str();
|
||||||
|
glShaderSource(*shader_id, 1, &source_cstr, nullptr);
|
||||||
|
glCompileShader(*shader_id);
|
||||||
|
|
||||||
|
// Check compilation errors
|
||||||
|
if (!check_compile_errors(*shader_id, type)) {
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Failed to compile " + type_name + " shader");
|
||||||
|
glDeleteShader(*shader_id);
|
||||||
|
*shader_id = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach shader to program
|
||||||
|
glAttachShader(program_, *shader_id);
|
||||||
|
|
||||||
|
ARE_LOG_INFO("ShaderProgram: Compiled " + type_name + " shader successfully");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgram::link() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (program_ == 0) {
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Cannot link invalid program");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link program
|
||||||
|
glLinkProgram(program_);
|
||||||
|
|
||||||
|
// Check link errors
|
||||||
|
if (!check_link_errors()) {
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Failed to link shader program");
|
||||||
|
linked_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach and delete shaders after successful link
|
||||||
|
if (vertex_shader_ != 0) {
|
||||||
|
glDetachShader(program_, vertex_shader_);
|
||||||
|
glDeleteShader(vertex_shader_);
|
||||||
|
vertex_shader_ = 0;
|
||||||
|
}
|
||||||
|
if (fragment_shader_ != 0) {
|
||||||
|
glDetachShader(program_, fragment_shader_);
|
||||||
|
glDeleteShader(fragment_shader_);
|
||||||
|
fragment_shader_ = 0;
|
||||||
|
}
|
||||||
|
if (compute_shader_ != 0) {
|
||||||
|
glDetachShader(program_, compute_shader_);
|
||||||
|
glDeleteShader(compute_shader_);
|
||||||
|
compute_shader_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
linked_ = true;
|
||||||
|
ARE_LOG_INFO("ShaderProgram: Linked shader program successfully");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::use() const {
|
||||||
|
if (is_valid()) {
|
||||||
|
glUseProgram(program_);
|
||||||
|
} else {
|
||||||
|
ARE_LOG_WARN("ShaderProgram: Attempting to use invalid program");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, int value) {
|
||||||
|
glUniform1i(get_uniform_location(name), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, float value) {
|
||||||
|
glUniform1f(get_uniform_location(name), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, const Vec2& value) {
|
||||||
|
glUniform2fv(get_uniform_location(name), 1, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, const Vec3& value) {
|
||||||
|
glUniform3fv(get_uniform_location(name), 1, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, const Vec4& value) {
|
||||||
|
glUniform4fv(get_uniform_location(name), 1, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, const Mat3& value) {
|
||||||
|
glUniformMatrix3fv(get_uniform_location(name), 1, GL_FALSE, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderProgram::set_uniform(const std::string& name, const Mat4& value) {
|
||||||
|
glUniformMatrix4fv(get_uniform_location(name), 1, GL_FALSE, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ShaderProgram::get_uniform_location(const std::string& name) {
|
||||||
|
// Check cache first
|
||||||
|
auto it = uniform_cache_.find(name);
|
||||||
|
if (it != uniform_cache_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query OpenGL
|
||||||
|
int location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location == -1) {
|
||||||
|
ARE_LOG_WARN("ShaderProgram: Uniform '" + name + "' not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the location
|
||||||
|
uniform_cache_[name] = location;
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgram::check_compile_errors(uint32_t shader, ShaderType type) {
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
GLint log_length;
|
||||||
|
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||||
|
|
||||||
|
std::string info_log;
|
||||||
|
info_log.resize(log_length);
|
||||||
|
glGetShaderInfoLog(shader, log_length, nullptr, &info_log[0]);
|
||||||
|
|
||||||
|
std::string type_name;
|
||||||
|
switch (type) {
|
||||||
|
case ShaderType::ARE_SHADER_VERTEX: type_name = "VERTEX"; break;
|
||||||
|
case ShaderType::ARE_SHADER_FRAGMENT: type_name = "FRAGMENT"; break;
|
||||||
|
case ShaderType::ARE_SHADER_COMPUTE: type_name = "COMPUTE"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: " + type_name + " shader compilation failed:");
|
||||||
|
ARE_LOG_ERROR(info_log);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShaderProgram::check_link_errors() {
|
||||||
|
GLint success;
|
||||||
|
glGetProgramiv(program_, GL_LINK_STATUS, &success);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
GLint log_length;
|
||||||
|
glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &log_length);
|
||||||
|
|
||||||
|
std::string info_log;
|
||||||
|
info_log.resize(log_length);
|
||||||
|
glGetProgramInfoLog(program_, log_length, nullptr, &info_log[0]);
|
||||||
|
|
||||||
|
ARE_LOG_ERROR("ShaderProgram: Program linking failed:");
|
||||||
|
ARE_LOG_ERROR(info_log);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
/**
|
||||||
|
* @file cpu_raytracer.cpp
|
||||||
|
* @brief CPU ray tracing implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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>
|
||||||
|
#include <are/scene/material.h>
|
||||||
|
#include <are/scene/light.h>
|
||||||
|
#include <are/scene/directional_light.h>
|
||||||
|
#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>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#ifdef ARE_USE_OPENMP
|
||||||
|
#include <omp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
CPURayTracer::CPURayTracer(const RayTracingConfig& config)
|
||||||
|
: RayTracer(config)
|
||||||
|
, bvh_(nullptr)
|
||||||
|
, scene_(nullptr)
|
||||||
|
, width_(0)
|
||||||
|
, height_(0)
|
||||||
|
{
|
||||||
|
ARE_LOG_INFO("CPU ray tracer initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
CPURayTracer::~CPURayTracer() {
|
||||||
|
ARE_LOG_INFO("CPU ray tracer destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPURayTracer::update_bvh(const BVH& bvh) {
|
||||||
|
bvh_ = &bvh;
|
||||||
|
ARE_LOG_INFO("BVH updated for CPU ray tracer (" +
|
||||||
|
std::to_string(bvh.get_nodes().size()) + " nodes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPURayTracer::render(const SceneManager& scene,
|
||||||
|
const Camera& camera,
|
||||||
|
const GBuffer* gbuffer,
|
||||||
|
uint32_t output_texture) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (!bvh_ || !bvh_->is_built()) {
|
||||||
|
ARE_LOG_ERROR("BVH not built, cannot render");
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (width_ <= 0 || height_ <= 0) {
|
||||||
|
ARE_LOG_ERROR("Invalid output texture dimensions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize framebuffer if needed
|
||||||
|
size_t pixel_count = width_ * height_;
|
||||||
|
if (framebuffer_.size() != pixel_count) {
|
||||||
|
framebuffer_.resize(pixel_count);
|
||||||
|
ARE_LOG_INFO("Framebuffer resized to " + std::to_string(width_) + "x" + std::to_string(height_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render using ray tracing
|
||||||
|
ARE_LOG_INFO("Starting CPU ray tracing (" + std::to_string(config_.spp) + " spp)");
|
||||||
|
|
||||||
|
const int spp = config_.spp;
|
||||||
|
const Real inv_spp = 1.0f / static_cast<Real>(spp);
|
||||||
|
|
||||||
|
#ifdef ARE_USE_OPENMP
|
||||||
|
#pragma omp parallel for schedule(dynamic, 16)
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < height_; ++y) {
|
||||||
|
for (int x = 0; x < width_; ++x) {
|
||||||
|
Vec3 color(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_);
|
||||||
|
|
||||||
|
// Generate ray
|
||||||
|
Vec3 ray_origin, ray_direction;
|
||||||
|
camera.generate_ray(u, v, ray_origin, ray_direction);
|
||||||
|
|
||||||
|
Ray ray(ray_origin, ray_direction);
|
||||||
|
|
||||||
|
// Trace ray
|
||||||
|
color += trace_ray(ray, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Average samples
|
||||||
|
color *= inv_spp;
|
||||||
|
|
||||||
|
// Store in framebuffer
|
||||||
|
size_t index = y * width_ + x;
|
||||||
|
framebuffer_[index] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress logging (every 10%)
|
||||||
|
if (y % (height_ / 10) == 0) {
|
||||||
|
Real progress = 100.0f * y / height_;
|
||||||
|
ARE_LOG_INFO("Ray tracing progress: " + std::to_string(static_cast<int>(progress)) + "%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ARE_LOG_INFO("Ray tracing complete, uploading to GPU");
|
||||||
|
|
||||||
|
// Upload to GPU texture
|
||||||
|
glBindTexture(GL_TEXTURE_2D, output_texture);
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_,
|
||||||
|
GL_RGB, 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Get material
|
||||||
|
const Material* material = scene_->get_material(hit.material_);
|
||||||
|
if (!material) {
|
||||||
|
return Vec3(1.0f, 0.0f, 1.0f); // Magenta for missing material
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
Real ao = 1.0f;
|
||||||
|
if (config_.enable_ao) {
|
||||||
|
ao = compute_ambient_occlusion(hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple diffuse shading
|
||||||
|
Vec3 color = albedo * direct_lighting * ao;
|
||||||
|
|
||||||
|
// Global illumination (indirect lighting)
|
||||||
|
if (config_.enable_gi && depth < config_.max_depth - 1) {
|
||||||
|
// Generate random direction in hemisphere
|
||||||
|
Vec3 scatter_direction = generator.random_cosine_direction(hit.normal_);
|
||||||
|
|
||||||
|
// Trace secondary ray
|
||||||
|
Ray scatter_ray(hit.position_ + hit.normal_ * are_epsilon, scatter_direction);
|
||||||
|
Vec3 indirect = trace_ray(scatter_ray, depth + 1);
|
||||||
|
|
||||||
|
// Add indirect lighting (weighted by albedo)
|
||||||
|
color += albedo * indirect * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 CPURayTracer::compute_direct_lighting(const HitRecord& hit) {
|
||||||
|
Vec3 lighting(0.0f);
|
||||||
|
|
||||||
|
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_)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 light_dir;
|
||||||
|
Vec3 light_color = light->get_color() * light->get_intensity();
|
||||||
|
Real light_distance = 1e30f;
|
||||||
|
|
||||||
|
// Compute light direction based on type
|
||||||
|
if (light->get_type() == LightType::ARE_LIGHT_DIRECTIONAL) {
|
||||||
|
auto* dir_light = static_cast<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
|
||||||
|
Real attenuation = point_light->calculate_attenuation(light_distance);
|
||||||
|
light_color *= attenuation;
|
||||||
|
}
|
||||||
|
else if (light->get_type() == LightType::ARE_LIGHT_SPOT) {
|
||||||
|
auto* spot_light = static_cast<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;
|
||||||
|
|
||||||
|
// Apply spotlight factor
|
||||||
|
Real spot_factor = spot_light->calculate_spot_factor(-light_dir);
|
||||||
|
if (spot_factor <= 0.0f) {
|
||||||
|
continue; // Outside spotlight cone
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ambient term
|
||||||
|
lighting += Vec3(0.03f);
|
||||||
|
|
||||||
|
return lighting;
|
||||||
|
}
|
||||||
|
|
||||||
|
Real CPURayTracer::compute_ambient_occlusion(const HitRecord& hit) {
|
||||||
|
// Random real generator
|
||||||
|
thread_local RandomGenerator generator;
|
||||||
|
|
||||||
|
const int num_samples = config_.ao_samples;
|
||||||
|
const Real radius = config_.ao_radius;
|
||||||
|
|
||||||
|
int occluded_count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < num_samples; ++i) {
|
||||||
|
// Generate random direction in hemisphere
|
||||||
|
Vec3 sample_dir = generator.random_in_hemisphere(hit.normal_);
|
||||||
|
|
||||||
|
// Cast AO ray
|
||||||
|
Ray ao_ray(hit.position_ + hit.normal_ * are_epsilon, sample_dir,
|
||||||
|
are_epsilon, radius);
|
||||||
|
|
||||||
|
// Check if ray hits anything within radius
|
||||||
|
if (bvh_->intersect_any(ao_ray, radius)) {
|
||||||
|
occluded_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AO factor (1.0 = no occlusion, 0.0 = full occlusion)
|
||||||
|
Real ao = 1.0f - (static_cast<Real>(occluded_count) / num_samples);
|
||||||
|
return ao;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* @file hit_record.cpp
|
||||||
|
* @brief Implementation of hit record
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/raytracer/hit_record.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
HitRecord::HitRecord()
|
||||||
|
: position_(0.0f)
|
||||||
|
, normal_(0.0f, 1.0f, 0.0f)
|
||||||
|
, texcoord_(0.0f)
|
||||||
|
, tangent_(1.0f, 0.0f, 0.0f)
|
||||||
|
, t_(-1.0f)
|
||||||
|
, material_(are_invalid_handle)
|
||||||
|
, triangle_index_(0)
|
||||||
|
, front_face_(true) {
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* @file ray.cpp
|
||||||
|
* @brief Implementation of Ray structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/raytracer/ray.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
Ray::Ray()
|
||||||
|
: origin_(0.0f)
|
||||||
|
, direction_(0.0f, 0.0f, 1.0f)
|
||||||
|
, t_min_(are_epsilon)
|
||||||
|
, t_max_(1e30f) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Ray::Ray(const Vec3& origin, const Vec3& direction, Real t_min, Real t_max)
|
||||||
|
: origin_(origin)
|
||||||
|
, t_min_(t_min)
|
||||||
|
, t_max_(t_max) {
|
||||||
|
// Normalize direction vector
|
||||||
|
Real length = glm::length(direction);
|
||||||
|
if (length < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("Ray: Direction vector has zero length, using default (0,0,1)");
|
||||||
|
direction_ = Vec3(0.0f, 0.0f, 1.0f);
|
||||||
|
} else {
|
||||||
|
direction_ = direction / length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 Ray::at(Real t) const {
|
||||||
|
return origin_ + direction_ * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Ray::is_valid_t(Real t) const {
|
||||||
|
return t >= t_min_ && t <= t_max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* @file raytracer.cpp
|
||||||
|
* @brief Implementation of RayTracer base class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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,9 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* @file camera.cpp
|
* @file camera.cpp
|
||||||
* @brief Implementation of camera system
|
* @brief Implementation of Camera class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/scene/camera.h>
|
#include <are/scene/camera.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
@ -26,7 +29,7 @@ Camera::Camera()
|
||||||
Camera::Camera(const Vec3& position, const Vec3& target, const Vec3& up)
|
Camera::Camera(const Vec3& position, const Vec3& target, const Vec3& up)
|
||||||
: position_(position)
|
: position_(position)
|
||||||
, target_(target)
|
, target_(target)
|
||||||
, up_(up)
|
, up_(glm::normalize(up))
|
||||||
, fov_(45.0f)
|
, fov_(45.0f)
|
||||||
, aspect_ratio_(16.0f / 9.0f)
|
, aspect_ratio_(16.0f / 9.0f)
|
||||||
, near_plane_(0.1f)
|
, near_plane_(0.1f)
|
||||||
|
|
@ -51,7 +54,13 @@ void Camera::set_target(const Vec3& target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_up(const Vec3& up) {
|
void Camera::set_up(const Vec3& up) {
|
||||||
up_ = up;
|
Real length = glm::length(up);
|
||||||
|
if (length < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("Camera: Invalid up vector (zero length), using default");
|
||||||
|
up_ = Vec3(0.0f, 1.0f, 0.0f);
|
||||||
|
} else {
|
||||||
|
up_ = up / length;
|
||||||
|
}
|
||||||
view_dirty_ = true;
|
view_dirty_ = true;
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
@ -59,42 +68,51 @@ void Camera::set_up(const Vec3& up) {
|
||||||
void Camera::look_at(const Vec3& position, const Vec3& target, const Vec3& up) {
|
void Camera::look_at(const Vec3& position, const Vec3& target, const Vec3& up) {
|
||||||
position_ = position;
|
position_ = position;
|
||||||
target_ = target;
|
target_ = target;
|
||||||
up_ = up;
|
set_up(up);
|
||||||
view_dirty_ = true;
|
|
||||||
dirty_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_fov(Real fov_degrees) {
|
void Camera::set_fov(Real fov_degrees) {
|
||||||
fov_ = fov_degrees;
|
// Clamp FOV to reasonable range
|
||||||
|
fov_ = clamp(fov_degrees, 1.0f, 179.0f);
|
||||||
projection_dirty_ = true;
|
projection_dirty_ = true;
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_aspect_ratio(Real aspect) {
|
void Camera::set_aspect_ratio(Real aspect) {
|
||||||
|
if (aspect <= 0.0f) {
|
||||||
|
ARE_LOG_ERROR("Camera: Invalid aspect ratio (must be positive)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
aspect_ratio_ = aspect;
|
aspect_ratio_ = aspect;
|
||||||
projection_dirty_ = true;
|
projection_dirty_ = true;
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_near_plane(Real near) {
|
void Camera::set_near_plane(Real near) {
|
||||||
|
if (near <= 0.0f) {
|
||||||
|
ARE_LOG_ERROR("Camera: Invalid near plane (must be positive)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
near_plane_ = near;
|
near_plane_ = near;
|
||||||
projection_dirty_ = true;
|
projection_dirty_ = true;
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_far_plane(Real far) {
|
void Camera::set_far_plane(Real far) {
|
||||||
|
if (far <= near_plane_) {
|
||||||
|
ARE_LOG_ERROR("Camera: Invalid far plane (must be greater than near plane)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
far_plane_ = far;
|
far_plane_ = far;
|
||||||
projection_dirty_ = true;
|
projection_dirty_ = true;
|
||||||
dirty_ = true;
|
dirty_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_perspective(Real fov_degrees, Real aspect, Real near, Real far) {
|
void Camera::set_perspective(Real fov_degrees, Real aspect, Real near, Real far) {
|
||||||
fov_ = fov_degrees;
|
set_fov(fov_degrees);
|
||||||
aspect_ratio_ = aspect;
|
set_aspect_ratio(aspect);
|
||||||
near_plane_ = near;
|
set_near_plane(near);
|
||||||
far_plane_ = far;
|
set_far_plane(far);
|
||||||
projection_dirty_ = true;
|
|
||||||
dirty_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3 Camera::get_forward() const {
|
Vec3 Camera::get_forward() const {
|
||||||
|
|
@ -124,36 +142,58 @@ Mat4 Camera::get_view_projection_matrix() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::generate_ray(Real u, Real v, Vec3& origin, Vec3& direction) const {
|
void Camera::generate_ray(Real u, Real v, Vec3& origin, Vec3& direction) const {
|
||||||
// Convert from [0,1] to [-1,1]
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Ray origin is camera position
|
||||||
|
origin = position_;
|
||||||
|
|
||||||
|
// Calculate ray direction in camera space
|
||||||
|
// u, v are in [0, 1], convert to [-1, 1]
|
||||||
Real x = 2.0f * u - 1.0f;
|
Real x = 2.0f * u - 1.0f;
|
||||||
Real y = 1.0f - 2.0f * v; // Flip Y
|
Real y = 1.0f - 2.0f * v; // Flip y for screen coordinates
|
||||||
|
|
||||||
// Compute ray direction in camera space
|
// Calculate direction based on FOV and aspect ratio
|
||||||
Real tan_half_fov = std::tan(glm::radians(fov_ * 0.5f));
|
Real tan_half_fov = std::tan(degrees_to_radians(fov_ * 0.5f));
|
||||||
Real camera_x = x * aspect_ratio_ * tan_half_fov;
|
Real viewport_height = 2.0f * tan_half_fov;
|
||||||
Real camera_y = y * tan_half_fov;
|
Real viewport_width = viewport_height * aspect_ratio_;
|
||||||
|
|
||||||
// Transform to world space
|
// Get camera basis vectors
|
||||||
Vec3 forward = get_forward();
|
Vec3 forward = get_forward();
|
||||||
Vec3 right = get_right();
|
Vec3 right = get_right();
|
||||||
Vec3 up = glm::normalize(glm::cross(right, forward));
|
Vec3 up = glm::normalize(glm::cross(right, forward));
|
||||||
|
|
||||||
origin = position_;
|
// Calculate ray direction
|
||||||
direction = glm::normalize(forward + camera_x * right + camera_y * up);
|
direction = glm::normalize(
|
||||||
|
forward +
|
||||||
|
right * (x * viewport_width * 0.5f) +
|
||||||
|
up * (y * viewport_height * 0.5f)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::update_view_matrix() const {
|
void Camera::update_view_matrix() const {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Check if camera is looking at itself
|
||||||
|
if (glm::length(target_ - position_) < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("Camera: Position and target are too close, using default view");
|
||||||
|
view_matrix_ = Mat4(1.0f);
|
||||||
|
} else {
|
||||||
view_matrix_ = glm::lookAt(position_, target_, up_);
|
view_matrix_ = glm::lookAt(position_, target_, up_);
|
||||||
|
}
|
||||||
|
|
||||||
view_dirty_ = false;
|
view_dirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::update_projection_matrix() const {
|
void Camera::update_projection_matrix() const {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
projection_matrix_ = glm::perspective(
|
projection_matrix_ = glm::perspective(
|
||||||
glm::radians(fov_),
|
degrees_to_radians(fov_),
|
||||||
aspect_ratio_,
|
aspect_ratio_,
|
||||||
near_plane_,
|
near_plane_,
|
||||||
far_plane_
|
far_plane_
|
||||||
);
|
);
|
||||||
|
|
||||||
projection_dirty_ = false;
|
projection_dirty_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
/**
|
||||||
|
* @file directional_light.cpp
|
||||||
|
* @brief Implementation of DirectionalLight class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/scene/directional_light.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
DirectionalLight::DirectionalLight()
|
||||||
|
: Light(LightType::ARE_LIGHT_DIRECTIONAL)
|
||||||
|
, direction_(0.0f, -1.0f, 0.0f) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectionalLight::DirectionalLight(const Vec3& direction, const Vec3& color, Real intensity)
|
||||||
|
: Light(LightType::ARE_LIGHT_DIRECTIONAL)
|
||||||
|
, direction_(glm::normalize(direction)) {
|
||||||
|
set_color(color);
|
||||||
|
set_intensity(intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectionalLight::set_direction(const Vec3& direction) {
|
||||||
|
Real length = glm::length(direction);
|
||||||
|
if (length < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("DirectionalLight: Invalid direction vector (zero length), using default");
|
||||||
|
direction_ = Vec3(0.0f, -1.0f, 0.0f);
|
||||||
|
} else {
|
||||||
|
direction_ = direction / length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LightData DirectionalLight::pack() const {
|
||||||
|
LightData data;
|
||||||
|
|
||||||
|
// position_type_: xyz unused for directional, w = light type
|
||||||
|
data.position_type_ = Vec4(0.0f, 0.0f, 0.0f,
|
||||||
|
static_cast<float>(LightType::ARE_LIGHT_DIRECTIONAL));
|
||||||
|
|
||||||
|
// direction_range_: xyz = direction, w = range (unused for directional)
|
||||||
|
data.direction_range_ = Vec4(direction_, 0.0f);
|
||||||
|
|
||||||
|
// color_intensity_: xyz = color, w = intensity
|
||||||
|
data.color_intensity_ = Vec4(color_, intensity_);
|
||||||
|
|
||||||
|
// params_: x = cast_shadows
|
||||||
|
data.params_ = Vec4(cast_shadows_ ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DirectionalLight::affects_point(const Vec3& point) const {
|
||||||
|
// Directional light affects all points
|
||||||
|
(void)point; // Suppress unused parameter warning
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* @file light.cpp
|
||||||
|
* @brief Implementation of base Light class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/scene/light.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
Light::Light(LightType type)
|
||||||
|
: type_(type)
|
||||||
|
, color_(1.0f, 1.0f, 1.0f)
|
||||||
|
, intensity_(1.0f)
|
||||||
|
, cast_shadows_(true) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Light::set_color(const Vec3& color) {
|
||||||
|
// Clamp color to valid range [0, 1]
|
||||||
|
color_.x = clamp(color.x, 0.0f, 1.0f);
|
||||||
|
color_.y = clamp(color.y, 0.0f, 1.0f);
|
||||||
|
color_.z = clamp(color.z, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Light::set_intensity(Real intensity) {
|
||||||
|
// Intensity can be HDR, so only clamp to non-negative
|
||||||
|
intensity_ = std::max(0.0f, intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Light::set_cast_shadows(bool cast) {
|
||||||
|
cast_shadows_ = cast;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
/**
|
/**
|
||||||
* @file material.cpp
|
* @file material.cpp
|
||||||
* @brief Implementation of PBR material
|
* @brief Implementation of PBR Material class
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <are/scene/material.h>
|
#include <are/scene/material.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
||||||
Material::Material()
|
Material::Material()
|
||||||
: albedo_(0.8f, 0.8f, 0.8f)
|
: albedo_(1.0f, 1.0f, 1.0f)
|
||||||
, metallic_(0.0f)
|
, metallic_(0.0f)
|
||||||
, roughness_(0.5f)
|
, roughness_(0.5f)
|
||||||
, emissive_(0.0f, 0.0f, 0.0f)
|
, emissive_(0.0f, 0.0f, 0.0f)
|
||||||
|
|
@ -21,7 +23,10 @@ Material::Material()
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_albedo(const Vec3& albedo) {
|
void Material::set_albedo(const Vec3& albedo) {
|
||||||
albedo_ = albedo;
|
// Clamp albedo to valid range [0, 1]
|
||||||
|
albedo_.x = clamp(albedo.x, 0.0f, 1.0f);
|
||||||
|
albedo_.y = clamp(albedo.y, 0.0f, 1.0f);
|
||||||
|
albedo_.z = clamp(albedo.z, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_albedo_map(const std::string& path) {
|
void Material::set_albedo_map(const std::string& path) {
|
||||||
|
|
@ -29,7 +34,7 @@ void Material::set_albedo_map(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_metallic(Real metallic) {
|
void Material::set_metallic(Real metallic) {
|
||||||
metallic_ = glm::clamp(metallic, 0.0f, 1.0f);
|
metallic_ = clamp(metallic, 0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_metallic_map(const std::string& path) {
|
void Material::set_metallic_map(const std::string& path) {
|
||||||
|
|
@ -37,7 +42,8 @@ void Material::set_metallic_map(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_roughness(Real roughness) {
|
void Material::set_roughness(Real roughness) {
|
||||||
roughness_ = glm::clamp(roughness, 0.0f, 1.0f);
|
// Clamp roughness to avoid division by zero in BRDF calculations
|
||||||
|
roughness_ = clamp(roughness, 0.04f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_roughness_map(const std::string& path) {
|
void Material::set_roughness_map(const std::string& path) {
|
||||||
|
|
@ -53,7 +59,10 @@ void Material::set_ao_map(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_emissive(const Vec3& emissive) {
|
void Material::set_emissive(const Vec3& emissive) {
|
||||||
emissive_ = emissive;
|
// Emissive can be HDR, so no upper clamp
|
||||||
|
emissive_.x = std::max(0.0f, emissive.x);
|
||||||
|
emissive_.y = std::max(0.0f, emissive.y);
|
||||||
|
emissive_.z = std::max(0.0f, emissive.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Material::set_emissive_map(const std::string& path) {
|
void Material::set_emissive_map(const std::string& path) {
|
||||||
|
|
@ -61,7 +70,12 @@ void Material::set_emissive_map(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Material::is_emissive() const {
|
bool Material::is_emissive() const {
|
||||||
return glm::length(emissive_) > are_epsilon || !emissive_map_.empty();
|
// Check if material has significant emission
|
||||||
|
const Real threshold = 0.001f;
|
||||||
|
return (emissive_.x > threshold ||
|
||||||
|
emissive_.y > threshold ||
|
||||||
|
emissive_.z > threshold) ||
|
||||||
|
has_emissive_map();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace are
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
/**
|
||||||
|
* @file mesh.cpp
|
||||||
|
* @brief Implementation of Mesh class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/scene/mesh.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
Mesh::Mesh()
|
||||||
|
: material_id_(are_invalid_handle)
|
||||||
|
, vao_(0)
|
||||||
|
, vbo_(0)
|
||||||
|
, ebo_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh::Mesh(const std::vector<Vertex>& vertices,
|
||||||
|
const std::vector<uint32_t>& indices,
|
||||||
|
MaterialHandle material_id)
|
||||||
|
: vertices_(vertices)
|
||||||
|
, indices_(indices)
|
||||||
|
, material_id_(material_id)
|
||||||
|
, vao_(0)
|
||||||
|
, vbo_(0)
|
||||||
|
, ebo_(0) {
|
||||||
|
compute_aabb();
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh::Mesh(const Vertex* vertices, size_t vertex_count,
|
||||||
|
const uint32_t* indices, size_t index_count,
|
||||||
|
MaterialHandle material_id)
|
||||||
|
: material_id_(material_id)
|
||||||
|
, vao_(0)
|
||||||
|
, vbo_(0)
|
||||||
|
, ebo_(0) {
|
||||||
|
if (vertices && vertex_count > 0) {
|
||||||
|
vertices_.assign(vertices, vertices + vertex_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indices && index_count > 0) {
|
||||||
|
indices_.assign(indices, indices + index_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_aabb();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::set_vertices(const std::vector<Vertex>& vertices) {
|
||||||
|
vertices_ = vertices;
|
||||||
|
compute_aabb();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::set_indices(const std::vector<uint32_t>& indices) {
|
||||||
|
indices_ = indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::set_material(MaterialHandle material_id) {
|
||||||
|
material_id_ = material_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::compute_aabb() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (vertices_.empty()) {
|
||||||
|
aabb_ = AABB::invalid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
aabb_ = AABB(vertices_[0].position_);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < vertices_.size(); ++i) {
|
||||||
|
aabb_.expand(vertices_[i].position_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::compute_tangents() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (vertices_.empty() || indices_.empty()) {
|
||||||
|
ARE_LOG_WARN("Mesh: Cannot compute tangents for empty mesh");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indices_.size() % 3 != 0) {
|
||||||
|
ARE_LOG_ERROR("Mesh: Index count is not a multiple of 3");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize tangents to zero
|
||||||
|
std::vector<Vec3> tangents(vertices_.size(), Vec3(0.0f));
|
||||||
|
std::vector<Vec3> bitangents(vertices_.size(), Vec3(0.0f));
|
||||||
|
|
||||||
|
// Calculate tangents for each triangle
|
||||||
|
for (size_t i = 0; i < indices_.size(); i += 3) {
|
||||||
|
uint32_t i0 = indices_[i];
|
||||||
|
uint32_t i1 = indices_[i + 1];
|
||||||
|
uint32_t i2 = indices_[i + 2];
|
||||||
|
|
||||||
|
if (i0 >= vertices_.size() || i1 >= vertices_.size() || i2 >= vertices_.size()) {
|
||||||
|
ARE_LOG_ERROR("Mesh: Invalid index in compute_tangents");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Vertex& v0 = vertices_[i0];
|
||||||
|
const Vertex& v1 = vertices_[i1];
|
||||||
|
const Vertex& v2 = vertices_[i2];
|
||||||
|
|
||||||
|
// Calculate edges
|
||||||
|
Vec3 edge1 = v1.position_ - v0.position_;
|
||||||
|
Vec3 edge2 = v2.position_ - v0.position_;
|
||||||
|
|
||||||
|
Vec2 delta_uv1 = v1.texcoord_ - v0.texcoord_;
|
||||||
|
Vec2 delta_uv2 = v2.texcoord_ - v0.texcoord_;
|
||||||
|
|
||||||
|
// Calculate tangent and bitangent
|
||||||
|
Real f = delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y;
|
||||||
|
|
||||||
|
if (std::abs(f) < are_epsilon) {
|
||||||
|
// Degenerate UV coordinates, use arbitrary tangent
|
||||||
|
Vec3 tangent, bitangent;
|
||||||
|
create_orthonormal_basis(v0.normal_, tangent, bitangent);
|
||||||
|
|
||||||
|
tangents[i0] += tangent;
|
||||||
|
tangents[i1] += tangent;
|
||||||
|
tangents[i2] += tangent;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = 1.0f / f;
|
||||||
|
|
||||||
|
Vec3 tangent;
|
||||||
|
tangent.x = f * (delta_uv2.y * edge1.x - delta_uv1.y * edge2.x);
|
||||||
|
tangent.y = f * (delta_uv2.y * edge1.y - delta_uv1.y * edge2.y);
|
||||||
|
tangent.z = f * (delta_uv2.y * edge1.z - delta_uv1.y * edge2.z);
|
||||||
|
|
||||||
|
Vec3 bitangent;
|
||||||
|
bitangent.x = f * (-delta_uv2.x * edge1.x + delta_uv1.x * edge2.x);
|
||||||
|
bitangent.y = f * (-delta_uv2.x * edge1.y + delta_uv1.x * edge2.y);
|
||||||
|
bitangent.z = f * (-delta_uv2.x * edge1.z + delta_uv1.x * edge2.z);
|
||||||
|
|
||||||
|
// Accumulate tangents for each vertex
|
||||||
|
tangents[i0] += tangent;
|
||||||
|
tangents[i1] += tangent;
|
||||||
|
tangents[i2] += tangent;
|
||||||
|
|
||||||
|
bitangents[i0] += bitangent;
|
||||||
|
bitangents[i1] += bitangent;
|
||||||
|
bitangents[i2] += bitangent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orthogonalize and normalize tangents (Gram-Schmidt)
|
||||||
|
for (size_t i = 0; i < vertices_.size(); ++i) {
|
||||||
|
const Vec3& n = vertices_[i].normal_;
|
||||||
|
const Vec3& t = tangents[i];
|
||||||
|
|
||||||
|
// Gram-Schmidt orthogonalize
|
||||||
|
Vec3 tangent = t - n * glm::dot(n, t);
|
||||||
|
|
||||||
|
Real length = glm::length(tangent);
|
||||||
|
if (length < are_epsilon) {
|
||||||
|
// If tangent is parallel to normal, create arbitrary tangent
|
||||||
|
create_orthonormal_basis(n, tangent, bitangents[i]);
|
||||||
|
} else {
|
||||||
|
tangent /= length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check handedness
|
||||||
|
Real handedness = glm::dot(glm::cross(n, t), bitangents[i]);
|
||||||
|
if (handedness < 0.0f) {
|
||||||
|
tangent = -tangent;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices_[i].tangent_ = tangent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mesh::get_triangle(size_t triangle_index, Vertex& v0, Vertex& v1, Vertex& v2) const {
|
||||||
|
if (triangle_index >= get_triangle_count()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t base_index = triangle_index * 3;
|
||||||
|
uint32_t i0 = indices_[base_index];
|
||||||
|
uint32_t i1 = indices_[base_index + 1];
|
||||||
|
uint32_t i2 = indices_[base_index + 2];
|
||||||
|
|
||||||
|
if (i0 >= vertices_.size() || i1 >= vertices_.size() || i2 >= vertices_.size()) {
|
||||||
|
ARE_LOG_ERROR("Mesh: Invalid indices in get_triangle");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
v0 = vertices_[i0];
|
||||||
|
v1 = vertices_[i1];
|
||||||
|
v2 = vertices_[i2];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* @file point_light.cpp
|
||||||
|
* @brief Implementation of PointLight class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/scene/point_light.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
PointLight::PointLight()
|
||||||
|
: Light(LightType::ARE_LIGHT_POINT)
|
||||||
|
, position_(0.0f)
|
||||||
|
, range_(10.0f)
|
||||||
|
, attenuation_constant_(1.0f)
|
||||||
|
, attenuation_linear_(0.09f)
|
||||||
|
, attenuation_quadratic_(0.032f) {
|
||||||
|
}
|
||||||
|
|
||||||
|
PointLight::PointLight(const Vec3& position, const Vec3& color, Real intensity, Real range)
|
||||||
|
: Light(LightType::ARE_LIGHT_POINT)
|
||||||
|
, position_(position)
|
||||||
|
, range_(range)
|
||||||
|
, attenuation_constant_(1.0f)
|
||||||
|
, attenuation_linear_(0.09f)
|
||||||
|
, attenuation_quadratic_(0.032f) {
|
||||||
|
set_color(color);
|
||||||
|
set_intensity(intensity);
|
||||||
|
set_range(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointLight::set_position(const Vec3& position) {
|
||||||
|
position_ = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointLight::set_range(Real range) {
|
||||||
|
if (range <= 0.0f) {
|
||||||
|
ARE_LOG_WARN("PointLight: Invalid range (must be positive), using default");
|
||||||
|
range_ = 10.0f;
|
||||||
|
} else {
|
||||||
|
range_ = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointLight::set_attenuation(Real constant, Real linear, Real quadratic) {
|
||||||
|
attenuation_constant_ = std::max(0.0f, constant);
|
||||||
|
attenuation_linear_ = std::max(0.0f, linear);
|
||||||
|
attenuation_quadratic_ = std::max(0.0f, quadratic);
|
||||||
|
|
||||||
|
// Ensure at least some attenuation to avoid division issues
|
||||||
|
if (attenuation_constant_ < are_epsilon &&
|
||||||
|
attenuation_linear_ < are_epsilon &&
|
||||||
|
attenuation_quadratic_ < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("PointLight: All attenuation factors near zero, setting constant to 1.0");
|
||||||
|
attenuation_constant_ = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Real PointLight::calculate_attenuation(Real distance) const {
|
||||||
|
// Standard attenuation formula: 1 / (constant + linear*d + quadratic*d^2)
|
||||||
|
Real attenuation = attenuation_constant_ +
|
||||||
|
attenuation_linear_ * distance +
|
||||||
|
attenuation_quadratic_ * distance * distance;
|
||||||
|
return 1.0f / std::max(attenuation, are_epsilon);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightData PointLight::pack() const {
|
||||||
|
LightData data;
|
||||||
|
|
||||||
|
// position_type_: xyz = position, w = light type
|
||||||
|
data.position_type_ = Vec4(position_,
|
||||||
|
static_cast<float>(LightType::ARE_LIGHT_POINT));
|
||||||
|
|
||||||
|
// direction_range_: xyz unused for point light, w = range
|
||||||
|
data.direction_range_ = Vec4(0.0f, 0.0f, 0.0f, range_);
|
||||||
|
|
||||||
|
// color_intensity_: xyz = color, w = intensity
|
||||||
|
data.color_intensity_ = Vec4(color_, intensity_);
|
||||||
|
|
||||||
|
// params_: x = cast_shadows, y = constant, z = linear, w = quadratic
|
||||||
|
data.params_ = Vec4(
|
||||||
|
cast_shadows_ ? 1.0f : 0.0f,
|
||||||
|
attenuation_constant_,
|
||||||
|
attenuation_linear_,
|
||||||
|
attenuation_quadratic_
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PointLight::affects_point(const Vec3& point) const {
|
||||||
|
Real distance = glm::length(point - position_);
|
||||||
|
return distance <= range_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
/**
|
||||||
|
* @file scene_manager.cpp
|
||||||
|
* @brief Implementation of SceneManager class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/core/profiler.h>
|
||||||
|
#include <are/scene/scene_manager.h>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
SceneManager::SceneManager()
|
||||||
|
: next_mesh_handle_(1)
|
||||||
|
, next_material_handle_(1)
|
||||||
|
, next_light_handle_(1)
|
||||||
|
, dirty_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneManager::~SceneManager() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshHandle SceneManager::add_mesh(const Mesh &mesh) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (mesh.is_empty()) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to add empty mesh");
|
||||||
|
return are_invalid_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshHandle handle = next_mesh_handle_++;
|
||||||
|
meshes_.push_back(mesh);
|
||||||
|
mesh_handle_map_[handle] = meshes_.size() - 1;
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Added mesh with handle " + std::to_string(handle));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::remove_mesh(MeshHandle handle) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
auto it = mesh_handle_map_.find(handle);
|
||||||
|
if (it == mesh_handle_map_.end()) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to remove invalid mesh handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
|
||||||
|
// Swap with last element and pop
|
||||||
|
if (index < meshes_.size() - 1) {
|
||||||
|
meshes_[index] = meshes_.back();
|
||||||
|
|
||||||
|
// Update handle map for swapped element
|
||||||
|
for (auto &pair : mesh_handle_map_) {
|
||||||
|
if (pair.second == meshes_.size() - 1) {
|
||||||
|
pair.second = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
meshes_.pop_back();
|
||||||
|
mesh_handle_map_.erase(it);
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Removed mesh with handle " + std::to_string(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::update_mesh(MeshHandle handle, const Mesh &mesh) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
Mesh *existing = get_mesh(handle);
|
||||||
|
if (!existing) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to update invalid mesh handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*existing = mesh;
|
||||||
|
dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mesh *SceneManager::get_mesh(MeshHandle handle) {
|
||||||
|
auto it = mesh_handle_map_.find(handle);
|
||||||
|
if (it == mesh_handle_map_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
if (index >= meshes_.size()) {
|
||||||
|
ARE_LOG_ERROR("SceneManager: Mesh handle map corrupted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meshes_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Mesh *SceneManager::get_mesh(MeshHandle handle) const {
|
||||||
|
auto it = mesh_handle_map_.find(handle);
|
||||||
|
if (it == mesh_handle_map_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
if (index >= meshes_.size()) {
|
||||||
|
ARE_LOG_ERROR("SceneManager: Mesh handle map corrupted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meshes_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialHandle SceneManager::add_material(const Material &material) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
MaterialHandle handle = next_material_handle_++;
|
||||||
|
materials_.push_back(material);
|
||||||
|
material_handle_map_[handle] = materials_.size() - 1;
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Added material with handle " + std::to_string(handle));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::remove_material(MaterialHandle handle) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
auto it = material_handle_map_.find(handle);
|
||||||
|
if (it == material_handle_map_.end()) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to remove invalid material handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
|
||||||
|
// Swap with last element and pop
|
||||||
|
if (index < materials_.size() - 1) {
|
||||||
|
materials_[index] = materials_.back();
|
||||||
|
|
||||||
|
// Update handle map for swapped element
|
||||||
|
for (auto &pair : material_handle_map_) {
|
||||||
|
if (pair.second == materials_.size() - 1) {
|
||||||
|
pair.second = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
materials_.pop_back();
|
||||||
|
material_handle_map_.erase(it);
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Removed material with handle " + std::to_string(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::update_material(MaterialHandle handle, const Material &material) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
Material *existing = get_material(handle);
|
||||||
|
if (!existing) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to update invalid material handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*existing = material;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material *SceneManager::get_material(MaterialHandle handle) {
|
||||||
|
auto it = material_handle_map_.find(handle);
|
||||||
|
if (it == material_handle_map_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
if (index >= materials_.size()) {
|
||||||
|
ARE_LOG_ERROR("SceneManager: Material handle map corrupted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &materials_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Material *SceneManager::get_material(MaterialHandle handle) const {
|
||||||
|
auto it = material_handle_map_.find(handle);
|
||||||
|
if (it == material_handle_map_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
if (index >= materials_.size()) {
|
||||||
|
ARE_LOG_ERROR("SceneManager: Material handle map corrupted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &materials_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
LightHandle SceneManager::add_light(const std::shared_ptr<Light> &light) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
if (!light) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to add null light");
|
||||||
|
return are_invalid_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightHandle handle = next_light_handle_++;
|
||||||
|
lights_.push_back(light);
|
||||||
|
light_handle_map_[handle] = lights_.size() - 1;
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Added light with handle " + std::to_string(handle));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::remove_light(LightHandle handle) {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
auto it = light_handle_map_.find(handle);
|
||||||
|
if (it == light_handle_map_.end()) {
|
||||||
|
ARE_LOG_WARN("SceneManager: Attempting to remove invalid light handle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
|
||||||
|
// Swap with last element and pop
|
||||||
|
if (index < lights_.size() - 1) {
|
||||||
|
lights_[index] = lights_.back();
|
||||||
|
|
||||||
|
// Update handle map for swapped element
|
||||||
|
for (auto &pair : light_handle_map_) {
|
||||||
|
if (pair.second == lights_.size() - 1) {
|
||||||
|
pair.second = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lights_.pop_back();
|
||||||
|
light_handle_map_.erase(it);
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Removed light with handle " + std::to_string(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Light> SceneManager::get_light(LightHandle handle) {
|
||||||
|
auto it = light_handle_map_.find(handle);
|
||||||
|
if (it == light_handle_map_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = it->second;
|
||||||
|
if (index >= lights_.size()) {
|
||||||
|
ARE_LOG_ERROR("SceneManager: Light handle map corrupted");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lights_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SceneManager::get_total_triangle_count() const {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
size_t total = 0;
|
||||||
|
for (const auto &mesh : meshes_) {
|
||||||
|
total += mesh.get_triangle_count();
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::clear() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
meshes_.clear();
|
||||||
|
materials_.clear();
|
||||||
|
lights_.clear();
|
||||||
|
|
||||||
|
mesh_handle_map_.clear();
|
||||||
|
material_handle_map_.clear();
|
||||||
|
light_handle_map_.clear();
|
||||||
|
|
||||||
|
next_mesh_handle_ = 1;
|
||||||
|
next_material_handle_ = 1;
|
||||||
|
next_light_handle_ = 1;
|
||||||
|
|
||||||
|
dirty_ = true;
|
||||||
|
|
||||||
|
ARE_LOG_INFO("SceneManager: Cleared all scene data");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneManager::compact() {
|
||||||
|
ARE_PROFILE_FUNCTION();
|
||||||
|
|
||||||
|
// Remove invalid entries (this is a placeholder for future optimization)
|
||||||
|
// Currently, the handle-based system ensures no invalid entries exist
|
||||||
|
|
||||||
|
ARE_LOG_DEBUG("SceneManager: Compacted scene data");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
/**
|
||||||
|
* @file spot_light.cpp
|
||||||
|
* @brief Implementation of SpotLight class
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <are/scene/spot_light.h>
|
||||||
|
#include <are/core/logger.h>
|
||||||
|
#include <are/utils/math_utils.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace are {
|
||||||
|
|
||||||
|
SpotLight::SpotLight()
|
||||||
|
: Light(LightType::ARE_LIGHT_SPOT)
|
||||||
|
, position_(0.0f)
|
||||||
|
, direction_(0.0f, -1.0f, 0.0f)
|
||||||
|
, inner_angle_(30.0f)
|
||||||
|
, outer_angle_(45.0f)
|
||||||
|
, range_(10.0f)
|
||||||
|
, cos_inner_(std::cos(degrees_to_radians(30.0f)))
|
||||||
|
, cos_outer_(std::cos(degrees_to_radians(45.0f))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
SpotLight::SpotLight(const Vec3& position, const Vec3& direction,
|
||||||
|
Real inner_angle, Real outer_angle,
|
||||||
|
const Vec3& color, Real intensity)
|
||||||
|
: Light(LightType::ARE_LIGHT_SPOT)
|
||||||
|
, position_(position)
|
||||||
|
, range_(10.0f) {
|
||||||
|
set_direction(direction);
|
||||||
|
set_inner_angle(inner_angle);
|
||||||
|
set_outer_angle(outer_angle);
|
||||||
|
set_color(color);
|
||||||
|
set_intensity(intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpotLight::set_position(const Vec3& position) {
|
||||||
|
position_ = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpotLight::set_direction(const Vec3& direction) {
|
||||||
|
Real length = glm::length(direction);
|
||||||
|
if (length < are_epsilon) {
|
||||||
|
ARE_LOG_WARN("SpotLight: Invalid direction vector (zero length), using default");
|
||||||
|
direction_ = Vec3(0.0f, -1.0f, 0.0f);
|
||||||
|
} else {
|
||||||
|
direction_ = direction / length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpotLight::set_inner_angle(Real angle) {
|
||||||
|
// Clamp to valid range [0, 90] degrees
|
||||||
|
inner_angle_ = clamp(angle, 0.0f, 90.0f);
|
||||||
|
cos_inner_ = std::cos(degrees_to_radians(inner_angle_));
|
||||||
|
|
||||||
|
// Ensure inner angle is not larger than outer angle
|
||||||
|
if (inner_angle_ > outer_angle_) {
|
||||||
|
ARE_LOG_WARN("SpotLight: Inner angle larger than outer angle, adjusting outer angle");
|
||||||
|
outer_angle_ = inner_angle_;
|
||||||
|
cos_outer_ = cos_inner_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpotLight::set_outer_angle(Real angle) {
|
||||||
|
// Clamp to valid range [0, 90] degrees
|
||||||
|
outer_angle_ = clamp(angle, 0.0f, 90.0f);
|
||||||
|
cos_outer_ = std::cos(degrees_to_radians(outer_angle_));
|
||||||
|
|
||||||
|
// Ensure outer angle is not smaller than inner angle
|
||||||
|
if (outer_angle_ < inner_angle_) {
|
||||||
|
ARE_LOG_WARN("SpotLight: Outer angle smaller than inner angle, adjusting inner angle");
|
||||||
|
inner_angle_ = outer_angle_;
|
||||||
|
cos_inner_ = cos_outer_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpotLight::set_range(Real range) {
|
||||||
|
if (range <= 0.0f) {
|
||||||
|
ARE_LOG_WARN("SpotLight: Invalid range (must be positive), using default");
|
||||||
|
range_ = 10.0f;
|
||||||
|
} else {
|
||||||
|
range_ = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Real SpotLight::calculate_spot_factor(const Vec3& to_point) const {
|
||||||
|
// Calculate angle between light direction and direction to point
|
||||||
|
Real cos_angle = glm::dot(direction_, glm::normalize(to_point));
|
||||||
|
|
||||||
|
// Outside outer cone
|
||||||
|
if (cos_angle < cos_outer_) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside inner cone
|
||||||
|
if (cos_angle > cos_inner_) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smooth transition between inner and outer cone
|
||||||
|
Real delta = cos_inner_ - cos_outer_;
|
||||||
|
if (delta < are_epsilon) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (cos_angle - cos_outer_) / delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightData SpotLight::pack() const {
|
||||||
|
LightData data;
|
||||||
|
|
||||||
|
// position_type_: xyz = position, w = light type
|
||||||
|
data.position_type_ = Vec4(position_,
|
||||||
|
static_cast<float>(LightType::ARE_LIGHT_SPOT));
|
||||||
|
|
||||||
|
// direction_range_: xyz = direction, w = range
|
||||||
|
data.direction_range_ = Vec4(direction_, range_);
|
||||||
|
|
||||||
|
// color_intensity_: xyz = color, w = intensity
|
||||||
|
data.color_intensity_ = Vec4(color_, intensity_);
|
||||||
|
|
||||||
|
// params_: x = cast_shadows, y = cos_inner, z = cos_outer, w unused
|
||||||
|
data.params_ = Vec4(
|
||||||
|
cast_shadows_ ? 1.0f : 0.0f,
|
||||||
|
cos_inner_,
|
||||||
|
cos_outer_,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpotLight::affects_point(const Vec3& point) const {
|
||||||
|
// Check if point is within range
|
||||||
|
Vec3 to_point = point - position_;
|
||||||
|
Real distance = glm::length(to_point);
|
||||||
|
|
||||||
|
if (distance > range_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if point is within spotlight cone
|
||||||
|
if (distance > are_epsilon) {
|
||||||
|
to_point /= distance;
|
||||||
|
Real cos_angle = glm::dot(direction_, to_point);
|
||||||
|
return cos_angle >= cos_outer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace are
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# query.sh - 遍历指定文件夹中的 .h 文件并生成 all_headers.md
|
||||||
|
|
||||||
|
# 检查是否提供了目录参数
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
echo "用法: $0 <目标文件夹路径>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_DIR="$1"
|
||||||
|
|
||||||
|
# 检查提供的路径是否为一个存在的目录
|
||||||
|
if [ ! -d "$TARGET_DIR" ]; then
|
||||||
|
echo "错误: 目录 '$TARGET_DIR' 不存在。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 输出文件
|
||||||
|
OUTPUT_FILE="all_headers.md"
|
||||||
|
|
||||||
|
# 清空或创建输出文件
|
||||||
|
> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
echo "正在扫描目录: $TARGET_DIR"
|
||||||
|
# 使用 find 命令查找所有 .h 文件
|
||||||
|
H_FILES=$(find "$TARGET_DIR" -type f -name "*.h")
|
||||||
|
|
||||||
|
# 检查是否找到了 .h 文件
|
||||||
|
if [ -z "$H_FILES" ]; then
|
||||||
|
echo "在目录 '$TARGET_DIR' 及其子目录中未找到任何 .h 文件。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 遍历找到的每个 .h 文件
|
||||||
|
for header_file in $H_FILES; do
|
||||||
|
# 获取相对于脚本执行位置的相对路径
|
||||||
|
RELATIVE_PATH=$(realpath --relative-to=. "$header_file")
|
||||||
|
|
||||||
|
# 写入分隔符和文件名
|
||||||
|
{
|
||||||
|
echo "### 文件:$RELATIVE_PATH"
|
||||||
|
echo ""
|
||||||
|
echo '```cpp'
|
||||||
|
cat "$header_file"
|
||||||
|
echo '```'
|
||||||
|
echo "" # 添加一个空行,使文件之间有分隔
|
||||||
|
} >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
echo "已处理: $RELATIVE_PATH"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "处理完成!所有头文件内容已合并到 $OUTPUT_FILE 中。"
|
||||||
Loading…
Reference in New Issue