diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..19a0f26 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,121 @@ +cmake_minimum_required(VERSION 3.15) +project(AuroraRenderingEngine VERSION 1.0.0 LANGUAGES CXX C) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Options +option(ARE_BUILD_TESTS "Build tests" ON) +option(ARE_BUILD_EXAMPLES "Build examples" ON) + +# Find dependencies +find_package(OpenGL REQUIRED) +find_package(glm REQUIRED) +find_package(glfw3 REQUIRED) + +# GLAD library +add_library(glad STATIC + lib/glad/src/glad.c +) +target_include_directories(glad PUBLIC lib/glad/include) + +# stb_image library +set(STB_IMAGE_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/lib/stb/stb_image.cpp) +if(NOT EXISTS ${STB_IMAGE_SOURCE}) + file(WRITE ${STB_IMAGE_SOURCE} +"#define STB_IMAGE_IMPLEMENTATION +#include \"stb_image.h\" +") +endif() + +add_library(stb_image STATIC ${STB_IMAGE_SOURCE}) +target_include_directories(stb_image PUBLIC lib/stb) + +# Collect all source files automatically +file(GLOB_RECURSE ARE_SOURCES + "src/*.cpp" + "src/*.c" +) + +# Collect all header files +file(GLOB_RECURSE ARE_HEADERS + "include/*.h" + "include/*.hpp" +) + +# Aurora Rendering Engine library +add_library(are STATIC ${ARE_SOURCES} ${ARE_HEADERS}) + +target_include_directories(are + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(are + PUBLIC + OpenGL::GL + glm::glm + PRIVATE + glad + stb_image +) + +# Copy shaders to build directory +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/shaders + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +# Examples +if(ARE_BUILD_EXAMPLES) + # Cornell Box example + add_executable(cornell_box examples/cornell_box.cpp) + target_link_libraries(cornell_box + PRIVATE + are + glfw + ) + + # Copy shaders to example directory + add_custom_command(TARGET cornell_box POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/shaders + $/shaders + ) +endif() + +# Tests +if(ARE_BUILD_TESTS) + enable_testing() + + # Basic test + add_executable(test_basic tests/test_basic.cpp) + target_link_libraries(test_basic PRIVATE are) + add_test(NAME BasicTest COMMAND test_basic) +endif() + +# Install +install(TARGETS are + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(DIRECTORY include/ + DESTINATION include + FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" +) + +install(DIRECTORY shaders/ + DESTINATION share/are/shaders +) + +# Print configuration +message(STATUS "Aurora Rendering Engine Configuration:") +message(STATUS " Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS " C++ standard: ${CMAKE_CXX_STANDARD}") +message(STATUS " Build examples: ${ARE_BUILD_EXAMPLES}") +message(STATUS " Build tests: ${ARE_BUILD_TESTS}") +message(STATUS " Install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/examples/cornell_box b/examples/cornell_box new file mode 100644 index 0000000..c8bcff2 Binary files /dev/null and b/examples/cornell_box differ diff --git a/examples/cornell_box.cpp b/examples/cornell_box.cpp new file mode 100644 index 0000000..752de40 --- /dev/null +++ b/examples/cornell_box.cpp @@ -0,0 +1,473 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace are; + +// Window dimensions +const uint WINDOW_WIDTH = 800; +const uint WINDOW_HEIGHT = 800; + +// Global state +GLFWwindow* g_window = nullptr; +std::unique_ptr g_renderer = nullptr; +std::unique_ptr g_scene = nullptr; + +// GLFW error callback +void glfw_error_callback(int error, const char* description) { + Logger::error("GLFW Error " + std::to_string(error) + ": " + std::string(description)); +} + +/// @brief Create a quad mesh +std::shared_ptr create_quad(const Vec3& v0, const Vec3& v1, const Vec3& v2, const Vec3& v3, + const Vec3& normal, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices = { + {v0, normal, Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f)}, + {v1, normal, Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f)}, + {v2, normal, Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f)}, + {v3, normal, Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f)} + }; + + std::vector indices = {0, 1, 2, 0, 2, 3}; + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + + return mesh; +} + +/// @brief Create a box mesh +std::shared_ptr create_box(const Vec3& min, const Vec3& max, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices = { + // Front face + {{min.x, min.y, max.z}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, min.y, max.z}, {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, max.y, max.z}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + {{min.x, max.y, max.z}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + + // Back face + {{max.x, min.y, min.z}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + {{min.x, min.y, min.z}, {0.0f, 0.0f, -1.0f}, {1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + {{min.x, max.y, min.z}, {0.0f, 0.0f, -1.0f}, {1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, + {{max.x, max.y, min.z}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, + + // Top face + {{min.x, max.y, max.z}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, max.y, max.z}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, max.y, min.z}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + {{min.x, max.y, min.z}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + + // Bottom face + {{min.x, min.y, min.z}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, min.y, min.z}, {0.0f, -1.0f, 0.0f}, {1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{max.x, min.y, max.z}, {0.0f, -1.0f, 0.0f}, {1.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + {{min.x, min.y, max.z}, {0.0f, -1.0f, 0.0f}, {0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}}, + + // Right face + {{max.x, min.y, max.z}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}}, + {{max.x, min.y, min.z}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, -1.0f}}, + {{max.x, max.y, min.z}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, -1.0f}}, + {{max.x, max.y, max.z}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, -1.0f}}, + + // Left face + {{min.x, min.y, min.z}, {-1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{min.x, min.y, max.z}, {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{min.x, max.y, max.z}, {-1.0f, 0.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, + {{min.x, max.y, min.z}, {-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}} + }; + + std::vector indices = { + 0, 1, 2, 0, 2, 3, // Front + 4, 5, 6, 4, 6, 7, // Back + 8, 9, 10, 8, 10, 11, // Top + 12, 13, 14, 12, 14, 15, // Bottom + 16, 17, 18, 16, 18, 19, // Right + 20, 21, 22, 20, 22, 23 // Left + }; + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + + return mesh; +} + +/// @brief Setup Cornell Box scene +void setup_cornell_box() { + g_scene = std::make_unique(); + + // Create materials + // 0: White diffuse + auto white_material = std::make_shared(); + white_material->set_albedo(Vec3(0.73f, 0.73f, 0.73f)); + white_material->set_type(MaterialType::DIFFUSE); + uint white_id = g_scene->add_material(white_material); + + // 1: Red diffuse (left wall) + auto red_material = std::make_shared(); + red_material->set_albedo(Vec3(0.65f, 0.05f, 0.05f)); + red_material->set_type(MaterialType::DIFFUSE); + uint red_id = g_scene->add_material(red_material); + + // 2: Green diffuse (right wall) + auto green_material = std::make_shared(); + green_material->set_albedo(Vec3(0.12f, 0.45f, 0.15f)); + green_material->set_type(MaterialType::DIFFUSE); + uint green_id = g_scene->add_material(green_material); + + // 3: Light emissive + auto light_material = std::make_shared(); + light_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); + light_material->set_emission(Vec3(15.0f, 15.0f, 15.0f)); + light_material->set_type(MaterialType::EMISSIVE); + uint light_id = g_scene->add_material(light_material); + + // 4: Metal (for one box) + auto metal_material = std::make_shared(); + metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f)); + metal_material->set_metallic(1.0f); + metal_material->set_roughness(0.1f); + metal_material->set_type(MaterialType::METAL); + uint metal_id = g_scene->add_material(metal_material); + + // Create room (Cornell Box) + float room_size = 2.0f; + + // Floor (white) + auto floor = create_quad( + Vec3(-room_size, -room_size, -room_size), + Vec3(room_size, -room_size, -room_size), + Vec3(room_size, -room_size, room_size), + Vec3(-room_size, -room_size, room_size), + Vec3(0.0f, 1.0f, 0.0f), + white_id + ); + floor->upload_to_gpu(); + g_scene->add_mesh(floor); + + // Ceiling (white) + auto ceiling = create_quad( + Vec3(-room_size, room_size, room_size), + Vec3(room_size, room_size, room_size), + Vec3(room_size, room_size, -room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(0.0f, -1.0f, 0.0f), + white_id + ); + ceiling->upload_to_gpu(); + g_scene->add_mesh(ceiling); + + // Back wall (white) + auto back_wall = create_quad( + Vec3(-room_size, -room_size, -room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(room_size, room_size, -room_size), + Vec3(room_size, -room_size, -room_size), + Vec3(0.0f, 0.0f, 1.0f), + white_id + ); + back_wall->upload_to_gpu(); + g_scene->add_mesh(back_wall); + + // Left wall (red) + auto left_wall = create_quad( + Vec3(-room_size, -room_size, room_size), + Vec3(-room_size, room_size, room_size), + Vec3(-room_size, room_size, -room_size), + Vec3(-room_size, -room_size, -room_size), + Vec3(1.0f, 0.0f, 0.0f), + red_id + ); + left_wall->upload_to_gpu(); + g_scene->add_mesh(left_wall); + + // Right wall (green) + auto right_wall = create_quad( + Vec3(room_size, -room_size, -room_size), + Vec3(room_size, room_size, -room_size), + Vec3(room_size, room_size, room_size), + Vec3(room_size, -room_size, room_size), + Vec3(-1.0f, 0.0f, 0.0f), + green_id + ); + right_wall->upload_to_gpu(); + g_scene->add_mesh(right_wall); + + // Area light on ceiling + float light_size = 0.5f; + auto area_light = create_quad( + Vec3(-light_size, room_size - 0.01f, -light_size), + Vec3(light_size, room_size - 0.01f, -light_size), + Vec3(light_size, room_size - 0.01f, light_size), + Vec3(-light_size, room_size - 0.01f, light_size), + Vec3(0.0f, -1.0f, 0.0f), + light_id + ); + area_light->upload_to_gpu(); + g_scene->add_mesh(area_light); + + // Tall box (white, left side) + auto tall_box = create_box(Vec3(-0.7f, -room_size, -0.7f), Vec3(-0.2f, 0.6f, -0.2f), white_id); + tall_box->upload_to_gpu(); + g_scene->add_mesh(tall_box); + + // Short box (metal, right side) + auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), metal_id); + short_box->upload_to_gpu(); + g_scene->add_mesh(short_box); + + // Setup camera + auto camera = std::make_shared(); + camera->set_position(Vec3(0.0f, 0.0f, 4.5f)); + camera->set_target(Vec3(0.0f, 0.0f, 0.0f)); + camera->set_up(Vec3(0.0f, 1.0f, 0.0f)); + camera->set_perspective(45.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); + g_scene->set_camera(camera); + + // Add point light + auto light = std::make_shared(); + light->set_type(LightType::POINT); + light->set_position(Vec3(0.0f, 1.8f, 0.0f)); + light->set_color(Vec3(1.0f, 1.0f, 1.0f)); + light->set_intensity(10.0f); + light->set_range(10.0f); + g_scene->add_light(light); + + Logger::info("Cornell Box scene created"); +} + +/// @brief Initialize GLFW and create window +bool init_window() { + // Set error callback before init + glfwSetErrorCallback(glfw_error_callback); + + if (!glfwInit()) { + Logger::error("Failed to initialize GLFW"); + return false; + } + + Logger::info("GLFW initialized successfully"); + + // Request OpenGL 4.5 Core Profile + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + + // Additional hints for better compatibility + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_SAMPLES, 0); + + Logger::info("Creating window..."); + g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); + + if (!g_window) { + Logger::error("Failed to create GLFW window"); + Logger::error("Possible reasons:"); + Logger::error(" 1. OpenGL 4.5 not supported by your GPU/driver"); + Logger::error(" 2. No display server running (X11/Wayland)"); + Logger::error(" 3. Insufficient GPU resources"); + + // Try to get more info + int major, minor, rev; + glfwGetVersion(&major, &minor, &rev); + Logger::info("GLFW version: " + std::to_string(major) + "." + + std::to_string(minor) + "." + std::to_string(rev)); + + glfwTerminate(); + return false; + } + + Logger::info("Window created successfully"); + + glfwMakeContextCurrent(g_window); + glfwSwapInterval(1); // Enable vsync + + // Load OpenGL functions + Logger::info("Loading OpenGL functions..."); + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + Logger::error("Failed to initialize GLAD"); + return false; + } + + // Print OpenGL info + const char* vendor = (const char*)glGetString(GL_VENDOR); + const char* renderer = (const char*)glGetString(GL_RENDERER); + const char* version = (const char*)glGetString(GL_VERSION); + const char* glsl_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + + Logger::info("OpenGL Vendor: " + std::string(vendor ? vendor : "Unknown")); + Logger::info("OpenGL Renderer: " + std::string(renderer ? renderer : "Unknown")); + Logger::info("OpenGL Version: " + std::string(version ? version : "Unknown")); + Logger::info("GLSL Version: " + std::string(glsl_version ? glsl_version : "Unknown")); + + // Check OpenGL version + GLint major_ver, minor_ver; + glGetIntegerv(GL_MAJOR_VERSION, &major_ver); + glGetIntegerv(GL_MINOR_VERSION, &minor_ver); + + Logger::info("OpenGL Context: " + std::to_string(major_ver) + "." + std::to_string(minor_ver)); + + // if (major_ver < 4 || (major_ver == 4 && minor_ver < 5)) { + // Logger::error("OpenGL 4.5 or higher is required!"); + // Logger::error("Your system supports: OpenGL " + std::to_string(major_ver) + "." + std::to_string(minor_ver)); + // return false; + // } + + // Check compute shader support + GLint max_compute_work_group_invocations; + glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &max_compute_work_group_invocations); + Logger::info("Max compute work group invocations: " + std::to_string(max_compute_work_group_invocations)); + + return true; +} + +/// @brief Main render loop +void render_loop() { + Logger::info("Entering render loop..."); + + int frame_count = 0; + double last_time = glfwGetTime(); + double fps_time = last_time; + + while (!glfwWindowShouldClose(g_window)) { + // Render + RenderStats stats = g_renderer->render(*g_scene); + + // Swap buffers + glfwSwapBuffers(g_window); + glfwPollEvents(); + + // Calculate FPS + frame_count++; + double current_time = glfwGetTime(); + double delta = current_time - fps_time; + + if (delta >= 1.0) { + double fps = frame_count / delta; + std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + + " | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms"; + glfwSetWindowTitle(g_window, title.c_str()); + + frame_count = 0; + fps_time = current_time; + } + + // Print detailed stats every 60 frames + static int stat_frame_count = 0; + if (++stat_frame_count % 60 == 0) { + Logger::info("Frame time: " + std::to_string(stats.frame_time_ms_) + " ms (" + + std::to_string(1000.0f / stats.frame_time_ms_) + " FPS)"); + Logger::info(" G-Buffer: " + std::to_string(stats.gbuffer_time_ms_) + " ms"); + Logger::info(" Ray trace: " + std::to_string(stats.raytrace_time_ms_) + " ms"); + Logger::info(" Triangles: " + std::to_string(stats.triangle_count_)); + } + + // ESC to exit + if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + glfwSetWindowShouldClose(g_window, true); + } + } + + Logger::info("Exiting render loop"); +} + +/// @brief Cleanup +void cleanup() { + Logger::info("Cleaning up..."); + + if (g_renderer) { + g_renderer->shutdown(); + g_renderer.reset(); + } + + g_scene.reset(); + + if (g_window) { + glfwDestroyWindow(g_window); + glfwTerminate(); + } + + Logger::info("Cleanup complete"); +} + +int main() { + // Initialize logger + Logger::initialize(); + Logger::info("==========================================="); + Logger::info("Aurora Rendering Engine - Cornell Box Demo"); + Logger::info("==========================================="); + + // Check environment + const char* display = getenv("DISPLAY"); + const char* wayland_display = getenv("WAYLAND_DISPLAY"); + + if (!display && !wayland_display) { + Logger::error("No display server detected!"); + Logger::error("Make sure you're running in a graphical environment (X11 or Wayland)"); + Logger::error("DISPLAY=" + std::string(display ? display : "not set")); + Logger::error("WAYLAND_DISPLAY=" + std::string(wayland_display ? wayland_display : "not set")); + return -1; + } + + Logger::info("Display server: " + std::string(display ? display : wayland_display)); + + // Initialize window + if (!init_window()) { + cleanup(); + Logger::error("Failed to initialize window"); + Logger::shutdown(); + return -1; + } + + // Setup scene + Logger::info("Setting up Cornell Box scene..."); + setup_cornell_box(); + + // Initialize renderer + Logger::info("Initializing renderer..."); + RendererConfig config; + config.width_ = WINDOW_WIDTH; + config.height_ = WINDOW_HEIGHT; + config.samples_per_pixel_ = 1; + config.max_ray_depth_ = 4; + config.enable_accumulation_ = false; + config.enable_denoising_ = false; + + g_renderer = std::make_unique(config); + if (!g_renderer->initialize()) { + Logger::error("Failed to initialize renderer"); + cleanup(); + Logger::shutdown(); + return -1; + } + + Logger::info("==========================================="); + Logger::info("Renderer initialized successfully!"); + Logger::info("Press ESC to exit"); + Logger::info("==========================================="); + + // Main loop + render_loop(); + + // Cleanup + cleanup(); + + Logger::info("Cornell Box demo finished"); + Logger::shutdown(); + + return 0; +} diff --git a/include/basic/constants.h b/include/basic/constants.h new file mode 100644 index 0000000..12c631d --- /dev/null +++ b/include/basic/constants.h @@ -0,0 +1,32 @@ +#ifndef ARE_INCLUDE_BASIC_CONSTANTS_H +#define ARE_INCLUDE_BASIC_CONSTANTS_H + +namespace are { + +/// @brief Maximum number of lights in scene +constexpr int MAX_LIGHTS = 16; + +/// @brief Maximum ray tracing depth +constexpr int MAX_RAY_DEPTH = 8; + +/// @brief Default samples per pixel for ray tracing +constexpr int DEFAULT_SPP = 1; + +/// @brief G-Buffer attachment indices +constexpr int GBUFFER_POSITION = 0; +constexpr int GBUFFER_NORMAL = 1; +constexpr int GBUFFER_ALBEDO = 2; +constexpr int GBUFFER_COUNT = 3; + +/// @brief Compute shader work group size +constexpr int COMPUTE_GROUP_SIZE_X = 16; +constexpr int COMPUTE_GROUP_SIZE_Y = 16; + +/// @brief Mathematical constants +constexpr float PI = 3.14159265359f; +constexpr float INV_PI = 0.31830988618f; +constexpr float EPSILON = 1e-4f; + +} // namespace are + +#endif // ARE_INCLUDE_BASIC_CONSTANTS_H diff --git a/include/basic/math.h b/include/basic/math.h new file mode 100644 index 0000000..dc2a07d --- /dev/null +++ b/include/basic/math.h @@ -0,0 +1,59 @@ +#ifndef ARE_INCLUDE_BASIC_MATH_UTILS_H +#define ARE_INCLUDE_BASIC_MATH_UTILS_H + +#include "types.h" +#include +#include + +namespace are { + +/// @brief Math utility functions wrapping GLM +class MathUtils { +public: + /// @brief Create perspective projection matrix + /// @param fov Field of view in radians + /// @param aspect Aspect ratio + /// @param near Near plane distance + /// @param far Far plane distance + /// @return Projection matrix + static Mat4 perspective(float fov, float aspect, float near, float far); + + /// @brief Create look-at view matrix + /// @param eye Camera position + /// @param center Look-at target + /// @param up Up vector + /// @return View matrix + static Mat4 look_at(const Vec3& eye, const Vec3& center, const Vec3& up); + + /// @brief Normalize a vector + /// @param v Input vector + /// @return Normalized vector + static Vec3 normalize(const Vec3& v); + + /// @brief Calculate dot product + /// @param a First vector + /// @param b Second vector + /// @return Dot product + static float dot(const Vec3& a, const Vec3& b); + + /// @brief Calculate cross product + /// @param a First vector + /// @param b Second vector + /// @return Cross product + static Vec3 cross(const Vec3& a, const Vec3& b); + + /// @brief Reflect vector around normal + /// @param incident Incident vector + /// @param normal Surface normal + /// @return Reflected vector + static Vec3 reflect(const Vec3& incident, const Vec3& normal); + + /// @brief Get pointer to matrix data (for OpenGL) + /// @param mat Input matrix + /// @return Pointer to matrix data + static const float* value_ptr(const Mat4& mat); +}; + +} // namespace are + +#endif // ARE_INCLUDE_BASIC_MATH_UTILS_H diff --git a/include/basic/types.h b/include/basic/types.h new file mode 100644 index 0000000..c4adb8b --- /dev/null +++ b/include/basic/types.h @@ -0,0 +1,71 @@ +#ifndef ARE_INCLUDE_BASIC_TYPES_H +#define ARE_INCLUDE_BASIC_TYPES_H + +#include +#include +#include +#include +#include + +namespace are { + +/// @brief Basic vector types using GLM +using Vec2 = glm::vec2; +using Vec3 = glm::vec3; +using Vec4 = glm::vec4; + +/// @brief Basic matrix types using GLM +using Mat3 = glm::mat3; +using Mat4 = glm::mat4; + +/// @brief Basic integer types +using uint = uint32_t; +using uchar = uint8_t; + +/// @brief Handle types for GPU resources +using TextureHandle = uint; +using BufferHandle = uint; +using ShaderHandle = uint; +using FramebufferHandle = uint; + +/// @brief Invalid handle constant +constexpr uint INVALID_HANDLE = 0; + +/// @brief Vertex structure for mesh data +struct Vertex { + Vec3 position_; + Vec3 normal_; + Vec2 texcoord_; + Vec3 tangent_; +}; + +/// @brief Ray structure for ray tracing +struct Ray { + Vec3 origin_; + Vec3 direction_; + float t_min_; + float t_max_; +}; + +/// @brief Hit information for ray-surface intersection +struct HitInfo { + bool hit_; + float t_; + Vec3 position_; + Vec3 normal_; + Vec2 texcoord_; + uint material_id_; +}; + +/// @brief Rendering statistics +struct RenderStats { + float frame_time_ms_; + uint triangle_count_; + uint ray_count_; + float gbuffer_time_ms_; + float raytrace_time_ms_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_BASIC_TYPES_H diff --git a/include/core/bvh.h b/include/core/bvh.h new file mode 100644 index 0000000..e45bd07 --- /dev/null +++ b/include/core/bvh.h @@ -0,0 +1,124 @@ +#ifndef ARE_INCLUDE_CORE_BVH_H +#define ARE_INCLUDE_CORE_BVH_H + +#include "basic/types.h" +#include "scene/mesh.h" +#include "resource/buffer.h" +#include +#include + +namespace are { + +/// @brief Axis-aligned bounding box +struct AABB { + Vec3 min_; + Vec3 max_; + + /// @brief Construct AABB from min and max points + AABB(const Vec3& min = Vec3(0.0f), const Vec3& max = Vec3(0.0f)) + : min_(min), max_(max) {} + + /// @brief Expand AABB to include point + void expand(const Vec3& point); + + /// @brief Expand AABB to include another AABB + void expand(const AABB& other); + + /// @brief Get center of AABB + Vec3 center() const { return (min_ + max_) * 0.5f; } + + /// @brief Get surface area of AABB + float surface_area() const; + + /// @brief Check if AABB is valid + bool is_valid() const; +}; + +/// @brief Triangle primitive for BVH +struct Triangle { + Vec3 v0_, v1_, v2_; + Vec3 n0_, n1_, n2_; + Vec2 uv0_, uv1_, uv2_; + uint material_id_; + + /// @brief Get bounding box of triangle + AABB get_bounds() const; + + /// @brief Get centroid of triangle + Vec3 get_centroid() const; +}; + +/// @brief BVH node for GPU +struct BVHNode { + Vec3 aabb_min_; + uint left_first_; // Left child index or first primitive index + Vec3 aabb_max_; + uint count_; // 0 for interior node, >0 for leaf node +}; + +/// @brief Bounding Volume Hierarchy for ray tracing acceleration +class BVH { +public: + /// @brief Constructor + BVH(); + + /// @brief Destructor + ~BVH(); + + /// @brief Build BVH from meshes + /// @param meshes Mesh list + /// @return True if build succeeded + bool build(const std::vector>& meshes); + + /// @brief Upload BVH to GPU + /// @param node_buffer Buffer for BVH nodes + /// @param triangle_buffer Buffer for triangles + /// @return True if upload succeeded + bool upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer); + + /// @brief Get total node count + /// @return Node count + uint get_node_count() const { return static_cast(nodes_.size()); } + + /// @brief Get total triangle count + /// @return Triangle count + uint get_triangle_count() const { return static_cast(triangles_.size()); } + + /// @brief Clear BVH data + void clear(); + +private: + std::vector nodes_; + std::vector triangles_; + std::vector triangle_indices_; + + /// @brief Recursively build BVH + /// @param node_idx Current node index + /// @param first_prim First primitive index + /// @param prim_count Primitive count + void build_recursive_(uint node_idx, uint first_prim, uint prim_count); + + /// @brief Find best split using SAH + /// @param first_prim First primitive index + /// @param prim_count Primitive count + /// @param axis Split axis (output) + /// @param split_pos Split position (output) + /// @return Split cost + float find_best_split_(uint first_prim, uint prim_count, int& axis, float& split_pos); + + /// @brief Calculate node bounds + /// @param first_prim First primitive index + /// @param prim_count Primitive count + /// @return Bounding box + AABB calculate_bounds_(uint first_prim, uint prim_count); + + /// @brief Calculate centroid bounds + /// @param first_prim First primitive index + /// @param prim_count Primitive count + /// @return Centroid bounding box + AABB calculate_centroid_bounds_(uint first_prim, uint prim_count); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_BVH_H diff --git a/include/core/gbuffer.h b/include/core/gbuffer.h new file mode 100644 index 0000000..9859cb0 --- /dev/null +++ b/include/core/gbuffer.h @@ -0,0 +1,72 @@ +#ifndef ARE_INCLUDE_CORE_GBUFFER_H +#define ARE_INCLUDE_CORE_GBUFFER_H + +#include "basic/types.h" +#include "basic/constants.h" +#include "scene/scene.h" +#include "resource/shader.h" + +namespace are { + +/// @brief G-Buffer manager for deferred rendering +class GBuffer { +public: + /// @brief Constructor + /// @param width Buffer width + /// @param height Buffer height + GBuffer(uint width, uint height); + + /// @brief Destructor + ~GBuffer(); + + /// @brief Initialize G-Buffer (create framebuffer and textures) + /// @return True if initialization succeeded + bool initialize(); + + /// @brief Release G-Buffer resources + void release(); + + /// @brief Render scene to G-Buffer + /// @param scene Scene to render + /// @param shader Shader program for G-Buffer pass + void render(const Scene& scene, const Shader& shader); + + /// @brief Resize G-Buffer + /// @param width New width + /// @param height New height + void resize(uint width, uint height); + + /// @brief Get texture handle for specific buffer + /// @param index Buffer index (GBUFFER_POSITION, GBUFFER_NORMAL, etc.) + /// @return Texture handle + TextureHandle get_texture(int index) const; + + /// @brief Get framebuffer handle + /// @return Framebuffer handle + FramebufferHandle get_framebuffer() const { return fbo_; } + + /// @brief Get buffer dimensions + /// @param width Output width + /// @param height Output height + void get_dimensions(uint& width, uint& height) const; + +private: + uint width_; + uint height_; + FramebufferHandle fbo_; + TextureHandle textures_[GBUFFER_COUNT]; + TextureHandle depth_texture_; + + bool initialized_; + + /// @brief Create texture for G-Buffer attachment + /// @param internal_format OpenGL internal format + /// @param format OpenGL format + /// @param type OpenGL type + /// @return Texture handle + TextureHandle create_texture_(uint internal_format, uint format, uint type); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_GBUFFER_H diff --git a/include/core/raytracer.h b/include/core/raytracer.h new file mode 100644 index 0000000..abe4d1d --- /dev/null +++ b/include/core/raytracer.h @@ -0,0 +1,106 @@ +#ifndef ARE_INCLUDE_CORE_RAYTRACER_H +#define ARE_INCLUDE_CORE_RAYTRACER_H + +#include "basic/types.h" +#include "core/bvh.h" // 添加 +#include "core/gbuffer.h" +#include "resource/buffer.h" +#include "resource/shader.h" +#include "scene/scene.h" + +namespace are { + +/// @brief Ray tracing configuration +struct RayTracerConfig { + uint samples_per_pixel_; + uint max_depth_; + bool enable_shadows_; + bool enable_reflections_; + bool enable_accumulation_; + bool use_bvh_; // 添加BVH开关 +}; + +/// @brief Compute shader based ray tracer +class RayTracer { +public: + /// @brief Constructor + /// @param width Output width + /// @param height Output height + /// @param config Ray tracer configuration + RayTracer(uint width, uint height, const RayTracerConfig &config); + + /// @brief Destructor + ~RayTracer(); + + /// @brief Initialize ray tracer + /// @return True if initialization succeeded + bool initialize(); + + /// @brief Release resources + void release(); + + /// @brief Trace rays using G-Buffer as input + /// @param scene Scene data + /// @param gbuffer G-Buffer containing geometry information + /// @param output_texture Output texture for ray traced result + void trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle output_texture); + + /// @brief Resize output + /// @param width New width + /// @param height New height + void resize(uint width, uint height); + + /// @brief Reset accumulation buffer + void reset_accumulation(); + + /// @brief Get current configuration + /// @return Current configuration + const RayTracerConfig &get_config() const { + return config_; + } + + /// @brief Update configuration + /// @param config New configuration + void set_config(const RayTracerConfig &config); + + /// @brief Rebuild BVH from scene + /// @param scene Scene to build BVH from + /// @return True if build succeeded + bool rebuild_bvh(const Scene &scene); + + /// @brief Set compute shader (called by renderer) + /// @param shader Compute shader + void set_compute_shader(const Shader &shader); + +private: + uint width_; + uint height_; + RayTracerConfig config_; + + Shader compute_shader_; + TextureHandle accumulation_texture_; + BufferHandle scene_buffer_; + BufferHandle material_buffer_; + BufferHandle light_buffer_; + + // BVH related + std::unique_ptr bvh_; // 添加 + Buffer bvh_node_buffer_; // 添加 + Buffer bvh_triangle_buffer_; // 添加 + bool bvh_built_; // 添加 + + uint frame_count_; + bool initialized_; + + /// @brief Upload scene data to GPU buffers + /// @param scene Scene to upload + void upload_scene_data_(const Scene &scene); + + /// @brief Bind G-Buffer textures to compute shader + /// @param gbuffer G-Buffer to bind + void bind_gbuffer_(const GBuffer &gbuffer); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_RAYTRACER_H diff --git a/include/core/renderer.h b/include/core/renderer.h new file mode 100644 index 0000000..9040710 --- /dev/null +++ b/include/core/renderer.h @@ -0,0 +1,73 @@ +#ifndef ARE_INCLUDE_CORE_RENDERER_H +#define ARE_INCLUDE_CORE_RENDERER_H + +#include "basic/types.h" +#include "scene/scene.h" +#include "core/gbuffer.h" +#include "core/raytracer.h" +#include "core/screen_blit.h" +#include "core/shader_manager.h" +#include + +namespace are { + +/// @brief Main renderer configuration +struct RendererConfig { + uint width_; + uint height_; + uint samples_per_pixel_; + uint max_ray_depth_; + bool enable_denoising_; + bool enable_accumulation_; +}; + +/// @brief Main rendering engine interface +class Renderer { +public: + /// @brief Constructor + /// @param config Renderer configuration + Renderer(const RendererConfig& config); + + /// @brief Destructor + ~Renderer(); + + /// @brief Initialize renderer (OpenGL context must be current) + /// @return True if initialization succeeded + bool initialize(); + + /// @brief Shutdown renderer and release resources + void shutdown(); + + /// @brief Render a frame + /// @param scene Scene to render + /// @param output_texture Output texture handle (0 for default framebuffer) + /// @return Rendering statistics + RenderStats render(const Scene& scene, TextureHandle output_texture = 0); + + /// @brief Resize render targets + /// @param width New width + /// @param height New height + void resize(uint width, uint height); + + /// @brief Get current configuration + /// @return Current configuration + const RendererConfig& get_config() const { return config_; } + + /// @brief Update configuration + /// @param config New configuration + void set_config(const RendererConfig& config); + +private: + RendererConfig config_; + std::unique_ptr gbuffer_; + std::unique_ptr raytracer_; + std::unique_ptr shader_manager_; + std::unique_ptr screen_blit_; + + bool initialized_; + uint frame_count_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_RENDERER_H diff --git a/include/core/screen_blit.h b/include/core/screen_blit.h new file mode 100644 index 0000000..6addbb4 --- /dev/null +++ b/include/core/screen_blit.h @@ -0,0 +1,49 @@ +#ifndef ARE_INCLUDE_CORE_SCREEN_BLIT_H +#define ARE_INCLUDE_CORE_SCREEN_BLIT_H + +#include "basic/types.h" +#include "resource/shader.h" + +namespace are { + +/// @brief Screen blit utility for rendering texture to screen +class ScreenBlit { +public: + /// @brief Constructor + ScreenBlit(); + + /// @brief Destructor + ~ScreenBlit(); + + /// @brief Initialize screen blit + /// @return True if initialization succeeded + bool initialize(); + + /// @brief Release resources + void release(); + + /// @brief Blit texture to screen + /// @param texture Texture to blit + /// @param x Screen X position + /// @param y Screen Y position + /// @param width Blit width + /// @param height Blit height + void blit(TextureHandle texture, int x, int y, uint width, uint height); + + /// @brief Blit texture to full screen + /// @param texture Texture to blit + void blit_fullscreen(TextureHandle texture); + +private: + Shader shader_; + uint vao_; + uint vbo_; + bool initialized_; + + /// @brief Create fullscreen quad + void create_quad_(); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_SCREEN_BLIT_H diff --git a/include/core/shader_manager.h b/include/core/shader_manager.h new file mode 100644 index 0000000..891dccc --- /dev/null +++ b/include/core/shader_manager.h @@ -0,0 +1,70 @@ +#ifndef ARE_INCLUDE_CORE_SHADER_MANAGER_H +#define ARE_INCLUDE_CORE_SHADER_MANAGER_H + +#include "basic/types.h" +#include "resource/shader.h" +#include +#include + +namespace are { + +/// @brief Shader manager for loading and caching shaders +class ShaderManager { +public: + /// @brief Constructor + ShaderManager(); + + /// @brief Destructor + ~ShaderManager(); + + /// @brief Initialize shader manager and load built-in shaders + /// @return True if initialization succeeded + bool initialize(); + + /// @brief Release all shaders + void release(); + + /// @brief Load shader from files + /// @param name Shader name for caching + /// @param vertex_path Vertex shader file path + /// @param fragment_path Fragment shader file path + /// @return Shader object + Shader load_shader(const std::string& name, + const std::string& vertex_path, + const std::string& fragment_path); + + /// @brief Load compute shader from file + /// @param name Shader name for caching + /// @param compute_path Compute shader file path + /// @return Shader object + Shader load_compute_shader(const std::string& name, + const std::string& compute_path); + + /// @brief Get cached shader by name + /// @param name Shader name + /// @return Shader object (invalid if not found) + Shader get_shader(const std::string& name) const; + + /// @brief Get G-Buffer shader + /// @return G-Buffer shader + const Shader& get_gbuffer_shader() const { return gbuffer_shader_; } + + /// @brief Get ray tracing compute shader + /// @return Ray tracing shader + const Shader& get_raytracing_shader() const { return raytracing_shader_; } + +private: + std::unordered_map shader_cache_; + Shader gbuffer_shader_; + Shader raytracing_shader_; + + bool initialized_; + + /// @brief Load built-in shaders + /// @return True if loading succeeded + bool load_builtin_shaders_(); +}; + +} // namespace are + +#endif // ARE_INCLUDE_CORE_SHADER_MANAGER_H diff --git a/include/extended_folders.list b/include/extended_folders.list new file mode 100644 index 0000000..6a611df --- /dev/null +++ b/include/extended_folders.list @@ -0,0 +1,4 @@ +core +scene +resource +utils diff --git a/include/resource/buffer.h b/include/resource/buffer.h new file mode 100644 index 0000000..d0ea6da --- /dev/null +++ b/include/resource/buffer.h @@ -0,0 +1,84 @@ +#ifndef ARE_INCLUDE_RESOURCE_BUFFER_H +#define ARE_INCLUDE_RESOURCE_BUFFER_H + +#include "basic/types.h" + +namespace are { + +/// @brief Buffer usage hint +enum class BufferUsage { + STATIC_DRAW, + DYNAMIC_DRAW, + STREAM_DRAW +}; + +/// @brief Buffer type +enum class BufferType { + VERTEX_BUFFER, + INDEX_BUFFER, + UNIFORM_BUFFER, + SHADER_STORAGE_BUFFER +}; + +/// @brief GPU buffer resource +class Buffer { +public: + /// @brief Constructor + Buffer(); + + /// @brief Destructor + ~Buffer(); + + /// @brief Create buffer + /// @param type Buffer type + /// @param size Buffer size in bytes + /// @param data Initial data (nullptr for empty buffer) + /// @param usage Usage hint + /// @return True if creation succeeded + bool create(BufferType type, size_t size, const void* data, BufferUsage usage); + + /// @brief Update buffer data + /// @param offset Offset in bytes + /// @param size Size in bytes + /// @param data Data to upload + void update(size_t offset, size_t size, const void* data); + + /// @brief Bind buffer + void bind() const; + + /// @brief Bind buffer to binding point (for UBO/SSBO) + /// @param binding_point Binding point index + void bind_base(uint binding_point) const; + + /// @brief Unbind buffer + void unbind() const; + + /// @brief Release buffer resources + void release(); + + /// @brief Get buffer handle + /// @return Buffer handle + BufferHandle get_handle() const { return handle_; } + + /// @brief Get buffer size + /// @return Size in bytes + size_t get_size() const { return size_; } + + /// @brief Get buffer type + /// @return Buffer type + BufferType get_type() const { return type_; } + + /// @brief Check if buffer is valid + /// @return True if valid + bool is_valid() const { return handle_ != INVALID_HANDLE; } + +private: + BufferHandle handle_; + BufferType type_; + size_t size_; + BufferUsage usage_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_RESOURCE_BUFFER_H diff --git a/include/resource/model_loader.h b/include/resource/model_loader.h new file mode 100644 index 0000000..27e6994 --- /dev/null +++ b/include/resource/model_loader.h @@ -0,0 +1,58 @@ +#ifndef ARE_INCLUDE_RESOURCE_MODEL_LOADER_H +#define ARE_INCLUDE_RESOURCE_MODEL_LOADER_H + +#include "basic/types.h" +#include "scene/mesh.h" +#include "scene/material.h" +#include +#include +#include + +namespace are { + +/// @brief Model loader using Assimp +class ModelLoader { +public: + /// @brief Load model from file + /// @param path Model file path + /// @param meshes Output mesh list + /// @param materials Output material list + /// @param flip_uvs Flip UV coordinates vertically + /// @return True if loading succeeded + static bool load(const std::string& path, + std::vector>& meshes, + std::vector>& materials, + bool flip_uvs = true); + + /// @brief Load model and automatically upload to GPU + /// @param path Model file path + /// @param meshes Output mesh list + /// @param materials Output material list + /// @param flip_uvs Flip UV coordinates vertically + /// @return True if loading succeeded + static bool load_and_upload(const std::string& path, + std::vector>& meshes, + std::vector>& materials, + bool flip_uvs = true); + +private: + /// @brief Process Assimp node recursively + static void process_node_(void* node, void* scene, + std::vector>& meshes, + std::vector>& materials, + const std::string& directory); + + /// @brief Process Assimp mesh + static std::shared_ptr process_mesh_(void* mesh, void* scene, + std::vector>& materials, + const std::string& directory); + + /// @brief Load material textures + static void load_material_textures_(void* material, int type, + std::shared_ptr& mat, + const std::string& directory); +}; + +} // namespace are + +#endif // ARE_INCLUDE_RESOURCE_MODEL_LOADER_H diff --git a/include/resource/shader.h b/include/resource/shader.h new file mode 100644 index 0000000..89e8175 --- /dev/null +++ b/include/resource/shader.h @@ -0,0 +1,129 @@ +#ifndef ARE_INCLUDE_RESOURCE_SHADER_H +#define ARE_INCLUDE_RESOURCE_SHADER_H + +#include "basic/types.h" +#include +#include + +namespace are { + +/// @brief Shader program resource +class Shader { +public: + /// @brief Constructor + Shader(); + + /// @brief Destructor + ~Shader(); + + /// @brief Load and compile shader from files + /// @param vertex_path Vertex shader path + /// @param fragment_path Fragment shader path + /// @return True if compilation succeeded + bool load(const std::string& vertex_path, const std::string& fragment_path); + + /// @brief Load and compile compute shader + /// @param compute_path Compute shader path + /// @return True if compilation succeeded + bool load_compute(const std::string& compute_path); + + /// @brief Compile shader from source strings + /// @param vertex_source Vertex shader source + /// @param fragment_source Fragment shader source + /// @return True if compilation succeeded + bool compile(const std::string& vertex_source, const std::string& fragment_source); + + /// @brief Compile compute shader from source + /// @param compute_source Compute shader source + /// @return True if compilation succeeded + bool compile_compute(const std::string& compute_source); + + /// @brief Use/activate shader program + void use() const; // 改为const + + /// @brief Release shader resources + void release(); + + /// @brief Set uniform boolean + /// @param name Uniform name + /// @param value Value + void set_bool(const std::string& name, bool value) const; // 新增,const + + /// @brief Set uniform integer + /// @param name Uniform name + /// @param value Value + void set_int(const std::string& name, int value) const; // 改为const + + /// @brief Set uniform unsigned integer + /// @param name Uniform name + /// @param value Value + void set_uint(const std::string& name, uint value) const; // 改为const + + /// @brief Set uniform float + /// @param name Uniform name + /// @param value Value + void set_float(const std::string& name, float value) const; // 改为const + + /// @brief Set uniform vec2 + /// @param name Uniform name + /// @param value Value + void set_vec2(const std::string& name, const Vec2& value) const; // 改为const + + /// @brief Set uniform vec3 + /// @param name Uniform name + /// @param value Value + void set_vec3(const std::string& name, const Vec3& value) const; // 改为const + + /// @brief Set uniform vec4 + /// @param name Uniform name + /// @param value Value + void set_vec4(const std::string& name, const Vec4& value) const; // 改为const + + /// @brief Set uniform mat3 + /// @param name Uniform name + /// @param value Value + void set_mat3(const std::string& name, const Mat3& value) const; // 改为const + + /// @brief Set uniform mat4 + /// @param name Uniform name + /// @param value Value + void set_mat4(const std::string& name, const Mat4& value) const; // 改为const + + /// @brief Get shader program handle + /// @return Shader handle + ShaderHandle get_handle() const { return handle_; } + + /// @brief Check if shader is valid + /// @return True if valid + bool is_valid() const { return handle_ != INVALID_HANDLE; } + +private: + ShaderHandle handle_; + mutable std::unordered_map uniform_cache_; // 改为mutable + + /// @brief Get uniform location (with caching) + /// @param name Uniform name + /// @return Uniform location + int get_uniform_location_(const std::string& name) const; // 改为const + + /// @brief Compile shader stage + /// @param source Shader source code + /// @param type Shader type (GL_VERTEX_SHADER, etc.) + /// @return Shader object handle (0 on failure) + uint compile_shader_(const std::string& source, uint type); + + /// @brief Link shader program + /// @param shaders Array of shader object handles + /// @param count Number of shaders + /// @return True if linking succeeded + bool link_program_(const uint* shaders, uint count); + + /// @brief Read file content + /// @param path File path + /// @return File content + std::string read_file_(const std::string& path); +}; + +} // namespace are + +#endif // ARE_INCLUDE_RESOURCE_SHADER_H diff --git a/include/resource/texture.h b/include/resource/texture.h new file mode 100644 index 0000000..e195ef8 --- /dev/null +++ b/include/resource/texture.h @@ -0,0 +1,127 @@ +#ifndef ARE_INCLUDE_RESOURCE_TEXTURE_H +#define ARE_INCLUDE_RESOURCE_TEXTURE_H + +#include "basic/types.h" +#include + +namespace are { + +/// @brief Texture format enumeration +enum class TextureFormat { + R8, + RG8, + RGB8, + RGBA8, + R16F, + RG16F, + RGB16F, + RGBA16F, + R32F, + RG32F, + RGB32F, + RGBA32F, + DEPTH24_STENCIL8 +}; + +/// @brief Texture filter mode +enum class TextureFilter { + NEAREST, + LINEAR, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR +}; + +/// @brief Texture wrap mode +enum class TextureWrap { + REPEAT, + MIRRORED_REPEAT, + CLAMP_TO_EDGE, + CLAMP_TO_BORDER +}; + +/// @brief Texture resource +class Texture { +public: + /// @brief Constructor + Texture(); + + /// @brief Destructor + ~Texture(); + + /// @brief Load texture from file + /// @param path File path + /// @param generate_mipmaps Generate mipmaps + /// @return True if loading succeeded + bool load_from_file(const std::string& path, bool generate_mipmaps = true); + + /// @brief Create empty texture + /// @param width Texture width + /// @param height Texture height + /// @param format Texture format + /// @return True if creation succeeded + bool create(uint width, uint height, TextureFormat format); + + /// @brief Upload data to texture + /// @param data Pixel data + /// @param width Data width + /// @param height Data height + /// @param format Data format + /// @return True if upload succeeded + bool upload(const void* data, uint width, uint height, TextureFormat format); + + /// @brief Set texture filter mode + /// @param min_filter Minification filter + /// @param mag_filter Magnification filter + void set_filter(TextureFilter min_filter, TextureFilter mag_filter); + + /// @brief Set texture wrap mode + /// @param wrap_s Wrap mode for S coordinate + /// @param wrap_t Wrap mode for T coordinate + void set_wrap(TextureWrap wrap_s, TextureWrap wrap_t); + + /// @brief Generate mipmaps + void generate_mipmaps(); + + /// @brief Bind texture to texture unit + /// @param unit Texture unit + void bind(uint unit) const; + + /// @brief Unbind texture + void unbind() const; + + /// @brief Release texture resources + void release(); + + /// @brief Get texture handle + /// @return Texture handle + TextureHandle get_handle() const { return handle_; } + + /// @brief Get texture width + /// @return Width + uint get_width() const { return width_; } + + /// @brief Get texture height + /// @return Height + uint get_height() const { return height_; } + + /// @brief Get texture format + /// @return Format + TextureFormat get_format() const { return format_; } + + /// @brief Check if texture is valid + /// @return True if valid + bool is_valid() const { return handle_ != INVALID_HANDLE; } + +private: + TextureHandle handle_; + uint width_; + uint height_; + TextureFormat format_; + bool has_mipmaps_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_RESOURCE_TEXTURE_H diff --git a/include/scene/camera.h b/include/scene/camera.h new file mode 100644 index 0000000..003f120 --- /dev/null +++ b/include/scene/camera.h @@ -0,0 +1,105 @@ +#ifndef ARE_INCLUDE_SCENE_CAMERA_H +#define ARE_INCLUDE_SCENE_CAMERA_H + +#include "basic/types.h" + +namespace are { + +/// @brief Camera projection type +enum class ProjectionType { + PERSPECTIVE, + ORTHOGRAPHIC +}; + +/// @brief Camera for rendering +class Camera { +public: + /// @brief Constructor + Camera(); + + /// @brief Destructor + ~Camera(); + + /// @brief Set perspective projection + /// @param fov Field of view in degrees + /// @param aspect Aspect ratio + /// @param near Near plane + /// @param far Far plane + void set_perspective(float fov, float aspect, float near, float far); + + /// @brief Set orthographic projection + /// @param left Left plane + /// @param right Right plane + /// @param bottom Bottom plane + /// @param top Top plane + /// @param near Near plane + /// @param far Far plane + void set_orthographic(float left, float right, float bottom, float top, float near, float far); + + /// @brief Set camera position + /// @param position Position + void set_position(const Vec3& position); + + /// @brief Set camera target + /// @param target Target position + void set_target(const Vec3& target); + + /// @brief Set camera up vector + /// @param up Up vector + void set_up(const Vec3& up); + + /// @brief Get view matrix + /// @return View matrix + Mat4 get_view_matrix() const; + + /// @brief Get projection matrix + /// @return Projection matrix + Mat4 get_projection_matrix() const; + + /// @brief Get view-projection matrix + /// @return View-projection matrix + Mat4 get_view_projection_matrix() const; + + /// @brief Get camera position + /// @return Position + const Vec3& get_position() const { return position_; } + + /// @brief Get camera forward direction + /// @return Forward direction + Vec3 get_forward() const; + + /// @brief Get camera right direction + /// @return Right direction + Vec3 get_right() const; + + /// @brief Get camera up direction + /// @return Up direction + Vec3 get_up() const; + +private: + Vec3 position_; + Vec3 target_; + Vec3 up_; + + ProjectionType projection_type_; + + // Perspective parameters + float fov_; + float aspect_; + + // Orthographic parameters + float left_, right_, bottom_, top_; + + // Common parameters + float near_; + float far_; + + mutable Mat4 view_matrix_; + mutable Mat4 projection_matrix_; + mutable bool view_dirty_; + mutable bool projection_dirty_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_SCENE_CAMERA_H diff --git a/include/scene/light.h b/include/scene/light.h new file mode 100644 index 0000000..30a6519 --- /dev/null +++ b/include/scene/light.h @@ -0,0 +1,114 @@ +#ifndef ARE_INCLUDE_SCENE_LIGHT_H +#define ARE_INCLUDE_SCENE_LIGHT_H + +#include "basic/types.h" + +namespace are { + +/// @brief Light type enumeration +enum class LightType { + DIRECTIONAL = 0, + POINT = 1, + SPOT = 2 +}; + +/// @brief Light source +class Light { +public: + /// @brief Constructor + Light(); + + /// @brief Destructor + ~Light(); + + /// @brief Set light type + /// @param type Light type + void set_type(LightType type); + + /// @brief Set light position (for point and spot lights) + /// @param position Light position + void set_position(const Vec3 &position); + + /// @brief Set light direction (for directional and spot lights) + /// @param direction Light direction + void set_direction(const Vec3 &direction); + + /// @brief Set light color + /// @param color Light color + void set_color(const Vec3 &color); + + /// @brief Set light intensity + /// @param intensity Light intensity + void set_intensity(float intensity); + + /// @brief Set light range (for point and spot lights) + /// @param range Light range + void set_range(float range); + + /// @brief Set spot light angles + /// @param inner_angle Inner cone angle in degrees + /// @param outer_angle Outer cone angle in degrees + void set_spot_angles(float inner_angle, float outer_angle); + + /// @brief Get light type + /// @return Light type + LightType get_type() const { + return type_; + } + + /// @brief Get light position + /// @return Light position + const Vec3 &get_position() const { + return position_; + } + + /// @brief Get light direction + /// @return Light direction + const Vec3 &get_direction() const { + return direction_; + } + + /// @brief Get light color + /// @return Light color + const Vec3 &get_color() const { + return color_; + } + + /// @brief Get light intensity + /// @return Light intensity + float get_intensity() const { + return intensity_; + } + + /// @brief Get light range + /// @return Light range + float get_range() const { + return range_; + } + + /// @brief Get spot light inner angle + /// @return Inner angle in radians + float get_inner_angle() const { + return inner_angle_; + } + + /// @brief Get spot light outer angle + /// @return Outer angle in radians + float get_outer_angle() const { + return outer_angle_; + } + +private: + LightType type_; + Vec3 position_; + Vec3 direction_; + Vec3 color_; + float intensity_; + float range_; + float inner_angle_; + float outer_angle_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_SCENE_LIGHT_H diff --git a/include/scene/material.h b/include/scene/material.h new file mode 100644 index 0000000..3fc933e --- /dev/null +++ b/include/scene/material.h @@ -0,0 +1,105 @@ +#ifndef ARE_INCLUDE_SCENE_MATERIAL_H +#define ARE_INCLUDE_SCENE_MATERIAL_H + +#include "basic/types.h" +#include "resource/texture.h" +#include + +namespace are { + +/// @brief Material type enumeration +enum class MaterialType { + DIFFUSE = 0, + METAL = 1, + DIELECTRIC = 2, + EMISSIVE = 3 +}; + +/// @brief Material properties +class Material { +public: + /// @brief Constructor + Material(); + + /// @brief Destructor + ~Material(); + + /// @brief Set albedo color + /// @param albedo Albedo color + void set_albedo(const Vec3& albedo); + + /// @brief Set emission color + /// @param emission Emission color + void set_emission(const Vec3& emission); + + /// @brief Set metallic value + /// @param metallic Metallic (0-1) + void set_metallic(float metallic); + + /// @brief Set roughness value + /// @param roughness Roughness (0-1) + void set_roughness(float roughness); + + /// @brief Set index of refraction + /// @param ior Index of refraction + void set_ior(float ior); + + /// @brief Set material type + /// @param type Material type + void set_type(MaterialType type); + + /// @brief Set albedo texture + /// @param texture Albedo texture + void set_albedo_texture(std::shared_ptr texture); + + /// @brief Set normal map + /// @param texture Normal map texture + void set_normal_texture(std::shared_ptr texture); + + /// @brief Get albedo color + /// @return Albedo color + const Vec3& get_albedo() const { return albedo_; } + + /// @brief Get emission color + /// @return Emission color + const Vec3& get_emission() const { return emission_; } + + /// @brief Get metallic value + /// @return Metallic + float get_metallic() const { return metallic_; } + + /// @brief Get roughness value + /// @return Roughness + float get_roughness() const { return roughness_; } + + /// @brief Get index of refraction + /// @return IOR + float get_ior() const { return ior_; } + + /// @brief Get material type + /// @return Material type + MaterialType get_type() const { return type_; } + + /// @brief Get albedo texture + /// @return Albedo texture (nullptr if none) + std::shared_ptr get_albedo_texture() const { return albedo_texture_; } + + /// @brief Get normal texture + /// @return Normal texture (nullptr if none) + std::shared_ptr get_normal_texture() const { return normal_texture_; } + +private: + Vec3 albedo_; + Vec3 emission_; + float metallic_; + float roughness_; + float ior_; + MaterialType type_; + + std::shared_ptr albedo_texture_; + std::shared_ptr normal_texture_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_SCENE_MATERIAL_H diff --git a/include/scene/mesh.h b/include/scene/mesh.h new file mode 100644 index 0000000..27acdc5 --- /dev/null +++ b/include/scene/mesh.h @@ -0,0 +1,79 @@ +#ifndef ARE_INCLUDE_SCENE_MESH_H +#define ARE_INCLUDE_SCENE_MESH_H + +#include "basic/types.h" +#include + +namespace are { + +/// @brief Mesh data container +class Mesh { +public: + /// @brief Constructor + Mesh(); + + /// @brief Destructor + ~Mesh(); + + /// @brief Set vertex data + /// @param vertices Vertex array + void set_vertices(const std::vector& vertices); + + /// @brief Set index data + /// @param indices Index array + void set_indices(const std::vector& indices); + + /// @brief Set material index + /// @param material_id Material index + void set_material(uint material_id); + + /// @brief Set transform matrix + /// @param transform Transform matrix + void set_transform(const Mat4& transform); + + /// @brief Get vertices + /// @return Vertex array + const std::vector& get_vertices() const { return vertices_; } + + /// @brief Get indices + /// @return Index array + const std::vector& get_indices() const { return indices_; } + + /// @brief Get material index + /// @return Material index + uint get_material() const { return material_id_; } + + /// @brief Get transform matrix + /// @return Transform matrix + const Mat4& get_transform() const { return transform_; } + + /// @brief Upload mesh data to GPU + /// @return True if upload succeeded + bool upload_to_gpu(); + + /// @brief Release GPU resources + void release_gpu_resources(); + + /// @brief Get VAO handle + /// @return VAO handle + uint get_vao() const { return vao_; } + + /// @brief Check if mesh is uploaded to GPU + /// @return True if uploaded + bool is_uploaded() const { return uploaded_; } + +private: + std::vector vertices_; + std::vector indices_; + uint material_id_; + Mat4 transform_; + + uint vao_; + uint vbo_; + uint ebo_; + bool uploaded_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_SCENE_MESH_H diff --git a/include/scene/scene.h b/include/scene/scene.h new file mode 100644 index 0000000..f5d34c2 --- /dev/null +++ b/include/scene/scene.h @@ -0,0 +1,74 @@ +#ifndef ARE_INCLUDE_SCENE_SCENE_H +#define ARE_INCLUDE_SCENE_SCENE_H + +#include "basic/types.h" +#include "scene/camera.h" +#include "scene/mesh.h" +#include "scene/material.h" +#include "scene/light.h" +#include +#include + +namespace are { + +/// @brief Scene container holding all scene objects +class Scene { +public: + /// @brief Constructor + Scene(); + + /// @brief Destructor + ~Scene(); + + /// @brief Add mesh to scene + /// @param mesh Mesh to add + /// @return Mesh index + uint add_mesh(std::shared_ptr mesh); + + /// @brief Add material to scene + /// @param material Material to add + /// @return Material index + uint add_material(std::shared_ptr material); + + /// @brief Add light to scene + /// @param light Light to add + /// @return Light index + uint add_light(std::shared_ptr light); + + /// @brief Set active camera + /// @param camera Camera to set + void set_camera(std::shared_ptr camera); + + /// @brief Get active camera + /// @return Active camera + const Camera& get_camera() const { return *camera_; } + + /// @brief Get all meshes + /// @return Mesh list + const std::vector>& get_meshes() const { return meshes_; } + + /// @brief Get all materials + /// @return Material list + const std::vector>& get_materials() const { return materials_; } + + /// @brief Get all lights + /// @return Light list + const std::vector>& get_lights() const { return lights_; } + + /// @brief Clear all scene objects + void clear(); + + /// @brief Update scene (animations, transforms, etc.) + /// @param delta_time Time since last update + void update(float delta_time); + +private: + std::shared_ptr camera_; + std::vector> meshes_; + std::vector> materials_; + std::vector> lights_; +}; + +} // namespace are + +#endif // ARE_INCLUDE_SCENE_SCENE_H diff --git a/include/utils/config.h b/include/utils/config.h new file mode 100644 index 0000000..f9a7790 --- /dev/null +++ b/include/utils/config.h @@ -0,0 +1,70 @@ +#ifndef ARE_INCLUDE_UTILS_CONFIG_H +#define ARE_INCLUDE_UTILS_CONFIG_H + +#include +#include + +namespace are { + +/// @brief Configuration manager for loading engine settings +/// @note This module should be implemented by the user +class Config { +public: + /// @brief Load configuration from file + /// @param path Configuration file path + /// @return True if loading succeeded + static bool load(const std::string& path); + + /// @brief Save configuration to file + /// @param path Configuration file path + /// @return True if saving succeeded + static bool save(const std::string& path); + + /// @brief Get string value + /// @param key Configuration key + /// @param default_value Default value if key not found + /// @return Configuration value + static std::string get_string(const std::string& key, const std::string& default_value = ""); + + /// @brief Get integer value + /// @param key Configuration key + /// @param default_value Default value if key not found + /// @return Configuration value + static int get_int(const std::string& key, int default_value = 0); + + /// @brief Get float value + /// @param key Configuration key + /// @param default_value Default value if key not found + /// @return Configuration value + static float get_float(const std::string& key, float default_value = 0.0f); + + /// @brief Get boolean value + /// @param key Configuration key + /// @param default_value Default value if key not found + /// @return Configuration value + static bool get_bool(const std::string& key, bool default_value = false); + + /// @brief Set string value + /// @param key Configuration key + /// @param value Configuration value + static void set_string(const std::string& key, const std::string& value); + + /// @brief Set integer value + /// @param key Configuration key + /// @param value Configuration value + static void set_int(const std::string& key, int value); + + /// @brief Set float value + /// @param key Configuration key + /// @param value Configuration value + static void set_float(const std::string& key, float value); + + /// @brief Set boolean value + /// @param key Configuration key + /// @param value Configuration value + static void set_bool(const std::string& key, bool value); +}; + +} // namespace are + +#endif // ARE_INCLUDE_UTILS_CONFIG_H diff --git a/include/utils/logger.h b/include/utils/logger.h new file mode 100644 index 0000000..1d8123f --- /dev/null +++ b/include/utils/logger.h @@ -0,0 +1,61 @@ +#ifndef ARE_INCLUDE_UTILS_LOGGER_H +#define ARE_INCLUDE_UTILS_LOGGER_H + +#include + +namespace are { + +/// @brief Log level enumeration +enum class LogLevel { + DEBUG, + INFO, + WARNING, + ERROR, + FATAL +}; + +/// @brief Logger interface for engine logging +/// @note This module should be implemented by the user +class Logger { +public: + /// @brief Initialize logger + /// @param log_file Log file path (empty for console only) + /// @return True if initialization succeeded + static bool initialize(const std::string& log_file = ""); + + /// @brief Shutdown logger + static void shutdown(); + + /// @brief Log message + /// @param level Log level + /// @param message Message content + static void log(LogLevel level, const std::string& message); + + /// @brief Log debug message + /// @param message Message content + static void debug(const std::string& message); + + /// @brief Log info message + /// @param message Message content + static void info(const std::string& message); + + /// @brief Log warning message + /// @param message Message content + static void warning(const std::string& message); + + /// @brief Log error message + /// @param message Message content + static void error(const std::string& message); + + /// @brief Log fatal message + /// @param message Message content + static void fatal(const std::string& message); + + /// @brief Set minimum log level + /// @param level Minimum level to log + static void set_level(LogLevel level); +}; + +} // namespace are + +#endif // ARE_INCLUDE_UTILS_LOGGER_H diff --git a/scripts/check_env.sh b/scripts/check_env.sh new file mode 100644 index 0000000..05f9201 --- /dev/null +++ b/scripts/check_env.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +echo "==========================================" +echo "Aurora Rendering Engine - System Check" +echo "==========================================" +echo "" + +# Check display server +echo "1. Display Server:" +if [ -n "$DISPLAY" ]; then + echo " ✓ X11 detected: $DISPLAY" +elif [ -n "$WAYLAND_DISPLAY" ]; then + echo " ✓ Wayland detected: $WAYLAND_DISPLAY" +else + echo " ✗ No display server detected!" + echo " Please run this in a graphical environment" +fi +echo "" + +# Check OpenGL +echo "2. OpenGL Information:" +if command -v glxinfo &> /dev/null; then + GL_VERSION=$(glxinfo | grep "OpenGL version" | cut -d: -f2) + GL_RENDERER=$(glxinfo | grep "OpenGL renderer" | cut -d: -f2) + GL_VENDOR=$(glxinfo | grep "OpenGL vendor" | cut -d: -f2) + + echo " Vendor: $GL_VENDOR" + echo " Renderer:$GL_RENDERER" + echo " Version: $GL_VERSION" + + # Check version + MAJOR=$(echo $GL_VERSION | grep -oP '\d+' | head -1) + MINOR=$(echo $GL_VERSION | grep -oP '\d+' | head -2 | tail -1) + + if [ "$MAJOR" -ge 4 ] && [ "$MINOR" -ge 5 ]; then + echo " ✓ OpenGL 4.5+ supported" + else + echo " ✗ OpenGL 4.5+ NOT supported (found $MAJOR.$MINOR)" + echo " This engine requires OpenGL 4.5 or higher" + fi +else + echo " ✗ glxinfo not found (install mesa-utils)" +fi +echo "" + +# Check GLFW +echo "3. GLFW Library:" +if ldconfig -p | grep -q libglfw; then + echo " ✓ GLFW library found" +else + echo " ✗ GLFW library not found" + echo " Install: sudo apt-get install libglfw3-dev" +fi +echo "" + +# Check GLM +echo "4. GLM Library:" +if [ -d "/usr/include/glm" ] || [ -d "/usr/local/include/glm" ]; then + echo " ✓ GLM headers found" +else + echo " ✗ GLM headers not found" + echo " Install: sudo apt-get install libglm-dev" +fi +echo "" + +# Check GPU +echo "5. GPU Information:" +if command -v lspci &> /dev/null; then + GPU=$(lspci | grep -i vga | cut -d: -f3) + echo " GPU:$GPU" +else + echo " ✗ lspci not found" +fi +echo "" + +# Check drivers +echo "6. Graphics Drivers:" +if lsmod | grep -q nvidia; then + echo " ✓ NVIDIA driver loaded" + if command -v nvidia-smi &> /dev/null; then + DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader) + echo " Driver version: $DRIVER_VERSION" + fi +elif lsmod | grep -q amdgpu; then + echo " ✓ AMD driver (amdgpu) loaded" +elif lsmod | grep -q i915; then + echo " ✓ Intel driver (i915) loaded" +else + echo " ? Unknown driver" +fi +echo "" + +echo "==========================================" +echo "System check complete" +echo "==========================================" diff --git a/shaders/gbuffer.frag b/shaders/gbuffer.frag new file mode 100644 index 0000000..61a7c4a --- /dev/null +++ b/shaders/gbuffer.frag @@ -0,0 +1,49 @@ +#version 430 core + +// Material types +const uint MATERIAL_DIFFUSE = 0u; +const uint MATERIAL_METAL = 1u; +const uint MATERIAL_DIELECTRIC = 2u; +const uint MATERIAL_EMISSIVE = 3u; + +in VS_OUT { + vec3 frag_pos; + vec3 normal; + vec2 texcoord; + vec3 tangent; +} fs_in; + +layout(location = 0) out vec4 g_position; +layout(location = 1) out vec4 g_normal; +layout(location = 2) out vec4 g_albedo; +layout(location = 3) out vec4 g_material; + +// Material uniforms +uniform vec3 u_albedo; +uniform float u_metallic; +uniform float u_roughness; +uniform float u_ior; +uniform vec3 u_emission; +uniform uint u_material_type; + +uniform bool u_has_albedo_map; +uniform sampler2D u_albedo_map; + +void main() { + // Position + g_position = vec4(fs_in.frag_pos, 1.0); + + // Normal + vec3 normal = normalize(fs_in.normal); + g_normal = vec4(normal, 0.0); + + // Albedo + vec3 albedo = u_albedo; + if (u_has_albedo_map) { + albedo *= texture(u_albedo_map, fs_in.texcoord).rgb; + } + g_albedo = vec4(albedo, 1.0); + + // Material properties + g_material = vec4(u_metallic, u_roughness, u_ior, float(u_material_type)); +} diff --git a/shaders/gbuffer.vert b/shaders/gbuffer.vert new file mode 100644 index 0000000..0556816 --- /dev/null +++ b/shaders/gbuffer.vert @@ -0,0 +1,27 @@ +#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; + +out VS_OUT { + vec3 frag_pos; + vec3 normal; + vec2 texcoord; + vec3 tangent; +} vs_out; + +uniform mat4 u_model; +uniform mat4 u_view; +uniform mat4 u_projection; + +void main() { + vec4 world_pos = u_model * vec4(a_position, 1.0); + vs_out.frag_pos = world_pos.xyz; + vs_out.normal = mat3(transpose(inverse(u_model))) * a_normal; + vs_out.texcoord = a_texcoord; + vs_out.tangent = mat3(u_model) * a_tangent; + + gl_Position = u_projection * u_view * world_pos; +} diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp new file mode 100644 index 0000000..28fb002 --- /dev/null +++ b/shaders/raytracing.comp @@ -0,0 +1,503 @@ +#version 430 core + +// Constants +#define PI 3.14159265359 +#define INV_PI 0.31830988618 +#define EPSILON 1e-4 +#define MAX_FLOAT 3.402823466e38 +#define MAX_RAY_DEPTH 8 +#define MAX_LIGHTS 16 + +// Material types +#define MATERIAL_DIFFUSE 0 +#define MATERIAL_METAL 1 +#define MATERIAL_DIELECTRIC 2 +#define MATERIAL_EMISSIVE 3 + +// Light types +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 +#define LIGHT_SPOT 2 + +// Structures +struct Material { + vec3 albedo; + float metallic; + vec3 emission; + float roughness; + int type; + float ior; + vec2 padding; +}; + +struct Light { + vec3 position; + int type; + vec3 direction; + float intensity; + vec3 color; + float range; + vec2 spot_angles; // inner, outer + vec2 padding; +}; + +struct Ray { + vec3 origin; + vec3 direction; +}; + +struct HitInfo { + bool hit; + float t; + vec3 position; + vec3 normal; + vec2 texcoord; + uint material_id; +}; + +// Utility functions +float saturate(float x) { + return clamp(x, 0.0, 1.0); +} + +vec3 saturate(vec3 x) { + return clamp(x, vec3(0.0), vec3(1.0)); +} + +// Random number generation (PCG Hash) +uint pcg_hash(uint seed) { + uint state = seed * 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +float random_float(inout uint seed) { + seed = pcg_hash(seed); + return float(seed) / 4294967296.0; +} + +vec2 random_vec2(inout uint seed) { + return vec2(random_float(seed), random_float(seed)); +} + +vec3 random_vec3(inout uint seed) { + return vec3(random_float(seed), random_float(seed), random_float(seed)); +} + +// Random direction in hemisphere +vec3 random_hemisphere_direction(vec3 normal, inout uint seed) { + float z = random_float(seed); + float r = sqrt(max(0.0, 1.0 - z * z)); + float phi = 2.0 * PI * random_float(seed); + + vec3 dir = vec3(r * cos(phi), r * sin(phi), z); + + // Create coordinate system + vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, normal)); + vec3 bitangent = cross(normal, tangent); + + return normalize(tangent * dir.x + bitangent * dir.y + normal * dir.z); +} + +// Cosine-weighted hemisphere sampling +// vec3 cosine_weighted_hemisphere(vec3 normal, inout uint seed) { +// vec2 r = random_vec2(seed); +// float r1 = 2.0 * PI * r.x; +// float r2 = r.y; +// float r2s = sqrt(r2); +// +// vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); +// vec3 tangent = normalize(cross(up, normal)); +// vec3 bitangent = cross(normal, tangent); +// +// vec3 dir = tangent * cos(r1) * r2s + bitangent * sin(r1) * r2s + normal * sqrt(1.0 - r2); +// return normalize(dir); +// } + +// Schlick's approximation for Fresnel +vec3 fresnel_schlick(float cos_theta, vec3 f0) { + return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); +} + +// GGX distribution +float distribution_ggx(vec3 N, vec3 H, float roughness) { + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + + float nom = a2; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return nom / max(denom, EPSILON); +} + +// Smith's geometry function +float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float r = roughness + 1.0; + float k = (r * r) / 8.0; + + float ggx1 = NdotV / (NdotV * (1.0 - k) + k); + float ggx2 = NdotL / (NdotL * (1.0 - k) + k); + + return ggx1 * ggx2; +} + +layout(local_size_x = 16, local_size_y = 16) in; + +// G-Buffer inputs +layout(binding = 0, rgba32f) uniform readonly image2D g_position; +layout(binding = 1, rgba32f) uniform readonly image2D g_normal; +layout(binding = 2, rgba8) uniform readonly image2D g_albedo; + +// Output +layout(binding = 3, rgba32f) uniform image2D output_image; +layout(binding = 4, rgba32f) uniform image2D accumulation_image; + +// Scene data +layout(std430, binding = 0) readonly buffer MaterialBuffer { + Material materials[]; +}; + +layout(std430, binding = 1) readonly buffer LightBuffer { + Light lights[]; +}; + +// Uniforms +uniform uint u_frame_count; +uniform uint u_samples_per_pixel; +uniform uint u_max_depth; +uniform uint u_light_count; +uniform vec3 u_camera_position; +uniform mat4 u_inv_view_projection; +uniform bool u_enable_accumulation; +uniform bool u_use_bvh; + +// Evaluate direct lighting +vec3 evaluate_direct_lighting(vec3 position, vec3 normal, vec3 view_dir, + Material material, inout uint seed) { + vec3 direct_light = vec3(0.0); + + for (uint i = 0u; i < u_light_count; i++) { + Light light = lights[i]; + vec3 light_dir; + float light_distance; + float attenuation = 1.0; + + if (light.type == LIGHT_POINT) { + vec3 to_light = light.position - position; + light_distance = length(to_light); + light_dir = to_light / light_distance; + attenuation = 1.0 / max(light_distance * light_distance, 0.01); + + if (light_distance > light.range) continue; + } else if (light.type == LIGHT_DIRECTIONAL) { + light_dir = normalize(-light.direction); + light_distance = MAX_FLOAT; + } else { + continue; + } + + float NdotL = max(dot(normal, light_dir), 0.0); + + if (NdotL > 0.0) { + vec3 H = normalize(view_dir + light_dir); + float NdotV = max(dot(normal, view_dir), 0.0); + + // PBR lighting + vec3 F0 = mix(vec3(0.04), material.albedo, material.metallic); + vec3 F = fresnel_schlick(max(dot(H, view_dir), 0.0), F0); + float D = distribution_ggx(normal, H, max(material.roughness, 0.04)); + float G = geometry_smith(normal, view_dir, light_dir, material.roughness); + + vec3 numerator = D * G * F; + float denominator = 4.0 * NdotV * NdotL + EPSILON; + vec3 specular = numerator / denominator; + + vec3 kS = F; + vec3 kD = (vec3(1.0) - kS) * (1.0 - material.metallic); + + vec3 radiance = light.color * light.intensity * attenuation; + direct_light += (kD * material.albedo * INV_PI + specular) * radiance * NdotL; + } + } + + return direct_light; +} + +void main() { + ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); + ivec2 image_size = imageSize(output_image); + + if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) { + return; + } + + // Read G-Buffer + vec4 position_data = imageLoad(g_position, pixel_coords); + vec4 normal_data = imageLoad(g_normal, pixel_coords); + vec4 albedo_data = imageLoad(g_albedo, pixel_coords); + + // Background + if (position_data.w < 0.5) { + vec3 background = vec3(0.1, 0.1, 0.15); + imageStore(output_image, pixel_coords, vec4(background, 1.0)); + imageStore(accumulation_image, pixel_coords, vec4(background, 1.0)); + return; + } + + vec3 position = position_data.xyz; + vec3 normal = normalize(normal_data.xyz); + vec3 albedo = albedo_data.rgb; + + // 关键修复:从G-Buffer的alpha通道读取material_id + // 注意:albedo_data是从RGBA8纹理读取的,alpha值范围是[0,1] + // 我们需要将其转换回整数ID + uint material_id = uint(albedo_data.a * 255.0 + 0.5); + + // Initialize random seed + uint seed = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x) + u_frame_count * 719393u; + + vec3 color = vec3(0.0); + + // Get material from buffer + Material material; + uint mat_count = uint(materials.length()); + + if (material_id < mat_count) { + // 从SSBO读取材质 + material = materials[material_id]; + } else { + // 使用G-Buffer中的albedo作为fallback + material.albedo = albedo; + material.metallic = 0.0; + material.roughness = 0.5; + material.emission = vec3(0.0); + material.type = MATERIAL_DIFFUSE; + material.ior = 1.5; + } + + // Add emission + color += material.emission; + + // Direct lighting + vec3 view_dir = normalize(u_camera_position - position); + + if (u_light_count > 0u) { + color += evaluate_direct_lighting(position, normal, view_dir, material, seed); + } + + // Ambient lighting + color += material.albedo * 0.05; + + // Clamp + color = clamp(color, vec3(0.0), vec3(10.0)); + + // Accumulation + if (u_enable_accumulation && u_frame_count > 0u) { + vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; + float weight = 1.0 / float(u_frame_count + 1u); + color = mix(accumulated, color, weight); + } + + imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); + imageStore(output_image, pixel_coords, vec4(color, 1.0)); +} + +// layout(local_size_x = 16, local_size_y = 16) in; +// +// // G-Buffer inputs +// layout(binding = 0, rgba32f) uniform readonly image2D g_position; +// layout(binding = 1, rgba32f) uniform readonly image2D g_normal; +// layout(binding = 2, rgba8) uniform readonly image2D g_albedo; +// +// // Output +// layout(binding = 3, rgba32f) uniform image2D output_image; +// layout(binding = 4, rgba32f) uniform image2D accumulation_image; +// +// // Scene data +// layout(std430, binding = 0) readonly buffer MaterialBuffer { +// Material materials[]; +// }; +// +// layout(std430, binding = 1) readonly buffer LightBuffer { +// Light lights[]; +// }; +// +// // Uniforms +// uniform uint u_frame_count; +// uniform uint u_samples_per_pixel; +// uniform uint u_max_depth; +// uniform uint u_light_count; +// uniform vec3 u_camera_position; +// uniform mat4 u_inv_view_projection; +// uniform bool u_enable_accumulation; +// uniform bool u_use_bvh; +// +// // Cosine-weighted hemisphere sampling +// vec3 cosine_weighted_hemisphere(vec3 normal, inout uint seed) { +// vec2 r = random_vec2(seed); +// float r1 = 2.0 * PI * r.x; +// float r2 = r.y; +// float r2s = sqrt(r2); +// +// vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); +// vec3 tangent = normalize(cross(up, normal)); +// vec3 bitangent = cross(normal, tangent); +// +// vec3 dir = tangent * cos(r1) * r2s + bitangent * sin(r1) * r2s + normal * sqrt(1.0 - r2); +// return normalize(dir); +// } +// +// // Evaluate direct lighting +// vec3 evaluate_direct_lighting(vec3 position, vec3 normal, vec3 view_dir, +// Material material, inout uint seed) { +// vec3 direct_light = vec3(0.0); +// +// for (uint i = 0u; i < u_light_count; i++) { +// Light light = lights[i]; +// vec3 light_dir; +// float light_distance; +// float attenuation = 1.0; +// +// if (light.type == LIGHT_POINT) { +// vec3 to_light = light.position - position; +// light_distance = length(to_light); +// light_dir = to_light / light_distance; +// attenuation = 1.0 / max(light_distance * light_distance, 0.01); +// +// if (light_distance > light.range) continue; +// } else if (light.type == LIGHT_DIRECTIONAL) { +// light_dir = normalize(-light.direction); +// light_distance = MAX_FLOAT; +// } else { +// continue; +// } +// +// float NdotL = max(dot(normal, light_dir), 0.0); +// +// if (NdotL > 0.0) { +// vec3 H = normalize(view_dir + light_dir); +// float NdotV = max(dot(normal, view_dir), 0.0); +// +// // PBR lighting +// vec3 F0 = mix(vec3(0.04), material.albedo, material.metallic); +// vec3 F = fresnel_schlick(max(dot(H, view_dir), 0.0), F0); +// float D = distribution_ggx(normal, H, max(material.roughness, 0.04)); +// float G = geometry_smith(normal, view_dir, light_dir, material.roughness); +// +// vec3 numerator = D * G * F; +// float denominator = 4.0 * NdotV * NdotL + EPSILON; +// vec3 specular = numerator / denominator; +// +// vec3 kS = F; +// vec3 kD = (vec3(1.0) - kS) * (1.0 - material.metallic); +// +// vec3 radiance = light.color * light.intensity * attenuation; +// direct_light += (kD * material.albedo * INV_PI + specular) * radiance * NdotL; +// } +// } +// +// return direct_light; +// } +// +// // Trace indirect lighting (simple path tracing) +// vec3 trace_indirect(vec3 position, vec3 normal, Material material, inout uint seed) { +// vec3 color = vec3(0.0); +// +// // Sample random direction +// vec3 ray_dir = cosine_weighted_hemisphere(normal, seed); +// +// // Sky color based on direction +// float t = 0.5 * (ray_dir.y + 1.0); +// vec3 sky_color = mix(vec3(1.0), vec3(0.5, 0.7, 1.0), t) * 0.2; +// +// color = sky_color; +// +// return color; +// } +// +// void main() { +// ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); +// ivec2 image_size = imageSize(output_image); +// +// if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) { +// return; +// } +// +// // Read G-Buffer +// vec4 position_data = imageLoad(g_position, pixel_coords); +// vec4 normal_data = imageLoad(g_normal, pixel_coords); +// vec4 albedo_data = imageLoad(g_albedo, pixel_coords); +// +// // Check if this pixel has valid geometry +// if (position_data.w < 0.5) { +// vec3 background = vec3(0.1, 0.1, 0.15); +// imageStore(output_image, pixel_coords, vec4(background, 1.0)); +// imageStore(accumulation_image, pixel_coords, vec4(background, 1.0)); +// return; +// } +// +// vec3 position = position_data.xyz; +// vec3 normal = normalize(normal_data.xyz); +// vec3 albedo = albedo_data.rgb; +// uint material_id = floatBitsToUint(albedo_data.a); +// +// if (material_id >= 1000u) { +// material_id = 0u; +// } +// +// // Initialize random seed +// uint seed = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x) + u_frame_count * 719393u; +// +// vec3 color = vec3(0.0); +// +// // Get material +// Material material; +// if (material_id < uint(materials.length())) { +// material = materials[material_id]; +// } else { +// material.albedo = albedo; +// material.metallic = 0.0; +// material.roughness = 0.5; +// material.emission = vec3(0.0); +// material.type = MATERIAL_DIFFUSE; +// material.ior = 1.5; +// } +// +// // Add emission +// color += material.emission; +// +// // Direct lighting +// vec3 view_dir = normalize(u_camera_position - position); +// +// if (u_light_count > 0u) { +// color += evaluate_direct_lighting(position, normal, view_dir, material, seed); +// } +// +// // Indirect lighting (path tracing) - THIS ADDS NOISE +// for (uint samp_idx = 0u; samp_idx < u_samples_per_pixel; samp_idx++) { // 修复: sample -> samp_idx +// vec3 indirect = trace_indirect(position, normal, material, seed); +// color += indirect * material.albedo * INV_PI; +// } +// +// // Ambient +// color += material.albedo * 0.02; +// +// // Clamp +// color = clamp(color, vec3(0.0), vec3(100.0)); +// +// // Accumulation for denoising +// if (u_enable_accumulation && u_frame_count > 0u) { +// vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; +// float weight = 1.0 / float(u_frame_count + 1u); +// color = mix(accumulated, color, weight); +// } +// +// imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); +// imageStore(output_image, pixel_coords, vec4(color, 1.0)); +// } diff --git a/shaders/screen_blit.frag b/shaders/screen_blit.frag new file mode 100644 index 0000000..99cb1ac --- /dev/null +++ b/shaders/screen_blit.frag @@ -0,0 +1,11 @@ +#version 430 core + +in vec2 v_texcoord; + +out vec4 frag_color; + +uniform sampler2D u_texture; + +void main() { + frag_color = texture(u_texture, v_texcoord); +} diff --git a/shaders/screen_blit.vert b/shaders/screen_blit.vert new file mode 100644 index 0000000..3b3bc01 --- /dev/null +++ b/shaders/screen_blit.vert @@ -0,0 +1,11 @@ +#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); +} diff --git a/src/basic/math.cpp b/src/basic/math.cpp new file mode 100644 index 0000000..d265c09 --- /dev/null +++ b/src/basic/math.cpp @@ -0,0 +1,33 @@ +#include "basic/math.h" + +namespace are { + +Mat4 MathUtils::perspective(float fov, float aspect, float near, float far) { + return glm::perspective(fov, aspect, near, far); +} + +Mat4 MathUtils::look_at(const Vec3& eye, const Vec3& center, const Vec3& up) { + return glm::lookAt(eye, center, up); +} + +Vec3 MathUtils::normalize(const Vec3& v) { + return glm::normalize(v); +} + +float MathUtils::dot(const Vec3& a, const Vec3& b) { + return glm::dot(a, b); +} + +Vec3 MathUtils::cross(const Vec3& a, const Vec3& b) { + return glm::cross(a, b); +} + +Vec3 MathUtils::reflect(const Vec3& incident, const Vec3& normal) { + return glm::reflect(incident, normal); +} + +const float* MathUtils::value_ptr(const Mat4& mat) { + return glm::value_ptr(mat); +} + +} // namespace are diff --git a/src/core/bvh.cpp b/src/core/bvh.cpp new file mode 100644 index 0000000..d8dfbf6 --- /dev/null +++ b/src/core/bvh.cpp @@ -0,0 +1,297 @@ +#include "core/bvh.h" +#include "utils/logger.h" +#include "basic/constants.h" +#include +#include + +namespace are { + +// AABB implementation +void AABB::expand(const Vec3& point) { + min_ = glm::min(min_, point); + max_ = glm::max(max_, point); +} + +void AABB::expand(const AABB& other) { + min_ = glm::min(min_, other.min_); + max_ = glm::max(max_, other.max_); +} + +float AABB::surface_area() const { + Vec3 extent = max_ - min_; + return 2.0f * (extent.x * extent.y + extent.y * extent.z + extent.z * extent.x); +} + +bool AABB::is_valid() const { + return min_.x <= max_.x && min_.y <= max_.y && min_.z <= max_.z; +} + +// Triangle implementation +AABB Triangle::get_bounds() const { + AABB bounds(v0_, v0_); + bounds.expand(v1_); + bounds.expand(v2_); + return bounds; +} + +Vec3 Triangle::get_centroid() const { + return (v0_ + v1_ + v2_) / 3.0f; +} + +// BVH implementation +BVH::BVH() { +} + +BVH::~BVH() { + clear(); +} + +bool BVH::build(const std::vector>& meshes) { + clear(); + + Logger::info("Building BVH..."); + + // Extract all triangles from meshes + for (const auto& mesh : meshes) { + const auto& vertices = mesh->get_vertices(); + const auto& indices = mesh->get_indices(); + uint material_id = mesh->get_material(); + Mat4 transform = mesh->get_transform(); + + for (size_t i = 0; i < indices.size(); i += 3) { + Triangle tri; + + // Transform vertices + Vec4 v0 = transform * Vec4(vertices[indices[i]].position_, 1.0f); + Vec4 v1 = transform * Vec4(vertices[indices[i + 1]].position_, 1.0f); + Vec4 v2 = transform * Vec4(vertices[indices[i + 2]].position_, 1.0f); + + tri.v0_ = Vec3(v0) / v0.w; + tri.v1_ = Vec3(v1) / v1.w; + tri.v2_ = Vec3(v2) / v2.w; + + // Transform normals + Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(transform))); + tri.n0_ = glm::normalize(normal_matrix * vertices[indices[i]].normal_); + tri.n1_ = glm::normalize(normal_matrix * vertices[indices[i + 1]].normal_); + tri.n2_ = glm::normalize(normal_matrix * vertices[indices[i + 2]].normal_); + + // Copy UVs + tri.uv0_ = vertices[indices[i]].texcoord_; + tri.uv1_ = vertices[indices[i + 1]].texcoord_; + tri.uv2_ = vertices[indices[i + 2]].texcoord_; + + tri.material_id_ = material_id; + + triangles_.push_back(tri); + } + } + + if (triangles_.empty()) { + Logger::warning("No triangles to build BVH"); + return false; + } + + // Initialize triangle indices + triangle_indices_.resize(triangles_.size()); + for (size_t i = 0; i < triangles_.size(); ++i) { + triangle_indices_[i] = static_cast(i); + } + + // Reserve space for nodes (estimate) + nodes_.reserve(triangles_.size() * 2); + + // Create root node + nodes_.emplace_back(); + + // Build BVH recursively + build_recursive_(0, 0, static_cast(triangles_.size())); + + Logger::info("BVH built: " + std::to_string(nodes_.size()) + " nodes, " + + std::to_string(triangles_.size()) + " triangles"); + + return true; +} + +void BVH::build_recursive_(uint node_idx, uint first_prim, uint prim_count) { + BVHNode& node = nodes_[node_idx]; + + // Calculate bounds + AABB bounds = calculate_bounds_(first_prim, prim_count); + node.aabb_min_ = bounds.min_; + node.aabb_max_ = bounds.max_; + + // Leaf node threshold + const uint LEAF_SIZE = 4; + + if (prim_count <= LEAF_SIZE) { + // Create leaf node + node.left_first_ = first_prim; + node.count_ = prim_count; + return; + } + + // Find best split + int axis; + float split_pos; + float split_cost = find_best_split_(first_prim, prim_count, axis, split_pos); + + // Check if split is beneficial + float no_split_cost = prim_count * bounds.surface_area(); + if (split_cost >= no_split_cost) { + // Create leaf node + node.left_first_ = first_prim; + node.count_ = prim_count; + return; + } + + // Partition primitives + uint mid = first_prim; + for (uint i = first_prim; i < first_prim + prim_count; ++i) { + Triangle& tri = triangles_[triangle_indices_[i]]; + float centroid = tri.get_centroid()[axis]; + + if (centroid < split_pos) { + std::swap(triangle_indices_[i], triangle_indices_[mid]); + mid++; + } + } + + // Ensure we have primitives on both sides + if (mid == first_prim || mid == first_prim + prim_count) { + mid = first_prim + prim_count / 2; + } + + // Create interior node + uint left_count = mid - first_prim; + uint right_count = prim_count - left_count; + + node.left_first_ = static_cast(nodes_.size()); + node.count_ = 0; + + // Create child nodes + nodes_.emplace_back(); + nodes_.emplace_back(); + + // Recursively build children + build_recursive_(node.left_first_, first_prim, left_count); + build_recursive_(node.left_first_ + 1, mid, right_count); +} + +float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float& split_pos) { + float best_cost = std::numeric_limits::max(); + + AABB centroid_bounds = calculate_centroid_bounds_(first_prim, prim_count); + + // Try each axis + for (int a = 0; a < 3; ++a) { + float extent = centroid_bounds.max_[a] - centroid_bounds.min_[a]; + if (extent < EPSILON) continue; + + // Try multiple split positions + const int NUM_BINS = 16; + for (int i = 1; i < NUM_BINS; ++i) { + float t = static_cast(i) / NUM_BINS; + float pos = centroid_bounds.min_[a] + t * extent; + + // Count primitives and calculate bounds for each side + AABB left_bounds, right_bounds; + uint left_count = 0, right_count = 0; + + for (uint j = first_prim; j < first_prim + prim_count; ++j) { + Triangle& tri = triangles_[triangle_indices_[j]]; + float centroid = tri.get_centroid()[a]; + + if (centroid < pos) { + left_bounds.expand(tri.get_bounds()); + left_count++; + } else { + right_bounds.expand(tri.get_bounds()); + right_count++; + } + } + + // Calculate SAH cost + if (left_count == 0 || right_count == 0) continue; + + float cost = left_count * left_bounds.surface_area() + + right_count * right_bounds.surface_area(); + + if (cost < best_cost) { + best_cost = cost; + axis = a; + split_pos = pos; + } + } + } + + return best_cost; +} + +AABB BVH::calculate_bounds_(uint first_prim, uint prim_count) { + AABB bounds{Vec3(std::numeric_limits::max()), + Vec3(std::numeric_limits::lowest())}; + + for (uint i = first_prim; i < first_prim + prim_count; ++i) { + Triangle& tri = triangles_[triangle_indices_[i]]; + bounds.expand(tri.get_bounds()); + } + + return bounds; +} + +AABB BVH::calculate_centroid_bounds_(uint first_prim, uint prim_count) { + AABB bounds{Vec3(std::numeric_limits::max()), + Vec3(std::numeric_limits::lowest())}; + + for (uint i = first_prim; i < first_prim + prim_count; ++i) { + Triangle& tri = triangles_[triangle_indices_[i]]; + bounds.expand(tri.get_centroid()); + } + + return bounds; +} + +bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) { + if (nodes_.empty() || triangles_.empty()) { + Logger::error("Cannot upload empty BVH to GPU"); + return false; + } + + // Reorder triangles according to BVH layout + std::vector ordered_triangles; + ordered_triangles.reserve(triangles_.size()); + + for (uint idx : triangle_indices_) { + ordered_triangles.push_back(triangles_[idx]); + } + + // Upload nodes + if (!node_buffer.create(BufferType::SHADER_STORAGE_BUFFER, + nodes_.size() * sizeof(BVHNode), + nodes_.data(), + BufferUsage::STATIC_DRAW)) { + Logger::error("Failed to upload BVH nodes to GPU"); + return false; + } + + // Upload triangles + if (!triangle_buffer.create(BufferType::SHADER_STORAGE_BUFFER, + ordered_triangles.size() * sizeof(Triangle), + ordered_triangles.data(), + BufferUsage::STATIC_DRAW)) { + Logger::error("Failed to upload BVH triangles to GPU"); + return false; + } + + Logger::info("BVH uploaded to GPU successfully"); + return true; +} + +void BVH::clear() { + nodes_.clear(); + triangles_.clear(); + triangle_indices_.clear(); +} + +} // namespace are diff --git a/src/core/gbuffer.cpp b/src/core/gbuffer.cpp new file mode 100644 index 0000000..b3e01d0 --- /dev/null +++ b/src/core/gbuffer.cpp @@ -0,0 +1,221 @@ +#include "core/gbuffer.h" +#include "utils/logger.h" +#include + +namespace are { + +GBuffer::GBuffer(uint width, uint height) + : width_(width) + , height_(height) + , fbo_(INVALID_HANDLE) + , depth_texture_(INVALID_HANDLE) + , initialized_(false) { + for (int i = 0; i < GBUFFER_COUNT; ++i) { + textures_[i] = INVALID_HANDLE; + } +} + +GBuffer::~GBuffer() { + release(); +} + +bool GBuffer::initialize() { + if (initialized_) { + Logger::warning("GBuffer already initialized"); + return true; + } + + // Create framebuffer + glGenFramebuffers(1, &fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + + // Create G-Buffer textures + textures_[GBUFFER_POSITION] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT); + textures_[GBUFFER_NORMAL] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT); + textures_[GBUFFER_ALBEDO] = create_texture_(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE); + + // Attach textures to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION, + GL_TEXTURE_2D, textures_[GBUFFER_POSITION], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL, + GL_TEXTURE_2D, textures_[GBUFFER_NORMAL], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO, + GL_TEXTURE_2D, textures_[GBUFFER_ALBEDO], 0); + + // Create depth texture + glGenTextures(1, &depth_texture_); + glBindTexture(GL_TEXTURE_2D, depth_texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width_, height_, 0, + GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_TEXTURE_2D, depth_texture_, 0); + + // Set draw buffers + GLenum draw_buffers[GBUFFER_COUNT] = { + GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION, + GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL, + GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO + }; + glDrawBuffers(GBUFFER_COUNT, draw_buffers); + + // Check framebuffer completeness + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + Logger::error("GBuffer framebuffer is not complete"); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + initialized_ = true; + Logger::info("GBuffer initialized successfully"); + return true; +} + +void GBuffer::release() { + if (!initialized_) return; + + if (fbo_ != INVALID_HANDLE) { + glDeleteFramebuffers(1, &fbo_); + fbo_ = INVALID_HANDLE; + } + + for (int i = 0; i < GBUFFER_COUNT; ++i) { + if (textures_[i] != INVALID_HANDLE) { + glDeleteTextures(1, &textures_[i]); + textures_[i] = INVALID_HANDLE; + } + } + + if (depth_texture_ != INVALID_HANDLE) { + glDeleteTextures(1, &depth_texture_); + depth_texture_ = INVALID_HANDLE; + } + + initialized_ = false; + Logger::info("GBuffer released"); +} + +void GBuffer::render(const Scene& scene, const Shader& shader) { + if (!initialized_) { + Logger::error("GBuffer not initialized"); + return; + } + + // Bind framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glViewport(0, 0, width_, height_); + + // Clear buffers + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Enable depth test + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + // Use shader + shader.use(); + + // Set camera matrices + const Camera& camera = scene.get_camera(); + Mat4 view = camera.get_view_matrix(); + Mat4 projection = camera.get_projection_matrix(); + + shader.set_mat4("u_view", view); + shader.set_mat4("u_projection", projection); + + // Render all meshes + const auto& meshes = scene.get_meshes(); + const auto& materials = scene.get_materials(); + + for (const auto& mesh : meshes) { + if (!mesh->is_uploaded()) { + Logger::warning("Mesh not uploaded to GPU, skipping"); + continue; + } + + // Set model matrix + Mat4 model = mesh->get_transform(); + shader.set_mat4("u_model", model); + + // Set material properties + uint material_id = mesh->get_material(); + if (material_id < materials.size()) { + const auto& material = materials[material_id]; + + shader.set_vec3("u_albedo", material->get_albedo()); + shader.set_float("u_metallic", material->get_metallic()); + shader.set_float("u_roughness", material->get_roughness()); + shader.set_uint("u_material_id", material_id); + + // Bind textures + auto albedo_tex = material->get_albedo_texture(); + if (albedo_tex && albedo_tex->is_valid()) { + albedo_tex->bind(0); + shader.set_int("u_albedo_map", 0); + shader.set_int("u_has_albedo_map", 1); + } else { + shader.set_int("u_has_albedo_map", 0); + } + + auto normal_tex = material->get_normal_texture(); + if (normal_tex && normal_tex->is_valid()) { + normal_tex->bind(1); + shader.set_int("u_normal_map", 1); + shader.set_int("u_has_normal_map", 1); + } else { + shader.set_int("u_has_normal_map", 0); + } + } + + // Draw mesh + glBindVertexArray(mesh->get_vao()); + glDrawElements(GL_TRIANGLES, mesh->get_indices().size(), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + } + + // Unbind framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void GBuffer::resize(uint width, uint height) { + if (width == width_ && height == height_) return; + + width_ = width; + height_ = height; + + if (initialized_) { + release(); + initialize(); + } +} + +TextureHandle GBuffer::get_texture(int index) const { + if (index < 0 || index >= GBUFFER_COUNT) { + Logger::error("Invalid G-Buffer texture index"); + return INVALID_HANDLE; + } + return textures_[index]; +} + +void GBuffer::get_dimensions(uint& width, uint& height) const { + width = width_; + height = height_; +} + +TextureHandle GBuffer::create_texture_(uint internal_format, uint format, uint type) { + TextureHandle texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width_, height_, 0, format, type, 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); + return texture; +} + +} // namespace are diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp new file mode 100644 index 0000000..a425b58 --- /dev/null +++ b/src/core/raytracer.cpp @@ -0,0 +1,316 @@ +#include "core/raytracer.h" +#include "utils/logger.h" +#include "basic/constants.h" +#include + +namespace are { + +RayTracer::RayTracer(uint width, uint height, const RayTracerConfig& config) + : width_(width) + , height_(height) + , config_(config) + , accumulation_texture_(INVALID_HANDLE) + , scene_buffer_(INVALID_HANDLE) + , material_buffer_(INVALID_HANDLE) + , light_buffer_(INVALID_HANDLE) + , bvh_(nullptr) + , bvh_built_(false) + , frame_count_(0) + , initialized_(false) { +} + +RayTracer::~RayTracer() { + release(); +} + +bool RayTracer::initialize() { + if (initialized_) { + Logger::warning("RayTracer already initialized"); + return true; + } + + // Create accumulation texture + glGenTextures(1, &accumulation_texture_); + glBindTexture(GL_TEXTURE_2D, accumulation_texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width_, height_, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Create shader storage buffers + glGenBuffers(1, &material_buffer_); + glGenBuffers(1, &light_buffer_); + + // Load compute shader + Logger::info("Loading ray tracing compute shader in RayTracer..."); + if (!compute_shader_.load_compute("shaders/raytracing.comp")) { + Logger::error("Failed to load ray tracing compute shader in RayTracer"); + return false; + } + Logger::info("Ray tracing compute shader loaded in RayTracer"); + + // Initialize BVH if enabled + if (config_.use_bvh_) { + bvh_ = std::make_unique(); + } + + initialized_ = true; + Logger::info("RayTracer initialized successfully"); + return true; +} + +void RayTracer::release() { + if (!initialized_) return; + + if (accumulation_texture_ != INVALID_HANDLE) { + glDeleteTextures(1, &accumulation_texture_); + accumulation_texture_ = INVALID_HANDLE; + } + + if (material_buffer_ != INVALID_HANDLE) { + glDeleteBuffers(1, &material_buffer_); + material_buffer_ = INVALID_HANDLE; + } + + if (light_buffer_ != INVALID_HANDLE) { + glDeleteBuffers(1, &light_buffer_); + light_buffer_ = INVALID_HANDLE; + } + + bvh_node_buffer_.release(); + bvh_triangle_buffer_.release(); + + compute_shader_.release(); + + bvh_.reset(); + bvh_built_ = false; + + initialized_ = false; + Logger::info("RayTracer released"); +} + +bool RayTracer::rebuild_bvh(const Scene& scene) { + if (!config_.use_bvh_) { + Logger::warning("BVH is disabled in configuration"); + return false; + } + + if (!bvh_) { + bvh_ = std::make_unique(); + } + + Logger::info("Building BVH for ray tracing..."); + + if (!bvh_->build(scene.get_meshes())) { + Logger::error("Failed to build BVH"); + return false; + } + + if (!bvh_->upload_to_gpu(bvh_node_buffer_, bvh_triangle_buffer_)) { + Logger::error("Failed to upload BVH to GPU"); + return false; + } + + bvh_built_ = true; + Logger::info("BVH built and uploaded successfully"); + return true; +} + +void RayTracer::trace(const Scene& scene, const GBuffer& gbuffer, TextureHandle output_texture) { + if (!initialized_) { + Logger::error("RayTracer not initialized"); + return; + } + + if (!compute_shader_.is_valid()) { + Logger::error("Ray tracing compute shader not loaded"); + return; + } + + // Build BVH if enabled and not built yet + if (config_.use_bvh_ && !bvh_built_) { + rebuild_bvh(scene); + } + + // Upload scene data + upload_scene_data_(scene); + + // Use compute shader + compute_shader_.use(); + + // Bind G-Buffer textures + bind_gbuffer_(gbuffer); + + // Bind output and accumulation textures + glBindImageTexture(3, output_texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); + glBindImageTexture(4, accumulation_texture_, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F); + + // Bind BVH buffers if enabled + if (config_.use_bvh_ && bvh_built_) { + bvh_node_buffer_.bind_base(2); + bvh_triangle_buffer_.bind_base(3); + compute_shader_.set_bool("u_use_bvh", true); + compute_shader_.set_uint("u_bvh_node_count", bvh_->get_node_count()); + } else { + compute_shader_.set_bool("u_use_bvh", false); + } + + // Set uniforms + compute_shader_.set_uint("u_frame_count", frame_count_); + compute_shader_.set_uint("u_samples_per_pixel", config_.samples_per_pixel_); + compute_shader_.set_uint("u_max_depth", config_.max_depth_); + compute_shader_.set_uint("u_light_count", static_cast(scene.get_lights().size())); + compute_shader_.set_bool("u_enable_accumulation", config_.enable_accumulation_); + + // Set camera data + const Camera& camera = scene.get_camera(); + compute_shader_.set_vec3("u_camera_position", camera.get_position()); + + Mat4 inv_vp = glm::inverse(camera.get_view_projection_matrix()); + compute_shader_.set_mat4("u_inv_view_projection", inv_vp); + + // Dispatch compute shader + uint num_groups_x = (width_ + COMPUTE_GROUP_SIZE_X - 1) / COMPUTE_GROUP_SIZE_X; + uint num_groups_y = (height_ + COMPUTE_GROUP_SIZE_Y - 1) / COMPUTE_GROUP_SIZE_Y; + + glDispatchCompute(num_groups_x, num_groups_y, 1); + + // Memory barrier + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + + // Increment frame count for accumulation + if (config_.enable_accumulation_) { + frame_count_++; + } +} + +void RayTracer::resize(uint width, uint height) { + if (width == width_ && height == height_) return; + + width_ = width; + height_ = height; + + if (initialized_) { + // Recreate accumulation texture + if (accumulation_texture_ != INVALID_HANDLE) { + glDeleteTextures(1, &accumulation_texture_); + } + + glGenTextures(1, &accumulation_texture_); + glBindTexture(GL_TEXTURE_2D, accumulation_texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width_, height_, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + reset_accumulation(); + } +} + +void RayTracer::reset_accumulation() { + frame_count_ = 0; +} + +void RayTracer::set_config(const RayTracerConfig& config) { + bool bvh_changed = (config.use_bvh_ != config_.use_bvh_); + + config_ = config; + reset_accumulation(); + + if (bvh_changed) { + if (config_.use_bvh_ && !bvh_) { + bvh_ = std::make_unique(); + bvh_built_ = false; + } else if (!config_.use_bvh_) { + bvh_.reset(); + bvh_built_ = false; + } + } +} + +void RayTracer::upload_scene_data_(const Scene& scene) { + // Upload materials + const auto& materials = scene.get_materials(); + if (!materials.empty()) { + struct MaterialData { + Vec3 albedo; + float metallic; + Vec3 emission; + float roughness; + int type; + float ior; + Vec2 padding; + }; + + std::vector material_data; + material_data.reserve(materials.size()); + + for (const auto& mat : materials) { + MaterialData data; + data.albedo = mat->get_albedo(); + data.metallic = mat->get_metallic(); + data.emission = mat->get_emission(); + data.roughness = mat->get_roughness(); + data.type = static_cast(mat->get_type()); + data.ior = mat->get_ior(); + material_data.push_back(data); + } + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, material_buffer_); + glBufferData(GL_SHADER_STORAGE_BUFFER, + material_data.size() * sizeof(MaterialData), + material_data.data(), GL_DYNAMIC_DRAW); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, material_buffer_); + } + + // Upload lights + const auto& lights = scene.get_lights(); + if (!lights.empty()) { + struct LightData { + Vec3 position; + int type; + Vec3 direction; + float intensity; + Vec3 color; + float range; + Vec2 spot_angles; + Vec2 padding; + }; + + std::vector light_data; + light_data.reserve(lights.size()); + + for (const auto& light : lights) { + LightData data; + data.position = light->get_position(); + data.type = static_cast(light->get_type()); + data.direction = light->get_direction(); + data.intensity = light->get_intensity(); + data.color = light->get_color(); + data.range = light->get_range(); + data.spot_angles = Vec2(light->get_inner_angle(), light->get_outer_angle()); + light_data.push_back(data); + } + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, light_buffer_); + glBufferData(GL_SHADER_STORAGE_BUFFER, + light_data.size() * sizeof(LightData), + light_data.data(), GL_DYNAMIC_DRAW); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, light_buffer_); + } +} + +void RayTracer::bind_gbuffer_(const GBuffer& gbuffer) { + glBindImageTexture(0, gbuffer.get_texture(GBUFFER_POSITION), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); + glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F); + glBindImageTexture(2, gbuffer.get_texture(GBUFFER_ALBEDO), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); +} + +void RayTracer::set_compute_shader(const Shader& shader) { + compute_shader_ = shader; + Logger::info("Compute shader set for RayTracer"); +} + +} // namespace are diff --git a/src/core/renderer.cpp b/src/core/renderer.cpp new file mode 100644 index 0000000..056c6d3 --- /dev/null +++ b/src/core/renderer.cpp @@ -0,0 +1,205 @@ +#include "core/renderer.h" +#include "utils/logger.h" +#include +#include + +namespace are { + +Renderer::Renderer(const RendererConfig &config) + : config_(config) + , initialized_(false) + , frame_count_(0) { +} + +Renderer::~Renderer() { + shutdown(); +} + +bool Renderer::initialize() { + if (initialized_) { + Logger::warning("Renderer already initialized"); + return true; + } + + Logger::info("Initializing Aurora Rendering Engine..."); + + // Initialize shader manager + shader_manager_ = std::make_unique(); + if (!shader_manager_->initialize()) { + Logger::error("Failed to initialize shader manager"); + return false; + } + + // Initialize G-Buffer + gbuffer_ = std::make_unique(config_.width_, config_.height_); + if (!gbuffer_->initialize()) { + Logger::error("Failed to initialize G-Buffer"); + return false; + } + + // Initialize ray tracer + RayTracerConfig rt_config; + rt_config.samples_per_pixel_ = config_.samples_per_pixel_; + rt_config.max_depth_ = config_.max_ray_depth_; + rt_config.enable_shadows_ = true; + rt_config.enable_reflections_ = true; + rt_config.enable_accumulation_ = config_.enable_accumulation_; + rt_config.use_bvh_ = true; + + raytracer_ = std::make_unique(config_.width_, config_.height_, rt_config); + if (!raytracer_->initialize()) { + Logger::error("Failed to initialize ray tracer"); + return false; + } + + // Pass compute shader to ray tracer + const Shader& rt_shader = shader_manager_->get_raytracing_shader(); + if (!rt_shader.is_valid()) { + Logger::error("Ray tracing shader is invalid"); + return false; + } + raytracer_->set_compute_shader(rt_shader); + + // Initialize screen blit + screen_blit_ = std::make_unique(); + if (!screen_blit_->initialize()) { + Logger::error("Failed to initialize screen blit"); + return false; + } + + initialized_ = true; + Logger::info("Aurora Rendering Engine initialized successfully"); + return true; +} + +void Renderer::shutdown() { + if (!initialized_) + return; + + Logger::info("Shutting down Aurora Rendering Engine..."); + + if (screen_blit_) { + screen_blit_->release(); + screen_blit_.reset(); + } + + if (raytracer_) { + raytracer_->release(); + raytracer_.reset(); + } + + if (gbuffer_) { + gbuffer_->release(); + gbuffer_.reset(); + } + + if (shader_manager_) { + shader_manager_->release(); + shader_manager_.reset(); + } + + initialized_ = false; + Logger::info("Aurora Rendering Engine shut down"); +} + +RenderStats Renderer::render(const Scene& scene, TextureHandle output_texture) { + RenderStats stats = {}; + + if (!initialized_) { + Logger::error("Renderer not initialized"); + return stats; + } + + // Start timing + auto start_time = std::chrono::high_resolution_clock::now(); + + // Phase 1: G-Buffer pass + auto gbuffer_start = std::chrono::high_resolution_clock::now(); + + const Shader& gbuffer_shader = shader_manager_->get_gbuffer_shader(); + gbuffer_->render(scene, gbuffer_shader); + + auto gbuffer_end = std::chrono::high_resolution_clock::now(); + stats.gbuffer_time_ms_ = std::chrono::duration(gbuffer_end - gbuffer_start).count(); + + // Phase 2: Ray tracing pass + auto raytrace_start = std::chrono::high_resolution_clock::now(); + + // Create output texture if not provided + TextureHandle rt_output = output_texture; + bool created_temp_texture = false; + + if (rt_output == 0) { + glGenTextures(1, &rt_output); + glBindTexture(GL_TEXTURE_2D, rt_output); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, config_.width_, config_.height_, + 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + created_temp_texture = true; + } + + raytracer_->trace(scene, *gbuffer_, rt_output); + + auto raytrace_end = std::chrono::high_resolution_clock::now(); + stats.raytrace_time_ms_ = std::chrono::duration(raytrace_end - raytrace_start).count(); + + // Phase 3: Blit to screen if output is default framebuffer + if (created_temp_texture && output_texture == 0) { + screen_blit_->blit_fullscreen(rt_output); + glDeleteTextures(1, &rt_output); + } + + // Calculate total frame time + auto end_time = std::chrono::high_resolution_clock::now(); + stats.frame_time_ms_ = std::chrono::duration(end_time - start_time).count(); + + // Count triangles + const auto& meshes = scene.get_meshes(); + for (const auto& mesh : meshes) { + stats.triangle_count_ += mesh->get_indices().size() / 3; + } + + // Estimate ray count (very rough) + stats.ray_count_ = config_.width_ * config_.height_ * config_.samples_per_pixel_ * config_.max_ray_depth_; + + frame_count_++; + + return stats; +} + +void Renderer::resize(uint width, uint height) { + if (width == config_.width_ && height == config_.height_) + return; + + config_.width_ = width; + config_.height_ = height; + + if (initialized_) { + gbuffer_->resize(width, height); + raytracer_->resize(width, height); + + Logger::info("Renderer resized to " + std::to_string(width) + "x" + std::to_string(height)); + } +} + +void Renderer::set_config(const RendererConfig &config) { + bool size_changed = (config.width_ != config_.width_ || config.height_ != config_.height_); + + config_ = config; + + if (initialized_) { + if (size_changed) { + resize(config_.width_, config_.height_); + } + + // Update ray tracer config + RayTracerConfig rt_config = raytracer_->get_config(); + rt_config.samples_per_pixel_ = config_.samples_per_pixel_; + rt_config.max_depth_ = config_.max_ray_depth_; + rt_config.enable_accumulation_ = config_.enable_accumulation_; + raytracer_->set_config(rt_config); + } +} + +} // namespace are diff --git a/src/core/screen_blit.cpp b/src/core/screen_blit.cpp new file mode 100644 index 0000000..011326f --- /dev/null +++ b/src/core/screen_blit.cpp @@ -0,0 +1,148 @@ +#include "core/screen_blit.h" +#include "utils/logger.h" +#include + +namespace are { + +namespace { + const char* VERTEX_SHADER_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* FRAGMENT_SHADER_SOURCE = R"( + #version 430 core + in vec2 v_texcoord; + out vec4 frag_color; + + uniform sampler2D u_texture; + + void main() { + frag_color = texture(u_texture, v_texcoord); + } + )"; +} + +ScreenBlit::ScreenBlit() + : vao_(0) + , vbo_(0) + , initialized_(false) { +} + +ScreenBlit::~ScreenBlit() { + release(); +} + +bool ScreenBlit::initialize() { + if (initialized_) { + Logger::warning("ScreenBlit already initialized"); + return true; + } + + // Compile shader + if (!shader_.compile(VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)) { + Logger::error("Failed to compile screen blit shader"); + return false; + } + + // Create fullscreen quad + create_quad_(); + + initialized_ = true; + Logger::info("ScreenBlit initialized successfully"); + return true; +} + +void ScreenBlit::release() { + if (!initialized_) return; + + shader_.release(); + + if (vao_ != 0) { + glDeleteVertexArrays(1, &vao_); + vao_ = 0; + } + + if (vbo_ != 0) { + glDeleteBuffers(1, &vbo_); + vbo_ = 0; + } + + initialized_ = false; +} + +void ScreenBlit::blit(TextureHandle texture, int x, int y, uint width, uint height) { + if (!initialized_) { + Logger::error("ScreenBlit not initialized"); + return; + } + + // Set viewport + glViewport(x, y, width, height); + + // Disable depth test + glDisable(GL_DEPTH_TEST); + + // Use shader + shader_.use(); + shader_.set_int("u_texture", 0); + + // Bind texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + // Draw quad + glBindVertexArray(vao_); + glDrawArrays(GL_TRIANGLES, 0, 6); + glBindVertexArray(0); + + // Re-enable depth test + glEnable(GL_DEPTH_TEST); +} + +void ScreenBlit::blit_fullscreen(TextureHandle texture) { + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + blit(texture, viewport[0], viewport[1], viewport[2], viewport[3]); +} + +void ScreenBlit::create_quad_() { + // Fullscreen quad vertices (position + texcoord) + 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, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 1.0f + }; + + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + + glBindVertexArray(vao_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Position attribute + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + + // TexCoord attribute + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + + glBindVertexArray(0); +} + +} // namespace are diff --git a/src/core/shader_manager.cpp b/src/core/shader_manager.cpp new file mode 100644 index 0000000..15c41a9 --- /dev/null +++ b/src/core/shader_manager.cpp @@ -0,0 +1,127 @@ +#include "core/shader_manager.h" +#include "utils/logger.h" + +namespace are { + +ShaderManager::ShaderManager() + : initialized_(false) { +} + +ShaderManager::~ShaderManager() { + release(); +} + +bool ShaderManager::initialize() { + if (initialized_) { + Logger::warning("ShaderManager already initialized"); + return true; + } + + Logger::info("Loading built-in shaders..."); + + if (!load_builtin_shaders_()) { + Logger::error("Failed to load built-in shaders"); + return false; + } + + initialized_ = true; + Logger::info("ShaderManager initialized successfully"); + return true; +} + +void ShaderManager::release() { + if (!initialized_) return; + + gbuffer_shader_.release(); + raytracing_shader_.release(); + + for (auto& pair : shader_cache_) { + pair.second.release(); + } + shader_cache_.clear(); + + initialized_ = false; + Logger::info("ShaderManager released"); +} + +Shader ShaderManager::load_shader(const std::string& name, + const std::string& vertex_path, + const std::string& fragment_path) { + // Check cache + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) { + Logger::info("Shader '" + name + "' loaded from cache"); + return it->second; + } + + // Load shader + Shader shader; + if (!shader.load(vertex_path, fragment_path)) { + Logger::error("Failed to load shader '" + name + "'"); + return Shader(); + } + + shader_cache_[name] = shader; + Logger::info("Shader '" + name + "' loaded successfully"); + return shader; +} + +Shader ShaderManager::load_compute_shader(const std::string& name, + const std::string& compute_path) { + // Check cache + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) { + Logger::info("Compute shader '" + name + "' loaded from cache"); + return it->second; + } + + // Load shader + Shader shader; + if (!shader.load_compute(compute_path)) { + Logger::error("Failed to load compute shader '" + name + "'"); + return Shader(); + } + + shader_cache_[name] = shader; + Logger::info("Compute shader '" + name + "' loaded successfully"); + return shader; +} + +Shader ShaderManager::get_shader(const std::string& name) const { + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) { + return it->second; + } + + Logger::warning("Shader '" + name + "' not found in cache"); + return Shader(); +} + +bool ShaderManager::load_builtin_shaders_() { + // Load G-Buffer shader + if (!gbuffer_shader_.load("shaders/gbuffer.vert", "shaders/gbuffer.frag")) { + Logger::error("Failed to load G-Buffer shader"); + return false; + } + shader_cache_["gbuffer"] = gbuffer_shader_; + + // Load ray tracing compute shader + if (!raytracing_shader_.load_compute("shaders/raytracing.comp")) { + Logger::error("Failed to load ray tracing shader"); + return false; + } + shader_cache_["raytracing"] = raytracing_shader_; + + // Load ray tracing compute shader + Logger::info("Loading ray tracing compute shader..."); + if (!raytracing_shader_.load_compute("shaders/raytracing.comp")) { + Logger::error("Failed to load ray tracing shader"); + return false; + } + shader_cache_["raytracing"] = raytracing_shader_; + Logger::info("Ray tracing shader loaded successfully"); + + return true; +} + +} // namespace are diff --git a/src/resource/buffer.cpp b/src/resource/buffer.cpp new file mode 100644 index 0000000..3172908 --- /dev/null +++ b/src/resource/buffer.cpp @@ -0,0 +1,114 @@ +#include "resource/buffer.h" +#include "utils/logger.h" +#include + +namespace are { + +namespace { + GLenum get_gl_buffer_type(BufferType type) { + switch (type) { + case BufferType::VERTEX_BUFFER: return GL_ARRAY_BUFFER; + case BufferType::INDEX_BUFFER: return GL_ELEMENT_ARRAY_BUFFER; + case BufferType::UNIFORM_BUFFER: return GL_UNIFORM_BUFFER; + case BufferType::SHADER_STORAGE_BUFFER: return GL_SHADER_STORAGE_BUFFER; + default: return GL_ARRAY_BUFFER; + } + } + + GLenum get_gl_usage(BufferUsage usage) { + switch (usage) { + case BufferUsage::STATIC_DRAW: return GL_STATIC_DRAW; + case BufferUsage::DYNAMIC_DRAW: return GL_DYNAMIC_DRAW; + case BufferUsage::STREAM_DRAW: return GL_STREAM_DRAW; + default: return GL_STATIC_DRAW; + } + } +} + +Buffer::Buffer() + : handle_(INVALID_HANDLE) + , type_(BufferType::VERTEX_BUFFER) + , size_(0) + , usage_(BufferUsage::STATIC_DRAW) { +} + +Buffer::~Buffer() { + // Don't auto-release, let user control lifetime +} + +bool Buffer::create(BufferType type, size_t size, const void* data, BufferUsage usage) { + if (handle_ != INVALID_HANDLE) { + Logger::warning("Buffer already created, releasing old buffer"); + release(); + } + + type_ = type; + size_ = size; + usage_ = usage; + + glGenBuffers(1, &handle_); + + GLenum gl_type = get_gl_buffer_type(type); + GLenum gl_usage = get_gl_usage(usage); + + glBindBuffer(gl_type, handle_); + glBufferData(gl_type, size, data, gl_usage); + glBindBuffer(gl_type, 0); + + Logger::info("Buffer created successfully"); + return true; +} + +void Buffer::update(size_t offset, size_t size, const void* data) { + if (handle_ == INVALID_HANDLE) { + Logger::error("Cannot update invalid buffer"); + return; + } + + if (offset + size > size_) { + Logger::error("Buffer update out of bounds"); + return; + } + + GLenum gl_type = get_gl_buffer_type(type_); + + glBindBuffer(gl_type, handle_); + glBufferSubData(gl_type, offset, size, data); + glBindBuffer(gl_type, 0); +} + +void Buffer::bind() const { + if (handle_ == INVALID_HANDLE) { + Logger::warning("Attempting to bind invalid buffer"); + return; + } + + GLenum gl_type = get_gl_buffer_type(type_); + glBindBuffer(gl_type, handle_); +} + +void Buffer::bind_base(uint binding_point) const { + if (handle_ == INVALID_HANDLE) { + Logger::warning("Attempting to bind invalid buffer"); + return; + } + + GLenum gl_type = get_gl_buffer_type(type_); + glBindBufferBase(gl_type, binding_point, handle_); +} + +void Buffer::unbind() const { + GLenum gl_type = get_gl_buffer_type(type_); + glBindBuffer(gl_type, 0); +} + +void Buffer::release() { + if (handle_ != INVALID_HANDLE) { + glDeleteBuffers(1, &handle_); + handle_ = INVALID_HANDLE; + } + + size_ = 0; +} + +} // namespace are diff --git a/src/resource/model_loader.cpp b/src/resource/model_loader.cpp new file mode 100644 index 0000000..b6c0424 --- /dev/null +++ b/src/resource/model_loader.cpp @@ -0,0 +1,187 @@ +#include "resource/model_loader.h" +#include "utils/logger.h" +#include "resource/texture.h" + +// Note: This is a simplified implementation without Assimp +// For full implementation, include Assimp and implement properly + +namespace are { + +bool ModelLoader::load(const std::string& path, + std::vector>& meshes, + std::vector>& materials, + bool flip_uvs) { + Logger::error("ModelLoader requires Assimp library (not implemented in this version)"); + Logger::info("To implement: include , , "); + + // Placeholder implementation + // TODO: Implement with Assimp + /* + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(path, + aiProcess_Triangulate | + aiProcess_GenNormals | + aiProcess_CalcTangentSpace | + (flip_uvs ? aiProcess_FlipUVs : 0)); + + if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { + Logger::error("Failed to load model: " + std::string(importer.GetErrorString())); + return false; + } + + std::string directory = path.substr(0, path.find_last_of('/')); + process_node_(scene->mRootNode, scene, meshes, materials, directory); + + Logger::info("Model loaded: " + path); + return true; + */ + + return false; +} + +bool ModelLoader::load_and_upload(const std::string& path, + std::vector>& meshes, + std::vector>& materials, + bool flip_uvs) { + if (!load(path, meshes, materials, flip_uvs)) { + return false; + } + + // Upload all meshes to GPU + for (auto& mesh : meshes) { + if (!mesh->upload_to_gpu()) { + Logger::error("Failed to upload mesh to GPU"); + return false; + } + } + + return true; +} + +void ModelLoader::process_node_(void* node, void* scene, + std::vector>& meshes, + std::vector>& materials, + const std::string& directory) { + // TODO: Implement with Assimp + /* + aiNode* ai_node = static_cast(node); + const aiScene* ai_scene = static_cast(scene); + + // Process all meshes in this node + for (uint i = 0; i < ai_node->mNumMeshes; ++i) { + aiMesh* ai_mesh = ai_scene->mMeshes[ai_node->mMeshes[i]]; + meshes.push_back(process_mesh_(ai_mesh, ai_scene, materials, directory)); + } + + // Process children recursively + for (uint i = 0; i < ai_node->mNumChildren; ++i) { + process_node_(ai_node->mChildren[i], ai_scene, meshes, materials, directory); + } + */ +} + +std::shared_ptr ModelLoader::process_mesh_(void* mesh, void* scene, + std::vector>& materials, + const std::string& directory) { + // TODO: Implement with Assimp + /* + aiMesh* ai_mesh = static_cast(mesh); + const aiScene* ai_scene = static_cast(scene); + + std::vector vertices; + std::vector indices; + + // Process vertices + for (uint i = 0; i < ai_mesh->mNumVertices; ++i) { + Vertex vertex; + + vertex.position_ = Vec3(ai_mesh->mVertices[i].x, + ai_mesh->mVertices[i].y, + ai_mesh->mVertices[i].z); + + if (ai_mesh->HasNormals()) { + vertex.normal_ = Vec3(ai_mesh->mNormals[i].x, + ai_mesh->mNormals[i].y, + ai_mesh->mNormals[i].z); + } + + if (ai_mesh->mTextureCoords[0]) { + vertex.texcoord_ = Vec2(ai_mesh->mTextureCoords[0][i].x, + ai_mesh->mTextureCoords[0][i].y); + } + + if (ai_mesh->HasTangentsAndBitangents()) { + vertex.tangent_ = Vec3(ai_mesh->mTangents[i].x, + ai_mesh->mTangents[i].y, + ai_mesh->mTangents[i].z); + } + + vertices.push_back(vertex); + } + + // Process indices + for (uint i = 0; i < ai_mesh->mNumFaces; ++i) { + aiFace face = ai_mesh->mFaces[i]; + for (uint j = 0; j < face.mNumIndices; ++j) { + indices.push_back(face.mIndices[j]); + } + } + + // Process material + uint material_id = materials.size(); + if (ai_mesh->mMaterialIndex >= 0) { + aiMaterial* ai_material = ai_scene->mMaterials[ai_mesh->mMaterialIndex]; + + auto material = std::make_shared(); + + // Load diffuse color + aiColor3D color; + if (ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + material->set_albedo(Vec3(color.r, color.g, color.b)); + } + + // Load textures + load_material_textures_(ai_material, aiTextureType_DIFFUSE, material, directory); + load_material_textures_(ai_material, aiTextureType_NORMALS, material, directory); + + materials.push_back(material); + } + + auto mesh_obj = std::make_shared(); + mesh_obj->set_vertices(vertices); + mesh_obj->set_indices(indices); + mesh_obj->set_material(material_id); + + return mesh_obj; + */ + + return nullptr; +} + +void ModelLoader::load_material_textures_(void* material, int type, + std::shared_ptr& mat, + const std::string& directory) { + // TODO: Implement with Assimp + /* + aiMaterial* ai_material = static_cast(material); + aiTextureType ai_type = static_cast(type); + + for (uint i = 0; i < ai_material->GetTextureCount(ai_type); ++i) { + aiString str; + ai_material->GetTexture(ai_type, i, &str); + + std::string filename = directory + "/" + std::string(str.C_Str()); + + auto texture = std::make_shared(); + if (texture->load_from_file(filename)) { + if (ai_type == aiTextureType_DIFFUSE) { + mat->set_albedo_texture(texture); + } else if (ai_type == aiTextureType_NORMALS) { + mat->set_normal_texture(texture); + } + } + } + */ +} + +} // namespace are diff --git a/src/resource/shader.cpp b/src/resource/shader.cpp new file mode 100644 index 0000000..73aa0f5 --- /dev/null +++ b/src/resource/shader.cpp @@ -0,0 +1,197 @@ +#include "resource/shader.h" +#include "utils/logger.h" +#include "basic/math.h" // 修改为math.h +#include +#include +#include + +namespace are { + +Shader::Shader() + : handle_(INVALID_HANDLE) { +} + +Shader::~Shader() { + // Don't auto-release, let user control lifetime +} + +bool Shader::load(const std::string& vertex_path, const std::string& fragment_path) { + std::string vertex_source = read_file_(vertex_path); + std::string fragment_source = read_file_(fragment_path); + + if (vertex_source.empty() || fragment_source.empty()) { + Logger::error("Failed to read shader files"); + return false; + } + + return compile(vertex_source, fragment_source); +} + +bool Shader::load_compute(const std::string& compute_path) { + std::string compute_source = read_file_(compute_path); + + if (compute_source.empty()) { + Logger::error("Failed to read compute shader file"); + return false; + } + + return compile_compute(compute_source); +} + +bool Shader::compile(const std::string& vertex_source, const std::string& fragment_source) { + uint vertex_shader = compile_shader_(vertex_source, GL_VERTEX_SHADER); + if (vertex_shader == 0) return false; + + uint fragment_shader = compile_shader_(fragment_source, GL_FRAGMENT_SHADER); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return false; + } + + uint shaders[] = { vertex_shader, fragment_shader }; + bool success = link_program_(shaders, 2); + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return success; +} + +bool Shader::compile_compute(const std::string& compute_source) { + uint compute_shader = compile_shader_(compute_source, GL_COMPUTE_SHADER); + if (compute_shader == 0) return false; + + uint shaders[] = { compute_shader }; + bool success = link_program_(shaders, 1); + + glDeleteShader(compute_shader); + + return success; +} + +void Shader::use() const { // 改为const + if (handle_ != INVALID_HANDLE) { + glUseProgram(handle_); + } +} + +void Shader::release() { + if (handle_ != INVALID_HANDLE) { + glDeleteProgram(handle_); + handle_ = INVALID_HANDLE; + } + uniform_cache_.clear(); +} + +void Shader::set_bool(const std::string& name, bool value) const { // 新增 + glUniform1i(get_uniform_location_(name), static_cast(value)); +} + +void Shader::set_int(const std::string& name, int value) const { // 改为const + glUniform1i(get_uniform_location_(name), value); +} + +void Shader::set_uint(const std::string& name, uint value) const { // 改为const + glUniform1ui(get_uniform_location_(name), value); +} + +void Shader::set_float(const std::string& name, float value) const { // 改为const + glUniform1f(get_uniform_location_(name), value); +} + +void Shader::set_vec2(const std::string& name, const Vec2& value) const { // 改为const + glUniform2fv(get_uniform_location_(name), 1, &value[0]); +} + +void Shader::set_vec3(const std::string& name, const Vec3& value) const { // 改为const + glUniform3fv(get_uniform_location_(name), 1, &value[0]); +} + +void Shader::set_vec4(const std::string& name, const Vec4& value) const { // 改为const + glUniform4fv(get_uniform_location_(name), 1, &value[0]); +} + +void Shader::set_mat3(const std::string& name, const Mat3& value) const { // 改为const + glUniformMatrix3fv(get_uniform_location_(name), 1, GL_FALSE, &value[0][0]); +} + +void Shader::set_mat4(const std::string& name, const Mat4& value) const { // 改为const + glUniformMatrix4fv(get_uniform_location_(name), 1, GL_FALSE, MathUtils::value_ptr(value)); +} + +int Shader::get_uniform_location_(const std::string& name) const { // 改为const + auto it = uniform_cache_.find(name); + if (it != uniform_cache_.end()) { + return it->second; + } + + int location = glGetUniformLocation(handle_, name.c_str()); + uniform_cache_[name] = location; // mutable允许修改 + + if (location == -1) { + Logger::warning("Uniform '" + name + "' not found in shader"); + } + + return location; +} + +uint Shader::compile_shader_(const std::string& source, uint type) { + uint shader = glCreateShader(type); + const char* source_cstr = source.c_str(); + glShaderSource(shader, 1, &source_cstr, nullptr); + glCompileShader(shader); + + int success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char info_log[512]; + glGetShaderInfoLog(shader, 512, nullptr, info_log); + + std::string type_str = (type == GL_VERTEX_SHADER) ? "VERTEX" : + (type == GL_FRAGMENT_SHADER) ? "FRAGMENT" : "COMPUTE"; + Logger::error("Shader compilation failed (" + type_str + "): " + std::string(info_log)); + + glDeleteShader(shader); + return 0; + } + + return shader; +} + +bool Shader::link_program_(const uint* shaders, uint count) { + handle_ = glCreateProgram(); + + for (uint i = 0; i < count; ++i) { + glAttachShader(handle_, shaders[i]); + } + + glLinkProgram(handle_); + + int success; + glGetProgramiv(handle_, GL_LINK_STATUS, &success); + if (!success) { + char info_log[512]; + glGetProgramInfoLog(handle_, 512, nullptr, info_log); + Logger::error("Shader linking failed: " + std::string(info_log)); + + glDeleteProgram(handle_); + handle_ = INVALID_HANDLE; + return false; + } + + return true; +} + +std::string Shader::read_file_(const std::string& path) { + std::ifstream file(path); + if (!file.is_open()) { + Logger::error("Failed to open file: " + path); + return ""; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +} // namespace are diff --git a/src/resource/texture.cpp b/src/resource/texture.cpp new file mode 100644 index 0000000..521e955 --- /dev/null +++ b/src/resource/texture.cpp @@ -0,0 +1,274 @@ +#include "resource/texture.h" +#include "utils/logger.h" +#include +#include + +namespace are { + +namespace { + GLenum get_gl_internal_format(TextureFormat format) { + switch (format) { + case TextureFormat::R8: return GL_R8; + case TextureFormat::RG8: return GL_RG8; + case TextureFormat::RGB8: return GL_RGB8; + case TextureFormat::RGBA8: return GL_RGBA8; + case TextureFormat::R16F: return GL_R16F; + case TextureFormat::RG16F: return GL_RG16F; + case TextureFormat::RGB16F: return GL_RGB16F; + case TextureFormat::RGBA16F: return GL_RGBA16F; + case TextureFormat::R32F: return GL_R32F; + case TextureFormat::RG32F: return GL_RG32F; + case TextureFormat::RGB32F: return GL_RGB32F; + case TextureFormat::RGBA32F: return GL_RGBA32F; + case TextureFormat::DEPTH24_STENCIL8: return GL_DEPTH24_STENCIL8; + default: return GL_RGBA8; + } + } + + GLenum get_gl_format(TextureFormat format) { + switch (format) { + case TextureFormat::R8: + case TextureFormat::R16F: + case TextureFormat::R32F: + return GL_RED; + case TextureFormat::RG8: + case TextureFormat::RG16F: + case TextureFormat::RG32F: + return GL_RG; + case TextureFormat::RGB8: + case TextureFormat::RGB16F: + case TextureFormat::RGB32F: + return GL_RGB; + case TextureFormat::RGBA8: + case TextureFormat::RGBA16F: + case TextureFormat::RGBA32F: + return GL_RGBA; + case TextureFormat::DEPTH24_STENCIL8: + return GL_DEPTH_STENCIL; + default: + return GL_RGBA; + } + } + + GLenum get_gl_type(TextureFormat format) { + switch (format) { + case TextureFormat::R8: + case TextureFormat::RG8: + case TextureFormat::RGB8: + case TextureFormat::RGBA8: + return GL_UNSIGNED_BYTE; + case TextureFormat::R16F: + case TextureFormat::RG16F: + case TextureFormat::RGB16F: + case TextureFormat::RGBA16F: + case TextureFormat::R32F: + case TextureFormat::RG32F: + case TextureFormat::RGB32F: + case TextureFormat::RGBA32F: + return GL_FLOAT; + case TextureFormat::DEPTH24_STENCIL8: + return GL_UNSIGNED_INT_24_8; + default: + return GL_UNSIGNED_BYTE; + } + } + + GLenum get_gl_filter(TextureFilter filter) { + switch (filter) { + case TextureFilter::NEAREST: return GL_NEAREST; + case TextureFilter::LINEAR: return GL_LINEAR; + case TextureFilter::NEAREST_MIPMAP_NEAREST: return GL_NEAREST_MIPMAP_NEAREST; + case TextureFilter::LINEAR_MIPMAP_NEAREST: return GL_LINEAR_MIPMAP_NEAREST; + case TextureFilter::NEAREST_MIPMAP_LINEAR: return GL_NEAREST_MIPMAP_LINEAR; + case TextureFilter::LINEAR_MIPMAP_LINEAR: return GL_LINEAR_MIPMAP_LINEAR; + default: return GL_LINEAR; + } + } + + GLenum get_gl_wrap(TextureWrap wrap) { + switch (wrap) { + case TextureWrap::REPEAT: return GL_REPEAT; + case TextureWrap::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT; + case TextureWrap::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE; + case TextureWrap::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER; + default: return GL_REPEAT; + } + } +} + +Texture::Texture() + : handle_(INVALID_HANDLE) + , width_(0) + , height_(0) + , format_(TextureFormat::RGBA8) + , has_mipmaps_(false) { +} + +Texture::~Texture() { + // Don't auto-release, let user control lifetime +} + +bool Texture::load_from_file(const std::string& path, bool generate_mipmaps) { + // Load image using stb_image + int width, height, channels; + stbi_set_flip_vertically_on_load(true); + unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0); + + if (!data) { + Logger::error("Failed to load texture: " + path); + return false; + } + + // Determine format based on channels + TextureFormat format; + switch (channels) { + case 1: format = TextureFormat::R8; break; + case 2: format = TextureFormat::RG8; break; + case 3: format = TextureFormat::RGB8; break; + case 4: format = TextureFormat::RGBA8; break; + default: + Logger::error("Unsupported channel count: " + std::to_string(channels)); + stbi_image_free(data); + return false; + } + + // Create texture + bool success = create(width, height, format); + if (!success) { + stbi_image_free(data); + return false; + } + + // Upload data + success = upload(data, width, height, format); + stbi_image_free(data); + + if (!success) { + return false; + } + + // Generate mipmaps if requested + if (generate_mipmaps) { + this->generate_mipmaps(); + } + + Logger::info("Texture loaded successfully: " + path); + return true; +} + +bool Texture::create(uint width, uint height, TextureFormat format) { + if (handle_ != INVALID_HANDLE) { + Logger::warning("Texture already created, releasing old texture"); + release(); + } + + width_ = width; + height_ = height; + format_ = format; + + glGenTextures(1, &handle_); + glBindTexture(GL_TEXTURE_2D, handle_); + + GLenum internal_format = get_gl_internal_format(format); + GLenum gl_format = get_gl_format(format); + GLenum type = get_gl_type(format); + + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, gl_format, type, nullptr); + + // Set default parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +bool Texture::upload(const void* data, uint width, uint height, TextureFormat format) { + if (handle_ == INVALID_HANDLE) { + Logger::error("Cannot upload to invalid texture"); + return false; + } + + if (width != width_ || height != height_ || format != format_) { + Logger::warning("Upload parameters differ from texture creation, recreating texture"); + create(width, height, format); + } + + glBindTexture(GL_TEXTURE_2D, handle_); + + GLenum gl_format = get_gl_format(format); + GLenum type = get_gl_type(format); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, gl_format, type, data); + + glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +void Texture::set_filter(TextureFilter min_filter, TextureFilter mag_filter) { + if (handle_ == INVALID_HANDLE) { + Logger::error("Cannot set filter on invalid texture"); + return; + } + + glBindTexture(GL_TEXTURE_2D, handle_); + 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 (handle_ == INVALID_HANDLE) { + Logger::error("Cannot set wrap mode on invalid texture"); + return; + } + + glBindTexture(GL_TEXTURE_2D, handle_); + 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 (handle_ == INVALID_HANDLE) { + Logger::error("Cannot generate mipmaps for invalid texture"); + return; + } + + glBindTexture(GL_TEXTURE_2D, handle_); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + has_mipmaps_ = true; +} + +void Texture::bind(uint unit) const { + if (handle_ == INVALID_HANDLE) { + Logger::warning("Attempting to bind invalid texture"); + return; + } + + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, handle_); +} + +void Texture::unbind() const { + glBindTexture(GL_TEXTURE_2D, 0); +} + +void Texture::release() { + if (handle_ != INVALID_HANDLE) { + glDeleteTextures(1, &handle_); + handle_ = INVALID_HANDLE; + } + + width_ = 0; + height_ = 0; + has_mipmaps_ = false; +} + +} // namespace are diff --git a/src/scene/camera.cpp b/src/scene/camera.cpp new file mode 100644 index 0000000..02821da --- /dev/null +++ b/src/scene/camera.cpp @@ -0,0 +1,101 @@ +#include "scene/camera.h" +#include "basic/math.h" +#include + +namespace are { + +Camera::Camera() + : position_(0.0f, 0.0f, 5.0f) + , target_(0.0f, 0.0f, 0.0f) + , up_(0.0f, 1.0f, 0.0f) + , projection_type_(ProjectionType::PERSPECTIVE) + , fov_(glm::radians(45.0f)) + , aspect_(16.0f / 9.0f) + , left_(-1.0f) + , right_(1.0f) + , bottom_(-1.0f) + , top_(1.0f) + , near_(0.1f) + , far_(100.0f) + , view_dirty_(true) + , projection_dirty_(true) { +} + +Camera::~Camera() { +} + +void Camera::set_perspective(float fov, float aspect, float near, float far) { + projection_type_ = ProjectionType::PERSPECTIVE; + fov_ = glm::radians(fov); + aspect_ = aspect; + near_ = near; + far_ = far; + projection_dirty_ = true; +} + +void Camera::set_orthographic(float left, float right, float bottom, float top, float near, float far) { + projection_type_ = ProjectionType::ORTHOGRAPHIC; + left_ = left; + right_ = right; + bottom_ = bottom; + top_ = top; + near_ = near; + far_ = far; + projection_dirty_ = true; +} + +void Camera::set_position(const Vec3& position) { + position_ = position; + view_dirty_ = true; +} + +void Camera::set_target(const Vec3& target) { + target_ = target; + view_dirty_ = true; +} + +void Camera::set_up(const Vec3& up) { + up_ = up; + view_dirty_ = true; +} + +Mat4 Camera::get_view_matrix() const { + if (view_dirty_) { + view_matrix_ = MathUtils::look_at(position_, target_, up_); + view_dirty_ = false; + } + return view_matrix_; +} + +Mat4 Camera::get_projection_matrix() const { + if (projection_dirty_) { + if (projection_type_ == ProjectionType::PERSPECTIVE) { + projection_matrix_ = MathUtils::perspective(fov_, aspect_, near_, far_); + } else { + projection_matrix_ = glm::ortho(left_, right_, bottom_, top_, near_, far_); + } + projection_dirty_ = false; + } + return projection_matrix_; +} + +Mat4 Camera::get_view_projection_matrix() const { + return get_projection_matrix() * get_view_matrix(); +} + +Vec3 Camera::get_forward() const { + return MathUtils::normalize(target_ - position_); +} + +Vec3 Camera::get_right() const { + Vec3 forward = get_forward(); + return MathUtils::normalize(MathUtils::cross(forward, up_)); +} + +Vec3 Camera::get_up() const { + Vec3 forward = get_forward(); + Vec3 right = get_right(); + return MathUtils::cross(right, forward); +} + +} // namespace are diff --git a/src/scene/light.cpp b/src/scene/light.cpp new file mode 100644 index 0000000..00d76bb --- /dev/null +++ b/src/scene/light.cpp @@ -0,0 +1,49 @@ +#include "scene/light.h" +#include + +namespace are { + +Light::Light() + : type_(LightType::POINT) + , position_(0.0f, 5.0f, 0.0f) + , direction_(0.0f, -1.0f, 0.0f) + , color_(1.0f, 1.0f, 1.0f) + , intensity_(1.0f) + , range_(10.0f) + , inner_angle_(glm::radians(30.0f)) + , outer_angle_(glm::radians(45.0f)) { +} + +Light::~Light() { +} + +void Light::set_type(LightType type) { + type_ = type; +} + +void Light::set_position(const Vec3& position) { + position_ = position; +} + +void Light::set_direction(const Vec3& direction) { + direction_ = glm::normalize(direction); +} + +void Light::set_color(const Vec3& color) { + color_ = color; +} + +void Light::set_intensity(float intensity) { + intensity_ = intensity; +} + +void Light::set_range(float range) { + range_ = range; +} + +void Light::set_spot_angles(float inner_angle, float outer_angle) { + inner_angle_ = glm::radians(inner_angle); + outer_angle_ = glm::radians(outer_angle); +} + +} // namespace are diff --git a/src/scene/material.cpp b/src/scene/material.cpp new file mode 100644 index 0000000..9e805de --- /dev/null +++ b/src/scene/material.cpp @@ -0,0 +1,51 @@ +#include "scene/material.h" + +namespace are { + +Material::Material() + : albedo_(1.0f, 1.0f, 1.0f) + , emission_(0.0f, 0.0f, 0.0f) + , metallic_(0.0f) + , roughness_(0.5f) + , ior_(1.5f) + , type_(MaterialType::DIFFUSE) + , albedo_texture_(nullptr) + , normal_texture_(nullptr) { +} + +Material::~Material() { +} + +void Material::set_albedo(const Vec3& albedo) { + albedo_ = albedo; +} + +void Material::set_emission(const Vec3& emission) { + emission_ = emission; +} + +void Material::set_metallic(float metallic) { + metallic_ = glm::clamp(metallic, 0.0f, 1.0f); +} + +void Material::set_roughness(float roughness) { + roughness_ = glm::clamp(roughness, 0.0f, 1.0f); +} + +void Material::set_ior(float ior) { + ior_ = ior; +} + +void Material::set_type(MaterialType type) { + type_ = type; +} + +void Material::set_albedo_texture(std::shared_ptr texture) { + albedo_texture_ = texture; +} + +void Material::set_normal_texture(std::shared_ptr texture) { + normal_texture_ = texture; +} + +} // namespace are diff --git a/src/scene/mesh.cpp b/src/scene/mesh.cpp new file mode 100644 index 0000000..610e75a --- /dev/null +++ b/src/scene/mesh.cpp @@ -0,0 +1,119 @@ +#include "scene/mesh.h" +#include "utils/logger.h" +#include + +namespace are { + +Mesh::Mesh() + : material_id_(0) + , transform_(1.0f) + , vao_(0) + , vbo_(0) + , ebo_(0) + , uploaded_(false) { +} + +Mesh::~Mesh() { + release_gpu_resources(); +} + +void Mesh::set_vertices(const std::vector& vertices) { + vertices_ = vertices; + uploaded_ = false; +} + +void Mesh::set_indices(const std::vector& indices) { + indices_ = indices; + uploaded_ = false; +} + +void Mesh::set_material(uint material_id) { + material_id_ = material_id; +} + +void Mesh::set_transform(const Mat4& transform) { + transform_ = transform; +} + +bool Mesh::upload_to_gpu() { + if (uploaded_) { + Logger::warning("Mesh already uploaded to GPU"); + return true; + } + + if (vertices_.empty()) { + Logger::error("Cannot upload mesh: no vertices"); + return false; + } + + if (indices_.empty()) { + Logger::error("Cannot upload mesh: no indices"); + return false; + } + + // Generate VAO + glGenVertexArrays(1, &vao_); + glBindVertexArray(vao_); + + // Generate and upload VBO + glGenBuffers(1, &vbo_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(Vertex), + vertices_.data(), GL_STATIC_DRAW); + + // Generate and upload EBO + glGenBuffers(1, &ebo_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(uint), + indices_.data(), GL_STATIC_DRAW); + + // Set vertex attributes + // Location 0: Position + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, position_)); + + // Location 1: Normal + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, normal_)); + + // Location 2: TexCoord + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, texcoord_)); + + // Location 3: Tangent + glEnableVertexAttribArray(3); + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), + (void*)offsetof(Vertex, tangent_)); + + glBindVertexArray(0); + + uploaded_ = true; + Logger::info("Mesh uploaded to GPU successfully"); + return true; +} + +void Mesh::release_gpu_resources() { + if (!uploaded_) return; + + if (vao_ != 0) { + glDeleteVertexArrays(1, &vao_); + vao_ = 0; + } + + if (vbo_ != 0) { + glDeleteBuffers(1, &vbo_); + vbo_ = 0; + } + + if (ebo_ != 0) { + glDeleteBuffers(1, &ebo_); + ebo_ = 0; + } + + uploaded_ = false; +} + +} // namespace are diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp new file mode 100644 index 0000000..7e27f3b --- /dev/null +++ b/src/scene/scene.cpp @@ -0,0 +1,44 @@ +#include "scene/scene.h" + +namespace are { + +Scene::Scene() { + // Create default camera + camera_ = std::make_shared(); +} + +Scene::~Scene() { + clear(); +} + +uint Scene::add_mesh(std::shared_ptr mesh) { + meshes_.push_back(mesh); + return static_cast(meshes_.size() - 1); +} + +uint Scene::add_material(std::shared_ptr material) { + materials_.push_back(material); + return static_cast(materials_.size() - 1); +} + +uint Scene::add_light(std::shared_ptr light) { + lights_.push_back(light); + return static_cast(lights_.size() - 1); +} + +void Scene::set_camera(std::shared_ptr camera) { + camera_ = camera; +} + +void Scene::clear() { + meshes_.clear(); + materials_.clear(); + lights_.clear(); +} + +void Scene::update(float delta_time) { + // Reserved for future animation/physics updates + (void)delta_time; // Suppress unused parameter warning +} + +} // namespace are diff --git a/src/utils/config.cpp b/src/utils/config.cpp new file mode 100644 index 0000000..23b61af --- /dev/null +++ b/src/utils/config.cpp @@ -0,0 +1,144 @@ +#include "utils/config.h" +#include "utils/logger.h" +#include +#include +#include + +namespace are { + +// Static storage +static std::unordered_map g_config_map; + +// Helper function to trim whitespace +static std::string trim(const std::string& str) { + size_t first = str.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) return ""; + size_t last = str.find_last_not_of(" \t\r\n"); + return str.substr(first, last - first + 1); +} + +bool Config::load(const std::string& path) { + std::ifstream file(path); + if (!file.is_open()) { + Logger::error("Failed to open config file: " + path); + return false; + } + + g_config_map.clear(); + + std::string line; + std::string current_section; + + while (std::getline(file, line)) { + line = trim(line); + + // Skip empty lines and comments + if (line.empty() || line[0] == '#' || line[0] == ';') { + continue; + } + + // Section header + if (line[0] == '[' && line.back() == ']') { + current_section = line.substr(1, line.length() - 2); + continue; + } + + // Key-value pair + size_t pos = line.find('='); + if (pos != std::string::npos) { + std::string key = trim(line.substr(0, pos)); + std::string value = trim(line.substr(pos + 1)); + + // Add section prefix if in a section + if (!current_section.empty()) { + key = current_section + "." + key; + } + + g_config_map[key] = value; + } + } + + Logger::info("Config loaded: " + path + " (" + std::to_string(g_config_map.size()) + " entries)"); + return true; +} + +bool Config::save(const std::string& path) { + std::ofstream file(path); + if (!file.is_open()) { + Logger::error("Failed to open config file for writing: " + path); + return false; + } + + for (const auto& pair : g_config_map) { + file << pair.first << "=" << pair.second << std::endl; + } + + Logger::info("Config saved: " + path); + return true; +} + +std::string Config::get_string(const std::string& key, const std::string& default_value) { + auto it = g_config_map.find(key); + if (it != g_config_map.end()) { + return it->second; + } + return default_value; +} + +int Config::get_int(const std::string& key, int default_value) { + auto it = g_config_map.find(key); + if (it != g_config_map.end()) { + try { + return std::stoi(it->second); + } catch (...) { + Logger::warning("Failed to parse int for key: " + key); + } + } + return default_value; +} + +float Config::get_float(const std::string& key, float default_value) { + auto it = g_config_map.find(key); + if (it != g_config_map.end()) { + try { + return std::stof(it->second); + } catch (...) { + Logger::warning("Failed to parse float for key: " + key); + } + } + return default_value; +} + +bool Config::get_bool(const std::string& key, bool default_value) { + auto it = g_config_map.find(key); + if (it != g_config_map.end()) { + std::string value = it->second; + std::transform(value.begin(), value.end(), value.begin(), ::tolower); + + if (value == "true" || value == "1" || value == "yes" || value == "on") { + return true; + } + if (value == "false" || value == "0" || value == "no" || value == "off") { + return false; + } + } + return default_value; +} + +void Config::set_string(const std::string& key, const std::string& value) { + g_config_map[key] = value; +} + +void Config::set_int(const std::string& key, int value) { + g_config_map[key] = std::to_string(value); +} + +void Config::set_float(const std::string& key, float value) { + g_config_map[key] = std::to_string(value); +} + +void Config::set_bool(const std::string& key, bool value) { + g_config_map[key] = value ? "true" : "false"; +} + +} // namespace are diff --git a/src/utils/logger.cpp b/src/utils/logger.cpp new file mode 100644 index 0000000..708e317 --- /dev/null +++ b/src/utils/logger.cpp @@ -0,0 +1,103 @@ +#include "utils/logger.h" +#include +#include +#include +#include +#include + +namespace are { + +// Static members +static LogLevel g_min_level = LogLevel::DEBUG; +static std::ofstream g_log_file; +static bool g_initialized = false; + +bool Logger::initialize(const std::string& log_file) { + if (g_initialized) { + return true; + } + + if (!log_file.empty()) { + g_log_file.open(log_file, std::ios::out | std::ios::app); + if (!g_log_file.is_open()) { + std::cerr << "Failed to open log file: " << log_file << std::endl; + return false; + } + } + + g_initialized = true; + return true; +} + +void Logger::shutdown() { + if (g_log_file.is_open()) { + g_log_file.close(); + } + g_initialized = false; +} + +static std::string get_current_time() { + auto now = std::time(nullptr); + auto tm = *std::localtime(&now); + std::ostringstream oss; + oss << std::put_time(&tm, "%H:%M:%S"); + return oss.str(); +} + +static std::string level_to_string(LogLevel level) { + switch (level) { + case LogLevel::DEBUG: return "DEBUG"; + case LogLevel::INFO: return "INFO"; + case LogLevel::WARNING: return "WARN"; + case LogLevel::ERROR: return "ERROR"; + case LogLevel::FATAL: return "FATAL"; + default: return "UNKNOWN"; + } +} + +void Logger::log(LogLevel level, const std::string& message) { + if (level < g_min_level) return; + + std::string time_str = get_current_time(); + std::string level_str = level_to_string(level); + std::string formatted = "[" + time_str + "] [" + level_str + "] " + message; + + // Console output + if (level >= LogLevel::ERROR) { + std::cerr << formatted << std::endl; + } else { + std::cout << formatted << std::endl; + } + + // File output + if (g_log_file.is_open()) { + g_log_file << formatted << std::endl; + g_log_file.flush(); + } +} + +void Logger::debug(const std::string& message) { + log(LogLevel::DEBUG, message); +} + +void Logger::info(const std::string& message) { + log(LogLevel::INFO, message); +} + +void Logger::warning(const std::string& message) { + log(LogLevel::WARNING, message); +} + +void Logger::error(const std::string& message) { + log(LogLevel::ERROR, message); +} + +void Logger::fatal(const std::string& message) { + log(LogLevel::FATAL, message); +} + +void Logger::set_level(LogLevel level) { + g_min_level = level; +} + +} // namespace are