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/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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue