diff --git a/TODO-List b/TODO-List index 3ce1c29..22fa7ce 100644 --- a/TODO-List +++ b/TODO-List @@ -1,7 +1,7 @@ * 1. 修复全局光照导致的采样问题 * 2. 重构代码,统一化结构、加入ResourceManager * 2.1. 优化cpu-gpu交互效率 -2.2 修改shader文件结构 +* 2.2 修改shader文件结构 3. 支持更多材质及pbr参数 4. 添加HDRI支持 5. 支持glTF 2.0模型加载 diff --git a/examples/normal_map_cornell_box b/examples/normal_map_cornell_box index a12afc5..97f066d 100644 Binary files a/examples/normal_map_cornell_box and b/examples/normal_map_cornell_box differ diff --git a/include/resource/shader.h b/include/resource/shader.h index 877b443..a09a6c0 100644 --- a/include/resource/shader.h +++ b/include/resource/shader.h @@ -170,6 +170,14 @@ private: * @return File content */ std::string read_file_(const std::string &path); + + /* + * @brief Process #include directives in shader source + * @param source Shader source code + * @param base_dir Base directory for relative includes + * @return Processed source with includes resolved + */ + std::string process_includes_(const std::string &source, const std::string &base_dir); }; } // namespace are diff --git a/shaders/gbuffer.frag b/shaders/gbuffer/gbuffer.frag similarity index 100% rename from shaders/gbuffer.frag rename to shaders/gbuffer/gbuffer.frag diff --git a/shaders/gbuffer.vert b/shaders/gbuffer/gbuffer.vert similarity index 100% rename from shaders/gbuffer.vert rename to shaders/gbuffer/gbuffer.vert diff --git a/shaders/include/bvh.glsl b/shaders/include/bvh.glsl new file mode 100644 index 0000000..58855b8 --- /dev/null +++ b/shaders/include/bvh.glsl @@ -0,0 +1,190 @@ +// BVH traversal and ray-triangle intersection + +#ifndef BVH_GLSL +#define BVH_GLSL + +// 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); +} + +// 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; + + 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; + + vec3 t0 = tri.t0.xyz; + vec3 t1 = tri.t1.xyz; + vec3 t2 = normalize(cross(n0, t0)); + + 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.tangent = normalize(t0 * w + t1 * u + t2 * v); + hit.material_id = as_uint(tri.v0_material.w); + return true; +} + +// 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; + } + + 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 { + uint left = left_first; + uint right = left_first + 1u; + if (sp < 63) stack[sp++] = right; + if (sp < 63) stack[sp++] = left; + } + } + + return hit; +} + +// 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; +} + +// Read primary hit from G-Buffer +HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { + HitInfo hit; + hit.hit = false; + hit.t = MAX_FLOAT; + 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; + + vec4 pos = imageLoad(g_position, pixel_coords); + if (pos.w <= 0.5) { + return hit; + } + + vec3 p = pos.xyz; + vec3 n = normalize(imageLoad(g_normal, pixel_coords).xyz); + uint mid = imageLoad(g_material_id, pixel_coords).r; + vec4 mat = imageLoad(g_material, pixel_coords); + int mtype = int(mat.w); + vec4 texcoord_tangent = imageLoad(g_texcoord, pixel_coords); + vec2 texcoord = texcoord_tangent.xy; + 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; + hit.t = length(p - ray.origin); + + return hit; +} + +#endif // BVH_GLSL diff --git a/shaders/include/common.glsl b/shaders/include/common.glsl new file mode 100644 index 0000000..c90642f --- /dev/null +++ b/shaders/include/common.glsl @@ -0,0 +1,32 @@ +// Common constants and definitions for ray tracing + +#ifndef COMMON_GLSL +#define COMMON_GLSL + +// Mathematical constants +#define PI 3.14159265359 +#define INV_PI 0.31830988618 +#define EPSILON 1e-4 +#define MAX_FLOAT 3.402823466e38 +#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 + +// Texture slots +#define TEXTURE_SLOT_ALBEDO 0 +#define TEXTURE_SLOT_NORMAL 1 +#define TEXTURE_SLOT_METALLIC 2 +#define TEXTURE_SLOT_ROUGHNESS 3 +#define TEXTURE_SLOT_AO 4 +#define TEXTURE_SLOT_EMISSION 5 + +#endif // COMMON_GLSL diff --git a/shaders/include/lighting.glsl b/shaders/include/lighting.glsl new file mode 100644 index 0000000..887d5b8 --- /dev/null +++ b/shaders/include/lighting.glsl @@ -0,0 +1,50 @@ +// Direct lighting with shadow rays + +#ifndef LIGHTING_GLSL +#define LIGHTING_GLSL + +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; + Light light = lights[light_idx]; + + vec3 L; + float dist = MAX_FLOAT; + vec3 radiance = vec3(0.0); + + if (light.type == LIGHT_POINT) { + vec3 to_light = light.position - hit.position; + dist = length(to_light); + if (dist > light.range) return vec3(0.0); + L = to_light / dist; + + float atten = 1.0 / max(dist * dist, 0.01); + radiance = light.color * light.intensity * atten; + } else if (light.type == LIGHT_DIRECTIONAL) { + L = normalize(-light.direction); + radiance = light.color * light.intensity; + } else { + return vec3(0.0); + } + + float n_dot_l = max(dot(hit.normal, L), 0.0); + if (n_dot_l <= 0.0) return vec3(0.0); + + 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); + + float pdf_light = 1.0 / float(u_light_count); + vec3 brdf = mat.albedo * INV_PI; + return brdf * radiance * n_dot_l * mat.ao / max(pdf_light, EPSILON); +} + +vec3 environment_color(vec3 dir) { + return vec3(0.1, 0.1, 0.15); +} + +#endif // LIGHTING_GLSL diff --git a/shaders/include/material.glsl b/shaders/include/material.glsl new file mode 100644 index 0000000..9da1afb --- /dev/null +++ b/shaders/include/material.glsl @@ -0,0 +1,158 @@ +// Material handling and PBR scattering + +#ifndef MATERIAL_GLSL +#define MATERIAL_GLSL + +// Helper function to sample texture from array by index +vec4 sample_texture_array(int slot, int index, vec2 uv) { + if (index <= 0) return vec4(1.0); + + if (slot == 0) return texture(u_texture_albedo_array, vec3(uv, float(index - 1))); + if (slot == 1) return texture(u_texture_normal_array, vec3(uv, float(index - 1))); + if (slot == 2) return texture(u_texture_metallic_array, vec3(uv, float(index - 1))); + if (slot == 3) return texture(u_texture_roughness_array, vec3(uv, float(index - 1))); + if (slot == 4) return texture(u_texture_ao_array, vec3(uv, float(index - 1))); + if (slot == 5) return texture(u_texture_emission_array, vec3(uv, float(index - 1))); + + return vec4(1.0); +} + +// Apply normal map in world space +vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_handle) { + if (normal_handle == 0 || !u_enable_textures) return normal; + + vec3 T = normalize(tangent - normal * dot(tangent, normal)); + vec3 B = cross(normal, T); + mat3 TBN = mat3(T, B, normal); + + vec3 map_n = sample_texture_array(1, int(normal_handle), texcoord).xyz * 2.0 - 1.0; + return normalize(TBN * map_n); +} + +// Apply material textures to get final PBR values +void apply_material_textures(inout Material mat, inout vec3 normal, vec2 texcoord, vec3 tangent) { + if (!u_enable_textures) return; + + if (mat.texture_handles[0] != 0) { + mat.albedo = sample_texture_array(0, int(mat.texture_handles[0]), texcoord).rgb; + } + + if (mat.texture_handles[1] != 0) { + normal = apply_normal_map(normal, texcoord, tangent, mat.texture_handles[1]); + } + + if (mat.texture_handles[2] != 0) { + mat.metallic = sample_texture_array(2, int(mat.texture_handles[2]), texcoord).r; + } + + if (mat.texture_handles[3] != 0) { + mat.roughness = sample_texture_array(3, int(mat.texture_handles[3]), texcoord).r; + } + + if (mat.texture_handles[4] != 0) { + mat.ao = sample_texture_array(4, int(mat.texture_handles[4]), texcoord).r; + } + + if (mat.texture_handles[5] != 0) { + mat.emission = sample_texture_array(5, int(mat.texture_handles[5]), texcoord).rgb; + } +} + +// Fresnel functions +vec3 fresnel_schlick(float cos_theta, vec3 f0) { + return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); +} + +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); +} + +// Scatter functions +ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { + ScatterResult r; + r.scattered = true; + r.attenuation = mat.albedo; + + vec3 dir = hit.normal + random_unit_vector(seed); + if (near_zero(dir)) dir = hit.normal; + + r.scattered_ray.origin = hit.position + hit.normal * EPSILON; + r.scattered_ray.direction = normalize(dir); + return r; +} + +ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { + ScatterResult r; + + vec3 reflected = reflect_vector(normalize(ray_in.direction), hit.normal); + vec3 fuzz = mat.roughness * random_in_unit_sphere(seed); + vec3 dir = reflected + fuzz; + + 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; +} + +ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { + ScatterResult r; + r.scattered = true; + r.attenuation = vec3(1.0); + + vec3 unit_dir = normalize(ray_in.direction); + float cos_theta = dot(-unit_dir, hit.normal); + float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); + + bool entering = cos_theta > 0.0; + float eta = entering ? (1.0 / mat.ior) : mat.ior; + vec3 normal = entering ? hit.normal : -hit.normal; + + float sin_theta_t = eta * sin_theta; + bool total_internal_reflection = sin_theta_t >= 1.0; + + float f0 = pow((1.0 - mat.ior) / (1.0 + mat.ior), 2.0); + float f = f0 + (1.0 - f0) * pow(1.0 - abs(cos_theta), 5.0); + + vec3 dir; + if (total_internal_reflection || random_float(seed) < f) { + dir = reflect_vector(unit_dir, normal); + } else { + dir = refract_vector(unit_dir, normal, eta); + } + + r.scattered_ray.origin = hit.position + dir * EPSILON; + r.scattered_ray.direction = normalize(dir); + return r; +} + +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); + 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; +} + +// Fetch material with fallback +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; + m.ao = 1.0; + return m; +} + +#endif // MATERIAL_GLSL diff --git a/shaders/include/math.glsl b/shaders/include/math.glsl new file mode 100644 index 0000000..9262127 --- /dev/null +++ b/shaders/include/math.glsl @@ -0,0 +1,24 @@ +// Math utility functions + +#ifndef MATH_GLSL +#define MATH_GLSL + +bool near_zero(vec3 v) { + return (abs(v.x) < EPSILON) && (abs(v.y) < EPSILON) && (abs(v.z) < EPSILON); +} + +vec3 reflect_vector(vec3 v, vec3 n) { + return v - 2.0 * dot(v, n) * n; +} + +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; +} + +uint as_uint(float f) { return floatBitsToUint(f); } +float as_float(uint u) { return uintBitsToFloat(u); } + +#endif // MATH_GLSL diff --git a/shaders/include/rng.glsl b/shaders/include/rng.glsl new file mode 100644 index 0000000..582c658 --- /dev/null +++ b/shaders/include/rng.glsl @@ -0,0 +1,21 @@ +// PCG Random Number Generator + +#ifndef RNG_GLSL +#define RNG_GLSL + +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; +} + +vec3 random_vec3(inout uint seed) { + return vec3(random_float(seed), random_float(seed), random_float(seed)); +} + +#endif // RNG_GLSL diff --git a/shaders/include/sampling.glsl b/shaders/include/sampling.glsl new file mode 100644 index 0000000..00a2c38 --- /dev/null +++ b/shaders/include/sampling.glsl @@ -0,0 +1,18 @@ +// Sampling utility functions + +#ifndef SAMPLING_GLSL +#define SAMPLING_GLSL + +// Cosine-weighted hemisphere sampling (avoids infinite loop) +vec3 random_in_unit_sphere(inout uint seed) { + 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) { + return normalize(random_in_unit_sphere(seed)); +} + +#endif // SAMPLING_GLSL diff --git a/shaders/include/structs.glsl b/shaders/include/structs.glsl new file mode 100644 index 0000000..11ce6f7 --- /dev/null +++ b/shaders/include/structs.glsl @@ -0,0 +1,69 @@ +// Data structures for ray tracing + +#ifndef STRUCTS_GLSL +#define STRUCTS_GLSL + +struct Material { + vec3 albedo; + vec3 emission; + float metallic; + float roughness; + int type; + float ior; + float ao; + float padding1; + uint texture_handles[6]; +}; + +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; + vec3 tangent; + uint material_id; + int material_type; +}; + +struct ScatterResult { + bool scattered; + vec3 attenuation; + Ray scattered_ray; +}; + +struct BVHNodeGpu { + vec4 aabb_min_left_first; + vec4 aabb_max_count; +}; + +struct TriangleGpu { + vec4 v0_material; + vec4 v1; + vec4 v2; + vec4 n0; + vec4 n1; + vec4 n2; + vec4 uv0_uv1; + vec4 uv2; + vec4 t0; + vec4 t1; +}; + +#endif // STRUCTS_GLSL diff --git a/shaders/denoiser.comp b/shaders/postprocess/denoiser.comp similarity index 100% rename from shaders/denoiser.comp rename to shaders/postprocess/denoiser.comp diff --git a/shaders/screen_blit.frag b/shaders/postprocess/screen_blit.frag similarity index 100% rename from shaders/screen_blit.frag rename to shaders/postprocess/screen_blit.frag diff --git a/shaders/screen_blit.vert b/shaders/postprocess/screen_blit.vert similarity index 100% rename from shaders/screen_blit.vert rename to shaders/postprocess/screen_blit.vert diff --git a/shaders/raytracing.comp b/shaders/raytracing.comp deleted file mode 100644 index d8f5852..0000000 --- a/shaders/raytracing.comp +++ /dev/null @@ -1,759 +0,0 @@ -#version 430 core - -#define PI 3.14159265359 -#define INV_PI 0.31830988618 -#define EPSILON 1e-4 -#define MAX_FLOAT 3.402823466e38 -#define RR_THRESHOLD 0.1 - -#define MATERIAL_DIFFUSE 0 -#define MATERIAL_METAL 1 -#define MATERIAL_DIELECTRIC 2 -#define MATERIAL_EMISSIVE 3 - -#define LIGHT_DIRECTIONAL 0 -#define LIGHT_POINT 1 -#define LIGHT_SPOT 2 - -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; - -// Material params + material id (for primary hit fast-path) -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; - -struct Material { - vec3 albedo; - vec3 emission; - float metallic; - float roughness; - int type; - float ior; - float ao; // ambient occlusion - float padding1; - uint texture_handles[6]; -}; - -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; - vec3 tangent; - uint material_id; - int material_type; // material type from G-Buffer -}; - -struct ScatterResult { - bool scattered; - vec3 attenuation; - Ray scattered_ray; -}; - -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) -}; - -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 - vec4 t0; // tangent at v0 - vec4 t1; // tangent at v1 -}; - -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; -uniform uint u_light_count; -uniform mat4 u_inv_view_projection; -uniform bool u_enable_accumulation; -uniform bool u_use_bvh; -uniform uint u_bvh_node_count; -uniform bool u_enable_textures; - -// Global texture arrays for bindless sampling (6 arrays for each texture type) -layout(binding = 10) uniform sampler2DArray u_texture_albedo_array; -layout(binding = 11) uniform sampler2DArray u_texture_normal_array; -layout(binding = 12) uniform sampler2DArray u_texture_metallic_array; -layout(binding = 13) uniform sampler2DArray u_texture_roughness_array; -layout(binding = 14) uniform sampler2DArray u_texture_ao_array; -layout(binding = 15) uniform sampler2DArray u_texture_emission_array; - -// Helper function to sample texture from array by index -vec4 sample_texture_array(int slot, int index, vec2 uv) { - if (index <= 0) return vec4(1.0); - - if (slot == 0) return texture(u_texture_albedo_array, vec3(uv, float(index - 1))); - if (slot == 1) return texture(u_texture_normal_array, vec3(uv, float(index - 1))); - if (slot == 2) return texture(u_texture_metallic_array, vec3(uv, float(index - 1))); - if (slot == 3) return texture(u_texture_roughness_array, vec3(uv, float(index - 1))); - if (slot == 4) return texture(u_texture_ao_array, vec3(uv, float(index - 1))); - if (slot == 5) return texture(u_texture_emission_array, vec3(uv, float(index - 1))); - - return vec4(1.0); -} - -// ============================================================================ -// 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) { - 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; -} - -uint as_uint(float f) { return floatBitsToUint(f); } -float as_float(uint u) { return uintBitsToFloat(u); } - -// ============================================================================ -// RNG (PCG) -// ============================================================================ - -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; -} - -vec3 random_vec3(inout uint seed) { - return vec3(random_float(seed), random_float(seed), random_float(seed)); -} - -vec3 random_in_unit_sphere(inout uint seed) { - // 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) { - return normalize(random_in_unit_sphere(seed)); -} - -// ============================================================================ -// Camera ray -// ============================================================================ - -/* - * @brief Generate primary ray in world space (center pixel, no jitter) - */ -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); - 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; -} - -// ============================================================================ -// Intersection -// ============================================================================ - -/* - * @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; - - 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; - - // 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; -} - -/* - * @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; - } - - 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 { - uint left = left_first; - uint right = left_first + 1u; - 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; -} - -// ============================================================================ -// 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). - */ -HitInfo trace_primary_gbuffer(Ray ray, ivec2 pixel_coords) { - HitInfo hit; - hit.hit = false; - hit.t = MAX_FLOAT; - 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; - - vec4 pos = imageLoad(g_position, pixel_coords); - if (pos.w <= 0.5) { - return hit; - } - - vec3 p = pos.xyz; - vec3 n = normalize(imageLoad(g_normal, pixel_coords).xyz); - - // integer material id - uint mid = imageLoad(g_material_id, pixel_coords).r; - - // material type stored in g_material.w - 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; - - // For RR/any debug usage; path tracing uses this as starting point only. - hit.t = length(p - ray.origin); - - return hit; -} - -// ============================================================================ -// Material + scattering -// ============================================================================ - -// Apply normal map in world space -vec3 apply_normal_map(vec3 normal, vec2 texcoord, vec3 tangent, uint normal_handle) { - if (normal_handle == 0 || !u_enable_textures) return normal; - - vec3 T = normalize(tangent - normal * dot(tangent, normal)); - vec3 B = cross(normal, T); - mat3 TBN = mat3(T, B, normal); - - vec3 map_n = sample_texture_array(1, int(normal_handle), texcoord).xyz * 2.0 - 1.0; - return normalize(TBN * map_n); -} - -// Apply material textures to get final PBR values -void apply_material_textures(inout Material mat, inout vec3 normal, vec2 texcoord, vec3 tangent) { - if (!u_enable_textures) return; - - // Albedo texture (replace) - if (mat.texture_handles[0] != 0) { - mat.albedo = sample_texture_array(0, int(mat.texture_handles[0]), texcoord).rgb; - } - - // Normal map - if (mat.texture_handles[1] != 0) { - normal = apply_normal_map(normal, texcoord, tangent, mat.texture_handles[1]); - } - - // Metallic texture (replace) - if (mat.texture_handles[2] != 0) { - mat.metallic = sample_texture_array(2, int(mat.texture_handles[2]), texcoord).r; - } - - // Roughness texture (replace) - if (mat.texture_handles[3] != 0) { - mat.roughness = sample_texture_array(3, int(mat.texture_handles[3]), texcoord).r; - } - - // AO texture (store in material, apply during lighting) - if (mat.texture_handles[4] != 0) { - mat.ao = sample_texture_array(4, int(mat.texture_handles[4]), texcoord).r; - } - - // Emission texture (replace) - if (mat.texture_handles[5] != 0) { - mat.emission = sample_texture_array(5, int(mat.texture_handles[5]), texcoord).rgb; - } -} - -vec3 fresnel_schlick(float cos_theta, vec3 f0) { - return f0 + (1.0 - f0) * pow(1.0 - cos_theta, 5.0); -} - -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); -} - -ScatterResult scatter_diffuse(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { - ScatterResult r; - r.scattered = true; - r.attenuation = mat.albedo; - - vec3 dir = hit.normal + random_unit_vector(seed); - if (near_zero(dir)) dir = hit.normal; - - r.scattered_ray.origin = hit.position + hit.normal * EPSILON; - r.scattered_ray.direction = normalize(dir); - return r; -} - -ScatterResult scatter_metal(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { - ScatterResult r; - - vec3 reflected = reflect_vector(normalize(ray_in.direction), hit.normal); - vec3 fuzz = mat.roughness * random_in_unit_sphere(seed); - vec3 dir = reflected + fuzz; - - 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; -} - -ScatterResult scatter_dielectric(Ray ray_in, HitInfo hit, Material mat, inout uint seed) { - ScatterResult r; - r.scattered = true; - r.attenuation = vec3(1.0); - - vec3 unit_dir = normalize(ray_in.direction); - float cos_theta = dot(-unit_dir, hit.normal); - float sin_theta = sqrt(max(0.0, 1.0 - cos_theta * cos_theta)); - - // Determine if ray is entering or exiting the material - // If dot(dir, normal) < 0, ray is entering (from air into material) - bool entering = cos_theta > 0.0; - - // eta: ratio of indices (etai/etat) - // Entering: eta = 1.0/ior (air to material) - // Exiting: eta = ior/1.0 (material to air) - float eta = entering ? (1.0 / mat.ior) : mat.ior; - - // Use correct normal for refraction calculation - // When exiting, we need to use -normal - vec3 normal = entering ? hit.normal : -hit.normal; - - // Check for total internal reflection - float sin_theta_t = eta * sin_theta; - bool total_internal_reflection = sin_theta_t >= 1.0; - - // Fresnel reflectance (Schlick approximation) - float f0 = pow((1.0 - mat.ior) / (1.0 + mat.ior), 2.0); - float f = f0 + (1.0 - f0) * pow(1.0 - abs(cos_theta), 5.0); - - vec3 dir; - if (total_internal_reflection || random_float(seed) < f) { - // Reflect - dir = reflect_vector(unit_dir, normal); - } else { - // Refract - dir = refract_vector(unit_dir, normal, eta); - } - - r.scattered_ray.origin = hit.position + dir * EPSILON; - r.scattered_ray.direction = normalize(dir); - return r; -} - -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); - 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; -} - -// ============================================================================ -// Direct lighting (with shadow ray) -// ============================================================================ - -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; - Light light = lights[light_idx]; - - vec3 L; - float dist = MAX_FLOAT; - vec3 radiance = vec3(0.0); - - if (light.type == LIGHT_POINT) { - vec3 to_light = light.position - hit.position; - dist = length(to_light); - if (dist > light.range) return vec3(0.0); - L = to_light / dist; - - float atten = 1.0 / max(dist * dist, 0.01); - radiance = light.color * light.intensity * atten; - } else if (light.type == LIGHT_DIRECTIONAL) { - L = normalize(-light.direction); - radiance = light.color * light.intensity; - } else { - return vec3(0.0); - } - - float n_dot_l = max(dot(hit.normal, L), 0.0); - if (n_dot_l <= 0.0) return vec3(0.0); - - 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); - - float pdf_light = 1.0 / float(u_light_count); - vec3 brdf = mat.albedo * INV_PI; - // Apply AO to the final lighting - return brdf * radiance * n_dot_l * mat.ao / 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; - m.ao = 1.0; // default: no AO - return m; -} - -vec3 environment_color(vec3 dir) { - return vec3(0.1, 0.1, 0.15); -} - -/* - * @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(pixel_coords, image_size); - - vec3 radiance = vec3(0.0); - vec3 throughput = vec3(1.0); - - // Depth 0: try G-Buffer hit first - HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords); - if (hit0.hit) { - Material mat0 = fetch_material(hit0.material_id); - - // Override material type from G-Buffer if available - if (hit0.material_type >= 0) { - mat0.type = hit0.material_type; - } - - // Apply PBR textures (use tangent from G-Buffer if available) - apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent); - - radiance += throughput * mat0.emission; - - ScatterResult sc0 = scatter_ray(ray, hit0, mat0, seed); - if (!sc0.scattered) return radiance; - - throughput *= sc0.attenuation; - ray = sc0.scattered_ray; - } - - // Subsequent bounces: BVH - for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) { - HitInfo hit = trace_ray_bvh(ray); - if (!hit.hit) { - radiance += throughput * environment_color(ray.direction); - break; - } - - Material mat = fetch_material(hit.material_id); - - // Apply PBR textures (use tangent from intersection) - apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent); - - radiance += throughput * mat.emission; - - ScatterResult sc = scatter_ray(ray, hit, mat, seed); - if (!sc.scattered) break; - - throughput *= sc.attenuation; - - if (depth > 3u) { - 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; - } - - ray = sc.scattered_ray; - - if (all(lessThan(throughput, vec3(EPSILON)))) break; - } - - return radiance; -} - -// ACES Filmic Tone Mapping -vec3 aces_tonemap(vec3 x) { - float a = 2.51; - float b = 0.03; - float c = 2.43; - float d = 0.59; - float e = 0.14; - return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); -} - -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; - - 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); - uint spp = max(u_samples_per_pixel, 1u); - - for (uint s = 0u; s < spp; ++s) { - color += trace_path_primary_gbuffer(pixel_coords, image_size, seed); - } - color /= float(spp); - - color = clamp(color, vec3(0.0), vec3(100.0)); - - // Store HDR color to accumulation buffer BEFORE tone mapping - vec3 accumulation_color = color; - - if (u_enable_accumulation && u_frame_count > 0u) { - vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; - float w = 1.0 / float(u_frame_count + 1u); - accumulation_color = mix(accumulated, color, w); - } - - // Apply ACES tone mapping to output (not accumulation) - vec3 output_color = aces_tonemap(accumulation_color); - - imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0)); - imageStore(output_image, pixel_coords, vec4(output_color, 1.0)); -} diff --git a/shaders/raytracing/raytracing.comp b/shaders/raytracing/raytracing.comp new file mode 100644 index 0000000..57c52c4 --- /dev/null +++ b/shaders/raytracing/raytracing.comp @@ -0,0 +1,171 @@ +#version 430 core + +// Include shared modules +#include "../include/common.glsl" +#include "../include/structs.glsl" +#include "../include/math.glsl" +#include "../include/rng.glsl" +#include "../include/sampling.glsl" + +// Workgroup size +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 = 5, rgba32f) uniform readonly image2D g_material; +layout(binding = 6, r32ui) uniform readonly uimage2D g_material_id; +layout(binding = 7, rgba32f) uniform readonly image2D g_texcoord; +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; + +// SSBO bindings +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[]; }; + +// Uniforms +uniform uint u_frame_count; +uniform uint u_samples_per_pixel; +uniform uint u_max_depth; +uniform uint u_light_count; +uniform mat4 u_inv_view_projection; +uniform bool u_enable_accumulation; +uniform bool u_use_bvh; +uniform uint u_bvh_node_count; +uniform bool u_enable_textures; + +// Texture arrays +layout(binding = 10) uniform sampler2DArray u_texture_albedo_array; +layout(binding = 11) uniform sampler2DArray u_texture_normal_array; +layout(binding = 12) uniform sampler2DArray u_texture_metallic_array; +layout(binding = 13) uniform sampler2DArray u_texture_roughness_array; +layout(binding = 14) uniform sampler2DArray u_texture_ao_array; +layout(binding = 15) uniform sampler2DArray u_texture_emission_array; + +// Include material, BVH, and lighting modules (needs uniform declarations above) +#include "../include/material.glsl" +#include "../include/bvh.glsl" +#include "../include/lighting.glsl" + +// Generate camera ray (center pixel, no jitter) +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); + 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; +} + +// Path tracing with G-Buffer acceleration for primary ray +vec3 trace_path_primary_gbuffer(ivec2 pixel_coords, ivec2 image_size, inout uint seed) { + Ray ray = generate_camera_ray(pixel_coords, image_size); + + vec3 radiance = vec3(0.0); + vec3 throughput = vec3(1.0); + + // Depth 0: try G-Buffer hit first + HitInfo hit0 = trace_primary_gbuffer(ray, pixel_coords); + if (hit0.hit) { + Material mat0 = fetch_material(hit0.material_id); + + if (hit0.material_type >= 0) { + mat0.type = hit0.material_type; + } + + apply_material_textures(mat0, hit0.normal, hit0.texcoord, hit0.tangent); + + radiance += throughput * mat0.emission; + + ScatterResult sc0 = scatter_ray(ray, hit0, mat0, seed); + if (!sc0.scattered) return radiance; + + throughput *= sc0.attenuation; + ray = sc0.scattered_ray; + } + + // Subsequent bounces: BVH + for (uint depth = (hit0.hit ? 1u : 0u); depth < u_max_depth; ++depth) { + HitInfo hit = trace_ray_bvh(ray); + if (!hit.hit) { + radiance += throughput * environment_color(ray.direction); + break; + } + + Material mat = fetch_material(hit.material_id); + apply_material_textures(mat, hit.normal, hit.texcoord, hit.tangent); + + radiance += throughput * mat.emission; + + ScatterResult sc = scatter_ray(ray, hit, mat, seed); + if (!sc.scattered) break; + + throughput *= sc.attenuation; + + if (depth > 3u) { + 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; + } + + ray = sc.scattered_ray; + + if (all(lessThan(throughput, vec3(EPSILON)))) break; + } + + return radiance; +} + +// ACES Filmic Tone Mapping +vec3 aces_tonemap(vec3 x) { + float a = 2.51; + float b = 0.03; + float c = 2.43; + float d = 0.59; + float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + +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; + + 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); + uint spp = max(u_samples_per_pixel, 1u); + + for (uint s = 0u; s < spp; ++s) { + color += trace_path_primary_gbuffer(pixel_coords, image_size, seed); + } + color /= float(spp); + + color = clamp(color, vec3(0.0), vec3(100.0)); + + vec3 accumulation_color = color; + + if (u_enable_accumulation && u_frame_count > 0u) { + vec3 accumulated = imageLoad(accumulation_image, pixel_coords).rgb; + float w = 1.0 / float(u_frame_count + 1u); + accumulation_color = mix(accumulated, color, w); + } + + vec3 output_color = aces_tonemap(accumulation_color); + + imageStore(accumulation_image, pixel_coords, vec4(accumulation_color, 1.0)); + imageStore(output_image, pixel_coords, vec4(output_color, 1.0)); +} diff --git a/src/core/shader_manager.cpp b/src/core/shader_manager.cpp index f22fa51..976d334 100644 --- a/src/core/shader_manager.cpp +++ b/src/core/shader_manager.cpp @@ -4,133 +4,135 @@ namespace are { ShaderManager::ShaderManager() - : initialized_(false) { + : initialized_(false) { } ShaderManager::~ShaderManager() { - release(); + release(); } bool ShaderManager::initialize() { - if (initialized_) { - ARE_LOG_WARN("ShaderManager already initialized"); - return true; - } - - ARE_LOG_INFO("Loading built-in shaders..."); - - if (!load_builtin_shaders_()) { - ARE_LOG_ERROR("Failed to load built-in shaders"); - return false; - } - - initialized_ = true; - ARE_LOG_INFO("ShaderManager initialized successfully"); - return true; + if (initialized_) { + ARE_LOG_WARN("ShaderManager already initialized"); + return true; + } + + ARE_LOG_INFO("Loading built-in shaders..."); + + if (!load_builtin_shaders_()) { + ARE_LOG_ERROR("Failed to load built-in shaders"); + return false; + } + + initialized_ = true; + ARE_LOG_INFO("ShaderManager initialized successfully"); + return true; } void ShaderManager::release() { - if (!initialized_) return; + if (!initialized_) + return; - shader_cache_.clear(); + shader_cache_.clear(); - gbuffer_shader_.reset(); + gbuffer_shader_.reset(); screen_blit_shader_.reset(); - raytracing_shader_.reset(); + raytracing_shader_.reset(); denoise_shader_.reset(); - initialized_ = false; - ARE_LOG_INFO("ShaderManager released"); + initialized_ = false; + ARE_LOG_INFO("ShaderManager released"); } -std::shared_ptr ShaderManager::load_shader(const std::string& name, - const std::string& vertex_path, - const std::string& fragment_path) { - auto it = shader_cache_.find(name); - if (it != shader_cache_.end()) { - ARE_LOG_INFO("Shader '" + name + "' loaded from cache"); - return it->second; - } +std::shared_ptr ShaderManager::load_shader(const std::string &name, + const std::string &vertex_path, + const std::string &fragment_path) { + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) { + ARE_LOG_INFO("Shader '" + name + "' loaded from cache"); + return it->second; + } - auto shader = std::make_shared(); - if (!shader->load(vertex_path, fragment_path)) { - ARE_LOG_ERROR("Failed to load shader '" + name + "'"); - return nullptr; - } + auto shader = std::make_shared(); + if (!shader->load(vertex_path, fragment_path)) { + ARE_LOG_ERROR("Failed to load shader '" + name + "'"); + return nullptr; + } - shader_cache_[name] = shader; - ARE_LOG_INFO("Shader '" + name + "' loaded successfully"); - return shader; + shader_cache_[name] = shader; + ARE_LOG_INFO("Shader '" + name + "' loaded successfully"); + return shader; } -std::shared_ptr ShaderManager::load_compute_shader(const std::string& name, - const std::string& compute_path) { - auto it = shader_cache_.find(name); - if (it != shader_cache_.end()) { - ARE_LOG_INFO("Compute shader '" + name + "' loaded from cache"); - return it->second; - } +std::shared_ptr ShaderManager::load_compute_shader(const std::string &name, + const std::string &compute_path) { + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) { + ARE_LOG_INFO("Compute shader '" + name + "' loaded from cache"); + return it->second; + } - auto shader = std::make_shared(); - if (!shader->load_compute(compute_path)) { - ARE_LOG_ERROR("Failed to load compute shader '" + name + "'"); - return nullptr; - } + auto shader = std::make_shared(); + if (!shader->load_compute(compute_path)) { + ARE_LOG_ERROR("Failed to load compute shader '" + name + "'"); + return nullptr; + } - shader_cache_[name] = shader; - ARE_LOG_INFO("Compute shader '" + name + "' loaded successfully"); - return shader; + shader_cache_[name] = shader; + ARE_LOG_INFO("Compute shader '" + name + "' loaded successfully"); + return shader; } -std::shared_ptr ShaderManager::get_shader(const std::string& name) const { - auto it = shader_cache_.find(name); - if (it != shader_cache_.end()) return it->second; +std::shared_ptr ShaderManager::get_shader(const std::string &name) const { + auto it = shader_cache_.find(name); + if (it != shader_cache_.end()) + return it->second; - ARE_LOG_WARN("Shader '" + name + "' not found in cache"); - return nullptr; + ARE_LOG_WARN("Shader '" + name + "' not found in cache"); + return nullptr; } bool ShaderManager::load_builtin_shaders_() { // Load G-buffer shader ARE_LOG_INFO("Loading G-buffer shaders.."); - gbuffer_shader_ = std::make_shared(); - if (!gbuffer_shader_->load("shaders/gbuffer.vert", "shaders/gbuffer.frag")) { - ARE_LOG_ERROR("Failed to load G-Buffer shader"); - return false; - } - shader_cache_["gbuffer"] = gbuffer_shader_; + gbuffer_shader_ = std::make_shared(); + if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) { + ARE_LOG_ERROR("Failed to load G-Buffer shader"); + return false; + } + shader_cache_["gbuffer"] = gbuffer_shader_; // Load screen bliting shader ARE_LOG_INFO("Loading screen blit shaders..."); - screen_blit_shader_ = std::make_shared(); - if (!screen_blit_shader_->load("shaders/screen_blit.vert", "shaders/screen_blit.frag")) { - ARE_LOG_ERROR("Failed to load screen blit shader"); - return false; - } - shader_cache_["screen_blit"] = screen_blit_shader_; - ARE_LOG_INFO("Screen blit shader loaded successfully"); + screen_blit_shader_ = std::make_shared(); + if (!screen_blit_shader_->load("shaders/postprocess/screen_blit.vert", "shaders/postprocess/screen_blit.frag")) { + ARE_LOG_ERROR("Failed to load screen blit shader"); + return false; + } + shader_cache_["screen_blit"] = screen_blit_shader_; + ARE_LOG_INFO("Screen blit shader loaded successfully"); // Load ray tracing shader - ARE_LOG_INFO("Loading ray tracing compute shader..."); - raytracing_shader_ = std::make_shared(); - if (!raytracing_shader_->load_compute("shaders/raytracing.comp")) { - ARE_LOG_ERROR("Failed to load ray tracing shader"); - return false; - } - shader_cache_["raytracing"] = raytracing_shader_; - ARE_LOG_INFO("Ray tracing shader loaded successfully"); + ARE_LOG_INFO("Loading ray tracing compute shader..."); + raytracing_shader_ = std::make_shared(); + if (!raytracing_shader_->load_compute("shaders/raytracing/raytracing.comp")) { + ARE_LOG_ERROR("Failed to load ray tracing shader"); + return false; + } + shader_cache_["raytracing"] = raytracing_shader_; + ARE_LOG_INFO("Ray tracing shader loaded successfully"); // Load denoising shader ARE_LOG_INFO("Loading denoise compute shader..."); - denoise_shader_ = std::make_shared(); - if (!denoise_shader_->load_compute("shaders/denoiser.comp")) { - ARE_LOG_ERROR("Failed to load denoise shader"); - return false; - } - shader_cache_["denoise"] = denoise_shader_; - ARE_LOG_INFO("Denoise shader loaded successfully"); + denoise_shader_ = std::make_shared(); + if (!denoise_shader_->load_compute("shaders/postprocess/denoiser.comp")) { + ARE_LOG_ERROR("Failed to load denoise shader"); + return false; + } + shader_cache_["denoise"] = denoise_shader_; + ARE_LOG_INFO("Denoise shader loaded successfully"); - return true; + return true; } } // namespace are diff --git a/src/resource/shader.cpp b/src/resource/shader.cpp index 6928c54..9f56052 100644 --- a/src/resource/shader.cpp +++ b/src/resource/shader.cpp @@ -1,216 +1,275 @@ #include "resource/shader.h" -#include "utils/logger.h" #include "basic/math.h" -#include +#include "utils/logger.h" #include +#include #include namespace are { Shader::Shader() - : handle_(INVALID_HANDLE) { + : handle_(INVALID_HANDLE) { } -Shader::Shader(Shader&& other) noexcept - : handle_(other.handle_) - , uniform_cache_(std::move(other.uniform_cache_)) { - other.handle_ = INVALID_HANDLE; - other.uniform_cache_.clear(); +Shader::Shader(Shader &&other) noexcept + : handle_(other.handle_) + , uniform_cache_(std::move(other.uniform_cache_)) { + other.handle_ = INVALID_HANDLE; + other.uniform_cache_.clear(); } Shader::~Shader() { - release(); + release(); } -Shader& Shader::operator=(Shader&& other) noexcept { - if (this == &other) return *this; +Shader &Shader::operator=(Shader &&other) noexcept { + if (this == &other) + return *this; - release(); - handle_ = other.handle_; - uniform_cache_ = std::move(other.uniform_cache_); + release(); + handle_ = other.handle_; + uniform_cache_ = std::move(other.uniform_cache_); - other.handle_ = INVALID_HANDLE; - other.uniform_cache_.clear(); - return *this; + other.handle_ = INVALID_HANDLE; + other.uniform_cache_.clear(); + return *this; } -bool Shader::load(const std::string& vertex_path, const std::string& fragment_path) { - std::string vertex_source = read_file_(vertex_path); - std::string fragment_source = read_file_(fragment_path); - - if (vertex_source.empty() || fragment_source.empty()) { - ARE_LOG_ERROR("Failed to read shader files"); - return false; - } - - return compile(vertex_source, fragment_source); +bool Shader::load(const std::string &vertex_path, const std::string &fragment_path) { + std::string vertex_source = read_file_(vertex_path); + std::string fragment_source = read_file_(fragment_path); + + if (vertex_source.empty() || fragment_source.empty()) { + ARE_LOG_ERROR("Failed to read shader files"); + return false; + } + + // Process #include directives + std::string vertex_dir = vertex_path.substr(0, vertex_path.find_last_of("/\\")); + std::string fragment_dir = fragment_path.substr(0, fragment_path.find_last_of("/\\")); + vertex_source = process_includes_(vertex_source, vertex_dir); + fragment_source = process_includes_(fragment_source, fragment_dir); + + return compile(vertex_source, fragment_source); } -bool Shader::load_compute(const std::string& compute_path) { - std::string compute_source = read_file_(compute_path); - - if (compute_source.empty()) { - ARE_LOG_ERROR("Failed to read compute shader file"); - return false; - } - - return compile_compute(compute_source); +bool Shader::load_compute(const std::string &compute_path) { + std::string compute_source = read_file_(compute_path); + + if (compute_source.empty()) { + ARE_LOG_ERROR("Failed to read compute shader file"); + return false; + } + + // Process #include directives + std::string compute_dir = compute_path.substr(0, compute_path.find_last_of("/\\")); + compute_source = process_includes_(compute_source, compute_dir); + + return compile_compute(compute_source); } -bool Shader::compile(const std::string& vertex_source, const std::string& fragment_source) { - uint vertex_shader = compile_shader_(vertex_source, GL_VERTEX_SHADER); - if (vertex_shader == 0) return false; - - uint fragment_shader = compile_shader_(fragment_source, GL_FRAGMENT_SHADER); - if (fragment_shader == 0) { - glDeleteShader(vertex_shader); - return false; - } - - uint shaders[] = { vertex_shader, fragment_shader }; - bool success = link_program_(shaders, 2); - - glDeleteShader(vertex_shader); - glDeleteShader(fragment_shader); - - return success; +bool Shader::compile(const std::string &vertex_source, const std::string &fragment_source) { + uint vertex_shader = compile_shader_(vertex_source, GL_VERTEX_SHADER); + if (vertex_shader == 0) + return false; + + uint fragment_shader = compile_shader_(fragment_source, GL_FRAGMENT_SHADER); + if (fragment_shader == 0) { + glDeleteShader(vertex_shader); + return false; + } + + uint shaders[] = { vertex_shader, fragment_shader }; + bool success = link_program_(shaders, 2); + + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return success; } -bool Shader::compile_compute(const std::string& compute_source) { - uint compute_shader = compile_shader_(compute_source, GL_COMPUTE_SHADER); - if (compute_shader == 0) return false; - - uint shaders[] = { compute_shader }; - bool success = link_program_(shaders, 1); - - glDeleteShader(compute_shader); - - return success; +bool Shader::compile_compute(const std::string &compute_source) { + uint compute_shader = compile_shader_(compute_source, GL_COMPUTE_SHADER); + if (compute_shader == 0) + return false; + + uint shaders[] = { compute_shader }; + bool success = link_program_(shaders, 1); + + glDeleteShader(compute_shader); + + return success; } void Shader::use() const { - if (handle_ != INVALID_HANDLE) { - glUseProgram(handle_); - } + if (handle_ != INVALID_HANDLE) { + glUseProgram(handle_); + } } void Shader::release() { - if (handle_ != INVALID_HANDLE) { - glDeleteProgram(handle_); - handle_ = INVALID_HANDLE; - } - uniform_cache_.clear(); + if (handle_ != INVALID_HANDLE) { + glDeleteProgram(handle_); + handle_ = INVALID_HANDLE; + } + uniform_cache_.clear(); } -void Shader::set_bool(const std::string& name, bool value) const { - glUniform1i(get_uniform_location_(name), static_cast(value)); +void Shader::set_bool(const std::string &name, bool value) const { + glUniform1i(get_uniform_location_(name), static_cast(value)); } -void Shader::set_int(const std::string& name, int value) const { - glUniform1i(get_uniform_location_(name), value); +void Shader::set_int(const std::string &name, int value) const { + glUniform1i(get_uniform_location_(name), value); } -void Shader::set_uint(const std::string& name, uint value) const { - glUniform1ui(get_uniform_location_(name), value); +void Shader::set_uint(const std::string &name, uint value) const { + glUniform1ui(get_uniform_location_(name), value); } -void Shader::set_float(const std::string& name, float value) const { - glUniform1f(get_uniform_location_(name), value); +void Shader::set_float(const std::string &name, float value) const { + glUniform1f(get_uniform_location_(name), value); } -void Shader::set_vec2(const std::string& name, const Vec2& value) const { - glUniform2fv(get_uniform_location_(name), 1, &value[0]); +void Shader::set_vec2(const std::string &name, const Vec2 &value) const { + glUniform2fv(get_uniform_location_(name), 1, &value[0]); } -void Shader::set_vec3(const std::string& name, const Vec3& value) const { - glUniform3fv(get_uniform_location_(name), 1, &value[0]); +void Shader::set_vec3(const std::string &name, const Vec3 &value) const { + glUniform3fv(get_uniform_location_(name), 1, &value[0]); } -void Shader::set_vec4(const std::string& name, const Vec4& value) const { - glUniform4fv(get_uniform_location_(name), 1, &value[0]); +void Shader::set_vec4(const std::string &name, const Vec4 &value) const { + glUniform4fv(get_uniform_location_(name), 1, &value[0]); } -void Shader::set_mat3(const std::string& name, const Mat3& value) const { - glUniformMatrix3fv(get_uniform_location_(name), 1, GL_FALSE, &value[0][0]); +void Shader::set_mat3(const std::string &name, const Mat3 &value) const { + glUniformMatrix3fv(get_uniform_location_(name), 1, GL_FALSE, &value[0][0]); } -void Shader::set_mat4(const std::string& name, const Mat4& value) const { - glUniformMatrix4fv(get_uniform_location_(name), 1, GL_FALSE, MathUtils::value_ptr(value)); +void Shader::set_mat4(const std::string &name, const Mat4 &value) const { + glUniformMatrix4fv(get_uniform_location_(name), 1, GL_FALSE, MathUtils::value_ptr(value)); } -int Shader::get_uniform_location_(const std::string& name) const { - auto it = uniform_cache_.find(name); - if (it != uniform_cache_.end()) { - return it->second; - } - - int location = glGetUniformLocation(handle_, name.c_str()); - uniform_cache_[name] = location; - - if (location == -1) { - ARE_LOG_WARN("Uniform '" + name + "' not found in shader"); - } - - return location; +int Shader::get_uniform_location_(const std::string &name) const { + auto it = uniform_cache_.find(name); + if (it != uniform_cache_.end()) { + return it->second; + } + + int location = glGetUniformLocation(handle_, name.c_str()); + uniform_cache_[name] = location; + + if (location == -1) { + ARE_LOG_WARN("Uniform '" + name + "' not found in shader"); + } + + return location; } -uint Shader::compile_shader_(const std::string& source, uint type) { - uint shader = glCreateShader(type); - const char* source_cstr = source.c_str(); - glShaderSource(shader, 1, &source_cstr, nullptr); - glCompileShader(shader); - - int success; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { - char info_log[512]; - glGetShaderInfoLog(shader, 512, nullptr, info_log); - - std::string type_str = (type == GL_VERTEX_SHADER) ? "VERTEX" : - (type == GL_FRAGMENT_SHADER) ? "FRAGMENT" : "COMPUTE"; - ARE_LOG_ERROR("Shader compilation failed (" + type_str + "): " + std::string(info_log)); - - glDeleteShader(shader); - return 0; - } - - return shader; +uint Shader::compile_shader_(const std::string &source, uint type) { + uint shader = glCreateShader(type); + const char *source_cstr = source.c_str(); + glShaderSource(shader, 1, &source_cstr, nullptr); + glCompileShader(shader); + + int success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char info_log[512]; + glGetShaderInfoLog(shader, 512, nullptr, info_log); + + std::string type_str = (type == GL_VERTEX_SHADER) ? "VERTEX" : (type == GL_FRAGMENT_SHADER) ? "FRAGMENT" + : "COMPUTE"; + ARE_LOG_ERROR("Shader compilation failed (" + type_str + "): " + std::string(info_log)); + + glDeleteShader(shader); + return 0; + } + + return shader; } -bool Shader::link_program_(const uint* shaders, uint count) { - handle_ = glCreateProgram(); - - for (uint i = 0; i < count; ++i) { - glAttachShader(handle_, shaders[i]); - } - - glLinkProgram(handle_); - - int success; - glGetProgramiv(handle_, GL_LINK_STATUS, &success); - if (!success) { - char info_log[512]; - glGetProgramInfoLog(handle_, 512, nullptr, info_log); - ARE_LOG_ERROR("Shader linking failed: " + std::string(info_log)); - - glDeleteProgram(handle_); - handle_ = INVALID_HANDLE; - return false; - } - - return true; +bool Shader::link_program_(const uint *shaders, uint count) { + handle_ = glCreateProgram(); + + for (uint i = 0; i < count; ++i) { + glAttachShader(handle_, shaders[i]); + } + + glLinkProgram(handle_); + + int success; + glGetProgramiv(handle_, GL_LINK_STATUS, &success); + if (!success) { + char info_log[512]; + glGetProgramInfoLog(handle_, 512, nullptr, info_log); + ARE_LOG_ERROR("Shader linking failed: " + std::string(info_log)); + + glDeleteProgram(handle_); + handle_ = INVALID_HANDLE; + return false; + } + + return true; } -std::string Shader::read_file_(const std::string& path) { - std::ifstream file(path); - if (!file.is_open()) { - ARE_LOG_ERROR("Failed to open file: " + path); - return ""; - } - - std::stringstream buffer; - buffer << file.rdbuf(); - return buffer.str(); +std::string Shader::read_file_(const std::string &path) { + std::ifstream file(path); + if (!file.is_open()) { + ARE_LOG_ERROR("Failed to open file: " + path); + return ""; + } + + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +std::string Shader::process_includes_(const std::string &source, const std::string &base_dir) { + std::string result; + std::istringstream stream(source); + std::string line; + + while (std::getline(stream, line)) { + // Check if line starts with #include + std::string trimmed = line; + // Trim leading whitespace + size_t start = trimmed.find_first_not_of(" \t"); + if (start != std::string::npos) { + trimmed = trimmed.substr(start); + } + + if (trimmed.find("#include") == 0) { + // Extract path: #include "path" or #include + size_t first_quote = line.find('"'); + size_t last_quote = line.rfind('"'); + + if (first_quote != std::string::npos && last_quote != std::string::npos && first_quote != last_quote) { + std::string include_path = line.substr(first_quote + 1, last_quote - first_quote - 1); + std::string full_path = base_dir + "/" + include_path; + + // Read included file + std::string included_content = read_file_(full_path); + if (!included_content.empty()) { + // Get directory of included file for nested includes + std::string included_dir = full_path.substr(0, full_path.find_last_of("/\\")); + + // Recursively process includes + result += process_includes_(included_content, included_dir) + "\n"; + } else { + ARE_LOG_WARN("Include file not found or empty: " + full_path); + } + } else { + // Invalid include syntax, keep original line + result += line + "\n"; + } + } else { + result += line + "\n"; + } + } + + return result; } } // namespace are