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 <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 <GLFW/glfw3.h>
#include <iostream>
#include <memory>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.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;
@ -130,6 +130,53 @@ std::shared_ptr<Mesh> create_box(const Vec3& min, const Vec3& max, uint material
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
void setup_cornell_box() {
g_scene = std::make_unique<Scene>();
@ -164,10 +211,25 @@ void setup_cornell_box() {
auto metal_material = std::make_shared<Material>();
metal_material->set_albedo(Vec3(0.95f, 0.93f, 0.88f));
metal_material->set_metallic(1.0f);
metal_material->set_roughness(0.1f);
metal_material->set_roughness(0.0f);
metal_material->set_type(MaterialType::METAL);
uint metal_id = g_scene->add_material(metal_material);
// 5: Glass/Dielectric (refraction)
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);
// 6: Yellow emissive sphere
auto emissive_sphere_mat = std::make_shared<Material>();
emissive_sphere_mat->set_albedo(Vec3(1.0f, 0.8f, 0.2f));
emissive_sphere_mat->set_emission(Vec3(5.0f, 4.0f, 1.0f));
emissive_sphere_mat->set_type(MaterialType::EMISSIVE);
uint emissive_sphere_id = g_scene->add_material(emissive_sphere_mat);
// Create room (Cornell Box)
float room_size = 2.0f;
@ -178,8 +240,7 @@ void setup_cornell_box() {
Vec3(room_size, -room_size, room_size),
Vec3(-room_size, -room_size, room_size),
Vec3(0.0f, 1.0f, 0.0f),
white_id
);
white_id);
floor->upload_to_gpu();
g_scene->add_mesh(floor);
@ -190,8 +251,7 @@ void setup_cornell_box() {
Vec3(room_size, room_size, -room_size),
Vec3(-room_size, room_size, -room_size),
Vec3(0.0f, -1.0f, 0.0f),
white_id
);
white_id);
ceiling->upload_to_gpu();
g_scene->add_mesh(ceiling);
@ -202,8 +262,7 @@ void setup_cornell_box() {
Vec3(room_size, room_size, -room_size),
Vec3(room_size, -room_size, -room_size),
Vec3(0.0f, 0.0f, 1.0f),
white_id
);
white_id);
back_wall->upload_to_gpu();
g_scene->add_mesh(back_wall);
@ -214,8 +273,7 @@ void setup_cornell_box() {
Vec3(-room_size, room_size, -room_size),
Vec3(-room_size, -room_size, -room_size),
Vec3(1.0f, 0.0f, 0.0f),
red_id
);
red_id);
left_wall->upload_to_gpu();
g_scene->add_mesh(left_wall);
@ -226,8 +284,7 @@ void setup_cornell_box() {
Vec3(room_size, room_size, room_size),
Vec3(room_size, -room_size, room_size),
Vec3(-1.0f, 0.0f, 0.0f),
green_id
);
green_id);
right_wall->upload_to_gpu();
g_scene->add_mesh(right_wall);
@ -239,8 +296,7 @@ void setup_cornell_box() {
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
);
light_id);
area_light->upload_to_gpu();
g_scene->add_mesh(area_light);
@ -250,10 +306,22 @@ void setup_cornell_box() {
g_scene->add_mesh(tall_box);
// Short box (metal, right side)
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), white_id);
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), glass_id);
short_box->upload_to_gpu();
g_scene->add_mesh(short_box);
// // 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);
@ -346,13 +414,14 @@ void process_input() {
g_pitch += yoffset;
// Constrain pitch
if (g_pitch > 89.0f) g_pitch = 89.0f;
if (g_pitch < -89.0f) g_pitch = -89.0f;
if (g_pitch > 89.0f)
g_pitch = 89.0f;
if (g_pitch < -89.0f)
g_pitch = -89.0f;
camera_changed = true;
}
}
else {
} else {
g_firstMouse = true; // Reset when released
}
@ -426,8 +495,7 @@ void render_loop() {
if (delta >= 1.0) {
double fps = frame_count / delta;
std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) +
" | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms";
std::string title = "Aurora - Cornell Box | FPS: " + std::to_string((int)fps) + " | Frame: " + std::to_string((int)stats.frame_time_ms_) + "ms";
glfwSetWindowTitle(g_window, title.c_str());
frame_count = 0;

View File

@ -3,10 +3,22 @@
#include "basic/types.h"
#include "resource/texture.h"
#include <array>
#include <memory>
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
enum class MaterialType {
DIFFUSE = 0,
@ -72,6 +84,37 @@ public:
*/
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
* @return Albedo color
@ -125,7 +168,7 @@ public:
* @return Albedo texture (nullptr if none)
*/
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)
*/
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:
@ -144,8 +237,7 @@ private:
float ior_;
MaterialType type_;
std::shared_ptr<Texture> albedo_texture_;
std::shared_ptr<Texture> normal_texture_;
std::array<std::shared_ptr<Texture>, static_cast<int>(TextureSlot::COUNT)> textures_;
};
} // namespace are

View File

@ -2,6 +2,7 @@
#define ARE_INCLUDE_SCENE_MESH_H
#include "basic/types.h"
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
namespace are {
@ -39,6 +40,14 @@ public:
*/
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
* @return Vertex array
@ -77,6 +86,12 @@ public:
*/
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
void release_gpu_resources();

View File

@ -32,12 +32,14 @@ layout(binding = 4, rgba32f) uniform image2D accumulation_image;
struct Material {
vec3 albedo;
float metallic;
vec3 emission;
float metallic;
float roughness;
int type;
float ior;
vec2 padding;
float padding1;
float padding2;
uint texture_handles[6];
};
struct Light {
@ -63,6 +65,7 @@ struct HitInfo {
vec3 normal;
vec2 texcoord;
uint material_id;
int material_type; // material type from G-Buffer
};
struct ScatterResult {
@ -100,6 +103,15 @@ uniform mat4 u_inv_view_projection;
uniform bool u_enable_accumulation;
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;
// ============================================================================
// Utility
@ -353,6 +365,7 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
hit.normal = vec3(0.0, 1.0, 0.0);
hit.texcoord = vec2(0.0);
hit.material_id = 0u;
hit.material_type = 0;
vec4 pos = imageLoad(g_position, pixel_coords);
if (pos.w <= 0.5) {
@ -365,10 +378,15 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
// integer material id
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.position = p;
hit.normal = n;
hit.material_id = mid;
hit.material_type = mtype;
// For RR/any debug usage; path tracing uses this as starting point only.
hit.t = length(p - ray.origin);
@ -380,6 +398,54 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
// 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) {
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);
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 refraction_ratio = dot(unit_dir, hit.normal) < 0.0 ? (1.0 / mat.ior) : mat.ior;
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
float reflect_prob = fresnel_dielectric(cos_theta, refraction_ratio);
// Determine if ray is entering or exiting the material
// If dot(dir, normal) < 0, ray is entering (from air into material)
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;
if (cannot_refract || random_float(seed) < reflect_prob) {
dir = reflect_vector(unit_dir, hit.normal);
if (total_internal_reflection || random_float(seed) < f) {
// Reflect
dir = reflect_vector(unit_dir, normal);
} 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;
@ -548,6 +633,16 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint
if (hit0.hit) {
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;
if (mat0.type == MATERIAL_DIFFUSE) {
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);
// 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;
if (mat.type == MATERIAL_DIFFUSE) {
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;
}
// 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() {
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 image_size = imageSize(output_image);
@ -611,14 +721,20 @@ void main() {
}
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) {
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
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));
imageStore(output_image, pixel_coords, vec4(color, 1.0));
// Apply ACES tone mapping to output (not accumulation)
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_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
const Camera &camera = scene.get_camera();
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)
const auto &materials = scene.get_materials();
if (!materials.empty()) {
// Aligned to match GLSL std430 layout (vec3 = vec4 = 16 bytes)
struct MaterialData {
Vec3 albedo;
alignas(16) Vec3 albedo;
alignas(16) Vec3 emission;
float metallic;
Vec3 emission;
float roughness;
int type;
float ior;
Vec2 padding;
float padding1;
float padding2;
uint texture_handles[6];
};
std::vector<MaterialData> material_data;
@ -268,6 +300,15 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
data.roughness = mat->get_roughness();
data.type = static_cast<int>(mat->get_type());
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);
}

View File

@ -1,4 +1,5 @@
#include "scene/material.h"
#include <array>
namespace are {
@ -9,8 +10,7 @@ Material::Material()
, roughness_(0.5f)
, ior_(1.5f)
, type_(MaterialType::DIFFUSE)
, albedo_texture_(nullptr)
, normal_texture_(nullptr) {
, textures_() {
}
Material::~Material() {
@ -41,11 +41,31 @@ void Material::set_type(MaterialType type) {
}
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) {
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

View File

@ -1,5 +1,6 @@
#include "scene/mesh.h"
#include "utils/logger.h"
#include <algorithm>
#include <glad/glad.h>
namespace are {
@ -96,7 +97,8 @@ bool Mesh::upload_to_gpu() {
}
void Mesh::release_gpu_resources() {
if (!uploaded_) return;
if (!uploaded_)
return;
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
@ -116,4 +118,53 @@ void Mesh::release_gpu_resources() {
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