aurora-rendering-engine/shaders/raytracing.comp

687 lines
20 KiB
Plaintext

#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
struct Material {
vec3 albedo;
float metallic;
vec3 emission;
float roughness;
int type;
float ior;
vec2 padding;
};
struct Light {
vec3 position;
int type;
vec3 direction;
float intensity;
vec3 color;
float range;
vec2 spot_angles;
vec2 padding;
};
struct Ray {
vec3 origin;
vec3 direction;
};
struct HitInfo {
bool hit;
float t;
vec3 position;
vec3 normal;
vec2 texcoord;
uint material_id;
};
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[];
};
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;
// ============================================================================
// 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;
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;
}
// 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);
// 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);
}
imageStore(accumulation_image, pixel_coords, vec4(color, 1.0));
imageStore(output_image, pixel_coords, vec4(color, 1.0));
}