Fix&Add:修复最多只能上传256个material的虫,添加BVH上传代码和光线追踪实现

master
ternaryop8479 2026-02-10 17:10:59 +08:00
parent 03baf12976
commit d0d97032db
13 changed files with 5129 additions and 567 deletions

2820
all_files.md Normal file

File diff suppressed because it is too large Load Diff

1762
all_headers.md Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -225,7 +225,7 @@ void setup_cornell_box() {
g_scene->add_mesh(tall_box);
// Short box (metal, right side)
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), metal_id);
auto short_box = create_box(Vec3(0.2f, -room_size, 0.2f), Vec3(0.9f, -0.4f, 0.9f), /*metal_id*/white_id);
short_box->upload_to_gpu();
g_scene->add_mesh(short_box);

View File

@ -16,7 +16,9 @@ constexpr int DEFAULT_SPP = 1;
constexpr int GBUFFER_POSITION = 0;
constexpr int GBUFFER_NORMAL = 1;
constexpr int GBUFFER_ALBEDO = 2;
constexpr int GBUFFER_COUNT = 3;
constexpr int GBUFFER_MATERIAL = 3;
constexpr int GBUFFER_MATERIAL_ID = 4;
constexpr int GBUFFER_COUNT = 5;
/// @brief Compute shader work group size
constexpr int COMPUTE_GROUP_SIZE_X = 16;

View File

@ -56,6 +56,24 @@ struct BVHNode {
uint count_; // 0 for interior node, >0 for leaf node
};
/// @brief GPU-friendly BVH node layout (std430 aligned)
struct BVHNodeGpu {
Vec4 aabb_min_left_first_; ///< xyz = aabb min, w = left_first (uint)
Vec4 aabb_max_count_; ///< xyz = aabb max, w = count (uint, 0 for interior)
};
/// @brief GPU-friendly triangle layout (std430 aligned)
struct TriangleGpu {
Vec4 v0_material_; ///< xyz = v0, w = material_id (uint)
Vec4 v1_; ///< xyz = v1, w = reserved
Vec4 v2_; ///< xyz = v2, w = reserved
Vec4 n0_; ///< xyz = n0, w = reserved
Vec4 n1_; ///< xyz = n1, w = reserved
Vec4 n2_; ///< xyz = n2, w = reserved
Vec4 uv0_uv1_; ///< xy = uv0, zw = uv1
Vec4 uv2_; ///< xy = uv2, zw = reserved
};
/// @brief Bounding Volume Hierarchy for ray tracing acceleration
class BVH {
public:

View File

@ -1,11 +1,5 @@
#version 430 core
// Material types
const uint MATERIAL_DIFFUSE = 0u;
const uint MATERIAL_METAL = 1u;
const uint MATERIAL_DIELECTRIC = 2u;
const uint MATERIAL_EMISSIVE = 3u;
in VS_OUT {
vec3 frag_pos;
vec3 normal;
@ -17,33 +11,31 @@ layout(location = 0) out vec4 g_position;
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;
// Material uniforms
uniform vec3 u_albedo;
uniform float u_metallic;
uniform float u_roughness;
uniform float u_ior;
uniform vec3 u_emission;
uniform uint u_material_type;
uniform uint u_material_id;
uniform bool u_has_albedo_map;
uniform sampler2D u_albedo_map;
void main() {
// Position
g_position = vec4(fs_in.frag_pos, 1.0);
// Normal
vec3 normal = normalize(fs_in.normal);
g_normal = vec4(normal, 0.0);
vec3 n = normalize(fs_in.normal);
g_normal = vec4(n, 0.0);
// Albedo
vec3 albedo = u_albedo;
if (u_has_albedo_map) {
albedo *= texture(u_albedo_map, fs_in.texcoord).rgb;
}
g_albedo = vec4(albedo, 1.0);
// Material properties
g_material = vec4(u_metallic, u_roughness, u_ior, float(u_material_type));
g_material_id = u_material_id;
}

View File

@ -1,26 +1,35 @@
#version 430 core
// Constants
#define PI 3.14159265359
#define INV_PI 0.31830988618
#define EPSILON 1e-4
#define MAX_FLOAT 3.402823466e38
#define MAX_RAY_DEPTH 8
#define MAX_LIGHTS 16
#define RR_THRESHOLD 0.1
// Material types
#define MATERIAL_DIFFUSE 0
#define MATERIAL_METAL 1
#define MATERIAL_DIELECTRIC 2
#define MATERIAL_EMISSIVE 3
// Light types
#define LIGHT_DIRECTIONAL 0
#define LIGHT_POINT 1
#define LIGHT_SPOT 2
// Structures
layout(local_size_x = 16, local_size_y = 16) in;
// G-Buffer inputs
layout(binding = 0, rgba32f) uniform readonly image2D g_position;
layout(binding = 1, rgba32f) uniform readonly image2D g_normal;
layout(binding = 2, rgba8) uniform readonly image2D g_albedo;
// New: material params + material id
layout(binding = 5, rgba32f) uniform readonly image2D g_material;
layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id;
// Output
layout(binding = 3, rgba32f) uniform image2D output_image;
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
struct Material {
vec3 albedo;
float metallic;
@ -60,31 +69,29 @@ struct ScatterResult {
bool scattered;
vec3 attenuation;
Ray scattered_ray;
float pdf;
};
layout(local_size_x = 16, local_size_y = 16) in;
// G-Buffer inputs
layout(binding = 0, rgba32f) uniform readonly image2D g_position;
layout(binding = 1, rgba32f) uniform readonly image2D g_normal;
layout(binding = 2, rgba8) uniform readonly image2D g_albedo;
// Output
layout(binding = 3, rgba32f) uniform image2D output_image;
layout(binding = 4, rgba32f) uniform image2D accumulation_image;
// Scene data
layout(std430, binding = 0) readonly buffer MaterialBuffer {
Material materials[];
struct BVHNodeGpu {
vec4 aabb_min_left_first; // xyz min, w = left_first (uint bits in float)
vec4 aabb_max_count; // xyz max, w = count (uint bits in float)
};
layout(std430, binding = 1) readonly buffer LightBuffer {
Light lights[];
struct TriangleGpu {
vec4 v0_material; // xyz v0, w material_id (uint bits in float)
vec4 v1;
vec4 v2;
vec4 n0;
vec4 n1;
vec4 n2;
vec4 uv0_uv1; // xy uv0, zw uv1
vec4 uv2; // xy uv2
};
// Uniforms
layout(std430, binding = 0) readonly buffer MaterialBuffer { Material materials[]; };
layout(std430, binding = 1) readonly buffer LightBuffer { Light lights[]; };
layout(std430, binding = 2) readonly buffer BVHNodeBuffer { BVHNodeGpu bvh_nodes[]; };
layout(std430, binding = 3) readonly buffer TriangleBuffer { TriangleGpu bvh_tris[]; };
uniform uint u_frame_count;
uniform uint u_samples_per_pixel;
uniform uint u_max_depth;
@ -93,25 +100,12 @@ uniform vec3 u_camera_position;
uniform mat4 u_inv_view_projection;
uniform bool u_enable_accumulation;
uniform bool u_use_bvh;
uniform uint u_bvh_node_count;
// ============================================================================
// Utility Functions
// Utility
// ============================================================================
/**
* @brief Saturate float value to [0, 1]
*/
float saturate(float x) {
return clamp(x, 0.0, 1.0);
}
/**
* @brief Saturate vec3 value to [0, 1]
*/
vec3 saturate(vec3 x) {
return clamp(x, vec3(0.0), vec3(1.0));
}
/**
* @brief Check if vector is near zero
*/
@ -136,44 +130,28 @@ vec3 refract_vector(vec3 uv, vec3 n, float etai_over_etat) {
return r_out_perp + r_out_parallel;
}
uint as_uint(float f) { return floatBitsToUint(f); }
float as_float(uint u) { return uintBitsToFloat(u); }
// ============================================================================
// Random Number Generation
// RNG (PCG)
// ============================================================================
/**
* @brief PCG hash function for random number generation
*/
uint pcg_hash(uint seed) {
uint state = seed * 747796405u + 2891336453u;
uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
return (word >> 22u) ^ word;
}
/**
* @brief Generate random float in [0, 1)
*/
float random_float(inout uint seed) {
seed = pcg_hash(seed);
return float(seed) / 4294967296.0;
}
/**
* @brief Generate random vec2
*/
vec2 random_vec2(inout uint seed) {
return vec2(random_float(seed), random_float(seed));
}
/**
* @brief Generate random vec3
*/
vec3 random_vec3(inout uint seed) {
return vec3(random_float(seed), random_float(seed), random_float(seed));
}
/**
* @brief Generate random vector in unit sphere
*/
vec3 random_in_unit_sphere(inout uint seed) {
while (true) {
vec3 p = 2.0 * random_vec3(seed) - vec3(1.0);
@ -181,504 +159,406 @@ vec3 random_in_unit_sphere(inout uint seed) {
}
}
/**
* @brief Generate random unit vector
*/
vec3 random_unit_vector(inout uint seed) {
return normalize(random_in_unit_sphere(seed));
}
// ============================================================================
// Sampling Functions
// Camera ray
// ============================================================================
/**
* @brief Cosine-weighted hemisphere sampling
* @return Sampled direction in local space (z-up)
* @brief Generate primary ray in world space
*/
vec3 cosine_weighted_hemisphere(inout uint seed) {
vec2 r = random_vec2(seed);
float phi = 2.0 * PI * r.x;
float cos_theta = sqrt(r.y);
float sin_theta = sqrt(1.0 - r.y);
Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size, inout uint seed) {
vec2 jitter = vec2(random_float(seed), random_float(seed));
vec2 uv = (vec2(pixel_coords) + jitter) / vec2(image_size);
vec2 ndc = uv * 2.0 - 1.0;
return vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);
}
vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0);
vec4 p_far = u_inv_view_projection * vec4(ndc, 1.0, 1.0);
vec3 near_ws = p_near.xyz / p_near.w;
vec3 far_ws = p_far.xyz / p_far.w;
/**
* @brief Build orthonormal basis from normal
*/
void build_onb(vec3 normal, out vec3 tangent, out vec3 bitangent) {
vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
tangent = normalize(cross(up, normal));
bitangent = cross(normal, tangent);
}
/**
* @brief Transform direction from local to world space
*/
vec3 local_to_world(vec3 local_dir, vec3 normal) {
vec3 tangent, bitangent;
build_onb(normal, tangent, bitangent);
return tangent * local_dir.x + bitangent * local_dir.y + normal * local_dir.z;
}
/**
* @brief Sample GGX distribution for microfacet normal
*/
vec3 sample_ggx(vec3 normal, float roughness, inout uint seed) {
vec2 r = random_vec2(seed);
float a = roughness * roughness;
float a2 = a * a;
float phi = 2.0 * PI * r.x;
float cos_theta = sqrt((1.0 - r.y) / (1.0 + (a2 - 1.0) * r.y));
float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
vec3 local_h = vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);
return local_to_world(local_h, normal);
Ray r;
r.origin = near_ws;
r.direction = normalize(far_ws - near_ws);
return r;
}
// ============================================================================
// BRDF Functions
// Intersection
// ============================================================================
/**
* @brief Schlick's approximation for Fresnel reflectance
* @brief Ray-AABB intersection
*/
bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) {
vec3 inv_d = 1.0 / ray.direction;
vec3 t0 = (aabb_min - ray.origin) * inv_d;
vec3 t1 = (aabb_max - ray.origin) * inv_d;
vec3 tmin3 = min(t0, t1);
vec3 tmax3 = max(t0, t1);
float tmin = max(max(tmin3.x, tmin3.y), tmin3.z);
float tmax2 = min(min(tmax3.x, tmax3.y), tmax3.z);
return (tmax2 >= max(tmin, 0.0)) && (tmin <= t_max);
}
/**
* @brief Moller-Trumbore triangle intersection
*/
bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) {
vec3 v0 = tri.v0_material.xyz;
vec3 v1 = tri.v1.xyz;
vec3 v2 = tri.v2.xyz;
vec3 e1 = v1 - v0;
vec3 e2 = v2 - v0;
vec3 pvec = cross(ray.direction, e2);
float det = dot(e1, pvec);
if (abs(det) < EPSILON) return false;
float inv_det = 1.0 / det;
vec3 tvec = ray.origin - v0;
float u = dot(tvec, pvec) * inv_det;
if (u < 0.0 || u > 1.0) return false;
vec3 qvec = cross(tvec, e1);
float v = dot(ray.direction, qvec) * inv_det;
if (v < 0.0 || u + v > 1.0) return false;
float t = dot(e2, qvec) * inv_det;
if (t < EPSILON || t >= hit.t) return false;
// Interpolate normal/uv
float w = 1.0 - u - v;
vec3 n0 = tri.n0.xyz;
vec3 n1 = tri.n1.xyz;
vec3 n2 = tri.n2.xyz;
vec2 uv0 = tri.uv0_uv1.xy;
vec2 uv1 = tri.uv0_uv1.zw;
vec2 uv2 = tri.uv2.xy;
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;
hit.material_id = as_uint(tri.v0_material.w);
return true;
}
/**
* @brief BVH traversal (closest hit)
*/
HitInfo trace_ray_bvh(Ray ray) {
HitInfo hit;
hit.hit = false;
hit.t = MAX_FLOAT;
if (!u_use_bvh || u_bvh_node_count == 0u) {
return hit;
}
// Small fixed stack
uint stack[64];
int sp = 0;
stack[sp++] = 0u;
while (sp > 0) {
uint node_idx = stack[--sp];
if (node_idx >= u_bvh_node_count) continue;
BVHNodeGpu node = bvh_nodes[node_idx];
vec3 bmin = node.aabb_min_left_first.xyz;
vec3 bmax = node.aabb_max_count.xyz;
uint left_first = as_uint(node.aabb_min_left_first.w);
uint count = as_uint(node.aabb_max_count.w);
if (!intersect_aabb(ray, bmin, bmax, hit.t)) continue;
if (count > 0u) {
for (uint i = 0u; i < count; ++i) {
TriangleGpu tri = bvh_tris[left_first + i];
intersect_triangle(ray, tri, hit);
}
} else {
// Interior: push children
uint left = left_first;
uint right = left_first + 1u;
// Depth-first; no sorting (simple)
if (sp < 63) stack[sp++] = right;
if (sp < 63) stack[sp++] = left;
}
}
return hit;
}
/**
* @brief Any-hit BVH for shadow ray
*/
bool trace_any_bvh(Ray ray, float t_max) {
if (!u_use_bvh || u_bvh_node_count == 0u) return false;
uint stack[64];
int sp = 0;
stack[sp++] = 0u;
HitInfo hit;
hit.hit = false;
hit.t = t_max;
while (sp > 0) {
uint node_idx = stack[--sp];
if (node_idx >= u_bvh_node_count) continue;
BVHNodeGpu node = bvh_nodes[node_idx];
vec3 bmin = node.aabb_min_left_first.xyz;
vec3 bmax = node.aabb_max_count.xyz;
uint left_first = as_uint(node.aabb_min_left_first.w);
uint count = as_uint(node.aabb_max_count.w);
if (!intersect_aabb(ray, bmin, bmax, hit.t)) continue;
if (count > 0u) {
for (uint i = 0u; i < count; ++i) {
TriangleGpu tri = bvh_tris[left_first + i];
if (intersect_triangle(ray, tri, hit)) {
return true;
}
}
} else {
uint left = left_first;
uint right = left_first + 1u;
if (sp < 63) stack[sp++] = right;
if (sp < 63) stack[sp++] = left;
}
}
return false;
}
// ============================================================================
// Material + scattering
// ============================================================================
vec3 fresnel_schlick(float cos_theta, vec3 f0) {
return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0);
}
/**
* @brief Fresnel reflectance for dielectrics
*/
float fresnel_dielectric(float cos_theta, float ior) {
float r0 = (1.0 - ior) / (1.0 + ior);
r0 = r0 * r0;
return r0 + (1.0 - r0) * pow(1.0 - cos_theta, 5.0);
}
/**
* @brief GGX normal distribution function
*/
float distribution_ggx(vec3 N, vec3 H, float roughness) {
float a = roughness * roughness;
float a2 = a * a;
float NdotH = max(dot(N, H), 0.0);
float NdotH2 = NdotH * NdotH;
float nom = a2;
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
denom = PI * denom * denom;
return nom / max(denom, EPSILON);
}
/**
* @brief Smith's geometry function for GGX
*/
float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness) {
float NdotV = max(dot(N, V), 0.0);
float NdotL = max(dot(N, L), 0.0);
float r = roughness + 1.0;
float k = (r * r) / 8.0;
float ggx1 = NdotV / (NdotV * (1.0 - k) + k);
float ggx2 = NdotL / (NdotL * (1.0 - k) + k);
return ggx1 * ggx2;
}
// ============================================================================
// Material Scattering
// ============================================================================
/**
* @brief Scatter ray for diffuse material
*/
ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint seed) {
ScatterResult result;
result.scattered = true;
result.attenuation = mat.albedo;
ScatterResult r;
r.scattered = true;
r.attenuation = mat.albedo;
// Cosine-weighted hemisphere sampling
vec3 local_dir = cosine_weighted_hemisphere(seed);
vec3 scatter_direction = local_to_world(local_dir, hit.normal);
vec3 dir = hit.normal + random_unit_vector(seed);
if (near_zero(dir)) dir = hit.normal;
// Prevent degenerate scatter direction
if (near_zero(scatter_direction)) {
scatter_direction = hit.normal;
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
r.scattered_ray.direction = normalize(dir);
return r;
}
result.scattered_ray.origin = hit.position + hit.normal * EPSILON;
result.scattered_ray.direction = normalize(scatter_direction);
result.pdf = max(dot(hit.normal, result.scattered_ray.direction), 0.0) * INV_PI;
return result;
}
/**
* @brief Scatter ray for metal material
*/
ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint seed) {
ScatterResult result;
ScatterResult r;
vec3 reflected = reflect_vector(normalize(ray_in.direction), hit.normal);
// Add roughness perturbation
vec3 fuzz = mat.roughness * random_in_unit_sphere(seed);
vec3 scatter_direction = reflected + fuzz;
vec3 dir = reflected + fuzz;
result.scattered = dot(scatter_direction, hit.normal) > 0.0;
if (result.scattered) {
result.scattered_ray.origin = hit.position + hit.normal * EPSILON;
result.scattered_ray.direction = normalize(scatter_direction);
vec3 V = -normalize(ray_in.direction);
vec3 H = normalize(V + result.scattered_ray.direction);
vec3 F0 = mat.albedo;
result.attenuation = fresnel_schlick(max(dot(H, V), 0.0), F0);
result.pdf = 1.0; // Delta distribution approximation
} else {
result.attenuation = vec3(0.0);
result.pdf = 0.0;
r.scattered = dot(dir, hit.normal) > 0.0;
r.attenuation = mat.albedo;
r.scattered_ray.origin = hit.position + hit.normal * EPSILON;
r.scattered_ray.direction = normalize(dir);
return r;
}
return result;
}
/**
* @brief Scatter ray for dielectric material (glass)
*/
ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout uint seed) {
ScatterResult result;
result.scattered = true;
result.attenuation = vec3(1.0);
ScatterResult r;
r.scattered = true;
r.attenuation = vec3(1.0);
float refraction_ratio = dot(ray_in.direction, hit.normal) < 0.0 ?
(1.0 / mat.ior) : mat.ior;
vec3 unit_direction = normalize(ray_in.direction);
float cos_theta = min(dot(-unit_direction, hit.normal), 1.0);
float sin_theta = sqrt(1.0 - cos_theta * cos_theta);
vec3 unit_dir = normalize(ray_in.direction);
float cos_theta = min(dot(-unit_dir, hit.normal), 1.0);
float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta));
float refraction_ratio = dot(unit_dir, hit.normal) < 0.0 ? (1.0 / mat.ior) : mat.ior;
bool cannot_refract = refraction_ratio * sin_theta > 1.0;
float reflectance = fresnel_dielectric(cos_theta, refraction_ratio);
float reflect_prob = fresnel_dielectric(cos_theta, refraction_ratio);
vec3 direction;
if (cannot_refract || reflectance > random_float(seed)) {
direction = reflect_vector(unit_direction, hit.normal);
vec3 dir;
if (cannot_refract || random_float(seed) < reflect_prob) {
dir = reflect_vector(unit_dir, hit.normal);
} else {
direction = refract_vector(unit_direction, hit.normal, refraction_ratio);
dir = refract_vector(unit_dir, hit.normal, refraction_ratio);
}
result.scattered_ray.origin = hit.position + direction * EPSILON;
result.scattered_ray.direction = normalize(direction);
result.pdf = 1.0; // Delta distribution
return result;
r.scattered_ray.origin = hit.position + dir * EPSILON;
r.scattered_ray.direction = normalize(dir);
return r;
}
/**
* @brief Scatter ray based on material type
*/
ScatterResult scatter_ray(Ray ray_in, HitInfo hit, Material mat, inout uint seed) {
if (mat.type == MATERIAL_DIFFUSE) {
return scatter_diffuse(ray_in, hit, mat, seed);
} else if (mat.type == MATERIAL_METAL) {
return scatter_metal(ray_in, hit, mat, seed);
} else if (mat.type == MATERIAL_DIELECTRIC) {
return scatter_dielectric(ray_in, hit, mat, seed);
} else {
// Emissive material doesn't scatter
ScatterResult result;
result.scattered = false;
result.attenuation = vec3(0.0);
result.pdf = 0.0;
return result;
}
if (mat.type == MATERIAL_DIFFUSE) return scatter_diffuse(ray_in, hit, mat, seed);
if (mat.type == MATERIAL_METAL) return scatter_metal(ray_in, hit, mat, seed);
if (mat.type == MATERIAL_DIELECTRIC) return scatter_dielectric(ray_in, hit, mat, seed);
ScatterResult r;
r.scattered = false;
r.attenuation = vec3(0.0);
return r;
}
// ============================================================================
// Scene Intersection (G-Buffer based)
// Direct lighting (with shadow ray)
// ============================================================================
/**
* @brief Trace ray against G-Buffer (single bounce only)
* @note This is a simplified version - full path tracing needs scene geometry
*/
HitInfo trace_ray_gbuffer(Ray ray, ivec2 pixel_coords) {
HitInfo hit;
// Read from G-Buffer at current pixel
vec4 position_data = imageLoad(g_position, pixel_coords);
vec4 normal_data = imageLoad(g_normal, pixel_coords);
vec4 albedo_data = imageLoad(g_albedo, pixel_coords);
if (position_data.w > 0.5) {
hit.hit = true;
hit.position = position_data.xyz;
hit.normal = normalize(normal_data.xyz);
hit.material_id = uint(albedo_data.a * 255.0 + 0.5);
hit.t = length(hit.position - ray.origin);
} else {
hit.hit = false;
hit.t = MAX_FLOAT;
}
return hit;
}
// ============================================================================
// Path Tracing Core
// ============================================================================
/**
* @brief Sample direct lighting from light sources
*/
vec3 sample_direct_lighting(vec3 position, vec3 normal, Material mat, inout uint seed) {
vec3 eval_direct_lighting(HitInfo hit, Material mat, inout uint seed) {
if (u_light_count == 0u) return vec3(0.0);
vec3 direct_light = vec3(0.0);
// Sample one random light (could be improved with MIS)
// sample one light
uint light_idx = uint(random_float(seed) * float(u_light_count)) % u_light_count;
Light light = lights[light_idx];
vec3 light_dir;
float light_distance;
float pdf_light = 1.0 / float(u_light_count);
vec3 L;
float dist = MAX_FLOAT;
vec3 radiance = vec3(0.0);
if (light.type == LIGHT_POINT) {
vec3 to_light = light.position - position;
light_distance = length(to_light);
light_dir = to_light / light_distance;
vec3 to_light = light.position - hit.position;
dist = length(to_light);
if (dist > light.range) return vec3(0.0);
L = to_light / dist;
if (light_distance > light.range) return vec3(0.0);
float NdotL = max(dot(normal, light_dir), 0.0);
if (NdotL > 0.0) {
float attenuation = 1.0 / max(light_distance * light_distance, 0.01);
vec3 radiance = light.color * light.intensity * attenuation;
// Simple BRDF evaluation (diffuse)
direct_light = mat.albedo * INV_PI * radiance * NdotL / pdf_light;
}
float atten = 1.0 / max(dist * dist, 0.01);
radiance = light.color * light.intensity * atten;
} else if (light.type == LIGHT_DIRECTIONAL) {
light_dir = normalize(-light.direction);
float NdotL = max(dot(normal, light_dir), 0.0);
if (NdotL > 0.0) {
vec3 radiance = light.color * light.intensity;
direct_light = mat.albedo * INV_PI * radiance * NdotL / pdf_light;
}
L = normalize(-light.direction);
radiance = light.color * light.intensity;
} else {
return vec3(0.0);
}
return direct_light;
float n_dot_l = max(dot(hit.normal, L), 0.0);
if (n_dot_l <= 0.0) return vec3(0.0);
// shadow ray
Ray shadow_ray;
shadow_ray.origin = hit.position + hit.normal * EPSILON;
shadow_ray.direction = L;
float t_max = (light.type == LIGHT_POINT) ? (dist - EPSILON) : MAX_FLOAT;
if (trace_any_bvh(shadow_ray, t_max)) {
return vec3(0.0);
}
/**
* @brief Trace path and accumulate radiance
*/
vec3 trace_path(Ray initial_ray, ivec2 pixel_coords, inout uint seed) {
float pdf_light = 1.0 / float(u_light_count);
vec3 brdf = mat.albedo * INV_PI; // diffuse direct only (simple)
return brdf * radiance * n_dot_l / max(pdf_light, EPSILON);
}
// ============================================================================
// Path tracing
// ============================================================================
Material fetch_material(uint material_id) {
uint cnt = uint(materials.length());
if (material_id < cnt) return materials[material_id];
Material m;
m.albedo = vec3(0.5);
m.metallic = 0.0;
m.emission = vec3(0.0);
m.roughness = 0.5;
m.type = MATERIAL_DIFFUSE;
m.ior = 1.5;
return m;
}
vec3 environment_color(vec3 dir) {
// simple dark sky
return vec3(0.1, 0.1, 0.15);
}
vec3 trace_path(Ray ray, inout uint seed) {
vec3 radiance = vec3(0.0);
vec3 throughput = vec3(1.0);
Ray current_ray = initial_ray;
uint mat_count = uint(materials.length());
for (uint depth = 0u; depth < u_max_depth; depth++) {
// Trace ray (only first bounce uses G-Buffer)
HitInfo hit;
if (depth == 0u) {
hit = trace_ray_gbuffer(current_ray, pixel_coords);
} else {
// For subsequent bounces, we can't trace without full scene geometry
// This is a limitation of G-Buffer based approach
// In a full path tracer, you'd trace against the actual scene here
hit.hit = false;
}
for (uint depth = 0u; depth < u_max_depth; ++depth) {
HitInfo hit = trace_ray_bvh(ray);
if (!hit.hit) {
// Hit sky/background
vec3 sky_color = vec3(0.1, 0.1, 0.15);
radiance += throughput * sky_color;
radiance += throughput * environment_color(ray.direction);
break;
}
// Get material
Material mat;
if (hit.material_id < mat_count) {
mat = materials[hit.material_id];
} else {
// Fallback material
mat.albedo = vec3(0.5);
mat.metallic = 0.0;
mat.roughness = 0.5;
mat.emission = vec3(0.0);
mat.type = MATERIAL_DIFFUSE;
mat.ior = 1.5;
}
Material mat = fetch_material(hit.material_id);
// Add emission
// emission
radiance += throughput * mat.emission;
// Sample direct lighting (only for diffuse surfaces)
if (mat.type == MATERIAL_DIFFUSE && depth == 0u) {
radiance += throughput * sample_direct_lighting(hit.position, hit.normal, mat, seed);
// direct light (only for diffuse to keep simple)
if (mat.type == MATERIAL_DIFFUSE) {
radiance += throughput * eval_direct_lighting(hit, mat, seed);
}
// Scatter ray
ScatterResult scatter = scatter_ray(current_ray, hit, mat, seed);
ScatterResult sc = scatter_ray(ray, hit, mat, seed);
if (!sc.scattered) break;
if (!scatter.scattered || scatter.pdf < EPSILON) {
break;
}
throughput *= sc.attenuation;
// Update throughput
throughput *= scatter.attenuation;
// Russian roulette path termination
// RR
if (depth > 3u) {
float rr_probability = max(throughput.r, max(throughput.g, throughput.b));
if (rr_probability < RR_THRESHOLD || random_float(seed) > rr_probability) {
break;
}
throughput /= rr_probability;
float p = max(throughput.r, max(throughput.g, throughput.b));
p = clamp(p, 0.0, 0.95);
if (p < RR_THRESHOLD || random_float(seed) > p) break;
throughput /= p;
}
// Continue with scattered ray
current_ray = scatter.scattered_ray;
ray = sc.scattered_ray;
// Safety check for throughput
if (all(lessThan(throughput, vec3(EPSILON)))) {
break;
}
if (all(lessThan(throughput, vec3(EPSILON)))) break;
}
return radiance;
}
/**
* @brief Enhanced direct lighting with G-Buffer
*/
vec3 render_direct_lighting(ivec2 pixel_coords, inout uint seed) {
// Read G-Buffer
vec4 position_data = imageLoad(g_position, pixel_coords);
vec4 normal_data = imageLoad(g_normal, pixel_coords);
vec4 albedo_data = imageLoad(g_albedo, pixel_coords);
if (position_data.w < 0.5) {
return vec3(0.1, 0.1, 0.15); // Sky
}
vec3 position = position_data.xyz;
vec3 normal = normalize(normal_data.xyz);
uint material_id = uint(albedo_data.a * 255.0 + 0.5);
// Get material
Material mat;
uint mat_count = uint(materials.length());
if (material_id < mat_count) {
mat = materials[material_id];
} else {
// Fallback: use G-Buffer albedo
mat.albedo = albedo_data.rgb;
mat.metallic = 0.0;
mat.roughness = 0.5;
mat.emission = vec3(0.0);
mat.type = MATERIAL_DIFFUSE;
mat.ior = 1.5;
}
vec3 color = vec3(0.0);
// Emission
color += mat.emission;
// View direction
vec3 view_dir = normalize(u_camera_position - position);
// Direct lighting from all lights
for (uint i = 0u; i < u_light_count; i++) {
Light light = lights[i];
vec3 light_dir;
float light_distance;
float attenuation = 1.0;
if (light.type == LIGHT_POINT) {
vec3 to_light = light.position - position;
light_distance = length(to_light);
light_dir = to_light / light_distance;
attenuation = 1.0 / max(light_distance * light_distance, 0.01);
if (light_distance > light.range) continue;
} else if (light.type == LIGHT_DIRECTIONAL) {
light_dir = normalize(-light.direction);
light_distance = MAX_FLOAT;
} else {
continue;
}
float NdotL = max(dot(normal, light_dir), 0.0);
if (NdotL > 0.0) {
vec3 H = normalize(view_dir + light_dir);
float NdotV = max(dot(normal, view_dir), 0.0);
float NdotH = max(dot(normal, H), 0.0);
float HdotV = max(dot(H, view_dir), 0.0);
// Cook-Torrance BRDF
vec3 F0 = mix(vec3(0.04), mat.albedo, mat.metallic);
vec3 F = fresnel_schlick(HdotV, F0);
float D = distribution_ggx(normal, H, max(mat.roughness, 0.04));
float G = geometry_smith(normal, view_dir, light_dir, mat.roughness);
vec3 numerator = D * G * F;
float denominator = 4.0 * NdotV * NdotL + EPSILON;
vec3 specular = numerator / denominator;
vec3 kS = F;
vec3 kD = (vec3(1.0) - kS) * (1.0 - mat.metallic);
vec3 radiance = light.color * light.intensity * attenuation;
color += (kD * mat.albedo * INV_PI + specular) * radiance * NdotL;
}
}
// Ambient occlusion approximation
color += mat.albedo * 0.03;
return color;
}
void main() {
ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy);
ivec2 image_size = imageSize(output_image);
if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) return;
if (pixel_coords.x >= image_size.x || pixel_coords.y >= image_size.y) {
return;
}
// Initialize random seed
uint base_seed = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x);
uint seed = base_seed + u_frame_count * 719393u;
vec3 color = render_direct_lighting(pixel_coords, seed);
vec3 color = vec3(0.0);
// Multi-sample
uint spp = max(u_samples_per_pixel, 1u);
for (uint s = 0u; s < spp; ++s) {
Ray cam_ray = generate_camera_ray(pixel_coords, image_size, seed);
color += trace_path(cam_ray, seed);
}
color /= float(spp);
// Clamp
color = clamp(color, vec3(0.0), vec3(10.0));
// Accumulation
if (u_enable_accumulation && u_frame_count > 0u) {
vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb;
float weight = 1.0 / float(u_frame_count + 1u);
color = mix(accumulated, color, weight);
float w = 1.0 / float(u_frame_count + 1u);
color = mix(accumulated, color, w);
}
imageStore(accumulation_image, pixel_coords, vec4(color, 1.0));

View File

@ -261,24 +261,53 @@ bool BVH::upload_to_gpu(Buffer& node_buffer, Buffer& triangle_buffer) {
// 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]);
}
// Upload nodes
// 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];
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.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;
}
if (!node_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
nodes_.size() * sizeof(BVHNode),
nodes_.data(),
node_gpu.size() * sizeof(BVHNodeGpu),
node_gpu.data(),
BufferUsage::STATIC_DRAW)) {
Logger::error("Failed to upload BVH nodes to GPU");
return false;
}
// Upload triangles
if (!triangle_buffer.create(BufferType::SHADER_STORAGE_BUFFER,
ordered_triangles.size() * sizeof(Triangle),
ordered_triangles.data(),
tri_gpu.size() * sizeof(TriangleGpu),
tri_gpu.data(),
BufferUsage::STATIC_DRAW)) {
Logger::error("Failed to upload BVH triangles to GPU");
return false;

View File

@ -25,24 +25,30 @@ bool GBuffer::initialize() {
return true;
}
// Create framebuffer
glGenFramebuffers(1, &fbo_);
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
// Create G-Buffer textures
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);
// Attach textures to framebuffer
// 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);
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);
// Create depth texture
glGenTextures(1, &depth_texture_);
glBindTexture(GL_TEXTURE_2D, depth_texture_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width_, height_, 0,
@ -52,15 +58,15 @@ bool GBuffer::initialize() {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_texture_, 0);
// Set draw buffers
GLenum draw_buffers[GBUFFER_COUNT] = {
GL_COLOR_ATTACHMENT0 + GBUFFER_POSITION,
GL_COLOR_ATTACHMENT0 + GBUFFER_NORMAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO
GL_COLOR_ATTACHMENT0 + GBUFFER_ALBEDO,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL,
GL_COLOR_ATTACHMENT0 + GBUFFER_MATERIAL_ID
};
glDrawBuffers(GBUFFER_COUNT, draw_buffers);
// Check framebuffer completeness
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
Logger::error("GBuffer framebuffer is not complete");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@ -98,6 +104,18 @@ void GBuffer::release() {
Logger::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;
}
void GBuffer::render(const Scene& scene, const Shader& shader) {
if (!initialized_) {
Logger::error("GBuffer not initialized");
@ -150,6 +168,9 @@ void GBuffer::render(const Scene& scene, const Shader& shader) {
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();
@ -206,16 +227,4 @@ void GBuffer::get_dimensions(uint& width, uint& height) const {
height = height_;
}
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;
}
} // namespace are

View File

@ -306,6 +306,9 @@ void RayTracer::bind_gbuffer_(const GBuffer& gbuffer) {
glBindImageTexture(0, gbuffer.get_texture(GBUFFER_POSITION), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
glBindImageTexture(1, gbuffer.get_texture(GBUFFER_NORMAL), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
glBindImageTexture(2, gbuffer.get_texture(GBUFFER_ALBEDO), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
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);
}
void RayTracer::set_compute_shader(const Shader& shader) {

View File

@ -105,13 +105,6 @@ bool ShaderManager::load_builtin_shaders_() {
}
shader_cache_["gbuffer"] = gbuffer_shader_;
// Load ray tracing compute shader
if (!raytracing_shader_.load_compute("shaders/raytracing.comp")) {
Logger::error("Failed to load ray tracing shader");
return false;
}
shader_cache_["raytracing"] = raytracing_shader_;
// Load ray tracing compute shader
Logger::info("Loading ray tracing compute shader...");
if (!raytracing_shader_.load_compute("shaders/raytracing.comp")) {

54
write.sh Normal file
View File

@ -0,0 +1,54 @@
#!/bin/bash
# query.sh - 遍历指定文件夹中的 .h 文件并生成 all_headers.md
# 检查是否提供了目录参数
if [ $# -ne 1 ]; then
echo "用法: $0 <目标文件夹路径>"
exit 1
fi
TARGET_DIR="$1"
# 检查提供的路径是否为一个存在的目录
if [ ! -d "$TARGET_DIR" ]; then
echo "错误: 目录 '$TARGET_DIR' 不存在。"
exit 1
fi
# 输出文件
OUTPUT_FILE="all_files.md"
# 清空或创建输出文件
> "$OUTPUT_FILE"
echo "正在扫描目录: $TARGET_DIR"
# 使用 find 命令查找所有 .h 文件
H_FILES=$(find "$TARGET_DIR" -type f -name "*.cpp")
# 检查是否找到了 .h 文件
if [ -z "$H_FILES" ]; then
echo "在目录 '$TARGET_DIR' 及其子目录中未找到任何 .h 文件。"
exit 0
fi
# 遍历找到的每个 .h 文件
for header_file in $H_FILES; do
# 获取相对于脚本执行位置的相对路径
RELATIVE_PATH=$(realpath --relative-to=. "$header_file")
# 写入分隔符和文件名
{
echo "### 文件:$RELATIVE_PATH"
echo ""
echo '```cpp'
cat "$header_file"
echo '```'
echo "" # 添加一个空行,使文件之间有分隔
} >> "$OUTPUT_FILE"
echo "已处理: $RELATIVE_PATH"
done
echo ""
echo "处理完成!所有头文件内容已合并到 $OUTPUT_FILE 中。"