feat: 实现纹理数组支持PBR贴图
- 添加纹理数组(bindless texture)支持 - RayTracer添加texture_arrays_成员存储6种纹理类型 - 添加build_texture_arrays_函数构建纹理数组 - 修改shader使用sampler2DArray进行纹理采样 - 添加sample_texture_array辅助函数master
parent
61739bf2d6
commit
39822e9dae
|
|
@ -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.
|
|
@ -7,6 +7,7 @@
|
|||
#include "resource/buffer.h"
|
||||
#include "resource/shader.h"
|
||||
#include "scene/scene.h"
|
||||
#include <glad/glad.h>
|
||||
#include <memory>
|
||||
|
||||
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<Shader> 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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,19 @@ bool RayTracer::initialize(const std::shared_ptr<Shader> &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<BVH>();
|
||||
|
|
@ -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<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
|
||||
|
|
|
|||
Loading…
Reference in New Issue