diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..07f98a4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,133 @@ +# AGENTS.md - Aurora Rendering Engine + +## Project Overview +Aurora Rendering Engine (ARE) is a high-performance path tracing library in C++ developed by NanoEra Studio. It provides a rendering framework using OpenGL 4.3 with support for GPU-accelerated ray tracing, BVH acceleration, and denoising. + +## Build Commands + +### Standard Build +```bash +mkdir build && cd build +cmake .. +cmake --build . +``` + +### Build Types +- **Debug**: `cmake -DCMAKE_BUILD_TYPE=Debug ..` +- **Release**: `cmake -DCMAKE_BUILD_TYPE=Release ..` (default) + +### Running Examples +The main example is the Cornell Box demo: +```bash +./examples/cornell_box +``` + +### Testing +This project currently has **no built-in unit tests**. Testing is performed manually by running the example executables. There are no test-specific build targets or test frameworks configured. + +### Linting/Code Formatting +- **Format**: Use `.clang-format` configuration in project root + ```bash + clang-format -i src/*.cpp include/**/*.h + ``` +- **Clangd**: IDE integration via `.clangd` file (C++20, includes paths configured) + +## Code Style Guidelines + +### General Conventions +- **C++ Standard**: C++17 (project CMake), C++20 (clangd for IDE) +- **Indentation**: Tabs (see `.clang-format`: `UseTab: Always`) +- **Line Length**: Unlimited (`ColumnLimit: 0`) +- **Brace Style**: Attach (see `.clang-format`: `BreakBeforeBraces: Attach`) + +### File Organization +- **Headers**: `include/` - Public API +- **Source**: `src/` - Implementation +- **Include format**: `#include "path/to/header.h"` (quotes for project headers) + +### Naming Conventions +- **Classes**: `PascalCase` (e.g., `Renderer`, `Scene`) +- **Functions**: `PascalCase` (e.g., `initialize()`, `render()`) +- **Member variables**: `snake_case_` with trailing underscore (e.g., `config_`, `frame_count_`) +- **Types (aliases)**: `PascalCase` (e.g., `Vec3`, `Mat4`, `TextureHandle`) +- **Enums**: `PascalCase` with `k` prefix for values (e.g., `LogLevel::ARE_LOG_INFO`) + +### Header Guards +```cpp +#ifndef ARE_INCLUDE_PATH_TO_FILENAME_H +#define ARE_INCLUDE_PATH_TO_FILENAME_H +// ... content ... +#endif // ARE_INCLUDE_PATH_TO_FILENAME_H +``` + +### Namespace +- All code lives in `are` namespace +- Namespace closing comment: `} // namespace are` + +### Imports/Includes Order +1. Corresponding header (for .cpp files) +2. Project headers (alphabetically within group) +3. External library headers (e.g., ``, ``) +4. Standard library headers + +### Comments +- Use Doxygen-style `/** */` or `/* */` for function documentation +- Brief descriptions in header files, implementation details in .cpp +- Avoid unnecessary inline comments + +### Error Handling +- Use `ARE_LOG_ERROR()`, `ARE_LOG_WARN()`, `ARE_LOG_CRITICAL()` macros +- Return `false` on failure, `true` on success +- Check initialization states before operations +- Log with context: `"Failed to initialize X"` + +### Smart Pointers +- Use `std::unique_ptr` for exclusive ownership +- Use `std::shared_ptr` for shared ownership +- Avoid raw `new`/`delete` + +### GLM Types +Use GLM for all math types: +```cpp +using Vec2 = glm::vec2; +using Vec3 = glm::vec3; +using Vec4 = glm::vec4; +using Mat3 = glm::mat3; +using Mat4 = glm::mat4; +``` + +### OpenGL Resources +- Follow OpenGL best practices +- Check OpenGL errors during development (debug builds) +- Clean up resources in destructors or `shutdown()` methods + +## Project Structure +``` +ARE/ +├── include/ # Public headers +│ ├── basic/ # Types, constants, math +│ ├── core/ # Renderer, raytracer, BVH +│ ├── scene/ # Scene objects (mesh, material, camera) +│ ├── resource/ # GPU resources (shader, texture, buffer) +│ └── utils/ # Utilities (logger, config) +├── src/ # Implementation files +├── examples/ # Example applications +├── shaders/ # GLSL shaders +├── lib/ # External libraries (glad, stb, spdlog) +└── build/ # Build output (generated) +``` + +## Key Classes +- `Renderer` - Main rendering engine interface +- `Scene` - Scene container (meshes, materials, lights, camera) +- `RayTracer` - GPU ray tracing implementation +- `BVH` - Acceleration structure +- `GBuffer` - Geometry buffer for deferred rendering + +## Dependencies +- OpenGL 4.3 +- GLFW 3 +- GLAD (OpenGL loader) +- GLM (math library) +- stb-image (image loading) +- spdlog (logging) diff --git a/examples/cornell_box b/examples/cornell_box index 2e4739c..53c8894 100644 Binary files a/examples/cornell_box and b/examples/cornell_box differ diff --git a/examples/cornell_box.cpp b/examples/cornell_box.cpp index d784b5f..f762c30 100644 --- a/examples/cornell_box.cpp +++ b/examples/cornell_box.cpp @@ -1,17 +1,17 @@ #include -#include -#include -#include -#include -#include -#include #include #include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include using namespace are; @@ -20,7 +20,7 @@ const uint WINDOW_WIDTH = 800; const uint WINDOW_HEIGHT = 800; // Global state -GLFWwindow* g_window = nullptr; +GLFWwindow *g_window = nullptr; std::unique_ptr g_renderer = nullptr; std::unique_ptr g_scene = nullptr; std::shared_ptr g_camera = nullptr; // Keep a direct reference to camera @@ -47,466 +47,534 @@ float g_deltaTime = 0.0f; float g_lastFrame = 0.0f; // GLFW error callback -void glfw_error_callback(int error, const char* description) { - ARE_LOG_ERROR("GLFW Error " + std::to_string(error) + ": " + std::string(description)); +void glfw_error_callback(int error, const char *description) { + ARE_LOG_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; +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; +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 Create a sphere mesh +std::shared_ptr create_sphere(float radius, uint segments, uint rings, uint material_id) { + auto mesh = std::make_shared(); + + std::vector vertices; + std::vector indices; + + for (uint ring = 0; ring <= rings; ++ring) { + float theta = ring * glm::pi() / rings; + float sin_theta = sin(theta); + float cos_theta = cos(theta); + + for (uint seg = 0; seg <= segments; ++seg) { + float phi = seg * 2.0f * glm::pi() / segments; + float x = cos(phi) * sin_theta; + float y = cos_theta; + float z = sin(phi) * sin_theta; + + Vec3 pos = Vec3(x, y, z) * radius; + Vec3 normal = Vec3(x, y, z); + Vec2 uv = Vec2((float)seg / segments, (float)ring / rings); + Vec3 tangent = Vec3(-sin(phi), 0.0f, cos(phi)); + + vertices.push_back({ pos, normal, uv, tangent }); + } + } + + for (uint ring = 0; ring < rings; ++ring) { + for (uint seg = 0; seg < segments; ++seg) { + uint current = ring * (segments + 1) + seg; + indices.push_back(current); + indices.push_back(current + segments + 1); + indices.push_back(current + 1); + indices.push_back(current + 1); + indices.push_back(current + segments + 1); + indices.push_back(current + segments + 2); + } + } + + mesh->set_vertices(vertices); + mesh->set_indices(indices); + mesh->set_material(material_id); + mesh->compute_tangents(); + + 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), white_id); - short_box->upload_to_gpu(); - g_scene->add_mesh(short_box); - - // Setup camera - g_camera = std::make_shared(); - g_camera->set_position(g_cameraPos); - g_camera->set_target(g_cameraTarget); - g_camera->set_up(g_cameraUp); - g_camera->set_perspective(45.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); - g_scene->set_camera(g_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); - - ARE_LOG_INFO("Cornell Box scene created"); + 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.0f); + metal_material->set_type(MaterialType::METAL); + uint metal_id = g_scene->add_material(metal_material); + + // 5: Glass/Dielectric (refraction) + auto glass_material = std::make_shared(); + glass_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); + glass_material->set_ior(0.5f); + glass_material->set_roughness(0.0f); + glass_material->set_type(MaterialType::DIELECTRIC); + uint glass_id = g_scene->add_material(glass_material); + + // 6: Yellow emissive sphere + auto emissive_sphere_mat = std::make_shared(); + emissive_sphere_mat->set_albedo(Vec3(1.0f, 0.8f, 0.2f)); + emissive_sphere_mat->set_emission(Vec3(5.0f, 4.0f, 1.0f)); + emissive_sphere_mat->set_type(MaterialType::EMISSIVE); + uint emissive_sphere_id = g_scene->add_material(emissive_sphere_mat); + + // 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), glass_id); + short_box->upload_to_gpu(); + g_scene->add_mesh(short_box); + + // // Glass sphere (dielectric/refraction test) + // auto glass_sphere = create_sphere(0.4f, 32, 16, glass_id); + // glass_sphere->set_position(Vec3(-0.5f, -0.6f, 0.0f)); + // glass_sphere->upload_to_gpu(); + // g_scene->add_mesh(glass_sphere); + // + // // Yellow emissive sphere (emission test) + // auto emissive_sphere = create_sphere(0.25f, 32, 16, emissive_sphere_id); + // emissive_sphere->set_position(Vec3(0.5f, -0.75f, 0.3f)); + // emissive_sphere->upload_to_gpu(); + // g_scene->add_mesh(emissive_sphere); + + // Setup camera + g_camera = std::make_shared(); + g_camera->set_position(g_cameraPos); + g_camera->set_target(g_cameraTarget); + g_camera->set_up(g_cameraUp); + g_camera->set_perspective(45.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); + g_scene->set_camera(g_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); + + ARE_LOG_INFO("Cornell Box scene created"); } /// @brief Initialize GLFW and create window bool init_window() { - glfwSetErrorCallback(glfw_error_callback); - - if (!glfwInit()) { - ARE_LOG_ERROR("Failed to initialize GLFW"); - return false; - } - - ARE_LOG_INFO("GLFW initialized successfully"); - - 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); - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - glfwWindowHint(GLFW_SAMPLES, 0); - - g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); - - if (!g_window) { - ARE_LOG_ERROR("Failed to create GLFW window"); - glfwTerminate(); - return false; - } - - glfwMakeContextCurrent(g_window); - glfwSwapInterval(1); - - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - ARE_LOG_ERROR("Failed to initialize GLAD"); - return false; - } - - return true; + glfwSetErrorCallback(glfw_error_callback); + + if (!glfwInit()) { + ARE_LOG_ERROR("Failed to initialize GLFW"); + return false; + } + + ARE_LOG_INFO("GLFW initialized successfully"); + + 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); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_SAMPLES, 0); + + g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); + + if (!g_window) { + ARE_LOG_ERROR("Failed to create GLFW window"); + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(g_window); + glfwSwapInterval(1); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + ARE_LOG_ERROR("Failed to initialize GLAD"); + return false; + } + + return true; } // --- Input Processing --- void process_input() { - // Calculate delta time - float currentFrame = glfwGetTime(); - g_deltaTime = currentFrame - g_lastFrame; - g_lastFrame = currentFrame; - - float velocity = g_moveSpeed * g_deltaTime; - bool camera_changed = false; + // Calculate delta time + float currentFrame = glfwGetTime(); + g_deltaTime = currentFrame - g_lastFrame; + g_lastFrame = currentFrame; - // 1. Mouse Rotation (Left Button Hold) - if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { - double xpos, ypos; - glfwGetCursorPos(g_window, &xpos, &ypos); - - if (g_firstMouse) { - g_lastX = xpos; - g_lastY = ypos; - g_firstMouse = false; - } - - float xoffset = xpos - g_lastX; - float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top - g_lastX = xpos; - g_lastY = ypos; - - // Only update if mouse actually moved - if (xoffset != 0.0f || yoffset != 0.0f) { - xoffset *= g_mouseSensitivity; - yoffset *= g_mouseSensitivity; - - g_yaw += xoffset; - g_pitch += yoffset; - - // Constrain pitch - if (g_pitch > 89.0f) g_pitch = 89.0f; - if (g_pitch < -89.0f) g_pitch = -89.0f; - - camera_changed = true; - } - } - else { - g_firstMouse = true; // Reset when released - } - - // 2. Calculate Direction Vectors - glm::vec3 front; - front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); - front.y = sin(glm::radians(g_pitch)); - front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); - glm::vec3 frontNorm = glm::normalize(front); - - glm::vec3 rightNorm = glm::normalize(glm::cross(frontNorm, glm::vec3(g_worldUp.x, g_worldUp.y, g_worldUp.z))); - - // 3. Keyboard Movement (WASD) - glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z); - - if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) { - pos += frontNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) { - pos -= frontNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) { - pos -= rightNorm * velocity; - camera_changed = true; - } - if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) { - pos += rightNorm * velocity; - camera_changed = true; - } - - // 4. Apply changes to Scene Camera and Notify Renderer - if (camera_changed) { - g_cameraPos = Vec3(pos.x, pos.y, pos.z); - - // Target = Position + Front - Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z); - - g_camera->set_position(g_cameraPos); - g_camera->set_target(newTarget); - - // CRITICAL: Notify renderer to reset accumulation - g_renderer->notify_scene_changed(*g_scene); - } + float velocity = g_moveSpeed * g_deltaTime; + bool camera_changed = false; + + // 1. Mouse Rotation (Left Button Hold) + if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { + double xpos, ypos; + glfwGetCursorPos(g_window, &xpos, &ypos); + + if (g_firstMouse) { + g_lastX = xpos; + g_lastY = ypos; + g_firstMouse = false; + } + + float xoffset = xpos - g_lastX; + float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top + g_lastX = xpos; + g_lastY = ypos; + + // Only update if mouse actually moved + if (xoffset != 0.0f || yoffset != 0.0f) { + xoffset *= g_mouseSensitivity; + yoffset *= g_mouseSensitivity; + + g_yaw += xoffset; + g_pitch += yoffset; + + // Constrain pitch + if (g_pitch > 89.0f) + g_pitch = 89.0f; + if (g_pitch < -89.0f) + g_pitch = -89.0f; + + camera_changed = true; + } + } else { + g_firstMouse = true; // Reset when released + } + + // 2. Calculate Direction Vectors + glm::vec3 front; + front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); + front.y = sin(glm::radians(g_pitch)); + front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); + glm::vec3 frontNorm = glm::normalize(front); + + glm::vec3 rightNorm = glm::normalize(glm::cross(frontNorm, glm::vec3(g_worldUp.x, g_worldUp.y, g_worldUp.z))); + + // 3. Keyboard Movement (WASD) + glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z); + + if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) { + pos += frontNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) { + pos -= frontNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) { + pos -= rightNorm * velocity; + camera_changed = true; + } + if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) { + pos += rightNorm * velocity; + camera_changed = true; + } + + // 4. Apply changes to Scene Camera and Notify Renderer + if (camera_changed) { + g_cameraPos = Vec3(pos.x, pos.y, pos.z); + + // Target = Position + Front + Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z); + + g_camera->set_position(g_cameraPos); + g_camera->set_target(newTarget); + + // CRITICAL: Notify renderer to reset accumulation + g_renderer->notify_scene_changed(*g_scene); + } } /// @brief Main render loop void render_loop() { - ARE_LOG_INFO("Entering render loop..."); - - int frame_count = 0; - double fps_time = glfwGetTime(); - g_lastFrame = glfwGetTime(); // Initialize for delta time - - while (!glfwWindowShouldClose(g_window)) { - // Process input at the start of the frame - process_input(); - - // 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; - } - - // ESC to exit - if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { - glfwSetWindowShouldClose(g_window, true); - } - } - - ARE_LOG_INFO("Exiting render loop"); + ARE_LOG_INFO("Entering render loop..."); + + int frame_count = 0; + double fps_time = glfwGetTime(); + g_lastFrame = glfwGetTime(); // Initialize for delta time + + while (!glfwWindowShouldClose(g_window)) { + // Process input at the start of the frame + process_input(); + + // 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; + } + + // ESC to exit + if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + glfwSetWindowShouldClose(g_window, true); + } + } + + ARE_LOG_INFO("Exiting render loop"); } /// @brief Cleanup void cleanup() { - ARE_LOG_INFO("Cleaning up..."); - - if (g_renderer) { - g_renderer->shutdown(); - g_renderer.reset(); - } - - g_scene.reset(); - - if (g_window) { - glfwDestroyWindow(g_window); - glfwTerminate(); - } - - ARE_LOG_INFO("Cleanup complete"); + ARE_LOG_INFO("Cleaning up..."); + + if (g_renderer) { + g_renderer->shutdown(); + g_renderer.reset(); + } + + g_scene.reset(); + + if (g_window) { + glfwDestroyWindow(g_window); + glfwTerminate(); + } + + ARE_LOG_INFO("Cleanup complete"); } int main() { - ARE_LOG_INFO("==========================================="); - ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo"); - ARE_LOG_INFO("==========================================="); + ARE_LOG_INFO("==========================================="); + ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo"); + ARE_LOG_INFO("==========================================="); - if (!init_window()) { - cleanup(); - ARE_LOG_ERROR("Failed to initialize window"); - Logger::shutdown(); - return -1; - } - - ARE_LOG_INFO("Setting up Cornell Box scene..."); - setup_cornell_box(); - - ARE_LOG_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_ = true; - config.enable_denoising_ = false; - - g_renderer = std::make_unique(config); - if (!g_renderer->initialize()) { - ARE_LOG_ERROR("Failed to initialize renderer"); - cleanup(); - Logger::shutdown(); - return -1; - } - - ARE_LOG_INFO("==========================================="); - ARE_LOG_INFO("Renderer initialized successfully!"); - ARE_LOG_INFO("Controls:"); - ARE_LOG_INFO(" WASD - Move Camera"); - ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera"); - ARE_LOG_INFO(" ESC - Exit"); - ARE_LOG_INFO("==========================================="); - - render_loop(); - cleanup(); - - ARE_LOG_INFO("Cornell Box demo finished"); - Logger::shutdown(); - - return 0; + if (!init_window()) { + cleanup(); + ARE_LOG_ERROR("Failed to initialize window"); + Logger::shutdown(); + return -1; + } + + ARE_LOG_INFO("Setting up Cornell Box scene..."); + setup_cornell_box(); + + ARE_LOG_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_ = true; + config.enable_denoising_ = false; + + g_renderer = std::make_unique(config); + if (!g_renderer->initialize()) { + ARE_LOG_ERROR("Failed to initialize renderer"); + cleanup(); + Logger::shutdown(); + return -1; + } + + ARE_LOG_INFO("==========================================="); + ARE_LOG_INFO("Renderer initialized successfully!"); + ARE_LOG_INFO("Controls:"); + ARE_LOG_INFO(" WASD - Move Camera"); + ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera"); + ARE_LOG_INFO(" ESC - Exit"); + ARE_LOG_INFO("==========================================="); + + render_loop(); + cleanup(); + + ARE_LOG_INFO("Cornell Box demo finished"); + Logger::shutdown(); + + return 0; } diff --git a/include/scene/material.h b/include/scene/material.h index caca8fc..810465b 100644 --- a/include/scene/material.h +++ b/include/scene/material.h @@ -3,10 +3,22 @@ #include "basic/types.h" #include "resource/texture.h" +#include #include namespace are { +// Texture slot enumeration for PBR materials +enum class TextureSlot { + ALBEDO = 0, + NORMAL = 1, + METALLIC = 2, + ROUGHNESS = 3, + AO = 4, + EMISSION = 5, + COUNT = 6 +}; + // Material type enumeration enum class MaterialType { DIFFUSE = 0, @@ -72,6 +84,37 @@ public: */ void set_normal_texture(std::shared_ptr texture); + /* + * @brief Set metallic map + * @param texture Metallic texture (R channel) + */ + void set_metallic_texture(std::shared_ptr texture); + + /* + * @brief Set roughness map + * @param texture Roughness texture (G channel) + */ + void set_roughness_texture(std::shared_ptr texture); + + /* + * @brief Set ambient occlusion map + * @param texture AO texture (R channel) + */ + void set_ao_texture(std::shared_ptr texture); + + /* + * @brief Set emission map + * @param texture Emission texture (RGB) + */ + void set_emission_texture(std::shared_ptr texture); + + /* + * @brief Set texture for a specific slot + * @param slot Texture slot + * @param texture Texture to set + */ + void set_texture(TextureSlot slot, std::shared_ptr texture); + /* * @brief Get albedo color * @return Albedo color @@ -125,7 +168,7 @@ public: * @return Albedo texture (nullptr if none) */ std::shared_ptr get_albedo_texture() const { - return albedo_texture_; + return textures_[static_cast(TextureSlot::ALBEDO)]; } /* @@ -133,7 +176,57 @@ public: * @return Normal texture (nullptr if none) */ std::shared_ptr get_normal_texture() const { - return normal_texture_; + return textures_[static_cast(TextureSlot::NORMAL)]; + } + + /* + * @brief Get metallic texture + * @return Metallic texture (nullptr if none) + */ + std::shared_ptr get_metallic_texture() const { + return textures_[static_cast(TextureSlot::METALLIC)]; + } + + /* + * @brief Get roughness texture + * @return Roughness texture (nullptr if none) + */ + std::shared_ptr get_roughness_texture() const { + return textures_[static_cast(TextureSlot::ROUGHNESS)]; + } + + /* + * @brief Get AO texture + * @return AO texture (nullptr if none) + */ + std::shared_ptr get_ao_texture() const { + return textures_[static_cast(TextureSlot::AO)]; + } + + /* + * @brief Get emission texture + * @return Emission texture (nullptr if none) + */ + std::shared_ptr get_emission_texture() const { + return textures_[static_cast(TextureSlot::EMISSION)]; + } + + /* + * @brief Get texture for a specific slot + * @param slot Texture slot + * @return Texture (nullptr if none) + */ + std::shared_ptr get_texture(TextureSlot slot) const { + return textures_[static_cast(slot)]; + } + + /* + * @brief Check if material has texture for slot + * @param slot Texture slot + * @return True if texture exists + */ + bool has_texture(TextureSlot slot) const { + return textures_[static_cast(slot)] != nullptr; } private: @@ -144,8 +237,7 @@ private: float ior_; MaterialType type_; - std::shared_ptr albedo_texture_; - std::shared_ptr normal_texture_; + std::array, static_cast(TextureSlot::COUNT)> textures_; }; } // namespace are diff --git a/include/scene/mesh.h b/include/scene/mesh.h index 12223dc..9cc238d 100644 --- a/include/scene/mesh.h +++ b/include/scene/mesh.h @@ -2,6 +2,7 @@ #define ARE_INCLUDE_SCENE_MESH_H #include "basic/types.h" +#include #include namespace are { @@ -39,6 +40,14 @@ public: */ void set_transform(const Mat4 &transform); + /* + * @brief Set position (sugar for transform) + * @param position Position vector + */ + void set_position(const Vec3 &position) { + transform_ = glm::translate(Mat4(1.0f), position); + } + /* * @brief Get vertices * @return Vertex array @@ -77,6 +86,12 @@ public: */ bool upload_to_gpu(); + /* + * @brief Compute tangents from positions, normals and texcoords + * Should be called after set_vertices and set_indices + */ + void compute_tangents(); + // Release GPU resources void release_gpu_resources(); diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index a34c5eb..fe0f9c8 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -32,12 +32,14 @@ layout(binding = 4, rgba32f) uniform image2D accumulation_image; struct Material { vec3 albedo; - float metallic; vec3 emission; + float metallic; float roughness; int type; float ior; - vec2 padding; + float padding1; + float padding2; + uint texture_handles[6]; }; struct Light { @@ -63,6 +65,7 @@ struct HitInfo { vec3 normal; vec2 texcoord; uint material_id; + int material_type; // material type from G-Buffer }; struct ScatterResult { @@ -100,6 +103,15 @@ uniform mat4 u_inv_view_projection; uniform bool u_enable_accumulation; uniform bool u_use_bvh; uniform uint u_bvh_node_count; +uniform bool u_enable_textures; + +// Texture samplers for PBR +layout(binding = 10) uniform sampler2D u_texture_albedo; +layout(binding = 11) uniform sampler2D u_texture_normal; +layout(binding = 12) uniform sampler2D u_texture_metallic; +layout(binding = 13) uniform sampler2D u_texture_roughness; +layout(binding = 14) uniform sampler2D u_texture_ao; +layout(binding = 15) uniform sampler2D u_texture_emission; // ============================================================================ // Utility @@ -353,6 +365,7 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { hit.normal = vec3(0.0, 1.0, 0.0); hit.texcoord = vec2(0.0); hit.material_id = 0u; + hit.material_type = 0; vec4 pos = imageLoad(g_position, pixel_coords); if (pos.w <= 0.5) { @@ -364,11 +377,16 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { // integer material id uint mid = imageLoad(g_material_id, pixel_coords).r; + + // material type stored in g_material.w + vec4 mat = imageLoad(g_material, pixel_coords); + int mtype = int(mat.w); hit.hit = true; hit.position = p; hit.normal = n; hit.material_id = mid; + hit.material_type = mtype; // For RR/any debug usage; path tracing uses this as starting point only. hit.t = length(p - ray.origin); @@ -380,6 +398,54 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { // Material + scattering // ============================================================================ +// Apply normal map in world space +vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_handle) { + if (normal_handle == 0 || !u_enable_textures) return normal; + + vec3 T = normalize(tangent - normal * dot(tangent, normal)); + vec3 B = cross(normal, T); + mat3 TBN = mat3(T, B, normal); + + vec3 map_n = texture(u_texture_normal, texcoord).xyz * 2.0 - 1.0; + return normalize(TBN * map_n); +} + +// Apply material textures to get final PBR values +void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec3 tangent) { + if (!u_enable_textures) return; + + // Albedo texture + if (mat.texture_handles[0] != 0) { + mat.albedo *= texture(u_texture_albedo, texcoord).rgb; + } + + // Normal map + if (mat.texture_handles[1] != 0) { + normal = apply_normal_map(normal, texcoord, tangent, mat.texture_handles[1]); + } + + // Metallic texture + if (mat.texture_handles[2] != 0) { + mat.metallic *= texture(u_texture_metallic, texcoord).r; + } + + // Roughness texture + if (mat.texture_handles[3] != 0) { + mat.roughness *= texture(u_texture_roughness, texcoord).r; + } + + // AO texture (multiply) + if (mat.texture_handles[4] != 0) { + float ao = texture(u_texture_ao, texcoord).r; + mat.albedo *= ao; + } + + // Emission texture (add) + if (mat.texture_handles[5] != 0) { + mat.emission += texture(u_texture_emission, texcoord).rgb; + } +} + vec3 fresnel_schlick(float cos_theta, vec3 f0) { return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); } @@ -423,18 +489,37 @@ ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout ui r.attenuation = vec3(1.0); vec3 unit_dir = normalize(ray_in.direction); - float cos_theta = min(dot(-unit_dir, hit.normal), 1.0); + float cos_theta = dot(-unit_dir, hit.normal); float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); - - float refraction_ratio = dot(unit_dir, hit.normal) < 0.0 ? (1.0 / mat.ior) : mat.ior; - bool cannot_refract = refraction_ratio * sin_theta > 1.0; - float reflect_prob = fresnel_dielectric(cos_theta, refraction_ratio); - + + // Determine if ray is entering or exiting the material + // If dot(dir, normal) < 0, ray is entering (from air into material) + bool entering = cos_theta > 0.0; + + // eta: ratio of indices (etai/etat) + // Entering: eta = 1.0/ior (air to material) + // Exiting: eta = ior/1.0 (material to air) + float eta = entering ? (1.0 / mat.ior) : mat.ior; + + // Use correct normal for refraction calculation + // When exiting, we need to use -normal + vec3 normal = entering ? hit.normal : -hit.normal; + + // Check for total internal reflection + float sin_theta_t = eta * sin_theta; + bool total_internal_reflection = sin_theta_t >= 1.0; + + // Fresnel reflectance (Schlick approximation) + float f0 = pow((1.0 - mat.ior) / (1.0 + mat.ior), 2.0); + float f = f0 + (1.0 - f0) * pow(1.0 - abs(cos_theta), 5.0); + vec3 dir; - if (cannot_refract || random_float(seed) < reflect_prob) { - dir = reflect_vector(unit_dir, hit.normal); + if (total_internal_reflection || random_float(seed) < f) { + // Reflect + dir = reflect_vector(unit_dir, normal); } else { - dir = refract_vector(unit_dir, hit.normal, refraction_ratio); + // Refract + dir = refract_vector(unit_dir, normal, eta); } r.scattered_ray.origin = hit.position + dir * EPSILON; @@ -547,6 +632,16 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords); if (hit0.hit) { Material mat0 = fetch_material(hit0.material_id); + + // Override material type from G-Buffer if available + if (hit0.material_type >= 0) { + mat0.type = hit0.material_type; + } + + // Apply PBR textures + vec3 tangent0 = normalize(cross(hit0.normal, vec3(0.0, 1.0, 0.0))); + if (length(tangent0) < 0.001) tangent0 = normalize(cross(hit0.normal, vec3(1.0, 0.0, 0.0))); + apply_material_textures(mat0, hit0.texcoord, hit0.normal, tangent0); radiance += throughput * mat0.emission; if (mat0.type == MATERIAL_DIFFUSE) { @@ -569,6 +664,11 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint } Material mat = fetch_material(hit.material_id); + + // Apply PBR textures + vec3 tangent = normalize(cross(hit.normal, vec3(0.0, 1.0, 0.0))); + if (length(tangent) < 0.001) tangent = normalize(cross(hit.normal, vec3(1.0, 0.0, 0.0))); + apply_material_textures(mat, hit.texcoord, hit.normal, tangent); radiance += throughput * mat.emission; if (mat.type == MATERIAL_DIFFUSE) { @@ -595,6 +695,16 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint return radiance; } +// ACES Filmic Tone Mapping +vec3 aces_tonemap(vec3 x) { + float a = 2.51; + float b = 0.03; + float c = 2.43; + float d = 0.59; + float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + void main() { ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); ivec2 image_size = imageSize(output_image); @@ -611,14 +721,20 @@ void main() { } color /= float(spp); - color = clamp(color, vec3(0.0), vec3(10.0)); + color = clamp(color, vec3(0.0), vec3(100.0)); + // Store HDR color to accumulation buffer BEFORE tone mapping + vec3 accumulation_color = color; + if (u_enable_accumulation && u_frame_count > 0u) { vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; float w = 1.0 / float(u_frame_count + 1u); - color = mix(accumulated, color, w); + accumulation_color = mix(accumulated, color, w); } + + // Apply ACES tone mapping to output (not accumulation) + vec3 output_color = aces_tonemap(accumulation_color); - imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); - imageStore(output_image, pixel_coords, vec4(color, 1.0)); + imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0)); + imageStore(output_image, pixel_coords, vec4(output_color, 1.0)); } diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp index b056d77..c9790b7 100644 --- a/src/core/raytracer.cpp +++ b/src/core/raytracer.cpp @@ -175,6 +175,35 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle compute_shader_->set_uint("u_light_count", static_cast(scene.get_lights().size())); compute_shader_->set_bool("u_enable_accumulation", config_.enable_accumulation_); + // Enable/disable textures based on material usage + const auto &materials = scene.get_materials(); + bool has_textures = false; + for (const auto &mat : materials) { + if (mat->has_texture(TextureSlot::ALBEDO) || mat->has_texture(TextureSlot::NORMAL) || mat->has_texture(TextureSlot::METALLIC) || mat->has_texture(TextureSlot::ROUGHNESS) || mat->has_texture(TextureSlot::AO) || mat->has_texture(TextureSlot::EMISSION)) { + has_textures = true; + break; + } + } + compute_shader_->set_bool("u_enable_textures", has_textures); + + // Bind texture samplers (binding 10-15) + if (has_textures) { + // Bind default textures (0 = no texture) for now + // In full implementation, would bind actual material textures + glActiveTexture(GL_TEXTURE10); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE11); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE12); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE13); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE14); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE15); + glBindTexture(GL_TEXTURE_2D, 0); + } + // Set camera data const Camera &camera = scene.get_camera(); compute_shader_->set_vec3("u_camera_position", camera.get_position()); @@ -247,14 +276,17 @@ void RayTracer::upload_scene_data_(const Scene &scene) { // Upload materials (on change only) const auto &materials = scene.get_materials(); if (!materials.empty()) { + // Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes) struct MaterialData { - Vec3 albedo; + alignas(16) Vec3 albedo; + alignas(16) Vec3 emission; float metallic; - Vec3 emission; float roughness; int type; float ior; - Vec2 padding; + float padding1; + float padding2; + uint texture_handles[6]; }; std::vector material_data; @@ -268,6 +300,15 @@ void RayTracer::upload_scene_data_(const Scene &scene) { data.roughness = mat->get_roughness(); data.type = static_cast(mat->get_type()); data.ior = mat->get_ior(); + + // Texture handles (0 = no texture) + data.texture_handles[0] = mat->get_texture(TextureSlot::ALBEDO) ? mat->get_texture(TextureSlot::ALBEDO)->get_handle() : 0; + data.texture_handles[1] = mat->get_texture(TextureSlot::NORMAL) ? mat->get_texture(TextureSlot::NORMAL)->get_handle() : 0; + data.texture_handles[2] = mat->get_texture(TextureSlot::METALLIC) ? mat->get_texture(TextureSlot::METALLIC)->get_handle() : 0; + data.texture_handles[3] = mat->get_texture(TextureSlot::ROUGHNESS) ? mat->get_texture(TextureSlot::ROUGHNESS)->get_handle() : 0; + data.texture_handles[4] = mat->get_texture(TextureSlot::AO) ? mat->get_texture(TextureSlot::AO)->get_handle() : 0; + data.texture_handles[5] = mat->get_texture(TextureSlot::EMISSION) ? mat->get_texture(TextureSlot::EMISSION)->get_handle() : 0; + material_data.push_back(data); } diff --git a/src/scene/material.cpp b/src/scene/material.cpp index 9e805de..a597f2c 100644 --- a/src/scene/material.cpp +++ b/src/scene/material.cpp @@ -1,51 +1,71 @@ #include "scene/material.h" +#include 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) { + : 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) + , textures_() { } Material::~Material() { } -void Material::set_albedo(const Vec3& albedo) { - albedo_ = albedo; +void Material::set_albedo(const Vec3 &albedo) { + albedo_ = albedo; } -void Material::set_emission(const Vec3& emission) { - emission_ = emission; +void Material::set_emission(const Vec3 &emission) { + emission_ = emission; } void Material::set_metallic(float metallic) { - metallic_ = glm::clamp(metallic, 0.0f, 1.0f); + metallic_ = glm::clamp(metallic, 0.0f, 1.0f); } void Material::set_roughness(float roughness) { - roughness_ = glm::clamp(roughness, 0.0f, 1.0f); + roughness_ = glm::clamp(roughness, 0.0f, 1.0f); } void Material::set_ior(float ior) { - ior_ = ior; + ior_ = ior; } void Material::set_type(MaterialType type) { - type_ = type; + type_ = type; } void Material::set_albedo_texture(std::shared_ptr texture) { - albedo_texture_ = texture; + textures_[static_cast(TextureSlot::ALBEDO)] = texture; } void Material::set_normal_texture(std::shared_ptr texture) { - normal_texture_ = texture; + textures_[static_cast(TextureSlot::NORMAL)] = texture; +} + +void Material::set_metallic_texture(std::shared_ptr texture) { + textures_[static_cast(TextureSlot::METALLIC)] = texture; +} + +void Material::set_roughness_texture(std::shared_ptr texture) { + textures_[static_cast(TextureSlot::ROUGHNESS)] = texture; +} + +void Material::set_ao_texture(std::shared_ptr texture) { + textures_[static_cast(TextureSlot::AO)] = texture; +} + +void Material::set_emission_texture(std::shared_ptr texture) { + textures_[static_cast(TextureSlot::EMISSION)] = texture; +} + +void Material::set_texture(TextureSlot slot, std::shared_ptr texture) { + textures_[static_cast(slot)] = texture; } } // namespace are diff --git a/src/scene/mesh.cpp b/src/scene/mesh.cpp index 51848ea..032fe19 100644 --- a/src/scene/mesh.cpp +++ b/src/scene/mesh.cpp @@ -1,119 +1,170 @@ #include "scene/mesh.h" #include "utils/logger.h" +#include #include namespace are { Mesh::Mesh() - : material_id_(0) - , transform_(1.0f) - , vao_(0) - , vbo_(0) - , ebo_(0) - , uploaded_(false) { + : material_id_(0) + , transform_(1.0f) + , vao_(0) + , vbo_(0) + , ebo_(0) + , uploaded_(false) { } Mesh::~Mesh() { - release_gpu_resources(); + release_gpu_resources(); } -void Mesh::set_vertices(const std::vector& vertices) { - vertices_ = vertices; - uploaded_ = false; +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_indices(const std::vector &indices) { + indices_ = indices; + uploaded_ = false; } void Mesh::set_material(uint material_id) { - material_id_ = material_id; + material_id_ = material_id; } -void Mesh::set_transform(const Mat4& transform) { - transform_ = transform; +void Mesh::set_transform(const Mat4 &transform) { + transform_ = transform; } bool Mesh::upload_to_gpu() { - if (uploaded_) { - ARE_LOG_WARN("Mesh already uploaded to GPU"); - return true; - } - - if (vertices_.empty()) { - ARE_LOG_ERROR("Cannot upload mesh: no vertices"); - return false; - } - - if (indices_.empty()) { - ARE_LOG_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; - ARE_LOG_INFO("Mesh uploaded to GPU successfully"); - return true; + if (uploaded_) { + ARE_LOG_WARN("Mesh already uploaded to GPU"); + return true; + } + + if (vertices_.empty()) { + ARE_LOG_ERROR("Cannot upload mesh: no vertices"); + return false; + } + + if (indices_.empty()) { + ARE_LOG_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; + ARE_LOG_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; + 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; +} + +void Mesh::compute_tangents() { + if (vertices_.empty() || indices_.empty()) { + ARE_LOG_WARN("Cannot compute tangents: mesh is empty"); + return; + } + + std::fill(vertices_.begin(), vertices_.end(), Vertex {}); + + for (size_t i = 0; i < indices_.size(); i += 3) { + uint i0 = indices_[i]; + uint i1 = indices_[i + 1]; + uint i2 = indices_[i + 2]; + + Vertex &v0 = vertices_[i0]; + Vertex &v1 = vertices_[i1]; + Vertex &v2 = vertices_[i2]; + + Vec3 pos0 = v0.position_; + Vec3 pos1 = v1.position_; + Vec3 pos2 = v2.position_; + + Vec2 uv0 = v0.texcoord_; + Vec2 uv1 = v1.texcoord_; + Vec2 uv2 = v2.texcoord_; + + Vec3 edge1 = pos1 - pos0; + Vec3 edge2 = pos2 - pos0; + Vec2 delta_uv1 = uv1 - uv0; + Vec2 delta_uv2 = uv2 - uv0; + + float r = 1.0f / (delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y); + if (std::abs(r) < 1e-6f) + r = 1.0f; + + Vec3 tangent = (edge1 * delta_uv2.y - edge2 * delta_uv1.y) * r; + tangent = glm::normalize(tangent - v0.normal_ * glm::dot(tangent, v0.normal_)); + + v0.tangent_ += tangent; + v1.tangent_ += tangent; + v2.tangent_ += tangent; + } + + for (auto &v : vertices_) { + v.tangent_ = glm::normalize(v.tangent_); + } + + ARE_LOG_INFO("Computed tangents for mesh"); } } // namespace are