feat: 实现完整PBR材质系统及修复

- 扩展Material类,添加PBR纹理槽(Albedo/Normal/Metallic/Roughness/AO/Emission)
- 添加Mesh::compute_tangents()方法用于法线贴图计算
- 扩展RayTracer材质上传,支持纹理句柄传递
- 更新raytracing compute shader,添加PBR纹理采样和法线贴图TBN变换
- 修复GLSL/C++结构体内存对齐问题
- 添加ACES色调映射解决自发光过曝问题
- 修复累积缓冲区应在色调映射前存储HDR值
- 修复G-Buffer材质类型未传递给光线追踪的问题
- 添加玻璃材质折射逻辑(折射比例、法线翻转、全内反射)
- Cornell Box示例添加玻璃球(折射)、发光球(自发光)和金属球测试
master
ternaryop8479 2026-03-05 21:57:26 +08:00
parent fab45b52a3
commit 0c48d53d5c
9 changed files with 1107 additions and 571 deletions

133
AGENTS.md Normal file
View File

@ -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., `<glm/glm.hpp>`, `<glad/glad.h>`)
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)

Binary file not shown.

View File

@ -1,17 +1,17 @@
#include <core/renderer.h> #include <core/renderer.h>
#include <scene/scene.h>
#include <scene/camera.h>
#include <scene/mesh.h>
#include <scene/material.h>
#include <scene/light.h>
#include <utils/logger.h>
#include <glad/glad.h> #include <glad/glad.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <iostream>
#include <memory>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <iostream>
#include <memory>
#include <scene/camera.h>
#include <scene/light.h>
#include <scene/material.h>
#include <scene/mesh.h>
#include <scene/scene.h>
#include <utils/logger.h>
using namespace are; using namespace are;
@ -20,7 +20,7 @@ const uint WINDOW_WIDTH = 800;
const uint WINDOW_HEIGHT = 800; const uint WINDOW_HEIGHT = 800;
// Global state // Global state
GLFWwindow* g_window = nullptr; GLFWwindow *g_window = nullptr;
std::unique_ptr<Renderer> g_renderer = nullptr; std::unique_ptr<Renderer> g_renderer = nullptr;
std::unique_ptr<Scene> g_scene = nullptr; std::unique_ptr<Scene> g_scene = nullptr;
std::shared_ptr<Camera> g_camera = nullptr; // Keep a direct reference to camera std::shared_ptr<Camera> g_camera = nullptr; // Keep a direct reference to camera
@ -47,466 +47,534 @@ float g_deltaTime = 0.0f;
float g_lastFrame = 0.0f; float g_lastFrame = 0.0f;
// GLFW error callback // GLFW error callback
void glfw_error_callback(int error, const char* description) { void glfw_error_callback(int error, const char *description) {
ARE_LOG_ERROR("GLFW Error " + std::to_string(error) + ": " + std::string(description)); ARE_LOG_ERROR("GLFW Error " + std::to_string(error) + ": " + std::string(description));
} }
/// @brief Create a quad mesh /// @brief Create a quad mesh
std::shared_ptr<Mesh> create_quad(const Vec3& v0, const Vec3& v1, const Vec3& v2, const Vec3& v3, std::shared_ptr<Mesh> create_quad(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2, const Vec3 &v3,
const Vec3& normal, uint material_id) { const Vec3 &normal, uint material_id) {
auto mesh = std::make_shared<Mesh>(); auto mesh = std::make_shared<Mesh>();
std::vector<Vertex> vertices = { std::vector<Vertex> vertices = {
{v0, normal, Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f)}, { 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)}, { 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)}, { 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)} { v3, normal, Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f) }
}; };
std::vector<uint> indices = {0, 1, 2, 0, 2, 3}; std::vector<uint> indices = { 0, 1, 2, 0, 2, 3 };
mesh->set_vertices(vertices); mesh->set_vertices(vertices);
mesh->set_indices(indices); mesh->set_indices(indices);
mesh->set_material(material_id); mesh->set_material(material_id);
return mesh; return mesh;
} }
/// @brief Create a box mesh /// @brief Create a box mesh
std::shared_ptr<Mesh> create_box(const Vec3& min, const Vec3& max, uint material_id) { std::shared_ptr<Mesh> create_box(const Vec3 &min, const Vec3 &max, uint material_id) {
auto mesh = std::make_shared<Mesh>(); auto mesh = std::make_shared<Mesh>();
std::vector<Vertex> vertices = { std::vector<Vertex> vertices = {
// Front face // Front face
{{min.x, min.y, max.z}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, { { 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, 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}}, { { 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}}, { { min.x, max.y, max.z }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } },
// Back face // Back face
{{max.x, min.y, min.z}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, { { 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, 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}}, { { 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}}, { { max.x, max.y, min.z }, { 0.0f, 0.0f, -1.0f }, { 0.0f, 1.0f }, { -1.0f, 0.0f, 0.0f } },
// Top face // Top face
{{min.x, max.y, max.z}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, { { 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, 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}}, { { 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}}, { { min.x, max.y, min.z }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } },
// Bottom face // Bottom face
{{min.x, min.y, min.z}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, { { 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, 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}}, { { 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}}, { { min.x, min.y, max.z }, { 0.0f, -1.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f, 0.0f } },
// Right face // 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, 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, 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, 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}}, { { max.x, max.y, max.z }, { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f } },
// Left face // 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, 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, 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, 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}} { { min.x, max.y, min.z }, { -1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } }
}; };
std::vector<uint> indices = { std::vector<uint> indices = {
0, 1, 2, 0, 2, 3, // Front 0, 1, 2, 0, 2, 3, // Front
4, 5, 6, 4, 6, 7, // Back 4, 5, 6, 4, 6, 7, // Back
8, 9, 10, 8, 10, 11, // Top 8, 9, 10, 8, 10, 11, // Top
12, 13, 14, 12, 14, 15, // Bottom 12, 13, 14, 12, 14, 15, // Bottom
16, 17, 18, 16, 18, 19, // Right 16, 17, 18, 16, 18, 19, // Right
20, 21, 22, 20, 22, 23 // Left 20, 21, 22, 20, 22, 23 // Left
}; };
mesh->set_vertices(vertices); mesh->set_vertices(vertices);
mesh->set_indices(indices); mesh->set_indices(indices);
mesh->set_material(material_id); mesh->set_material(material_id);
return mesh; return mesh;
}
/// @brief Create a sphere mesh
std::shared_ptr<Mesh> create_sphere(float radius, uint segments, uint rings, uint material_id) {
auto mesh = std::make_shared<Mesh>();
std::vector<Vertex> vertices;
std::vector<uint> indices;
for (uint ring = 0; ring <= rings; ++ring) {
float theta = ring * glm::pi<float>() / 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<float>() / 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 /// @brief Setup Cornell Box scene
void setup_cornell_box() { void setup_cornell_box() {
g_scene = std::make_unique<Scene>(); g_scene = std::make_unique<Scene>();
// Create materials // Create materials
// 0: White diffuse // 0: White diffuse
auto white_material = std::make_shared<Material>(); auto white_material = std::make_shared<Material>();
white_material->set_albedo(Vec3(0.73f, 0.73f, 0.73f)); white_material->set_albedo(Vec3(0.73f, 0.73f, 0.73f));
white_material->set_type(MaterialType::DIFFUSE); white_material->set_type(MaterialType::DIFFUSE);
uint white_id = g_scene->add_material(white_material); uint white_id = g_scene->add_material(white_material);
// 1: Red diffuse (left wall) // 1: Red diffuse (left wall)
auto red_material = std::make_shared<Material>(); auto red_material = std::make_shared<Material>();
red_material->set_albedo(Vec3(0.65f, 0.05f, 0.05f)); red_material->set_albedo(Vec3(0.65f, 0.05f, 0.05f));
red_material->set_type(MaterialType::DIFFUSE); red_material->set_type(MaterialType::DIFFUSE);
uint red_id = g_scene->add_material(red_material); uint red_id = g_scene->add_material(red_material);
// 2: Green diffuse (right wall) // 2: Green diffuse (right wall)
auto green_material = std::make_shared<Material>(); auto green_material = std::make_shared<Material>();
green_material->set_albedo(Vec3(0.12f, 0.45f, 0.15f)); green_material->set_albedo(Vec3(0.12f, 0.45f, 0.15f));
green_material->set_type(MaterialType::DIFFUSE); green_material->set_type(MaterialType::DIFFUSE);
uint green_id = g_scene->add_material(green_material); uint green_id = g_scene->add_material(green_material);
// 3: Light emissive // 3: Light emissive
auto light_material = std::make_shared<Material>(); auto light_material = std::make_shared<Material>();
light_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f)); 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_emission(Vec3(15.0f, 15.0f, 15.0f));
light_material->set_type(MaterialType::EMISSIVE); light_material->set_type(MaterialType::EMISSIVE);
uint light_id = g_scene->add_material(light_material); uint light_id = g_scene->add_material(light_material);
// 4: Metal (for one box) // 4: Metal (for one box)
auto metal_material = std::make_shared<Material>(); auto metal_material = std::make_shared<Material>();
metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f)); metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f));
metal_material->set_metallic(1.0f); metal_material->set_metallic(1.0f);
metal_material->set_roughness(0.1f); metal_material->set_roughness(0.0f);
metal_material->set_type(MaterialType::METAL); metal_material->set_type(MaterialType::METAL);
uint metal_id = g_scene->add_material(metal_material); uint metal_id = g_scene->add_material(metal_material);
// Create room (Cornell Box) // 5: Glass/Dielectric (refraction)
float room_size = 2.0f; auto glass_material = std::make_shared<Material>();
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);
// Floor (white) // 6: Yellow emissive sphere
auto floor = create_quad( auto emissive_sphere_mat = std::make_shared<Material>();
Vec3(-room_size, -room_size, -room_size), emissive_sphere_mat->set_albedo(Vec3(1.0f, 0.8f, 0.2f));
Vec3(room_size, -room_size, -room_size), emissive_sphere_mat->set_emission(Vec3(5.0f, 4.0f, 1.0f));
Vec3(room_size, -room_size, room_size), emissive_sphere_mat->set_type(MaterialType::EMISSIVE);
Vec3(-room_size, -room_size, room_size), uint emissive_sphere_id = g_scene->add_material(emissive_sphere_mat);
Vec3(0.0f, 1.0f, 0.0f),
white_id
);
floor->upload_to_gpu();
g_scene->add_mesh(floor);
// Ceiling (white) // Create room (Cornell Box)
auto ceiling = create_quad( float room_size = 2.0f;
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) // Floor (white)
auto back_wall = create_quad( 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(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), Vec3(0.0f, 1.0f, 0.0f),
white_id white_id);
); floor->upload_to_gpu();
back_wall->upload_to_gpu(); g_scene->add_mesh(floor);
g_scene->add_mesh(back_wall);
// Left wall (red) // Ceiling (white)
auto left_wall = create_quad( 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(-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), Vec3(0.0f, -1.0f, 0.0f),
red_id white_id);
); ceiling->upload_to_gpu();
left_wall->upload_to_gpu(); g_scene->add_mesh(ceiling);
g_scene->add_mesh(left_wall);
// Right wall (green) // Back wall (white)
auto right_wall = create_quad( 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(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), Vec3(0.0f, 0.0f, 1.0f),
green_id white_id);
); back_wall->upload_to_gpu();
right_wall->upload_to_gpu(); g_scene->add_mesh(back_wall);
g_scene->add_mesh(right_wall);
// Area light on ceiling // Left wall (red)
float light_size = 0.5f; auto left_wall = create_quad(
auto area_light = create_quad( Vec3(-room_size, -room_size, room_size),
Vec3(-light_size, room_size - 0.01f, -light_size), Vec3(-room_size, room_size, room_size),
Vec3(light_size, room_size - 0.01f, -light_size), Vec3(-room_size, room_size, -room_size),
Vec3(light_size, room_size - 0.01f, light_size), Vec3(-room_size, -room_size, -room_size),
Vec3(-light_size, room_size - 0.01f, light_size), Vec3(1.0f, 0.0f, 0.0f),
Vec3(0.0f, -1.0f, 0.0f), red_id);
light_id left_wall->upload_to_gpu();
); g_scene->add_mesh(left_wall);
area_light->upload_to_gpu();
g_scene->add_mesh(area_light);
// Tall box (white, left side) // Right wall (green)
auto tall_box = create_box(Vec3(-0.7f, -room_size, -0.7f), Vec3(-0.2f, 0.6f, -0.2f), white_id); auto right_wall = create_quad(
tall_box->upload_to_gpu(); Vec3(room_size, -room_size, -room_size),
g_scene->add_mesh(tall_box); 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);
// Short box (metal, right side) // Area light on ceiling
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), white_id); float light_size = 0.5f;
short_box->upload_to_gpu(); auto area_light = create_quad(
g_scene->add_mesh(short_box); 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);
// Setup camera // Tall box (white, left side)
g_camera = std::make_shared<Camera>(); auto tall_box = create_box(Vec3(-0.7f, -room_size, -0.7f), Vec3(-0.2f, 0.6f, -0.2f), white_id);
g_camera->set_position(g_cameraPos); tall_box->upload_to_gpu();
g_camera->set_target(g_cameraTarget); g_scene->add_mesh(tall_box);
g_camera->set_up(g_cameraUp);
g_camera->set_perspective(45.0f, static_cast<float>(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f);
g_scene->set_camera(g_camera);
// Add point light // Short box (metal, right side)
auto light = std::make_shared<Light>(); auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), glass_id);
light->set_type(LightType::POINT); short_box->upload_to_gpu();
light->set_position(Vec3(0.0f, 1.8f, 0.0f)); g_scene->add_mesh(short_box);
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"); // // 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<Camera>();
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<float>(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f);
g_scene->set_camera(g_camera);
// Add point light
auto light = std::make_shared<Light>();
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 /// @brief Initialize GLFW and create window
bool init_window() { bool init_window() {
glfwSetErrorCallback(glfw_error_callback); glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) { if (!glfwInit()) {
ARE_LOG_ERROR("Failed to initialize GLFW"); ARE_LOG_ERROR("Failed to initialize GLFW");
return false; return false;
} }
ARE_LOG_INFO("GLFW initialized successfully"); ARE_LOG_INFO("GLFW initialized successfully");
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
glfwWindowHint(GLFW_SAMPLES, 0); glfwWindowHint(GLFW_SAMPLES, 0);
g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr); g_window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Aurora - Cornell Box", nullptr, nullptr);
if (!g_window) { if (!g_window) {
ARE_LOG_ERROR("Failed to create GLFW window"); ARE_LOG_ERROR("Failed to create GLFW window");
glfwTerminate(); glfwTerminate();
return false; return false;
} }
glfwMakeContextCurrent(g_window); glfwMakeContextCurrent(g_window);
glfwSwapInterval(1); glfwSwapInterval(1);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
ARE_LOG_ERROR("Failed to initialize GLAD"); ARE_LOG_ERROR("Failed to initialize GLAD");
return false; return false;
} }
return true; return true;
} }
// --- Input Processing --- // --- Input Processing ---
void process_input() { void process_input() {
// Calculate delta time // Calculate delta time
float currentFrame = glfwGetTime(); float currentFrame = glfwGetTime();
g_deltaTime = currentFrame - g_lastFrame; g_deltaTime = currentFrame - g_lastFrame;
g_lastFrame = currentFrame; g_lastFrame = currentFrame;
float velocity = g_moveSpeed * g_deltaTime; float velocity = g_moveSpeed * g_deltaTime;
bool camera_changed = false; bool camera_changed = false;
// 1. Mouse Rotation (Left Button Hold) // 1. Mouse Rotation (Left Button Hold)
if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { if (glfwGetMouseButton(g_window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
double xpos, ypos; double xpos, ypos;
glfwGetCursorPos(g_window, &xpos, &ypos); glfwGetCursorPos(g_window, &xpos, &ypos);
if (g_firstMouse) { if (g_firstMouse) {
g_lastX = xpos; g_lastX = xpos;
g_lastY = ypos; g_lastY = ypos;
g_firstMouse = false; g_firstMouse = false;
} }
float xoffset = xpos - g_lastX; float xoffset = xpos - g_lastX;
float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top float yoffset = g_lastY - ypos; // Reversed since y-coordinates go from bottom to top
g_lastX = xpos; g_lastX = xpos;
g_lastY = ypos; g_lastY = ypos;
// Only update if mouse actually moved // Only update if mouse actually moved
if (xoffset != 0.0f || yoffset != 0.0f) { if (xoffset != 0.0f || yoffset != 0.0f) {
xoffset *= g_mouseSensitivity; xoffset *= g_mouseSensitivity;
yoffset *= g_mouseSensitivity; yoffset *= g_mouseSensitivity;
g_yaw += xoffset; g_yaw += xoffset;
g_pitch += yoffset; g_pitch += yoffset;
// Constrain pitch // Constrain pitch
if (g_pitch > 89.0f) g_pitch = 89.0f; if (g_pitch > 89.0f)
if (g_pitch < -89.0f) g_pitch = -89.0f; g_pitch = 89.0f;
if (g_pitch < -89.0f)
g_pitch = -89.0f;
camera_changed = true; camera_changed = true;
} }
} } else {
else { g_firstMouse = true; // Reset when released
g_firstMouse = true; // Reset when released }
}
// 2. Calculate Direction Vectors // 2. Calculate Direction Vectors
glm::vec3 front; glm::vec3 front;
front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); front.x = cos(glm::radians(g_yaw)) * cos(glm::radians(g_pitch));
front.y = sin(glm::radians(g_pitch)); front.y = sin(glm::radians(g_pitch));
front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch)); front.z = sin(glm::radians(g_yaw)) * cos(glm::radians(g_pitch));
glm::vec3 frontNorm = glm::normalize(front); 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))); glm::vec3 rightNorm = glm::normalize(glm::cross(frontNorm, glm::vec3(g_worldUp.x, g_worldUp.y, g_worldUp.z)));
// 3. Keyboard Movement (WASD) // 3. Keyboard Movement (WASD)
glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z); glm::vec3 pos = glm::vec3(g_cameraPos.x, g_cameraPos.y, g_cameraPos.z);
if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) { if (glfwGetKey(g_window, GLFW_KEY_W) == GLFW_PRESS) {
pos += frontNorm * velocity; pos += frontNorm * velocity;
camera_changed = true; camera_changed = true;
} }
if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) { if (glfwGetKey(g_window, GLFW_KEY_S) == GLFW_PRESS) {
pos -= frontNorm * velocity; pos -= frontNorm * velocity;
camera_changed = true; camera_changed = true;
} }
if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) { if (glfwGetKey(g_window, GLFW_KEY_A) == GLFW_PRESS) {
pos -= rightNorm * velocity; pos -= rightNorm * velocity;
camera_changed = true; camera_changed = true;
} }
if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) { if (glfwGetKey(g_window, GLFW_KEY_D) == GLFW_PRESS) {
pos += rightNorm * velocity; pos += rightNorm * velocity;
camera_changed = true; camera_changed = true;
} }
// 4. Apply changes to Scene Camera and Notify Renderer // 4. Apply changes to Scene Camera and Notify Renderer
if (camera_changed) { if (camera_changed) {
g_cameraPos = Vec3(pos.x, pos.y, pos.z); g_cameraPos = Vec3(pos.x, pos.y, pos.z);
// Target = Position + Front // Target = Position + Front
Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z); Vec3 newTarget = g_cameraPos + Vec3(frontNorm.x, frontNorm.y, frontNorm.z);
g_camera->set_position(g_cameraPos); g_camera->set_position(g_cameraPos);
g_camera->set_target(newTarget); g_camera->set_target(newTarget);
// CRITICAL: Notify renderer to reset accumulation // CRITICAL: Notify renderer to reset accumulation
g_renderer->notify_scene_changed(*g_scene); g_renderer->notify_scene_changed(*g_scene);
} }
} }
/// @brief Main render loop /// @brief Main render loop
void render_loop() { void render_loop() {
ARE_LOG_INFO("Entering render loop..."); ARE_LOG_INFO("Entering render loop...");
int frame_count = 0; int frame_count = 0;
double fps_time = glfwGetTime(); double fps_time = glfwGetTime();
g_lastFrame = glfwGetTime(); // Initialize for delta time g_lastFrame = glfwGetTime(); // Initialize for delta time
while (!glfwWindowShouldClose(g_window)) { while (!glfwWindowShouldClose(g_window)) {
// Process input at the start of the frame // Process input at the start of the frame
process_input(); process_input();
// Render // Render
RenderStats stats = g_renderer->render(*g_scene); RenderStats stats = g_renderer->render(*g_scene);
// Swap buffers // Swap buffers
glfwSwapBuffers(g_window); glfwSwapBuffers(g_window);
glfwPollEvents(); glfwPollEvents();
// Calculate FPS // Calculate FPS
frame_count++; frame_count++;
double current_time = glfwGetTime(); double current_time = glfwGetTime();
double delta = current_time - fps_time; double delta = current_time - fps_time;
if (delta >= 1.0) { if (delta >= 1.0) {
double fps = frame_count / delta; double fps = frame_count / delta;
std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + " | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms";
" | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms"; glfwSetWindowTitle(g_window, title.c_str());
glfwSetWindowTitle(g_window, title.c_str());
frame_count = 0; frame_count = 0;
fps_time = current_time; fps_time = current_time;
} }
// ESC to exit // ESC to exit
if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { if (glfwGetKey(g_window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(g_window, true); glfwSetWindowShouldClose(g_window, true);
} }
} }
ARE_LOG_INFO("Exiting render loop"); ARE_LOG_INFO("Exiting render loop");
} }
/// @brief Cleanup /// @brief Cleanup
void cleanup() { void cleanup() {
ARE_LOG_INFO("Cleaning up..."); ARE_LOG_INFO("Cleaning up...");
if (g_renderer) { if (g_renderer) {
g_renderer->shutdown(); g_renderer->shutdown();
g_renderer.reset(); g_renderer.reset();
} }
g_scene.reset(); g_scene.reset();
if (g_window) { if (g_window) {
glfwDestroyWindow(g_window); glfwDestroyWindow(g_window);
glfwTerminate(); glfwTerminate();
} }
ARE_LOG_INFO("Cleanup complete"); ARE_LOG_INFO("Cleanup complete");
} }
int main() { int main() {
ARE_LOG_INFO("==========================================="); ARE_LOG_INFO("===========================================");
ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo"); ARE_LOG_INFO("Aurora Rendering Engine - Cornell Box Demo");
ARE_LOG_INFO("==========================================="); ARE_LOG_INFO("===========================================");
if (!init_window()) { if (!init_window()) {
cleanup(); cleanup();
ARE_LOG_ERROR("Failed to initialize window"); ARE_LOG_ERROR("Failed to initialize window");
Logger::shutdown(); Logger::shutdown();
return -1; return -1;
} }
ARE_LOG_INFO("Setting up Cornell Box scene..."); ARE_LOG_INFO("Setting up Cornell Box scene...");
setup_cornell_box(); setup_cornell_box();
ARE_LOG_INFO("Initializing renderer..."); ARE_LOG_INFO("Initializing renderer...");
RendererConfig config; RendererConfig config;
config.width_ = WINDOW_WIDTH; config.width_ = WINDOW_WIDTH;
config.height_ = WINDOW_HEIGHT; config.height_ = WINDOW_HEIGHT;
config.samples_per_pixel_ = 1; config.samples_per_pixel_ = 1;
config.max_ray_depth_ = 4; config.max_ray_depth_ = 4;
config.enable_accumulation_ = true; config.enable_accumulation_ = true;
config.enable_denoising_ = false; config.enable_denoising_ = false;
g_renderer = std::make_unique<Renderer>(config); g_renderer = std::make_unique<Renderer>(config);
if (!g_renderer->initialize()) { if (!g_renderer->initialize()) {
ARE_LOG_ERROR("Failed to initialize renderer"); ARE_LOG_ERROR("Failed to initialize renderer");
cleanup(); cleanup();
Logger::shutdown(); Logger::shutdown();
return -1; return -1;
} }
ARE_LOG_INFO("==========================================="); ARE_LOG_INFO("===========================================");
ARE_LOG_INFO("Renderer initialized successfully!"); ARE_LOG_INFO("Renderer initialized successfully!");
ARE_LOG_INFO("Controls:"); ARE_LOG_INFO("Controls:");
ARE_LOG_INFO(" WASD - Move Camera"); ARE_LOG_INFO(" WASD - Move Camera");
ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera"); ARE_LOG_INFO(" Hold Left Mouse Button - Rotate Camera");
ARE_LOG_INFO(" ESC - Exit"); ARE_LOG_INFO(" ESC - Exit");
ARE_LOG_INFO("==========================================="); ARE_LOG_INFO("===========================================");
render_loop(); render_loop();
cleanup(); cleanup();
ARE_LOG_INFO("Cornell Box demo finished"); ARE_LOG_INFO("Cornell Box demo finished");
Logger::shutdown(); Logger::shutdown();
return 0; return 0;
} }

View File

@ -3,10 +3,22 @@
#include "basic/types.h" #include "basic/types.h"
#include "resource/texture.h" #include "resource/texture.h"
#include <array>
#include <memory> #include <memory>
namespace are { 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 // Material type enumeration
enum class MaterialType { enum class MaterialType {
DIFFUSE = 0, DIFFUSE = 0,
@ -72,6 +84,37 @@ public:
*/ */
void set_normal_texture(std::shared_ptr<Texture> texture); void set_normal_texture(std::shared_ptr<Texture> texture);
/*
* @brief Set metallic map
* @param texture Metallic texture (R channel)
*/
void set_metallic_texture(std::shared_ptr<Texture> texture);
/*
* @brief Set roughness map
* @param texture Roughness texture (G channel)
*/
void set_roughness_texture(std::shared_ptr<Texture> texture);
/*
* @brief Set ambient occlusion map
* @param texture AO texture (R channel)
*/
void set_ao_texture(std::shared_ptr<Texture> texture);
/*
* @brief Set emission map
* @param texture Emission texture (RGB)
*/
void set_emission_texture(std::shared_ptr<Texture> 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> texture);
/* /*
* @brief Get albedo color * @brief Get albedo color
* @return Albedo color * @return Albedo color
@ -125,7 +168,7 @@ public:
* @return Albedo texture (nullptr if none) * @return Albedo texture (nullptr if none)
*/ */
std::shared_ptr<Texture> get_albedo_texture() const { std::shared_ptr<Texture> get_albedo_texture() const {
return albedo_texture_; return textures_[static_cast<int>(TextureSlot::ALBEDO)];
} }
/* /*
@ -133,7 +176,57 @@ public:
* @return Normal texture (nullptr if none) * @return Normal texture (nullptr if none)
*/ */
std::shared_ptr<Texture> get_normal_texture() const { std::shared_ptr<Texture> get_normal_texture() const {
return normal_texture_; return textures_[static_cast<int>(TextureSlot::NORMAL)];
}
/*
* @brief Get metallic texture
* @return Metallic texture (nullptr if none)
*/
std::shared_ptr<Texture> get_metallic_texture() const {
return textures_[static_cast<int>(TextureSlot::METALLIC)];
}
/*
* @brief Get roughness texture
* @return Roughness texture (nullptr if none)
*/
std::shared_ptr<Texture> get_roughness_texture() const {
return textures_[static_cast<int>(TextureSlot::ROUGHNESS)];
}
/*
* @brief Get AO texture
* @return AO texture (nullptr if none)
*/
std::shared_ptr<Texture> get_ao_texture() const {
return textures_[static_cast<int>(TextureSlot::AO)];
}
/*
* @brief Get emission texture
* @return Emission texture (nullptr if none)
*/
std::shared_ptr<Texture> get_emission_texture() const {
return textures_[static_cast<int>(TextureSlot::EMISSION)];
}
/*
* @brief Get texture for a specific slot
* @param slot Texture slot
* @return Texture (nullptr if none)
*/
std::shared_ptr<Texture> get_texture(TextureSlot slot) const {
return textures_[static_cast<int>(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<int>(slot)] != nullptr;
} }
private: private:
@ -144,8 +237,7 @@ private:
float ior_; float ior_;
MaterialType type_; MaterialType type_;
std::shared_ptr<Texture> albedo_texture_; std::array<std::shared_ptr<Texture>, static_cast<int>(TextureSlot::COUNT)> textures_;
std::shared_ptr<Texture> normal_texture_;
}; };
} // namespace are } // namespace are

View File

@ -2,6 +2,7 @@
#define ARE_INCLUDE_SCENE_MESH_H #define ARE_INCLUDE_SCENE_MESH_H
#include "basic/types.h" #include "basic/types.h"
#include <glm/gtc/matrix_transform.hpp>
#include <vector> #include <vector>
namespace are { namespace are {
@ -39,6 +40,14 @@ public:
*/ */
void set_transform(const Mat4 &transform); 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 * @brief Get vertices
* @return Vertex array * @return Vertex array
@ -77,6 +86,12 @@ public:
*/ */
bool upload_to_gpu(); 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 // Release GPU resources
void release_gpu_resources(); void release_gpu_resources();

View File

@ -32,12 +32,14 @@ layout(binding = 4, rgba32f) uniform image2D accumulation_image;
struct Material { struct Material {
vec3 albedo; vec3 albedo;
float metallic;
vec3 emission; vec3 emission;
float metallic;
float roughness; float roughness;
int type; int type;
float ior; float ior;
vec2 padding; float padding1;
float padding2;
uint texture_handles[6];
}; };
struct Light { struct Light {
@ -63,6 +65,7 @@ struct HitInfo {
vec3 normal; vec3 normal;
vec2 texcoord; vec2 texcoord;
uint material_id; uint material_id;
int material_type; // material type from G-Buffer
}; };
struct ScatterResult { struct ScatterResult {
@ -100,6 +103,15 @@ uniform mat4 u_inv_view_projection;
uniform bool u_enable_accumulation; uniform bool u_enable_accumulation;
uniform bool u_use_bvh; uniform bool u_use_bvh;
uniform uint u_bvh_node_count; 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 // Utility
@ -353,6 +365,7 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
hit.normal = vec3(0.0, 1.0, 0.0); hit.normal = vec3(0.0, 1.0, 0.0);
hit.texcoord = vec2(0.0); hit.texcoord = vec2(0.0);
hit.material_id = 0u; hit.material_id = 0u;
hit.material_type = 0;
vec4 pos = imageLoad(g_position, pixel_coords); vec4 pos = imageLoad(g_position, pixel_coords);
if (pos.w <= 0.5) { if (pos.w <= 0.5) {
@ -365,10 +378,15 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
// integer material id // integer material id
uint mid = imageLoad(g_material_id, pixel_coords).r; 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.hit = true;
hit.position = p; hit.position = p;
hit.normal = n; hit.normal = n;
hit.material_id = mid; hit.material_id = mid;
hit.material_type = mtype;
// For RR/any debug usage; path tracing uses this as starting point only. // For RR/any debug usage; path tracing uses this as starting point only.
hit.t = length(p - ray.origin); hit.t = length(p - ray.origin);
@ -380,6 +398,54 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
// Material + scattering // 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) { vec3 fresnel_schlick(float cos_theta, vec3 f0) {
return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); 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); r.attenuation = vec3(1.0);
vec3 unit_dir = normalize(ray_in.direction); 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 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; // Determine if ray is entering or exiting the material
bool cannot_refract = refraction_ratio * sin_theta > 1.0; // If dot(dir, normal) < 0, ray is entering (from air into material)
float reflect_prob = fresnel_dielectric(cos_theta, refraction_ratio); 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; vec3 dir;
if (cannot_refract || random_float(seed) < reflect_prob) { if (total_internal_reflection || random_float(seed) < f) {
dir = reflect_vector(unit_dir, hit.normal); // Reflect
dir = reflect_vector(unit_dir, normal);
} else { } 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; r.scattered_ray.origin = hit.position + dir * EPSILON;
@ -548,6 +633,16 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint
if (hit0.hit) { if (hit0.hit) {
Material mat0 = fetch_material(hit0.material_id); 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; radiance += throughput * mat0.emission;
if (mat0.type == MATERIAL_DIFFUSE) { if (mat0.type == MATERIAL_DIFFUSE) {
radiance += throughput * eval_direct_lighting(hit0, mat0, seed); radiance += throughput * eval_direct_lighting(hit0, mat0, seed);
@ -570,6 +665,11 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint
Material mat = fetch_material(hit.material_id); 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; radiance += throughput * mat.emission;
if (mat.type == MATERIAL_DIFFUSE) { if (mat.type == MATERIAL_DIFFUSE) {
radiance += throughput * eval_direct_lighting(hit, mat, seed); radiance += throughput * eval_direct_lighting(hit, mat, seed);
@ -595,6 +695,16 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint
return radiance; 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() { void main() {
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 image_size = imageSize(output_image); ivec2 image_size = imageSize(output_image);
@ -611,14 +721,20 @@ void main() {
} }
color /= float(spp); 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) { if (u_enable_accumulation && u_frame_count > 0u) {
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
float w = 1.0 / float(u_frame_count + 1u); float w = 1.0 / float(u_frame_count + 1u);
color = mix(accumulated, color, w); accumulation_color = mix(accumulated, color, w);
} }
imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); // Apply ACES tone mapping to output (not accumulation)
imageStore(output_image, pixel_coords, vec4(color, 1.0)); vec3 output_color = aces_tonemap(accumulation_color);
imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0));
imageStore(output_image, pixel_coords, vec4(output_color, 1.0));
} }

View File

@ -175,6 +175,35 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
compute_shader_->set_uint("u_light_count", static_cast<uint>(scene.get_lights().size())); compute_shader_->set_uint("u_light_count", static_cast<uint>(scene.get_lights().size()));
compute_shader_->set_bool("u_enable_accumulation", config_.enable_accumulation_); 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 // Set camera data
const Camera &camera = scene.get_camera(); const Camera &camera = scene.get_camera();
compute_shader_->set_vec3("u_camera_position", camera.get_position()); 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) // Upload materials (on change only)
const auto &materials = scene.get_materials(); const auto &materials = scene.get_materials();
if (!materials.empty()) { if (!materials.empty()) {
// Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes)
struct MaterialData { struct MaterialData {
Vec3 albedo; alignas(16) Vec3 albedo;
alignas(16) Vec3 emission;
float metallic; float metallic;
Vec3 emission;
float roughness; float roughness;
int type; int type;
float ior; float ior;
Vec2 padding; float padding1;
float padding2;
uint texture_handles[6];
}; };
std::vector<MaterialData> material_data; std::vector<MaterialData> material_data;
@ -268,6 +300,15 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
data.roughness = mat->get_roughness(); data.roughness = mat->get_roughness();
data.type = static_cast<int>(mat->get_type()); data.type = static_cast<int>(mat->get_type());
data.ior = mat->get_ior(); 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); material_data.push_back(data);
} }

View File

@ -1,51 +1,71 @@
#include "scene/material.h" #include "scene/material.h"
#include <array>
namespace are { namespace are {
Material::Material() Material::Material()
: albedo_(1.0f, 1.0f, 1.0f) : albedo_(1.0f, 1.0f, 1.0f)
, emission_(0.0f, 0.0f, 0.0f) , emission_(0.0f, 0.0f, 0.0f)
, metallic_(0.0f) , metallic_(0.0f)
, roughness_(0.5f) , roughness_(0.5f)
, ior_(1.5f) , ior_(1.5f)
, type_(MaterialType::DIFFUSE) , type_(MaterialType::DIFFUSE)
, albedo_texture_(nullptr) , textures_() {
, normal_texture_(nullptr) {
} }
Material::~Material() { Material::~Material() {
} }
void Material::set_albedo(const Vec3& albedo) { void Material::set_albedo(const Vec3 &albedo) {
albedo_ = albedo; albedo_ = albedo;
} }
void Material::set_emission(const Vec3& emission) { void Material::set_emission(const Vec3 &emission) {
emission_ = emission; emission_ = emission;
} }
void Material::set_metallic(float metallic) { 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) { 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) { void Material::set_ior(float ior) {
ior_ = ior; ior_ = ior;
} }
void Material::set_type(MaterialType type) { void Material::set_type(MaterialType type) {
type_ = type; type_ = type;
} }
void Material::set_albedo_texture(std::shared_ptr<Texture> texture) { void Material::set_albedo_texture(std::shared_ptr<Texture> texture) {
albedo_texture_ = texture; textures_[static_cast<int>(TextureSlot::ALBEDO)] = texture;
} }
void Material::set_normal_texture(std::shared_ptr<Texture> texture) { void Material::set_normal_texture(std::shared_ptr<Texture> texture) {
normal_texture_ = texture; textures_[static_cast<int>(TextureSlot::NORMAL)] = texture;
}
void Material::set_metallic_texture(std::shared_ptr<Texture> texture) {
textures_[static_cast<int>(TextureSlot::METALLIC)] = texture;
}
void Material::set_roughness_texture(std::shared_ptr<Texture> texture) {
textures_[static_cast<int>(TextureSlot::ROUGHNESS)] = texture;
}
void Material::set_ao_texture(std::shared_ptr<Texture> texture) {
textures_[static_cast<int>(TextureSlot::AO)] = texture;
}
void Material::set_emission_texture(std::shared_ptr<Texture> texture) {
textures_[static_cast<int>(TextureSlot::EMISSION)] = texture;
}
void Material::set_texture(TextureSlot slot, std::shared_ptr<Texture> texture) {
textures_[static_cast<int>(slot)] = texture;
} }
} // namespace are } // namespace are

View File

@ -1,119 +1,170 @@
#include "scene/mesh.h" #include "scene/mesh.h"
#include "utils/logger.h" #include "utils/logger.h"
#include <algorithm>
#include <glad/glad.h> #include <glad/glad.h>
namespace are { namespace are {
Mesh::Mesh() Mesh::Mesh()
: material_id_(0) : material_id_(0)
, transform_(1.0f) , transform_(1.0f)
, vao_(0) , vao_(0)
, vbo_(0) , vbo_(0)
, ebo_(0) , ebo_(0)
, uploaded_(false) { , uploaded_(false) {
} }
Mesh::~Mesh() { Mesh::~Mesh() {
release_gpu_resources(); release_gpu_resources();
} }
void Mesh::set_vertices(const std::vector<Vertex>& vertices) { void Mesh::set_vertices(const std::vector<Vertex> &vertices) {
vertices_ = vertices; vertices_ = vertices;
uploaded_ = false; uploaded_ = false;
} }
void Mesh::set_indices(const std::vector<uint>& indices) { void Mesh::set_indices(const std::vector<uint> &indices) {
indices_ = indices; indices_ = indices;
uploaded_ = false; uploaded_ = false;
} }
void Mesh::set_material(uint material_id) { void Mesh::set_material(uint material_id) {
material_id_ = material_id; material_id_ = material_id;
} }
void Mesh::set_transform(const Mat4& transform) { void Mesh::set_transform(const Mat4 &transform) {
transform_ = transform; transform_ = transform;
} }
bool Mesh::upload_to_gpu() { bool Mesh::upload_to_gpu() {
if (uploaded_) { if (uploaded_) {
ARE_LOG_WARN("Mesh already uploaded to GPU"); ARE_LOG_WARN("Mesh already uploaded to GPU");
return true; return true;
} }
if (vertices_.empty()) { if (vertices_.empty()) {
ARE_LOG_ERROR("Cannot upload mesh: no vertices"); ARE_LOG_ERROR("Cannot upload mesh: no vertices");
return false; return false;
} }
if (indices_.empty()) { if (indices_.empty()) {
ARE_LOG_ERROR("Cannot upload mesh: no indices"); ARE_LOG_ERROR("Cannot upload mesh: no indices");
return false; return false;
} }
// Generate VAO // Generate VAO
glGenVertexArrays(1, &vao_); glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_); glBindVertexArray(vao_);
// Generate and upload VBO // Generate and upload VBO
glGenBuffers(1, &vbo_); glGenBuffers(1, &vbo_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_); glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(Vertex), glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(Vertex),
vertices_.data(), GL_STATIC_DRAW); vertices_.data(), GL_STATIC_DRAW);
// Generate and upload EBO // Generate and upload EBO
glGenBuffers(1, &ebo_); glGenBuffers(1, &ebo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(uint), glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(uint),
indices_.data(), GL_STATIC_DRAW); indices_.data(), GL_STATIC_DRAW);
// Set vertex attributes // Set vertex attributes
// Location 0: Position // Location 0: Position
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, position_)); (void *)offsetof(Vertex, position_));
// Location 1: Normal // Location 1: Normal
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, normal_)); (void *)offsetof(Vertex, normal_));
// Location 2: TexCoord // Location 2: TexCoord
glEnableVertexAttribArray(2); glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, texcoord_)); (void *)offsetof(Vertex, texcoord_));
// Location 3: Tangent // Location 3: Tangent
glEnableVertexAttribArray(3); glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, tangent_)); (void *)offsetof(Vertex, tangent_));
glBindVertexArray(0); glBindVertexArray(0);
uploaded_ = true; uploaded_ = true;
ARE_LOG_INFO("Mesh uploaded to GPU successfully"); ARE_LOG_INFO("Mesh uploaded to GPU successfully");
return true; return true;
} }
void Mesh::release_gpu_resources() { void Mesh::release_gpu_resources() {
if (!uploaded_) return; if (!uploaded_)
return;
if (vao_ != 0) { if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_); glDeleteVertexArrays(1, &vao_);
vao_ = 0; vao_ = 0;
} }
if (vbo_ != 0) { if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_); glDeleteBuffers(1, &vbo_);
vbo_ = 0; vbo_ = 0;
} }
if (ebo_ != 0) { if (ebo_ != 0) {
glDeleteBuffers(1, &ebo_); glDeleteBuffers(1, &ebo_);
ebo_ = 0; ebo_ = 0;
} }
uploaded_ = false; 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 } // namespace are