diff --git a/.gitignore b/.gitignore index ed37395..23721ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Build directories build/ -cmake-build-*/ # IDE files .vscode/ @@ -26,7 +25,6 @@ cmake_install.cmake Makefile # Output files -output/ *.ppm *.bmp *.png @@ -38,4 +36,3 @@ Thumbs.db # Experiments (optional, can be tracked) experiments/* -!experiments/.gitkeep diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index 28fb002..afb40ba 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -7,6 +7,7 @@ #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 @@ -37,7 +38,7 @@ struct Light { float intensity; vec3 color; float range; - vec2 spot_angles; // inner, outer + vec2 spot_angles; vec2 padding; }; @@ -55,97 +56,13 @@ struct HitInfo { uint material_id; }; -// Utility functions -float saturate(float x) { - return clamp(x, 0.0, 1.0); -} +struct ScatterResult { + bool scattered; + vec3 attenuation; + Ray scattered_ray; + float pdf; +}; -vec3 saturate(vec3 x) { - return clamp(x, vec3(0.0), vec3(1.0)); -} - -// Random number generation (PCG Hash) -uint pcg_hash(uint seed) { - uint state = seed * 747796405u + 2891336453u; - uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; - return (word >> 22u) ^ word; -} - -float random_float(inout uint seed) { - seed = pcg_hash(seed); - return float(seed) / 4294967296.0; -} - -vec2 random_vec2(inout uint seed) { - return vec2(random_float(seed), random_float(seed)); -} - -vec3 random_vec3(inout uint seed) { - return vec3(random_float(seed), random_float(seed), random_float(seed)); -} - -// Random direction in hemisphere -vec3 random_hemisphere_direction(vec3 normal, inout uint seed) { - float z = random_float(seed); - float r = sqrt(max(0.0, 1.0 - z * z)); - float phi = 2.0 * PI * random_float(seed); - - vec3 dir = vec3(r * cos(phi), r * sin(phi), z); - - // Create coordinate system - vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, normal)); - vec3 bitangent = cross(normal, tangent); - - return normalize(tangent * dir.x + bitangent * dir.y + normal * dir.z); -} - -// Cosine-weighted hemisphere sampling -// vec3 cosine_weighted_hemisphere(vec3 normal, inout uint seed) { -// vec2 r = random_vec2(seed); -// float r1 = 2.0 * PI * r.x; -// float r2 = r.y; -// float r2s = sqrt(r2); -// -// vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); -// vec3 tangent = normalize(cross(up, normal)); -// vec3 bitangent = cross(normal, tangent); -// -// vec3 dir = tangent * cos(r1) * r2s + bitangent * sin(r1) * r2s + normal * sqrt(1.0 - r2); -// return normalize(dir); -// } - -// Schlick's approximation for Fresnel -vec3 fresnel_schlick(float cos_theta, vec3 f0) { - return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); -} - -// GGX distribution -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); -} - -// Smith's geometry function -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; -} layout(local_size_x = 16, local_size_y = 16) in; @@ -177,11 +94,517 @@ uniform mat4 u_inv_view_projection; uniform bool u_enable_accumulation; uniform bool u_use_bvh; -// Evaluate direct lighting -vec3 evaluate_direct_lighting(vec3 position, vec3 normal, vec3 view_dir, - Material material, inout uint seed) { +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * @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 + */ +bool near_zero(vec3 v) { + return (abs(v.x) < EPSILON) && (abs(v.y) < EPSILON) && (abs(v.z) < EPSILON); +} + +/** + * @brief Reflect vector around normal + */ +vec3 reflect_vector(vec3 v, vec3 n) { + return v - 2.0 * dot(v, n) * n; +} + +/** + * @brief Refract vector through surface + */ +vec3 refract_vector(vec3 uv, vec3 n, float etai_over_etat) { + float cos_theta = min(dot(-uv, n), 1.0); + vec3 r_out_perp = etai_over_etat * (uv + cos_theta * n); + vec3 r_out_parallel = -sqrt(abs(1.0 - dot(r_out_perp, r_out_perp))) * n; + return r_out_perp + r_out_parallel; +} + +// ============================================================================ +// Random Number Generation +// ============================================================================ + +/** + * @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); + if (dot(p, p) < 1.0) return p; + } +} + +/** + * @brief Generate random unit vector + */ +vec3 random_unit_vector(inout uint seed) { + return normalize(random_in_unit_sphere(seed)); +} + +// ============================================================================ +// Sampling Functions +// ============================================================================ + +/** + * @brief Cosine-weighted hemisphere sampling + * @return Sampled direction in local space (z-up) + */ +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); + + return vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta); +} + +/** + * @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); +} + +// ============================================================================ +// BRDF Functions +// ============================================================================ + +/** + * @brief Schlick's approximation for Fresnel reflectance + */ +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; + + // Cosine-weighted hemisphere sampling + vec3 local_dir = cosine_weighted_hemisphere(seed); + vec3 scatter_direction = local_to_world(local_dir, hit.normal); + + // Prevent degenerate scatter direction + if (near_zero(scatter_direction)) { + scatter_direction = hit.normal; + } + + 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; + + 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; + + 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; + } + + 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); + + 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); + + bool cannot_refract = refraction_ratio * sin_theta > 1.0; + float reflectance = fresnel_dielectric(cos_theta, refraction_ratio); + + vec3 direction; + if (cannot_refract || reflectance > random_float(seed)) { + direction = reflect_vector(unit_direction, hit.normal); + } else { + direction = refract_vector(unit_direction, 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; +} + +/** + * @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; + } +} + +// ============================================================================ +// Scene Intersection (G-Buffer based) +// ============================================================================ + +/** + * @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) { + if (u_light_count == 0u) return vec3(0.0); + vec3 direct_light = vec3(0.0); + // Sample one random light (could be improved with MIS) + 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); + + if (light.type == LIGHT_POINT) { + vec3 to_light = light.position - position; + light_distance = length(to_light); + light_dir = to_light / light_distance; + + 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; + } + } 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; + } + } + + return direct_light; +} + +/** + * @brief Trace path and accumulate radiance + */ +vec3 trace_path(Ray initial_ray, ivec2 pixel_coords, 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; + } + + if (!hit.hit) { + // Hit sky/background + vec3 sky_color = vec3(0.1, 0.1, 0.15); + radiance += throughput * sky_color; + 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; + } + + // Add 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); + } + + // Scatter ray + ScatterResult scatter = scatter_ray(current_ray, hit, mat, seed); + + if (!scatter.scattered || scatter.pdf < EPSILON) { + break; + } + + // Update throughput + throughput *= scatter.attenuation; + + // Russian roulette path termination + 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; + } + + // Continue with scattered ray + current_ray = scatter.scattered_ray; + + // Safety check for throughput + 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; @@ -207,26 +630,31 @@ vec3 evaluate_direct_lighting(vec3 position, vec3 normal, vec3 view_dir, 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); - // PBR lighting - vec3 F0 = mix(vec3(0.04), material.albedo, material.metallic); - vec3 F = fresnel_schlick(max(dot(H, view_dir), 0.0), F0); - float D = distribution_ggx(normal, H, max(material.roughness, 0.04)); - float G = geometry_smith(normal, view_dir, light_dir, material.roughness); + // 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 - material.metallic); + vec3 kD = (vec3(1.0) - kS) * (1.0 - mat.metallic); vec3 radiance = light.color * light.intensity * attenuation; - direct_light += (kD * material.albedo * INV_PI + specular) * radiance * NdotL; + color += (kD * mat.albedo * INV_PI + specular) * radiance * NdotL; } } - return direct_light; + // Ambient occlusion approximation + color += mat.albedo * 0.03; + + return color; } void main() { @@ -237,62 +665,11 @@ void main() { return; } - // 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); - - // Background - if (position_data.w < 0.5) { - vec3 background = vec3(0.1, 0.1, 0.15); - imageStore(output_image, pixel_coords, vec4(background, 1.0)); - imageStore(accumulation_image, pixel_coords, vec4(background, 1.0)); - return; - } - - vec3 position = position_data.xyz; - vec3 normal = normalize(normal_data.xyz); - vec3 albedo = albedo_data.rgb; - - // 关键修复:从G-Buffer的alpha通道读取material_id - // 注意:albedo_data是从RGBA8纹理读取的,alpha值范围是[0,1] - // 我们需要将其转换回整数ID - uint material_id = uint(albedo_data.a * 255.0 + 0.5); - // Initialize random seed - uint seed = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x) + u_frame_count * 719393u; + 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 = vec3(0.0); - - // Get material from buffer - Material material; - uint mat_count = uint(materials.length()); - - if (material_id < mat_count) { - // 从SSBO读取材质 - material = materials[material_id]; - } else { - // 使用G-Buffer中的albedo作为fallback - material.albedo = albedo; - material.metallic = 0.0; - material.roughness = 0.5; - material.emission = vec3(0.0); - material.type = MATERIAL_DIFFUSE; - material.ior = 1.5; - } - - // Add emission - color += material.emission; - - // Direct lighting - vec3 view_dir = normalize(u_camera_position - position); - - if (u_light_count > 0u) { - color += evaluate_direct_lighting(position, normal, view_dir, material, seed); - } - - // Ambient lighting - color += material.albedo * 0.05; + vec3 color = render_direct_lighting(pixel_coords, seed); // Clamp color = clamp(color, vec3(0.0), vec3(10.0)); @@ -307,197 +684,3 @@ void main() { imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); imageStore(output_image, pixel_coords, vec4(color, 1.0)); } - -// 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[]; -// }; -// -// layout(std430, binding = 1) readonly buffer LightBuffer { -// Light lights[]; -// }; -// -// // Uniforms -// uniform uint u_frame_count; -// uniform uint u_samples_per_pixel; -// uniform uint u_max_depth; -// uniform uint u_light_count; -// uniform vec3 u_camera_position; -// uniform mat4 u_inv_view_projection; -// uniform bool u_enable_accumulation; -// uniform bool u_use_bvh; -// -// // Cosine-weighted hemisphere sampling -// vec3 cosine_weighted_hemisphere(vec3 normal, inout uint seed) { -// vec2 r = random_vec2(seed); -// float r1 = 2.0 * PI * r.x; -// float r2 = r.y; -// float r2s = sqrt(r2); -// -// vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); -// vec3 tangent = normalize(cross(up, normal)); -// vec3 bitangent = cross(normal, tangent); -// -// vec3 dir = tangent * cos(r1) * r2s + bitangent * sin(r1) * r2s + normal * sqrt(1.0 - r2); -// return normalize(dir); -// } -// -// // Evaluate direct lighting -// vec3 evaluate_direct_lighting(vec3 position, vec3 normal, vec3 view_dir, -// Material material, inout uint seed) { -// vec3 direct_light = vec3(0.0); -// -// 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); -// -// // PBR lighting -// vec3 F0 = mix(vec3(0.04), material.albedo, material.metallic); -// vec3 F = fresnel_schlick(max(dot(H, view_dir), 0.0), F0); -// float D = distribution_ggx(normal, H, max(material.roughness, 0.04)); -// float G = geometry_smith(normal, view_dir, light_dir, material.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 - material.metallic); -// -// vec3 radiance = light.color * light.intensity * attenuation; -// direct_light += (kD * material.albedo * INV_PI + specular) * radiance * NdotL; -// } -// } -// -// return direct_light; -// } -// -// // Trace indirect lighting (simple path tracing) -// vec3 trace_indirect(vec3 position, vec3 normal, Material material, inout uint seed) { -// vec3 color = vec3(0.0); -// -// // Sample random direction -// vec3 ray_dir = cosine_weighted_hemisphere(normal, seed); -// -// // Sky color based on direction -// float t = 0.5 * (ray_dir.y + 1.0); -// vec3 sky_color = mix(vec3(1.0), vec3(0.5, 0.7, 1.0), t) * 0.2; -// -// color = sky_color; -// -// 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; -// } -// -// // 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); -// -// // Check if this pixel has valid geometry -// if (position_data.w < 0.5) { -// vec3 background = vec3(0.1, 0.1, 0.15); -// imageStore(output_image, pixel_coords, vec4(background, 1.0)); -// imageStore(accumulation_image, pixel_coords, vec4(background, 1.0)); -// return; -// } -// -// vec3 position = position_data.xyz; -// vec3 normal = normalize(normal_data.xyz); -// vec3 albedo = albedo_data.rgb; -// uint material_id = floatBitsToUint(albedo_data.a); -// -// if (material_id >= 1000u) { -// material_id = 0u; -// } -// -// // Initialize random seed -// uint seed = uint(pixel_coords.x) + uint(pixel_coords.y) * uint(image_size.x) + u_frame_count * 719393u; -// -// vec3 color = vec3(0.0); -// -// // Get material -// Material material; -// if (material_id < uint(materials.length())) { -// material = materials[material_id]; -// } else { -// material.albedo = albedo; -// material.metallic = 0.0; -// material.roughness = 0.5; -// material.emission = vec3(0.0); -// material.type = MATERIAL_DIFFUSE; -// material.ior = 1.5; -// } -// -// // Add emission -// color += material.emission; -// -// // Direct lighting -// vec3 view_dir = normalize(u_camera_position - position); -// -// if (u_light_count > 0u) { -// color += evaluate_direct_lighting(position, normal, view_dir, material, seed); -// } -// -// // Indirect lighting (path tracing) - THIS ADDS NOISE -// for (uint samp_idx = 0u; samp_idx < u_samples_per_pixel; samp_idx++) { // 修复: sample -> samp_idx -// vec3 indirect = trace_indirect(position, normal, material, seed); -// color += indirect * material.albedo * INV_PI; -// } -// -// // Ambient -// color += material.albedo * 0.02; -// -// // Clamp -// color = clamp(color, vec3(0.0), vec3(100.0)); -// -// // Accumulation for denoising -// 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); -// } -// -// imageStore(accumulation_image, pixel_coords, vec4(color, 1.0)); -// imageStore(output_image, pixel_coords, vec4(color, 1.0)); -// }