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,18 +1,18 @@
#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) {
void AABB::expand(const Vec3 &point) {
min_ = glm::min(min_, point);
max_ = glm::max(max_, point);
}
void AABB::expand(const AABB& other) {
void AABB::expand(const AABB &other) {
min_ = glm::min(min_, other.min_);
max_ = glm::max(max_, other.max_);
}
@ -46,15 +46,15 @@ BVH::~BVH() {
clear();
}
bool BVH::build(const std::vector<std::shared_ptr<Mesh>>& meshes) {
bool BVH::build(const std::vector<std::shared_ptr<Mesh>> &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();
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();
@ -76,6 +76,11 @@ bool BVH::build(const std::vector<std::shared_ptr<Mesh>>& meshes) {
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_;
@ -107,14 +112,13 @@ bool BVH::build(const std::vector<std::shared_ptr<Mesh>>& meshes) {
// Build BVH recursively
build_recursive_(0, 0, static_cast<uint>(triangles_.size()));
ARE_LOG_INFO("BVH built: " + std::to_string(nodes_.size()) + " nodes, " +
std::to_string(triangles_.size()) + " triangles");
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);
@ -135,7 +139,7 @@ void BVH::build_recursive_(uint node_idx, uint first_prim, uint prim_count) {
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 (split_cost == std::numeric_limits<float>::max()) {
node.left_first_ = first_prim;
node.count_ = prim_count;
return;
@ -153,7 +157,7 @@ void BVH::build_recursive_(uint node_idx, uint first_prim, uint prim_count) {
// Partition primitives
uint mid = first_prim;
for (uint i = first_prim; i < first_prim + prim_count; ++i) {
Triangle& tri = triangles_[triangle_indices_[i]];
Triangle &tri = triangles_[triangle_indices_[i]];
float centroid = tri.get_centroid()[axis];
if (centroid < split_pos) {
@ -183,7 +187,7 @@ void BVH::build_recursive_(uint node_idx, uint first_prim, uint prim_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 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;
@ -192,7 +196,8 @@ float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float&
// Try each axis
for (int a = 0; a < 3; ++a) {
float extent = centroid_bounds.max_[a] - centroid_bounds.min_[a];
if (extent < EPSILON) continue;
if (extent < EPSILON)
continue;
// Try multiple split positions
const int NUM_BINS = 16;
@ -205,7 +210,7 @@ float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float&
uint left_count = 0, right_count = 0;
for (uint j = first_prim; j < first_prim + prim_count; ++j) {
Triangle& tri = triangles_[triangle_indices_[j]];
Triangle &tri = triangles_[triangle_indices_[j]];
float centroid = tri.get_centroid()[a];
if (centroid < pos) {
@ -218,10 +223,10 @@ float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float&
}
// Calculate SAH cost
if (left_count == 0 || right_count == 0) continue;
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;
@ -235,11 +240,11 @@ float BVH::find_best_split_(uint first_prim, uint prim_count, int& axis, float&
}
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]];
Triangle &tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_bounds());
}
@ -247,18 +252,18 @@ AABB BVH::calculate_bounds_(uint first_prim, uint prim_count) {
}
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]];
Triangle &tri = triangles_[triangle_indices_[i]];
bounds.expand(tri.get_centroid());
}
return bounds;
}
bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
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;
@ -275,7 +280,7 @@ bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
std::vector<BVHNodeGpu> node_gpu;
node_gpu.resize(nodes_.size());
for (size_t i = 0; i < nodes_.size(); ++i) {
const BVHNode& n = nodes_[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_));
@ -286,9 +291,9 @@ bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
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];
const Triangle &t = ordered_triangles[i];
TriangleGpu g{};
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);
@ -300,6 +305,10 @@ bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
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);
// Pack tangents
g.t0_ = Vec4(t.t0_, 0.0f);
g.t1_ = Vec4(t.t1_, 0.0f);
tri_gpu[i] = g;
}

View File

@ -38,6 +38,12 @@ bool GBuffer::initialize() {
// New: material id (integer)
textures_[GBUFFER_MATERIAL_ID] = create_texture_(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT);
// New: texcoord + tangent (xy = texcoord, zw = tangent)
textures_[GBUFFER_TEXCOORD] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
// New: tangent (xyz = tangent, w = unused)
textures_[GBUFFER_TANGENT] = create_texture_(GL_RGBA32F, GL_RGBA, GL_FLOAT);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_TEXTURE_2D, textures_[GBUFFER_POSITION], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
@ -48,6 +54,10 @@ bool GBuffer::initialize() {
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);
glGenTextures(1, &depth_texture_);
glBindTexture(GL_TEXTURE_2D, depth_texture_);
@ -63,7 +73,9 @@ bool GBuffer::initialize() {
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_MATERIAL_ID,
GL_COLOR_ATTACHMENT0 + GBUFFER_TEXCOORD,
GL_COLOR_ATTACHMENT0 + GBUFFER_TANGENT
};
glDrawBuffers(GBUFFER_COUNT, draw_buffers);
@ -81,7 +93,8 @@ bool GBuffer::initialize() {
}
void GBuffer::release() {
if (!initialized_) return;
if (!initialized_)
return;
if (fbo_ != INVALID_HANDLE) {
glDeleteFramebuffers(1, &fbo_);
@ -116,7 +129,7 @@ TextureHandle GBuffer::create_texture_(uint internal_format, uint format, uint t
return texture;
}
void GBuffer::render(const Scene& scene, const Shader& shader) {
void GBuffer::render(const Scene &scene, const Shader &shader) {
if (!initialized_) {
ARE_LOG_ERROR("GBuffer not initialized");
return;
@ -138,7 +151,7 @@ void GBuffer::render(const Scene& scene, const Shader& shader) {
shader.use();
// Set camera matrices
const Camera& camera = scene.get_camera();
const Camera &camera = scene.get_camera();
Mat4 view = camera.get_view_matrix();
Mat4 projection = camera.get_projection_matrix();
@ -146,10 +159,10 @@ void GBuffer::render(const Scene& scene, const Shader& shader) {
shader.set_mat4("u_projection", projection);
// Render all meshes
const auto& meshes = scene.get_meshes();
const auto& materials = scene.get_materials();
const auto &meshes = scene.get_meshes();
const auto &materials = scene.get_materials();
for (const auto& mesh : meshes) {
for (const auto &mesh : meshes) {
if (!mesh->is_uploaded()) {
ARE_LOG_WARN("Mesh not uploaded to GPU, skipping");
continue;
@ -162,7 +175,7 @@ void GBuffer::render(const Scene& scene, const Shader& shader) {
// Set material properties
uint material_id = mesh->get_material();
if (material_id < materials.size()) {
const auto& material = materials[material_id];
const auto &material = materials[material_id];
shader.set_vec3("u_albedo", material->get_albedo());
shader.set_float("u_metallic", material->get_metallic());
@ -203,7 +216,8 @@ void GBuffer::render(const Scene& scene, const Shader& shader) {
}
void GBuffer::resize(uint width, uint height) {
if (width == width_ && height == height_) return;
if (width == width_ && height == height_)
return;
width_ = width;
height_ = height;
@ -222,7 +236,7 @@ TextureHandle GBuffer::get_texture(int index) const {
return textures_[index];
}
void GBuffer::get_dimensions(uint& width, uint& height) const {
void GBuffer::get_dimensions(uint &width, uint &height) const {
width = width_;
height = height_;
}

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() {