feat: 实现纹理数组支持PBR贴图

- 添加纹理数组(bindless texture)支持
- RayTracer添加texture_arrays_成员存储6种纹理类型
- 添加build_texture_arrays_函数构建纹理数组
- 修改shader使用sampler2DArray进行纹理采样
- 添加sample_texture_array辅助函数
master
ternaryop8479 2026-03-06 19:24:44 +08:00
parent 61739bf2d6
commit 39822e9dae
5 changed files with 268 additions and 22 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

@ -7,6 +7,7 @@
#include "resource/buffer.h" #include "resource/buffer.h"
#include "resource/shader.h" #include "resource/shader.h"
#include "scene/scene.h" #include "scene/scene.h"
#include <glad/glad.h>
#include <memory> #include <memory>
namespace are { namespace are {
@ -88,6 +89,10 @@ private:
uint height_; uint height_;
RayTracerConfig config_; 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<Shader> compute_shader_; std::shared_ptr<Shader> compute_shader_;
TextureHandle accumulation_texture_; TextureHandle accumulation_texture_;
BufferHandle scene_buffer_; BufferHandle scene_buffer_;
@ -117,6 +122,12 @@ private:
* @param gbuffer G-Buffer to bind * @param gbuffer G-Buffer to bind
*/ */
void bind_gbuffer_(const GBuffer &gbuffer); 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 } // namespace are

View File

@ -105,13 +105,27 @@ uniform bool u_use_bvh;
uniform uint u_bvh_node_count; uniform uint u_bvh_node_count;
uniform bool u_enable_textures; uniform bool u_enable_textures;
// Texture samplers for PBR // Global texture arrays for bindless sampling (6 arrays for each texture type)
layout(binding = 10) uniform sampler2D u_texture_albedo; layout(binding = 10) uniform sampler2DArray u_texture_albedo_array;
layout(binding = 11) uniform sampler2D u_texture_normal; layout(binding = 11) uniform sampler2DArray u_texture_normal_array;
layout(binding = 12) uniform sampler2D u_texture_metallic; layout(binding = 12) uniform sampler2DArray u_texture_metallic_array;
layout(binding = 13) uniform sampler2D u_texture_roughness; layout(binding = 13) uniform sampler2DArray u_texture_roughness_array;
layout(binding = 14) uniform sampler2D u_texture_ao; layout(binding = 14) uniform sampler2DArray u_texture_ao_array;
layout(binding = 15) uniform sampler2D u_texture_emission; 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 // Utility
@ -406,7 +420,7 @@ vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_hand
vec3 B = cross(normal, T); vec3 B = cross(normal, T);
mat3 TBN = mat3(T, B, normal); 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); return normalize(TBN * map_n);
} }
@ -416,7 +430,7 @@ void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec
// Albedo texture // Albedo texture
if (mat.texture_handles[0] != 0) { 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 // Normal map
@ -426,23 +440,23 @@ void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec
// Metallic texture // Metallic texture
if (mat.texture_handles[2] != 0) { 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 // Roughness texture
if (mat.texture_handles[3] != 0) { 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) // AO texture (multiply)
if (mat.texture_handles[4] != 0) { 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; mat.albedo *= ao;
} }
// Emission texture (add) // Emission texture (add)
if (mat.texture_handles[5] != 0) { 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;
} }
} }

View File

@ -58,6 +58,19 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &shader) {
glGenBuffers(1, &material_buffer_); glGenBuffers(1, &material_buffer_);
glGenBuffers(1, &light_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 // Initialize BVH if enabled
if (config_.use_bvh_) { if (config_.use_bvh_) {
bvh_ = std::make_unique<BVH>(); bvh_ = std::make_unique<BVH>();
@ -77,6 +90,14 @@ void RayTracer::release() {
accumulation_texture_ = INVALID_HANDLE; 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) { if (material_buffer_ != INVALID_HANDLE) {
glDeleteBuffers(1, &material_buffer_); glDeleteBuffers(1, &material_buffer_);
material_buffer_ = INVALID_HANDLE; 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); compute_shader_->set_bool("u_enable_textures", has_textures);
// Bind texture samplers (binding 10-15) // Build texture arrays if needed
if (has_textures) { if (has_textures) {
// Bind default textures (0 = no texture) for now build_texture_arrays_(scene);
// In full implementation, would bind actual material textures
// Bind texture arrays
glActiveTexture(GL_TEXTURE10); glActiveTexture(GL_TEXTURE10);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]);
glActiveTexture(GL_TEXTURE11); glActiveTexture(GL_TEXTURE11);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]);
glActiveTexture(GL_TEXTURE12); glActiveTexture(GL_TEXTURE12);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]);
glActiveTexture(GL_TEXTURE13); glActiveTexture(GL_TEXTURE13);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]);
glActiveTexture(GL_TEXTURE14); glActiveTexture(GL_TEXTURE14);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]);
glActiveTexture(GL_TEXTURE15); glActiveTexture(GL_TEXTURE15);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]);
} }
// Set camera data // 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); 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<std::shared_ptr<Texture>> textures[6];
for (const auto &mat : materials) {
for (int slot = 0; slot < 6; slot++) {
auto tex = mat->get_texture(static_cast<TextureSlot>(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<uint>(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<int>(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<uint8_t> 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<int>(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 } // namespace are