diff --git a/examples/cornell_box b/examples/cornell_box index bb88f13..e75e179 100644 Binary files a/examples/cornell_box and b/examples/cornell_box differ diff --git a/shaders/denoiser.comp b/shaders/denoiser.comp index 5cf4d29..b7fa895 100644 --- a/shaders/denoiser.comp +++ b/shaders/denoiser.comp @@ -7,22 +7,47 @@ layout(binding = 1, rgba32f) uniform writeonly image2D u_output; uniform int u_radius; // 1 => 3x3, 2 => 5x5 +// Gaussian weight based on distance +float gaussian_weight(float dist, float sigma) { + return exp(-0.5 * dist * dist / (sigma * sigma)); +} + +// Bilateral filter: considers both spatial distance and color similarity void main() { ivec2 p = ivec2(gl_GlobalInvocationID.xy); ivec2 size = imageSize(u_output); if (p.x >= size.x || p.y >= size.y) return; + vec3 center_color = imageLoad(u_input, p).rgb; + + // Sigma values for bilateral filter + float sigma_space = float(u_radius); // spatial sigma + float sigma_color = 0.3; // color sigma (adjust for more/less smoothing) + vec3 sum = vec3(0.0); - int count = 0; + float weight_sum = 0.0; for (int dy = -u_radius; dy <= u_radius; ++dy) { for (int dx = -u_radius; dx <= u_radius; ++dx) { ivec2 q = clamp(p + ivec2(dx, dy), ivec2(0), size - ivec2(1)); - sum += imageLoad(u_input, q).rgb; - count += 1; + vec3 sample_color = imageLoad(u_input, q).rgb; + + // Spatial weight (Gaussian based on distance) + float spatial_dist = length(vec2(float(dx), float(dy))); + float spatial_weight = gaussian_weight(spatial_dist, sigma_space); + + // Color weight (Gaussian based on color difference) + float color_dist = length(sample_color - center_color); + float color_weight = gaussian_weight(color_dist, sigma_color); + + // Combined bilateral weight + float weight = spatial_weight * color_weight; + + sum += sample_color * weight; + weight_sum += weight; } } - vec3 out_color = sum / float(count); + vec3 out_color = sum / max(weight_sum, 1e-6); imageStore(u_output, p, vec4(out_color, 1.0)); } diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp index 07db985..d8f5852 100644 --- a/shaders/raytracing.comp +++ b/shaders/raytracing.comp @@ -139,21 +139,21 @@ vec4 sample_texture_array(int slot, int index, vec2 uv) { // Utility // ============================================================================ -/** +/* * @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) { @@ -186,10 +186,11 @@ vec3 random_vec3(inout uint seed) { } 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; - } + // Use cosine-weighted hemisphere sampling to avoid infinite loop + float z = 1.0 - 2.0 * random_float(seed); + float r = sqrt(max(0.0, 1.0 - z * z)); + float phi = 2.0 * PI * random_float(seed); + return vec3(r * cos(phi), r * sin(phi), z); } vec3 random_unit_vector(inout uint seed) { @@ -200,12 +201,11 @@ vec3 random_unit_vector(inout uint seed) { // Camera ray // ============================================================================ -/** - * @brief Generate primary ray in world space +/* + * @brief Generate primary ray in world space (center pixel, no jitter) */ -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); +Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size) { + vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size); vec2 ndc = uv * 2.0 - 1.0; vec4 p_near = u_inv_view_projection * vec4(ndc, 0.0, 1.0); @@ -223,7 +223,7 @@ Ray generate_camera_ray(ivec2 pixel_coords, ivec2 image_size, inout uint seed) { // Intersection // ============================================================================ -/** +/* * @brief Ray-AABB intersection */ bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) { @@ -240,7 +240,7 @@ bool intersect_aabb(Ray ray, vec3 aabb_min, vec3 aabb_max, float t_max) { return (tmax2 >= max(tmin, 0.0)) && (tmin <= t_max); } -/** +/* * @brief Moller-Trumbore triangle intersection */ bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) { @@ -295,7 +295,7 @@ bool intersect_triangle(Ray ray, TriangleGpu tri, inout HitInfo hit) { return true; } -/** +/* * @brief BVH traversal (closest hit) */ HitInfo trace_ray_bvh(Ray ray) { @@ -339,7 +339,7 @@ HitInfo trace_ray_bvh(Ray ray) { return hit; } -/** +/* * @brief Any-hit BVH for shadow ray */ bool trace_any_bvh(Ray ray, float t_max) { @@ -385,7 +385,7 @@ bool trace_any_bvh(Ray ray, float t_max) { // Primary-ray fast path via G-Buffer // ============================================================================ -/** +/* * @brief Read primary hit from G-Buffer if current pixel has geometry * @note Uses g_position.w as "valid" marker (your gbuffer writes 1.0 on hits, clear is 0). */ @@ -648,26 +648,11 @@ vec3 environment_color(vec3 dir) { return vec3(0.1, 0.1, 0.15); } -Ray generate_camera_ray_center(ivec2 pixel_coords, ivec2 image_size) { - vec2 uv = (vec2(pixel_coords) + vec2(0.5)) / vec2(image_size); - vec2 ndc = uv * 2.0 - 1.0; - - 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; - - Ray r; - r.origin = near_ws; - r.direction = normalize(far_ws - near_ws); - return r; -} - -/** +/* * @brief Trace path with primary-ray G-Buffer acceleration */ vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint seed) { - Ray ray = generate_camera_ray_center(pixel_coords, image_size); + Ray ray = generate_camera_ray(pixel_coords, image_size); vec3 radiance = vec3(0.0); vec3 throughput = vec3(1.0);