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 7b6ca13..20a4b8b 100644 Binary files a/examples/cornell_box and b/examples/cornell_box differ diff --git a/include/core/raytracer.h b/include/core/raytracer.h index 9554308..c08176a 100644 --- a/include/core/raytracer.h +++ b/include/core/raytracer.h @@ -7,6 +7,7 @@ #include "resource/buffer.h" #include "resource/shader.h" #include "scene/scene.h" +#include #include namespace are { @@ -88,6 +89,10 @@ private: uint height_; RayTracerConfig config_; + // Texture arrays for PBR materials + GLuint texture_arrays_[6]; // albedo, normal, metallic, roughness, ao, emission + uint texture_array_sizes_[6]; // Number of textures in each array + std::shared_ptr compute_shader_; TextureHandle accumulation_texture_; BufferHandle scene_buffer_; @@ -117,6 +122,12 @@ private: * @param gbuffer G-Buffer to bind */ void bind_gbuffer_(const GBuffer &gbuffer); + + /* + * @brief Build texture arrays from scene materials + * @param scene Scene containing materials + */ + void build_texture_arrays_(const Scene &scene); }; } // namespace are diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index fe0f9c8..ea0bf69 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -105,13 +105,27 @@ 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; +// Global texture arrays for bindless sampling (6 arrays for each texture type) +layout(binding = 10) uniform sampler2DArray u_texture_albedo_array; +layout(binding = 11) uniform sampler2DArray u_texture_normal_array; +layout(binding = 12) uniform sampler2DArray u_texture_metallic_array; +layout(binding = 13) uniform sampler2DArray u_texture_roughness_array; +layout(binding = 14) uniform sampler2DArray u_texture_ao_array; +layout(binding = 15) uniform sampler2DArray u_texture_emission_array; + +// Helper function to sample texture from array by index +vec4 sample_texture_array(int slot, int index, vec2 uv) { + if (index <= 0) return vec4(1.0); + + if (slot == 0) return texture(u_texture_albedo_array, vec3(uv, float(index - 1))); + if (slot == 1) return texture(u_texture_normal_array, vec3(uv, float(index - 1))); + if (slot == 2) return texture(u_texture_metallic_array, vec3(uv, float(index - 1))); + if (slot == 3) return texture(u_texture_roughness_array, vec3(uv, float(index - 1))); + if (slot == 4) return texture(u_texture_ao_array, vec3(uv, float(index - 1))); + if (slot == 5) return texture(u_texture_emission_array, vec3(uv, float(index - 1))); + + return vec4(1.0); +} // ============================================================================ // Utility @@ -406,7 +420,7 @@ vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_hand vec3 B = cross(normal, T); mat3 TBN = mat3(T, B, normal); - vec3 map_n = texture(u_texture_normal, texcoord).xyz * 2.0 - 1.0; + vec3 map_n = sample_texture_array(1, int(normal_handle), texcoord).xyz * 2.0 - 1.0; return normalize(TBN * map_n); } @@ -416,7 +430,7 @@ void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec // Albedo texture if (mat.texture_handles[0] != 0) { - mat.albedo *= texture(u_texture_albedo, texcoord).rgb; + mat.albedo *= sample_texture_array(0, int(mat.texture_handles[0]), texcoord).rgb; } // Normal map @@ -426,23 +440,23 @@ void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec // Metallic texture if (mat.texture_handles[2] != 0) { - mat.metallic *= texture(u_texture_metallic, texcoord).r; + mat.metallic *= sample_texture_array(2, int(mat.texture_handles[2]), texcoord).r; } // Roughness texture if (mat.texture_handles[3] != 0) { - mat.roughness *= texture(u_texture_roughness, texcoord).r; + mat.roughness *= sample_texture_array(3, int(mat.texture_handles[3]), texcoord).r; } // AO texture (multiply) if (mat.texture_handles[4] != 0) { - float ao = texture(u_texture_ao, texcoord).r; + float ao = sample_texture_array(4, int(mat.texture_handles[4]), texcoord).r; mat.albedo *= ao; } // Emission texture (add) if (mat.texture_handles[5] != 0) { - mat.emission += texture(u_texture_emission, texcoord).rgb; + mat.emission += sample_texture_array(5, int(mat.texture_handles[5]), texcoord).rgb; } } diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp index c9790b7..77f6e88 100644 --- a/src/core/raytracer.cpp +++ b/src/core/raytracer.cpp @@ -58,6 +58,19 @@ bool RayTracer::initialize(const std::shared_ptr &shader) { glGenBuffers(1, &material_buffer_); glGenBuffers(1, &light_buffer_); + // Initialize texture arrays (empty for now) + for (int i = 0; i < 6; i++) { + texture_arrays_[i] = 0; + texture_array_sizes_[i] = 0; + glGenTextures(1, &texture_arrays_[i]); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[i]); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + // Initialize BVH if enabled if (config_.use_bvh_) { bvh_ = std::make_unique(); @@ -77,6 +90,14 @@ void RayTracer::release() { accumulation_texture_ = INVALID_HANDLE; } + // Release texture arrays + for (int i = 0; i < 6; i++) { + if (texture_arrays_[i] != 0) { + glDeleteTextures(1, &texture_arrays_[i]); + texture_arrays_[i] = 0; + } + } + if (material_buffer_ != INVALID_HANDLE) { glDeleteBuffers(1, &material_buffer_); material_buffer_ = INVALID_HANDLE; @@ -186,22 +207,23 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle } compute_shader_->set_bool("u_enable_textures", has_textures); - // Bind texture samplers (binding 10-15) + // Build texture arrays if needed if (has_textures) { - // Bind default textures (0 = no texture) for now - // In full implementation, would bind actual material textures + build_texture_arrays_(scene); + + // Bind texture arrays glActiveTexture(GL_TEXTURE10); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]); glActiveTexture(GL_TEXTURE11); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]); glActiveTexture(GL_TEXTURE12); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]); glActiveTexture(GL_TEXTURE13); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]); glActiveTexture(GL_TEXTURE14); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]); glActiveTexture(GL_TEXTURE15); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]); } // Set camera data @@ -388,4 +410,70 @@ void RayTracer::bind_gbuffer_(const GBuffer &gbuffer) { glBindImageTexture(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); } +void RayTracer::build_texture_arrays_(const Scene &scene) { + const auto &materials = scene.get_materials(); + + // Collect all textures for each slot + std::vector> textures[6]; + + for (const auto &mat : materials) { + for (int slot = 0; slot < 6; slot++) { + auto tex = mat->get_texture(static_cast(slot)); + if (tex && tex->is_valid()) { + // Check if texture already added + bool found = false; + for (const auto &t : textures[slot]) { + if (t.get() == tex.get()) { + found = true; + break; + } + } + if (!found) { + textures[slot].push_back(tex); + } + } + } + } + + // Build arrays for each slot + for (int slot = 0; slot < 6; slot++) { + if (textures[slot].empty()) { + texture_array_sizes_[slot] = 0; + continue; + } + + texture_array_sizes_[slot] = static_cast(textures[slot].size()); + + // Get dimensions from first texture (assume all same size) + int width = textures[slot][0]->get_width(); + int height = textures[slot][0]->get_height(); + + // Create texture array + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[slot]); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, width, height, + static_cast(textures[slot].size()), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + + // Copy each texture to array layer + for (size_t i = 0; i < textures[slot].size(); i++) { + auto &tex = textures[slot][i]; + GLuint tex_handle = tex->get_handle(); + if (tex_handle != 0) { + // Copy texture data using GetTexImage and CopyTexSubImage3D + std::vector pixels(width * height * 4); + glBindTexture(GL_TEXTURE_2D, tex_handle); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + glBindTexture(GL_TEXTURE_2D, 0); + + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, static_cast(i), + width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + } + } + + // Generate mipmaps + glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + } + + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); +} + } // namespace are