fix: 修复法线贴图计算问题并修复G-Buffer和非Primary Ray的tangent有关bug

- G-Buffer添加tangent上传
- BVH部分附加tangent到Triangle数据
- 删除原tangent近似计算代码并在代码中使用传入的tangent
master
ternaryop8479 2026-03-06 23:59:46 +08:00
parent c88e786deb
commit 330fdcf43d
11 changed files with 653 additions and 510 deletions

Binary file not shown.

View File

@ -226,18 +226,19 @@ void setup_cornell_box() {
// 5: Textured material with normal map (for short box)
auto textured_material = std::make_shared<Material>();
textured_material->set_albedo(Vec3(1.0f, 1.0f, 1.0f));
textured_material->set_metallic(0.0f);
textured_material->set_roughness(0.8f);
textured_material->set_type(MaterialType::DIFFUSE);
textured_material->set_metallic(1.0f);
textured_material->set_roughness(1.0f);
// textured_material->set_type(MaterialType::DIFFUSE);
textured_material->set_type(MaterialType::METAL);
// Load textures
auto albedo_tex = std::make_shared<are::Texture>();
if (albedo_tex->load_from_file("examples/assets/normal_map_cornell_box/albedo.png")) {
textured_material->set_albedo_texture(albedo_tex);
ARE_LOG_INFO("Loaded albedo texture");
} else {
ARE_LOG_WARN("Failed to load albedo texture");
}
// auto albedo_tex = std::make_shared<are::Texture>();
// if (albedo_tex->load_from_file("examples/assets/normal_map_cornell_box/albedo.png")) {
// textured_material->set_albedo_texture(albedo_tex);
// ARE_LOG_INFO("Loaded albedo texture");
// } else {
// ARE_LOG_WARN("Failed to load albedo texture");
// }
auto normal_tex = std::make_shared<are::Texture>();
if (normal_tex->load_from_file("examples/assets/normal_map_cornell_box/normal.png")) {
@ -296,7 +297,8 @@ 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);
metal_id);
// white_id);
back_wall->upload_to_gpu();
g_scene->add_mesh(back_wall);
@ -361,7 +363,7 @@ void setup_cornell_box() {
g_camera->set_position(g_cameraPos);
g_camera->set_target(g_cameraTarget);
g_camera->set_up(g_cameraUp);
g_camera->set_perspective(45.0f, static_cast<float>(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f);
g_camera->set_perspective(90.0f, static_cast<float>(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f);
g_scene->set_camera(g_camera);
// Add point light

View File

@ -18,7 +18,9 @@ constexpr int GBUFFER_NORMAL = 1;
constexpr int GBUFFER_ALBEDO = 2;
constexpr int GBUFFER_MATERIAL = 3;
constexpr int GBUFFER_MATERIAL_ID = 4;
constexpr int GBUFFER_COUNT = 5;
constexpr int GBUFFER_TEXCOORD = 5;
constexpr int GBUFFER_TANGENT = 6;
constexpr int GBUFFER_COUNT = 7;
// Compute shader work group size
constexpr int COMPUTE_GROUP_SIZE_X = 16;

View File

@ -42,6 +42,7 @@ struct Triangle {
Vec3 v0_, v1_, v2_;
Vec3 n0_, n1_, n2_;
Vec2 uv0_, uv1_, uv2_;
Vec3 t0_, t1_, t2_; // Tangents for each vertex
uint material_id_;
// Get bounding box of triangle
@ -75,6 +76,8 @@ struct TriangleGpu {
Vec4 n2_; ///< xyz = n2, w = reserved
Vec4 uv0_uv1_; ///< xy = uv0, zw = uv1
Vec4 uv2_; ///< xy = uv2, zw = reserved
Vec4 t0_; ///< xyz = t0 (tangent at v0), w = reserved
Vec4 t1_; ///< xyz = t1 (tangent at v1), w = reserved
};
// Bounding Volume Hierarchy for ray tracing acceleration

View File

@ -229,6 +229,24 @@ public:
return textures_[static_cast<int>(slot)] != nullptr;
}
/*
* @brief Set texture array index for a slot (used by RayTracer)
* @param slot Texture slot
* @param index Index into the texture array
*/
void set_texture_index(TextureSlot slot, uint32_t index) {
texture_indices_[static_cast<int>(slot)] = index;
}
/*
* @brief Get texture array index for a slot
* @param slot Texture slot
* @return Index into the texture array (0 = no texture)
*/
uint32_t get_texture_index(TextureSlot slot) const {
return texture_indices_[static_cast<int>(slot)];
}
private:
Vec3 albedo_;
Vec3 emission_;
@ -238,6 +256,7 @@ private:
MaterialType type_;
std::array<std::shared_ptr<Texture>, static_cast<int>(TextureSlot::COUNT)> textures_;
std::array<uint32_t, static_cast<int>(TextureSlot::COUNT)> texture_indices_;
};
} // namespace are

View File

@ -12,6 +12,8 @@ layout(location = 1) out vec4 g_normal;
layout(location = 2) out vec4 g_albedo;
layout(location = 3) out vec4 g_material;
layout(location = 4) out uint g_material_id;
layout(location = 5) out vec4 g_texcoord;
layout(location = 6) out vec4 g_tangent;
uniform vec3 u_albedo;
uniform float u_metallic;
@ -38,4 +40,10 @@ void main() {
g_material = vec4(u_metallic, u_roughness, u_ior, float(u_material_type));
g_material_id = u_material_id;
// Store texcoord
g_texcoord = vec4(fs_in.texcoord, 0.0, 0.0);
// Store tangent (xyz = tangent, w = unused)
g_tangent = vec4(fs_in.tangent, 0.0);
}

View File

@ -26,6 +26,12 @@ layout(binding = 2, rgba8) uniform readonly image2D g_albedo;
layout(binding = 5, rgba32f) uniform readonly image2D g_material;
layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id;
// Texcoord from G-Buffer
layout(binding = 7, rgba32f) uniform readonly image2D g_texcoord;
// Tangent from G-Buffer
layout(binding = 8, rgba32f) uniform readonly image2D g_tangent;
// Output
layout(binding = 3, rgba32f) uniform image2D output_image;
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
@ -64,6 +70,7 @@ struct HitInfo {
vec3 position;
vec3 normal;
vec2 texcoord;
vec3 tangent;
uint material_id;
int material_type; // material type from G-Buffer
};
@ -88,6 +95,8 @@ struct TriangleGpu {
vec4 n2;
vec4 uv0_uv1; // xy uv0, zw uv1
vec4 uv2; // xy uv2
vec4 t0; // tangent at v0
vec4 t1; // tangent at v1
};
layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; };
@ -268,11 +277,21 @@ bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) {
vec2 uv1 = tri.uv0_uv1.zw;
vec2 uv2 = tri.uv2.xy;
// Interpolate tangents
vec3 t0 = tri.t0.xyz;
vec3 t1 = tri.t1.xyz;
// Compute t2 from normal and t0 (t2 = cross(n, t0))
vec3 t2 = normalize(cross(n0, t0)); // approximate third tangent
hit.hit = true;
hit.t = t;
hit.position = ray.origin + t * ray.direction;
hit.normal = normalize(n0 * w + n1 * u + n2 * v);
hit.texcoord = uv0 * w + uv1 * u + uv2 * v;
// Interpolate tangent using barycentric coordinates
hit.tangent = normalize(t0 * w + t1 * u + t2 * v);
hit.material_id = as_uint(tri.v0_material.w);
return true;
}
@ -378,6 +397,7 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
hit.position = vec3(0.0);
hit.normal = vec3(0.0, 1.0, 0.0);
hit.texcoord = vec2(0.0);
hit.tangent = vec3(0.0);
hit.material_id = 0u;
hit.material_type = 0;
@ -396,9 +416,19 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) {
vec4 mat = imageLoad(g_material, pixel_coords);
int mtype = int(mat.w);
// Read texcoord from G-Buffer
vec4 texcoord_tangent = imageLoad(g_texcoord, pixel_coords);
vec2 texcoord = texcoord_tangent.xy;
// Read tangent from G-Buffer
vec4 tangent_data = imageLoad(g_tangent, pixel_coords);
vec3 tangent = tangent_data.xyz;
hit.hit = true;
hit.position = p;
hit.normal = n;
hit.texcoord = texcoord;
hit.tangent = tangent;
hit.material_id = mid;
hit.material_type = mtype;
@ -425,7 +455,7 @@ vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_hand
}
// Apply material textures to get final PBR values
void apply_material_textures(inout Material mat, vec2 texcoord, vec3 normal, vec3 tangent) {
void apply_material_textures(inout Material mat, inout vec3 normal, vec2 texcoord, vec3 tangent) {
if (!u_enable_textures) return;
// Albedo texture
@ -556,7 +586,7 @@ ScatterResult scatter_ray(Ray ray_in, HitInfo hit, Material mat, inout uint seed
// Direct lighting (with shadow ray)
// ============================================================================
vec3 eval_direct_lighting(HitInfo hit, Material mat, inout uint seed) {
vec3 eval_direct_lighting(inout HitInfo hit, Material mat, inout uint seed) {
if (u_light_count == 0u) return vec3(0.0);
uint light_idx = uint(random_float(seed) * float(u_light_count)) % u_light_count;
@ -652,10 +682,8 @@ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint
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);
// Apply PBR textures (use tangent from G-Buffer if available)
apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent);
radiance += throughput * mat0.emission;
if (mat0.type == MATERIAL_DIFFUSE) {
@ -679,10 +707,8 @@ 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);
// Apply PBR textures (use tangent from intersection)
apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent);
radiance += throughput * mat.emission;
if (mat.type == MATERIAL_DIFFUSE) {

View File

@ -1,41 +1,41 @@
#include "core/bvh.h"
#include "utils/logger.h"
#include "basic/constants.h"
#include "utils/logger.h"
#include <algorithm>
#include <limits>
namespace are {
// AABB implementation
void AABB::expand(const Vec3& point) {
min_ = glm::min(min_, point);
max_ = glm::max(max_, point);
void AABB::expand(const Vec3 &point) {
min_ = glm::min(min_, point);
max_ = glm::max(max_, point);
}
void AABB::expand(const AABB& other) {
min_ = glm::min(min_, other.min_);
max_ = glm::max(max_, other.max_);
void AABB::expand(const AABB &other) {
min_ = glm::min(min_, other.min_);
max_ = glm::max(max_, other.max_);
}
float AABB::surface_area() const {
Vec3 extent = max_ - min_;
return 2.0f * (extent.x * extent.y + extent.y * extent.z + extent.z * extent.x);
Vec3 extent = max_ - min_;
return 2.0f * (extent.x * extent.y + extent.y * extent.z + extent.z * extent.x);
}
bool AABB::is_valid() const {
return min_.x <= max_.x && min_.y <= max_.y && min_.z <= max_.z;
return min_.x <= max_.x && min_.y <= max_.y && min_.z <= max_.z;
}
// Triangle implementation
AABB Triangle::get_bounds() const {
AABB bounds(v0_, v0_);
bounds.expand(v1_);
bounds.expand(v2_);
return bounds;
AABB bounds(v0_, v0_);
bounds.expand(v1_);
bounds.expand(v2_);
return bounds;
}
Vec3 Triangle::get_centroid() const {
return (v0_ + v1_ + v2_) / 3.0f;
return (v0_ + v1_ + v2_) / 3.0f;
}
// BVH implementation
@ -43,290 +43,299 @@ BVH::BVH() {
}
BVH::~BVH() {
clear();
clear();
}
bool BVH::build(const std::vector<std::shared_ptr<Mesh>>& meshes) {
clear();
bool BVH::build(const std::vector<std::shared_ptr<Mesh>> &meshes) {
clear();
ARE_LOG_INFO("Building BVH...");
ARE_LOG_INFO("Building BVH...");
// Extract all triangles from meshes
for (const auto& mesh : meshes) {
const auto& vertices = mesh->get_vertices();
const auto& indices = mesh->get_indices();
uint material_id = mesh->get_material();
Mat4 transform = mesh->get_transform();
// Extract all triangles from meshes
for (const auto &mesh : meshes) {
const auto &vertices = mesh->get_vertices();
const auto &indices = mesh->get_indices();
uint material_id = mesh->get_material();
Mat4 transform = mesh->get_transform();
for (size_t i = 0; i < indices.size(); i += 3) {
Triangle tri;
for (size_t i = 0; i < indices.size(); i += 3) {
Triangle tri;
// Transform vertices
Vec4 v0 = transform * Vec4(vertices[indices[i]].position_, 1.0f);
Vec4 v1 = transform * Vec4(vertices[indices[i + 1]].position_, 1.0f);
Vec4 v2 = transform * Vec4(vertices[indices[i + 2]].position_, 1.0f);
// Transform vertices
Vec4 v0 = transform * Vec4(vertices[indices[i]].position_, 1.0f);
Vec4 v1 = transform * Vec4(vertices[indices[i + 1]].position_, 1.0f);
Vec4 v2 = transform * Vec4(vertices[indices[i + 2]].position_, 1.0f);
tri.v0_ = Vec3(v0) / v0.w;
tri.v1_ = Vec3(v1) / v1.w;
tri.v2_ = Vec3(v2) / v2.w;
tri.v0_ = Vec3(v0) / v0.w;
tri.v1_ = Vec3(v1) / v1.w;
tri.v2_ = Vec3(v2) / v2.w;
// Transform normals
Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(transform)));
tri.n0_ = glm::normalize(normal_matrix * vertices[indices[i]].normal_);
tri.n1_ = glm::normalize(normal_matrix * vertices[indices[i + 1]].normal_);
tri.n2_ = glm::normalize(normal_matrix * vertices[indices[i + 2]].normal_);
// Transform normals
Mat3 normal_matrix = glm::transpose(glm::inverse(Mat3(transform)));
tri.n0_ = glm::normalize(normal_matrix * vertices[indices[i]].normal_);
tri.n1_ = glm::normalize(normal_matrix * vertices[indices[i + 1]].normal_);
tri.n2_ = glm::normalize(normal_matrix * vertices[indices[i + 2]].normal_);
// Copy UVs
tri.uv0_ = vertices[indices[i]].texcoord_;
tri.uv1_ = vertices[indices[i + 1]].texcoord_;
tri.uv2_ = vertices[indices[i + 2]].texcoord_;
// Transform tangents
tri.t0_ = glm::normalize(normal_matrix * vertices[indices[i]].tangent_);
tri.t1_ = glm::normalize(normal_matrix * vertices[indices[i + 1]].tangent_);
tri.t2_ = glm::normalize(normal_matrix * vertices[indices[i + 2]].tangent_);
tri.material_id_ = material_id;
// Copy UVs
tri.uv0_ = vertices[indices[i]].texcoord_;
tri.uv1_ = vertices[indices[i + 1]].texcoord_;
tri.uv2_ = vertices[indices[i + 2]].texcoord_;
triangles_.push_back(tri);
}
}
tri.material_id_ = material_id;
if (triangles_.empty()) {
ARE_LOG_WARN("No triangles to build BVH");
return false;
}
triangles_.push_back(tri);
}
}
// Initialize triangle indices
triangle_indices_.resize(triangles_.size());
for (size_t i = 0; i < triangles_.size(); ++i) {
triangle_indices_[i] = static_cast<uint>(i);
}
if (triangles_.empty()) {
ARE_LOG_WARN("No triangles to build BVH");
return false;
}
// Reserve space for nodes (estimate)
nodes_.reserve(triangles_.size() * 2);
// Initialize triangle indices
triangle_indices_.resize(triangles_.size());
for (size_t i = 0; i < triangles_.size(); ++i) {
triangle_indices_[i] = static_cast<uint>(i);
}
// Create root node
nodes_.emplace_back();
// Reserve space for nodes (estimate)
nodes_.reserve(triangles_.size() * 2);
// Build BVH recursively
build_recursive_(0, 0, static_cast<uint>(triangles_.size()));
// Create root node
nodes_.emplace_back();
ARE_LOG_INFO("BVH built: " + std::to_string(nodes_.size()) + " nodes, " +
std::to_string(triangles_.size()) + " triangles");
// Build BVH recursively
build_recursive_(0, 0, static_cast<uint>(triangles_.size()));
return true;
ARE_LOG_INFO("BVH built: " + std::to_string(nodes_.size()) + " nodes, " + std::to_string(triangles_.size()) + " triangles");
return true;
}
void BVH::build_recursive_(uint node_idx, uint first_prim, uint prim_count) {
BVHNode& node = nodes_[node_idx];
BVHNode &node = nodes_[node_idx];
// Calculate bounds
AABB bounds = calculate_bounds_(first_prim, prim_count);
node.aabb_min_ = bounds.min_;
node.aabb_max_ = bounds.max_;
// Calculate bounds
AABB bounds = calculate_bounds_(first_prim, prim_count);
node.aabb_min_ = bounds.min_;
node.aabb_max_ = bounds.max_;
// Leaf node threshold
const uint LEAF_SIZE = 4;
// Leaf node threshold
const uint LEAF_SIZE = 4;
if (prim_count <= LEAF_SIZE) {
// Create leaf node
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
}
// Find best split
int axis;
float split_pos;
float split_cost = find_best_split_(first_prim, prim_count, axis, split_pos);
if(split_cost == std::numeric_limits<float>::max()) {
if (prim_count <= LEAF_SIZE) {
// Create leaf node
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
}
// Check if split is beneficial
float no_split_cost = prim_count * bounds.surface_area();
if (split_cost >= no_split_cost) {
// Create leaf node
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
}
// Find best split
int axis;
float split_pos;
float split_cost = find_best_split_(first_prim, prim_count, axis, split_pos);
if (split_cost == std::numeric_limits<float>::max()) {
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
}
// Partition primitives
uint mid = first_prim;
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle& tri = triangles_[triangle_indices_[i]];
float centroid = tri.get_centroid()[axis];
// Check if split is beneficial
float no_split_cost = prim_count * bounds.surface_area();
if (split_cost >= no_split_cost) {
// Create leaf node
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
}
if (centroid < split_pos) {
std::swap(triangle_indices_[i], triangle_indices_[mid]);
mid++;
}
}
// Partition primitives
uint mid = first_prim;
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle &tri = triangles_[triangle_indices_[i]];
float centroid = tri.get_centroid()[axis];
// Ensure we have primitives on both sides
if (mid == first_prim || mid == first_prim + prim_count) {
mid = first_prim + prim_count / 2;
}
if (centroid < split_pos) {
std::swap(triangle_indices_[i], triangle_indices_[mid]);
mid++;
}
}
// Create interior node
uint left_count = mid - first_prim;
uint right_count = prim_count - left_count;
// Ensure we have primitives on both sides
if (mid == first_prim || mid == first_prim + prim_count) {
mid = first_prim + prim_count / 2;
}
node.left_first_ = static_cast<uint>(nodes_.size());
node.count_ = 0;
// Create interior node
uint left_count = mid - first_prim;
uint right_count = prim_count - left_count;
// Create child nodes
nodes_.emplace_back();
nodes_.emplace_back();
node.left_first_ = static_cast<uint>(nodes_.size());
node.count_ = 0;
// Recursively build children
build_recursive_(node.left_first_, first_prim, left_count);
build_recursive_(node.left_first_ + 1, mid, right_count);
// Create child nodes
nodes_.emplace_back();
nodes_.emplace_back();
// Recursively build children
build_recursive_(node.left_first_, first_prim, left_count);
build_recursive_(node.left_first_ + 1, mid, right_count);
}
float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float& split_pos) {
float best_cost = std::numeric_limits<float>::max();
float BVH::find_best_split_(uint first_prim, uint prim_count, int &axis, float &split_pos) {
float best_cost = std::numeric_limits<float>::max();
axis = 0, split_pos = 0.0f;
AABB centroid_bounds = calculate_centroid_bounds_(first_prim, prim_count);
AABB centroid_bounds = calculate_centroid_bounds_(first_prim, prim_count);
// Try each axis
for (int a = 0; a < 3; ++a) {
float extent = centroid_bounds.max_[a] - centroid_bounds.min_[a];
if (extent < EPSILON) continue;
// Try each axis
for (int a = 0; a < 3; ++a) {
float extent = centroid_bounds.max_[a] - centroid_bounds.min_[a];
if (extent < EPSILON)
continue;
// Try multiple split positions
const int NUM_BINS = 16;
for (int i = 1; i < NUM_BINS; ++i) {
float t = static_cast<float>(i) / NUM_BINS;
float pos = centroid_bounds.min_[a] + t * extent;
// Try multiple split positions
const int NUM_BINS = 16;
for (int i = 1; i < NUM_BINS; ++i) {
float t = static_cast<float>(i) / NUM_BINS;
float pos = centroid_bounds.min_[a] + t * extent;
// Count primitives and calculate bounds for each side
AABB left_bounds, right_bounds;
uint left_count = 0, right_count = 0;
// Count primitives and calculate bounds for each side
AABB left_bounds, right_bounds;
uint left_count = 0, right_count = 0;
for (uint j = first_prim; j < first_prim + prim_count; ++j) {
Triangle& tri = triangles_[triangle_indices_[j]];
float centroid = tri.get_centroid()[a];
for (uint j = first_prim; j < first_prim + prim_count; ++j) {
Triangle &tri = triangles_[triangle_indices_[j]];
float centroid = tri.get_centroid()[a];
if (centroid < pos) {
left_bounds.expand(tri.get_bounds());
left_count++;
} else {
right_bounds.expand(tri.get_bounds());
right_count++;
}
}
if (centroid < pos) {
left_bounds.expand(tri.get_bounds());
left_count++;
} else {
right_bounds.expand(tri.get_bounds());
right_count++;
}
}
// Calculate SAH cost
if (left_count == 0 || right_count == 0) continue;
// Calculate SAH cost
if (left_count == 0 || right_count == 0)
continue;
float cost = left_count * left_bounds.surface_area() +
right_count * right_bounds.surface_area();
float cost = left_count * left_bounds.surface_area() + right_count * right_bounds.surface_area();
if (cost < best_cost) {
best_cost = cost;
axis = a;
split_pos = pos;
}
}
}
if (cost < best_cost) {
best_cost = cost;
axis = a;
split_pos = pos;
}
}
}
return best_cost;
return best_cost;
}
AABB BVH::calculate_bounds_(uint first_prim, uint prim_count) {
AABB bounds{Vec3(std::numeric_limits<float>::max()),
Vec3(std::numeric_limits<float>::lowest())};
AABB bounds { Vec3(std::numeric_limits<float>::max()),
Vec3(std::numeric_limits<float>::lowest()) };
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle& tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_bounds());
}
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle &tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_bounds());
}
return bounds;
return bounds;
}
AABB BVH::calculate_centroid_bounds_(uint first_prim, uint prim_count) {
AABB bounds{Vec3(std::numeric_limits<float>::max()),
Vec3(std::numeric_limits<float>::lowest())};
AABB bounds { Vec3(std::numeric_limits<float>::max()),
Vec3(std::numeric_limits<float>::lowest()) };
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle& tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_centroid());
}
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle &tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_centroid());
}
return bounds;
return bounds;
}
bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
if (nodes_.empty() || triangles_.empty()) {
ARE_LOG_ERROR("Cannot upload empty BVH to GPU");
return false;
}
bool BVH::upload_to_gpu(Buffer &node_buffer, Buffer &triangle_buffer) {
if (nodes_.empty() || triangles_.empty()) {
ARE_LOG_ERROR("Cannot upload empty BVH to GPU");
return false;
}
// Reorder triangles according to BVH layout
std::vector<Triangle> ordered_triangles;
ordered_triangles.reserve(triangles_.size());
for (uint idx : triangle_indices_) {
ordered_triangles.push_back(triangles_[idx]);
}
// Reorder triangles according to BVH layout
std::vector<Triangle> ordered_triangles;
ordered_triangles.reserve(triangles_.size());
for (uint idx : triangle_indices_) {
ordered_triangles.push_back(triangles_[idx]);
}
// Pack nodes to GPU layout
std::vector<BVHNodeGpu> node_gpu;
node_gpu.resize(nodes_.size());
for (size_t i = 0; i < nodes_.size(); ++i) {
const BVHNode& n = nodes_[i];
BVHNodeGpu g;
g.aabb_min_left_first_ = Vec4(n.aabb_min_, glm::uintBitsToFloat(n.left_first_));
g.aabb_max_count_ = Vec4(n.aabb_max_, glm::uintBitsToFloat(n.count_));
node_gpu[i] = g;
}
// Pack nodes to GPU layout
std::vector<BVHNodeGpu> node_gpu;
node_gpu.resize(nodes_.size());
for (size_t i = 0; i < nodes_.size(); ++i) {
const BVHNode &n = nodes_[i];
BVHNodeGpu g;
g.aabb_min_left_first_ = Vec4(n.aabb_min_, glm::uintBitsToFloat(n.left_first_));
g.aabb_max_count_ = Vec4(n.aabb_max_, glm::uintBitsToFloat(n.count_));
node_gpu[i] = g;
}
// Pack triangles to GPU layout
std::vector<TriangleGpu> tri_gpu;
tri_gpu.resize(ordered_triangles.size());
for (size_t i = 0; i < ordered_triangles.size(); ++i) {
const Triangle& t = ordered_triangles[i];
// Pack triangles to GPU layout
std::vector<TriangleGpu> tri_gpu;
tri_gpu.resize(ordered_triangles.size());
for (size_t i = 0; i < ordered_triangles.size(); ++i) {
const Triangle &t = ordered_triangles[i];
TriangleGpu g{};
g.v0_material_ = Vec4(t.v0_, glm::uintBitsToFloat(t.material_id_));
g.v1_ = Vec4(t.v1_, 0.0f);
g.v2_ = Vec4(t.v2_, 0.0f);
TriangleGpu g {};
g.v0_material_ = Vec4(t.v0_, glm::uintBitsToFloat(t.material_id_));
g.v1_ = Vec4(t.v1_, 0.0f);
g.v2_ = Vec4(t.v2_, 0.0f);
g.n0_ = Vec4(t.n0_, 0.0f);
g.n1_ = Vec4(t.n1_, 0.0f);
g.n2_ = Vec4(t.n2_, 0.0f);
g.n0_ = Vec4(t.n0_, 0.0f);
g.n1_ = Vec4(t.n1_, 0.0f);
g.n2_ = Vec4(t.n2_, 0.0f);
g.uv0_uv1_ = Vec4(t.uv0_.x, t.uv0_.y, t.uv1_.x, t.uv1_.y);
g.uv2_ = Vec4(t.uv2_.x, t.uv2_.y, 0.0f, 0.0f);
g.uv0_uv1_ = Vec4(t.uv0_.x, t.uv0_.y, t.uv1_.x, t.uv1_.y);
g.uv2_ = Vec4(t.uv2_.x, t.uv2_.y, 0.0f, 0.0f);
tri_gpu[i] = g;
}
// Pack tangents
g.t0_ = Vec4(t.t0_, 0.0f);
g.t1_ = Vec4(t.t1_, 0.0f);
if (!node_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
node_gpu.size() * sizeof(BVHNodeGpu),
node_gpu.data(),
BufferUsage::STATIC_DRAW)) {
ARE_LOG_ERROR("Failed to upload BVH nodes to GPU");
return false;
}
tri_gpu[i] = g;
}
if (!triangle_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
tri_gpu.size() * sizeof(TriangleGpu),
tri_gpu.data(),
BufferUsage::STATIC_DRAW)) {
ARE_LOG_ERROR("Failed to upload BVH triangles to GPU");
return false;
}
if (!node_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
node_gpu.size() * sizeof(BVHNodeGpu),
node_gpu.data(),
BufferUsage::STATIC_DRAW)) {
ARE_LOG_ERROR("Failed to upload BVH nodes to GPU");
return false;
}
ARE_LOG_INFO("BVH uploaded to GPU successfully");
return true;
if (!triangle_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
tri_gpu.size() * sizeof(TriangleGpu),
tri_gpu.data(),
BufferUsage::STATIC_DRAW)) {
ARE_LOG_ERROR("Failed to upload BVH triangles to GPU");
return false;
}
ARE_LOG_INFO("BVH uploaded to GPU successfully");
return true;
}
void BVH::clear() {
nodes_.clear();
triangles_.clear();
triangle_indices_.clear();
nodes_.clear();
triangles_.clear();
triangle_indices_.clear();
}
} // namespace are

View File

@ -5,226 +5,240 @@
namespace are {
GBuffer::GBuffer(uint width, uint height)
: width_(width)
, height_(height)
, fbo_(INVALID_HANDLE)
, depth_texture_(INVALID_HANDLE)
, initialized_(false) {
for (int i = 0; i < GBUFFER_COUNT; ++i) {
textures_[i] = INVALID_HANDLE;
}
: width_(width)
, height_(height)
, fbo_(INVALID_HANDLE)
, depth_texture_(INVALID_HANDLE)
, initialized_(false) {
for (int i = 0; i < GBUFFER_COUNT; ++i) {
textures_[i] = INVALID_HANDLE;
}
}
GBuffer::~GBuffer() {
release();
release();
}
bool GBuffer::initialize() {
if (initialized_) {
ARE_LOG_WARN("GBuffer already initialized");
return true;
}
if (initialized_) {
ARE_LOG_WARN("GBuffer already initialized");
return true;
}
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
textures_[GBUFFER_POSITION] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
textures_[GBUFFER_NORMAL] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
textures_[GBUFFER_ALBEDO] = create_texture_(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE);
textures_[GBUFFER_POSITION] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
textures_[GBUFFER_NORMAL] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
textures_[GBUFFER_ALBEDO] = create_texture_(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE);
// New: material params (metallic, roughness, ior, type)
textures_[GBUFFER_MATERIAL] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
// New: material params (metallic, roughness, ior, type)
textures_[GBUFFER_MATERIAL] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
// New: material id (integer)
textures_[GBUFFER_MATERIAL_ID] = create_texture_(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT);
// New: material id (integer)
textures_[GBUFFER_MATERIAL_ID] = create_texture_(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_TEXTURE_2D, textures_[GBUFFER_POSITION], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
GL_TEXTURE_2D, textures_[GBUFFER_NORMAL], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO,
GL_TEXTURE_2D, textures_[GBUFFER_ALBEDO], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL,
GL_TEXTURE_2D, textures_[GBUFFER_MATERIAL], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL_ID,
GL_TEXTURE_2D, textures_[GBUFFER_MATERIAL_ID], 0);
// New: texcoord + tangent (xy = texcoord, zw = tangent)
textures_[GBUFFER_TEXCOORD] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
glGenTextures(1, &depth_texture_);
glBindTexture(GL_TEXTURE_2D, depth_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width_, height_, 0,
GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_texture_, 0);
// New: tangent (xyz = tangent, w = unused)
textures_[GBUFFER_TANGENT] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
GLenum draw_buffers[GBUFFER_COUNT] = {
GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL_ID
};
glDrawBuffers(GBUFFER_COUNT, draw_buffers);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_TEXTURE_2D, textures_[GBUFFER_POSITION], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
GL_TEXTURE_2D, textures_[GBUFFER_NORMAL], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO,
GL_TEXTURE_2D, textures_[GBUFFER_ALBEDO], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL,
GL_TEXTURE_2D, textures_[GBUFFER_MATERIAL], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL_ID,
GL_TEXTURE_2D, textures_[GBUFFER_MATERIAL_ID], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_TEXCOORD,
GL_TEXTURE_2D, textures_[GBUFFER_TEXCOORD], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_TANGENT,
GL_TEXTURE_2D, textures_[GBUFFER_TANGENT], 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
ARE_LOG_ERROR("GBuffer framebuffer is not complete");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
glGenTextures(1, &depth_texture_);
glBindTexture(GL_TEXTURE_2D, depth_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width_, height_, 0,
GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_texture_, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLenum draw_buffers[GBUFFER_COUNT] = {
GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL_ID,
GL_COLOR_ATTACHMENT0 + GBUFFER_TEXCOORD,
GL_COLOR_ATTACHMENT0 + GBUFFER_TANGENT
};
glDrawBuffers(GBUFFER_COUNT, draw_buffers);
initialized_ = true;
ARE_LOG_INFO("GBuffer initialized successfully");
return true;
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
ARE_LOG_ERROR("GBuffer framebuffer is not complete");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
initialized_ = true;
ARE_LOG_INFO("GBuffer initialized successfully");
return true;
}
void GBuffer::release() {
if (!initialized_) return;
if (!initialized_)
return;
if (fbo_ != INVALID_HANDLE) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = INVALID_HANDLE;
}
if (fbo_ != INVALID_HANDLE) {
glDeleteFramebuffers(1, &fbo_);
fbo_ = INVALID_HANDLE;
}
for (int i = 0; i < GBUFFER_COUNT; ++i) {
if (textures_[i] != INVALID_HANDLE) {
glDeleteTextures(1, &textures_[i]);
textures_[i] = INVALID_HANDLE;
}
}
for (int i = 0; i < GBUFFER_COUNT; ++i) {
if (textures_[i] != INVALID_HANDLE) {
glDeleteTextures(1, &textures_[i]);
textures_[i] = INVALID_HANDLE;
}
}
if (depth_texture_ != INVALID_HANDLE) {
glDeleteTextures(1, &depth_texture_);
depth_texture_ = INVALID_HANDLE;
}
if (depth_texture_ != INVALID_HANDLE) {
glDeleteTextures(1, &depth_texture_);
depth_texture_ = INVALID_HANDLE;
}
initialized_ = false;
ARE_LOG_INFO("GBuffer released");
initialized_ = false;
ARE_LOG_INFO("GBuffer released");
}
TextureHandle GBuffer::create_texture_(uint internal_format, uint format, uint type) {
TextureHandle texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width_, height_, 0, format, type, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return texture;
TextureHandle texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width_, height_, 0, format, type, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return texture;
}
void GBuffer::render(const Scene& scene, const Shader& shader) {
if (!initialized_) {
ARE_LOG_ERROR("GBuffer not initialized");
return;
}
void GBuffer::render(const Scene &scene, const Shader &shader) {
if (!initialized_) {
ARE_LOG_ERROR("GBuffer not initialized");
return;
}
// Bind framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glViewport(0, 0, width_, height_);
// Bind framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
glViewport(0, 0, width_, height_);
// Clear buffers
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear buffers
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Enable depth test
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Enable depth test
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
// Use shader
shader.use();
// Use shader
shader.use();
// Set camera matrices
const Camera& camera = scene.get_camera();
Mat4 view = camera.get_view_matrix();
Mat4 projection = camera.get_projection_matrix();
// Set camera matrices
const Camera &camera = scene.get_camera();
Mat4 view = camera.get_view_matrix();
Mat4 projection = camera.get_projection_matrix();
shader.set_mat4("u_view", view);
shader.set_mat4("u_projection", projection);
shader.set_mat4("u_view", view);
shader.set_mat4("u_projection", projection);
// Render all meshes
const auto& meshes = scene.get_meshes();
const auto& materials = scene.get_materials();
// Render all meshes
const auto &meshes = scene.get_meshes();
const auto &materials = scene.get_materials();
for (const auto& mesh : meshes) {
if (!mesh->is_uploaded()) {
ARE_LOG_WARN("Mesh not uploaded to GPU, skipping");
continue;
}
for (const auto &mesh : meshes) {
if (!mesh->is_uploaded()) {
ARE_LOG_WARN("Mesh not uploaded to GPU, skipping");
continue;
}
// Set model matrix
Mat4 model = mesh->get_transform();
shader.set_mat4("u_model", model);
// Set model matrix
Mat4 model = mesh->get_transform();
shader.set_mat4("u_model", model);
// Set material properties
uint material_id = mesh->get_material();
if (material_id < materials.size()) {
const auto& material = materials[material_id];
// Set material properties
uint material_id = mesh->get_material();
if (material_id < materials.size()) {
const auto &material = materials[material_id];
shader.set_vec3("u_albedo", material->get_albedo());
shader.set_float("u_metallic", material->get_metallic());
shader.set_float("u_roughness", material->get_roughness());
shader.set_uint("u_material_id", material_id);
shader.set_vec3("u_albedo", material->get_albedo());
shader.set_float("u_metallic", material->get_metallic());
shader.set_float("u_roughness", material->get_roughness());
shader.set_uint("u_material_id", material_id);
shader.set_float("u_ior", material->get_ior());
shader.set_vec3("u_emission", material->get_emission());
shader.set_uint("u_material_type", static_cast<uint>(material->get_type()));
// Bind textures
auto albedo_tex = material->get_albedo_texture();
if (albedo_tex && albedo_tex->is_valid()) {
albedo_tex->bind(0);
shader.set_int("u_albedo_map", 0);
shader.set_int("u_has_albedo_map", 1);
} else {
shader.set_int("u_has_albedo_map", 0);
}
// Bind textures
auto albedo_tex = material->get_albedo_texture();
if (albedo_tex && albedo_tex->is_valid()) {
albedo_tex->bind(0);
shader.set_int("u_albedo_map", 0);
shader.set_int("u_has_albedo_map", 1);
} else {
shader.set_int("u_has_albedo_map", 0);
}
auto normal_tex = material->get_normal_texture();
if (normal_tex && normal_tex->is_valid()) {
normal_tex->bind(1);
shader.set_int("u_normal_map", 1);
shader.set_int("u_has_normal_map", 1);
} else {
shader.set_int("u_has_normal_map", 0);
}
}
auto normal_tex = material->get_normal_texture();
if (normal_tex && normal_tex->is_valid()) {
normal_tex->bind(1);
shader.set_int("u_normal_map", 1);
shader.set_int("u_has_normal_map", 1);
} else {
shader.set_int("u_has_normal_map", 0);
}
}
// Draw mesh
glBindVertexArray(mesh->get_vao());
glDrawElements(GL_TRIANGLES, mesh->get_indices().size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
// Draw mesh
glBindVertexArray(mesh->get_vao());
glDrawElements(GL_TRIANGLES, mesh->get_indices().size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
// Unbind framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Unbind framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void GBuffer::resize(uint width, uint height) {
if (width == width_ && height == height_) return;
if (width == width_ && height == height_)
return;
width_ = width;
height_ = height;
width_ = width;
height_ = height;
if (initialized_) {
release();
initialize();
}
if (initialized_) {
release();
initialize();
}
}
TextureHandle GBuffer::get_texture(int index) const {
if (index < 0 || index >= GBUFFER_COUNT) {
ARE_LOG_ERROR("Invalid G-Buffer texture index");
return INVALID_HANDLE;
}
return textures_[index];
if (index < 0 || index >= GBUFFER_COUNT) {
ARE_LOG_ERROR("Invalid G-Buffer texture index");
return INVALID_HANDLE;
}
return textures_[index];
}
void GBuffer::get_dimensions(uint& width, uint& height) const {
width = width_;
height = height_;
void GBuffer::get_dimensions(uint &width, uint &height) const {
width = width_;
height = height_;
}
} // namespace are

View File

@ -162,7 +162,34 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
rebuild_bvh(scene);
}
// Upload scene data
// Build texture arrays BEFORE uploading materials (so indices are available)
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;
}
}
if (has_textures) {
build_texture_arrays_(scene);
// Bind texture arrays
glActiveTexture(GL_TEXTURE10);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]);
glActiveTexture(GL_TEXTURE11);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]);
glActiveTexture(GL_TEXTURE12);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]);
glActiveTexture(GL_TEXTURE13);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]);
glActiveTexture(GL_TEXTURE14);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]);
glActiveTexture(GL_TEXTURE15);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]);
}
// Upload scene data (materials now have correct texture indices)
upload_scene_data_(scene);
// Use compute shader
@ -197,35 +224,8 @@ void RayTracer::trace(const Scene &scene, const GBuffer &gbuffer, TextureHandle
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);
// Build texture arrays if needed
if (has_textures) {
build_texture_arrays_(scene);
// Bind texture arrays
glActiveTexture(GL_TEXTURE10);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[0]);
glActiveTexture(GL_TEXTURE11);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[1]);
glActiveTexture(GL_TEXTURE12);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[2]);
glActiveTexture(GL_TEXTURE13);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[3]);
glActiveTexture(GL_TEXTURE14);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[4]);
glActiveTexture(GL_TEXTURE15);
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_arrays_[5]);
}
// Set camera data
const Camera &camera = scene.get_camera();
compute_shader_->set_vec3("u_camera_position", camera.get_position());
@ -323,13 +323,13 @@ void RayTracer::upload_scene_data_(const Scene &scene) {
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;
// Texture array indices (0 = no texture, 1+ = index into array)
data.texture_handles[0] = mat->get_texture_index(TextureSlot::ALBEDO);
data.texture_handles[1] = mat->get_texture_index(TextureSlot::NORMAL);
data.texture_handles[2] = mat->get_texture_index(TextureSlot::METALLIC);
data.texture_handles[3] = mat->get_texture_index(TextureSlot::ROUGHNESS);
data.texture_handles[4] = mat->get_texture_index(TextureSlot::AO);
data.texture_handles[5] = mat->get_texture_index(TextureSlot::EMISSION);
material_data.push_back(data);
}
@ -408,6 +408,12 @@ void RayTracer::bind_gbuffer_(const GBuffer &gbuffer) {
glBindImageTexture(5, gbuffer.get_texture(GBUFFER_MATERIAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
glBindImageTexture(6, gbuffer.get_texture(GBUFFER_MATERIAL_ID), 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
// Texcoord
glBindImageTexture(7, gbuffer.get_texture(GBUFFER_TEXCOORD), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
// Tangent
glBindImageTexture(8, gbuffer.get_texture(GBUFFER_TANGENT), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
}
void RayTracer::build_texture_arrays_(const Scene &scene) {
@ -453,19 +459,72 @@ void RayTracer::build_texture_arrays_(const Scene &scene) {
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
// Copy each texture to array layer and set indices on materials
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);
// Get original texture format
TextureFormat orig_format = tex->get_format();
int orig_width = tex->get_width();
int orig_height = tex->get_height();
// Get the correct format for reading
GLenum orig_gl_format = GL_RGBA;
int channels = 4;
switch (orig_format) {
case TextureFormat::R8:
orig_gl_format = GL_RED;
channels = 1;
break;
case TextureFormat::RG8:
orig_gl_format = GL_RG;
channels = 2;
break;
case TextureFormat::RGB8:
orig_gl_format = GL_RGB;
channels = 3;
break;
case TextureFormat::RGBA8:
orig_gl_format = GL_RGBA;
channels = 4;
break;
default:
orig_gl_format = GL_RGBA;
channels = 4;
break;
}
// Read texture data with correct format
std::vector<uint8_t> orig_pixels(orig_width * orig_height * channels);
glBindTexture(GL_TEXTURE_2D, tex_handle);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glGetTexImage(GL_TEXTURE_2D, 0, orig_gl_format, GL_UNSIGNED_BYTE, orig_pixels.data());
glBindTexture(GL_TEXTURE_2D, 0);
// Convert to RGBA for texture array (always RGBA8)
std::vector<uint8_t> pixels(orig_width * orig_height * 4, 255);
for (int y = 0; y < orig_height; y++) {
for (int x = 0; x < orig_width; x++) {
int src_idx = (y * orig_width + x) * channels;
int dst_idx = (y * orig_width + x) * 4;
pixels[dst_idx + 0] = orig_pixels[src_idx + 0];
pixels[dst_idx + 1] = (channels >= 2) ? orig_pixels[src_idx + 1] : orig_pixels[src_idx + 0];
pixels[dst_idx + 2] = (channels >= 3) ? orig_pixels[src_idx + 2] : orig_pixels[src_idx + 0];
pixels[dst_idx + 3] = (channels >= 4) ? orig_pixels[src_idx + 3] : 255;
}
}
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, static_cast<int>(i),
width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
// Set texture index on all materials using this texture
// Index is i+1 because 0 means "no texture" in the shader
uint32_t array_index = static_cast<uint32_t>(i) + 1;
for (const auto &mat : materials) {
if (mat->get_texture(static_cast<TextureSlot>(slot)).get() == tex.get()) {
mat->set_texture_index(static_cast<TextureSlot>(slot), array_index);
}
}
}
}

View File

@ -10,7 +10,8 @@ Material::Material()
, roughness_(0.5f)
, ior_(1.5f)
, type_(MaterialType::DIFFUSE)
, textures_() {
, textures_()
, texture_indices_() {
}
Material::~Material() {