diff --git a/examples/normal_map_cornell_box b/examples/normal_map_cornell_box index 9e9afad..d06b298 100644 Binary files a/examples/normal_map_cornell_box and b/examples/normal_map_cornell_box differ diff --git a/examples/normal_map_cornell_box.cpp b/examples/normal_map_cornell_box.cpp index 294fc4b..ee41d70 100644 --- a/examples/normal_map_cornell_box.cpp +++ b/examples/normal_map_cornell_box.cpp @@ -226,18 +226,19 @@ void setup_cornell_box() { // 5: Textured material with normal map (for short box) auto textured_material = std::make_shared(); 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(); - 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(); + // 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(); 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(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); + g_camera->set_perspective(90.0f, static_cast(WINDOW_WIDTH) / WINDOW_HEIGHT, 0.1f, 100.0f); g_scene->set_camera(g_camera); // Add point light diff --git a/include/basic/constants.h b/include/basic/constants.h index e6ee2af..67bda55 100644 --- a/include/basic/constants.h +++ b/include/basic/constants.h @@ -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; diff --git a/include/core/bvh.h b/include/core/bvh.h index 34ca074..411fead 100644 --- a/include/core/bvh.h +++ b/include/core/bvh.h @@ -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 diff --git a/include/scene/material.h b/include/scene/material.h index 810465b..d72bf81 100644 --- a/include/scene/material.h +++ b/include/scene/material.h @@ -229,6 +229,24 @@ public: return textures_[static_cast(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(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(slot)]; + } + private: Vec3 albedo_; Vec3 emission_; @@ -238,6 +256,7 @@ private: MaterialType type_; std::array, static_cast(TextureSlot::COUNT)> textures_; + std::array(TextureSlot::COUNT)> texture_indices_; }; } // namespace are diff --git a/shaders/gbuffer.frag b/shaders/gbuffer.frag index fbed580..3661108 100644 --- a/shaders/gbuffer.frag +++ b/shaders/gbuffer.frag @@ -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); } diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index ea0bf69..3b931b1 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -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; @@ -395,10 +415,20 @@ HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { // material type stored in g_material.w 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) { diff --git a/src/core/bvh.cpp b/src/core/bvh.cpp index 71560ab..e5f2289 100644 --- a/src/core/bvh.cpp +++ b/src/core/bvh.cpp @@ -1,41 +1,41 @@ #include "core/bvh.h" -#include "utils/logger.h" #include "basic/constants.h" +#include "utils/logger.h" #include #include 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>& meshes) { - clear(); - - 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(); - - 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); - - 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_); - - // Copy UVs - tri.uv0_ = vertices[indices[i]].texcoord_; - tri.uv1_ = vertices[indices[i + 1]].texcoord_; - tri.uv2_ = vertices[indices[i + 2]].texcoord_; - - tri.material_id_ = material_id; - - triangles_.push_back(tri); - } - } - - if (triangles_.empty()) { - ARE_LOG_WARN("No triangles to build BVH"); - return false; - } - - // Initialize triangle indices - triangle_indices_.resize(triangles_.size()); - for (size_t i = 0; i < triangles_.size(); ++i) { - triangle_indices_[i] = static_cast(i); - } - - // Reserve space for nodes (estimate) - nodes_.reserve(triangles_.size() * 2); - - // Create root node - nodes_.emplace_back(); - - // Build BVH recursively - build_recursive_(0, 0, static_cast(triangles_.size())); - - ARE_LOG_INFO("BVH built: " + std::to_string(nodes_.size()) + " nodes, " + - std::to_string(triangles_.size()) + " triangles"); - - return true; +bool BVH::build(const std::vector> &meshes) { + clear(); + + 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(); + + 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); + + 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 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_); + + // Copy UVs + tri.uv0_ = vertices[indices[i]].texcoord_; + tri.uv1_ = vertices[indices[i + 1]].texcoord_; + tri.uv2_ = vertices[indices[i + 2]].texcoord_; + + tri.material_id_ = material_id; + + triangles_.push_back(tri); + } + } + + if (triangles_.empty()) { + ARE_LOG_WARN("No triangles to build BVH"); + return false; + } + + // Initialize triangle indices + triangle_indices_.resize(triangles_.size()); + for (size_t i = 0; i < triangles_.size(); ++i) { + triangle_indices_[i] = static_cast(i); + } + + // Reserve space for nodes (estimate) + nodes_.reserve(triangles_.size() * 2); + + // Create root node + nodes_.emplace_back(); + + // Build BVH recursively + build_recursive_(0, 0, static_cast(triangles_.size())); + + 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]; - - // 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; - - 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::max()) { + 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_; + + // 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; } - - // 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; - } - - // 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]; - - if (centroid < split_pos) { - std::swap(triangle_indices_[i], triangle_indices_[mid]); - mid++; - } - } - - // Ensure we have primitives on both sides - if (mid == first_prim || mid == first_prim + prim_count) { - mid = first_prim + prim_count / 2; - } - - // Create interior node - uint left_count = mid - first_prim; - uint right_count = prim_count - left_count; - - node.left_first_ = static_cast(nodes_.size()); - node.count_ = 0; - - // 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); + + // 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::max()) { + 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; + } + + // 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]; + + if (centroid < split_pos) { + std::swap(triangle_indices_[i], triangle_indices_[mid]); + mid++; + } + } + + // Ensure we have primitives on both sides + if (mid == first_prim || mid == first_prim + prim_count) { + mid = first_prim + prim_count / 2; + } + + // Create interior node + uint left_count = mid - first_prim; + uint right_count = prim_count - left_count; + + node.left_first_ = static_cast(nodes_.size()); + node.count_ = 0; + + // 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::max(); +float BVH::find_best_split_(uint first_prim, uint prim_count, int &axis, float &split_pos) { + float best_cost = std::numeric_limits::max(); axis = 0, split_pos = 0.0f; - - 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 multiple split positions - const int NUM_BINS = 16; - for (int i = 1; i < NUM_BINS; ++i) { - float t = static_cast(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; - - 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++; - } - } - - // 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(); - - if (cost < best_cost) { - best_cost = cost; - axis = a; - split_pos = pos; - } - } - } - - return best_cost; + + 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 multiple split positions + const int NUM_BINS = 16; + for (int i = 1; i < NUM_BINS; ++i) { + float t = static_cast(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; + + 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++; + } + } + + // 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(); + + if (cost < best_cost) { + best_cost = cost; + axis = a; + split_pos = pos; + } + } + } + + return best_cost; } AABB BVH::calculate_bounds_(uint first_prim, uint prim_count) { - AABB bounds{Vec3(std::numeric_limits::max()), - Vec3(std::numeric_limits::lowest())}; - - for (uint i = first_prim; i < first_prim + prim_count; ++i) { - Triangle& tri = triangles_[triangle_indices_[i]]; - bounds.expand(tri.get_bounds()); - } - - return bounds; + AABB bounds { Vec3(std::numeric_limits::max()), + Vec3(std::numeric_limits::lowest()) }; + + for (uint i = first_prim; i < first_prim + prim_count; ++i) { + Triangle &tri = triangles_[triangle_indices_[i]]; + bounds.expand(tri.get_bounds()); + } + + return bounds; } AABB BVH::calculate_centroid_bounds_(uint first_prim, uint prim_count) { - AABB bounds{Vec3(std::numeric_limits::max()), - Vec3(std::numeric_limits::lowest())}; - - for (uint i = first_prim; i < first_prim + prim_count; ++i) { - Triangle& tri = triangles_[triangle_indices_[i]]; - bounds.expand(tri.get_centroid()); - } - - return bounds; + AABB bounds { Vec3(std::numeric_limits::max()), + Vec3(std::numeric_limits::lowest()) }; + + for (uint i = first_prim; i < first_prim + prim_count; ++i) { + Triangle &tri = triangles_[triangle_indices_[i]]; + bounds.expand(tri.get_centroid()); + } + + 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 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 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 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 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 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 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 diff --git a/src/core/gbuffer.cpp b/src/core/gbuffer.cpp index 5e4abfa..504f64c 100644 --- a/src/core/gbuffer.cpp +++ b/src/core/gbuffer.cpp @@ -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; - } - - // 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); - - // Enable depth test - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - - // 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(); - - 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(); - - 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 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); +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_); + + // 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); + + // 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(); + + 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(); + + 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 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_float("u_ior", material->get_ior()); shader.set_vec3("u_emission", material->get_emission()); shader.set_uint("u_material_type", static_cast(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); - } - - 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); - } - - // Unbind framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, 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); + } + } + + // 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); } void GBuffer::resize(uint width, uint height) { - if (width == width_ && height == height_) return; - - width_ = width; - height_ = height; - - if (initialized_) { - release(); - initialize(); - } + if (width == width_ && height == height_) + return; + + width_ = width; + height_ = height; + + 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 diff --git a/src/core/raytracer.cpp b/src/core/raytracer.cpp index 77f6e88..38cd735 100644 --- a/src/core/raytracer.cpp +++ b/src/core/raytracer.cpp @@ -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(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(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 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 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 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(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(i) + 1; + for (const auto &mat : materials) { + if (mat->get_texture(static_cast(slot)).get() == tex.get()) { + mat->set_texture_index(static_cast(slot), array_index); + } + } } } diff --git a/src/scene/material.cpp b/src/scene/material.cpp index a597f2c..86889ba 100644 --- a/src/scene/material.cpp +++ b/src/scene/material.cpp @@ -10,7 +10,8 @@ Material::Material() , roughness_(0.5f) , ior_(1.5f) , type_(MaterialType::DIFFUSE) - , textures_() { + , textures_() + , texture_indices_() { } Material::~Material() {