refactor: 重构shaders目录结构,手动编写include处理逻辑
parent
d7f5c7e5cc
commit
58d6184085
|
|
@ -1,7 +1,7 @@
|
||||||
* 1. 修复全局光照导致的采样问题
|
* 1. 修复全局光照导致的采样问题
|
||||||
* 2. 重构代码,统一化结构、加入ResourceManager
|
* 2. 重构代码,统一化结构、加入ResourceManager
|
||||||
* 2.1. 优化cpu-gpu交互效率
|
* 2.1. 优化cpu-gpu交互效率
|
||||||
2.2 修改shader文件结构
|
* 2.2 修改shader文件结构
|
||||||
3. 支持更多材质及pbr参数
|
3. 支持更多材质及pbr参数
|
||||||
4. 添加HDRI支持
|
4. 添加HDRI支持
|
||||||
5. 支持glTF 2.0模型加载
|
5. 支持glTF 2.0模型加载
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -170,6 +170,14 @@ private:
|
||||||
* @return File content
|
* @return File content
|
||||||
*/
|
*/
|
||||||
std::string read_file_(const std::string &path);
|
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
|
} // namespace are
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,8 @@ bool ShaderManager::initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderManager::release() {
|
void ShaderManager::release() {
|
||||||
if (!initialized_) return;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
shader_cache_.clear();
|
shader_cache_.clear();
|
||||||
|
|
||||||
|
|
@ -43,9 +44,9 @@ void ShaderManager::release() {
|
||||||
ARE_LOG_INFO("ShaderManager released");
|
ARE_LOG_INFO("ShaderManager released");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Shader> ShaderManager::load_shader(const std::string& name,
|
std::shared_ptr<Shader> ShaderManager::load_shader(const std::string &name,
|
||||||
const std::string& vertex_path,
|
const std::string &vertex_path,
|
||||||
const std::string& fragment_path) {
|
const std::string &fragment_path) {
|
||||||
auto it = shader_cache_.find(name);
|
auto it = shader_cache_.find(name);
|
||||||
if (it != shader_cache_.end()) {
|
if (it != shader_cache_.end()) {
|
||||||
ARE_LOG_INFO("Shader '" + name + "' loaded from cache");
|
ARE_LOG_INFO("Shader '" + name + "' loaded from cache");
|
||||||
|
|
@ -63,8 +64,8 @@ std::shared_ptr<Shader> ShaderManager::load_shader(const std::string& name,
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Shader> ShaderManager::load_compute_shader(const std::string& name,
|
std::shared_ptr<Shader> ShaderManager::load_compute_shader(const std::string &name,
|
||||||
const std::string& compute_path) {
|
const std::string &compute_path) {
|
||||||
auto it = shader_cache_.find(name);
|
auto it = shader_cache_.find(name);
|
||||||
if (it != shader_cache_.end()) {
|
if (it != shader_cache_.end()) {
|
||||||
ARE_LOG_INFO("Compute shader '" + name + "' loaded from cache");
|
ARE_LOG_INFO("Compute shader '" + name + "' loaded from cache");
|
||||||
|
|
@ -82,9 +83,10 @@ std::shared_ptr<Shader> ShaderManager::load_compute_shader(const std::string& na
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Shader> ShaderManager::get_shader(const std::string& name) const {
|
std::shared_ptr<Shader> ShaderManager::get_shader(const std::string &name) const {
|
||||||
auto it = shader_cache_.find(name);
|
auto it = shader_cache_.find(name);
|
||||||
if (it != shader_cache_.end()) return it->second;
|
if (it != shader_cache_.end())
|
||||||
|
return it->second;
|
||||||
|
|
||||||
ARE_LOG_WARN("Shader '" + name + "' not found in cache");
|
ARE_LOG_WARN("Shader '" + name + "' not found in cache");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -94,7 +96,7 @@ bool ShaderManager::load_builtin_shaders_() {
|
||||||
// Load G-buffer shader
|
// Load G-buffer shader
|
||||||
ARE_LOG_INFO("Loading G-buffer shaders..");
|
ARE_LOG_INFO("Loading G-buffer shaders..");
|
||||||
gbuffer_shader_ = std::make_shared<Shader>();
|
gbuffer_shader_ = std::make_shared<Shader>();
|
||||||
if (!gbuffer_shader_->load("shaders/gbuffer.vert", "shaders/gbuffer.frag")) {
|
if (!gbuffer_shader_->load("shaders/gbuffer/gbuffer.vert", "shaders/gbuffer/gbuffer.frag")) {
|
||||||
ARE_LOG_ERROR("Failed to load G-Buffer shader");
|
ARE_LOG_ERROR("Failed to load G-Buffer shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +105,7 @@ bool ShaderManager::load_builtin_shaders_() {
|
||||||
// Load screen bliting shader
|
// Load screen bliting shader
|
||||||
ARE_LOG_INFO("Loading screen blit shaders...");
|
ARE_LOG_INFO("Loading screen blit shaders...");
|
||||||
screen_blit_shader_ = std::make_shared<Shader>();
|
screen_blit_shader_ = std::make_shared<Shader>();
|
||||||
if (!screen_blit_shader_->load("shaders/screen_blit.vert", "shaders/screen_blit.frag")) {
|
if (!screen_blit_shader_->load("shaders/postprocess/screen_blit.vert", "shaders/postprocess/screen_blit.frag")) {
|
||||||
ARE_LOG_ERROR("Failed to load screen blit shader");
|
ARE_LOG_ERROR("Failed to load screen blit shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +115,7 @@ bool ShaderManager::load_builtin_shaders_() {
|
||||||
// Load ray tracing shader
|
// Load ray tracing shader
|
||||||
ARE_LOG_INFO("Loading ray tracing compute shader...");
|
ARE_LOG_INFO("Loading ray tracing compute shader...");
|
||||||
raytracing_shader_ = std::make_shared<Shader>();
|
raytracing_shader_ = std::make_shared<Shader>();
|
||||||
if (!raytracing_shader_->load_compute("shaders/raytracing.comp")) {
|
if (!raytracing_shader_->load_compute("shaders/raytracing/raytracing.comp")) {
|
||||||
ARE_LOG_ERROR("Failed to load ray tracing shader");
|
ARE_LOG_ERROR("Failed to load ray tracing shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +125,7 @@ bool ShaderManager::load_builtin_shaders_() {
|
||||||
// Load denoising shader
|
// Load denoising shader
|
||||||
ARE_LOG_INFO("Loading denoise compute shader...");
|
ARE_LOG_INFO("Loading denoise compute shader...");
|
||||||
denoise_shader_ = std::make_shared<Shader>();
|
denoise_shader_ = std::make_shared<Shader>();
|
||||||
if (!denoise_shader_->load_compute("shaders/denoiser.comp")) {
|
if (!denoise_shader_->load_compute("shaders/postprocess/denoiser.comp")) {
|
||||||
ARE_LOG_ERROR("Failed to load denoise shader");
|
ARE_LOG_ERROR("Failed to load denoise shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#include "resource/shader.h"
|
#include "resource/shader.h"
|
||||||
#include "utils/logger.h"
|
|
||||||
#include "basic/math.h"
|
#include "basic/math.h"
|
||||||
#include <glad/glad.h>
|
#include "utils/logger.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <glad/glad.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace are {
|
namespace are {
|
||||||
|
|
@ -11,7 +11,7 @@ Shader::Shader()
|
||||||
: handle_(INVALID_HANDLE) {
|
: handle_(INVALID_HANDLE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader::Shader(Shader&& other) noexcept
|
Shader::Shader(Shader &&other) noexcept
|
||||||
: handle_(other.handle_)
|
: handle_(other.handle_)
|
||||||
, uniform_cache_(std::move(other.uniform_cache_)) {
|
, uniform_cache_(std::move(other.uniform_cache_)) {
|
||||||
other.handle_ = INVALID_HANDLE;
|
other.handle_ = INVALID_HANDLE;
|
||||||
|
|
@ -22,8 +22,9 @@ Shader::~Shader() {
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader& Shader::operator=(Shader&& other) noexcept {
|
Shader &Shader::operator=(Shader &&other) noexcept {
|
||||||
if (this == &other) return *this;
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
|
||||||
release();
|
release();
|
||||||
handle_ = other.handle_;
|
handle_ = other.handle_;
|
||||||
|
|
@ -34,7 +35,7 @@ Shader& Shader::operator=(Shader&& other) noexcept {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::load(const std::string& vertex_path, const std::string& fragment_path) {
|
bool Shader::load(const std::string &vertex_path, const std::string &fragment_path) {
|
||||||
std::string vertex_source = read_file_(vertex_path);
|
std::string vertex_source = read_file_(vertex_path);
|
||||||
std::string fragment_source = read_file_(fragment_path);
|
std::string fragment_source = read_file_(fragment_path);
|
||||||
|
|
||||||
|
|
@ -43,10 +44,16 @@ bool Shader::load(const std::string& vertex_path, const std::string& fragment_pa
|
||||||
return false;
|
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);
|
return compile(vertex_source, fragment_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::load_compute(const std::string& compute_path) {
|
bool Shader::load_compute(const std::string &compute_path) {
|
||||||
std::string compute_source = read_file_(compute_path);
|
std::string compute_source = read_file_(compute_path);
|
||||||
|
|
||||||
if (compute_source.empty()) {
|
if (compute_source.empty()) {
|
||||||
|
|
@ -54,12 +61,17 @@ bool Shader::load_compute(const std::string& compute_path) {
|
||||||
return false;
|
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);
|
return compile_compute(compute_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::compile(const std::string& vertex_source, const std::string& fragment_source) {
|
bool Shader::compile(const std::string &vertex_source, const std::string &fragment_source) {
|
||||||
uint vertex_shader = compile_shader_(vertex_source, GL_VERTEX_SHADER);
|
uint vertex_shader = compile_shader_(vertex_source, GL_VERTEX_SHADER);
|
||||||
if (vertex_shader == 0) return false;
|
if (vertex_shader == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
uint fragment_shader = compile_shader_(fragment_source, GL_FRAGMENT_SHADER);
|
uint fragment_shader = compile_shader_(fragment_source, GL_FRAGMENT_SHADER);
|
||||||
if (fragment_shader == 0) {
|
if (fragment_shader == 0) {
|
||||||
|
|
@ -76,9 +88,10 @@ bool Shader::compile(const std::string& vertex_source, const std::string& fragme
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::compile_compute(const std::string& compute_source) {
|
bool Shader::compile_compute(const std::string &compute_source) {
|
||||||
uint compute_shader = compile_shader_(compute_source, GL_COMPUTE_SHADER);
|
uint compute_shader = compile_shader_(compute_source, GL_COMPUTE_SHADER);
|
||||||
if (compute_shader == 0) return false;
|
if (compute_shader == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
uint shaders[] = { compute_shader };
|
uint shaders[] = { compute_shader };
|
||||||
bool success = link_program_(shaders, 1);
|
bool success = link_program_(shaders, 1);
|
||||||
|
|
@ -102,43 +115,43 @@ void Shader::release() {
|
||||||
uniform_cache_.clear();
|
uniform_cache_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_bool(const std::string& name, bool value) const {
|
void Shader::set_bool(const std::string &name, bool value) const {
|
||||||
glUniform1i(get_uniform_location_(name), static_cast<int>(value));
|
glUniform1i(get_uniform_location_(name), static_cast<int>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_int(const std::string& name, int value) const {
|
void Shader::set_int(const std::string &name, int value) const {
|
||||||
glUniform1i(get_uniform_location_(name), value);
|
glUniform1i(get_uniform_location_(name), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_uint(const std::string& name, uint value) const {
|
void Shader::set_uint(const std::string &name, uint value) const {
|
||||||
glUniform1ui(get_uniform_location_(name), value);
|
glUniform1ui(get_uniform_location_(name), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_float(const std::string& name, float value) const {
|
void Shader::set_float(const std::string &name, float value) const {
|
||||||
glUniform1f(get_uniform_location_(name), value);
|
glUniform1f(get_uniform_location_(name), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_vec2(const std::string& name, const Vec2& value) const {
|
void Shader::set_vec2(const std::string &name, const Vec2 &value) const {
|
||||||
glUniform2fv(get_uniform_location_(name), 1, &value[0]);
|
glUniform2fv(get_uniform_location_(name), 1, &value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_vec3(const std::string& name, const Vec3& value) const {
|
void Shader::set_vec3(const std::string &name, const Vec3 &value) const {
|
||||||
glUniform3fv(get_uniform_location_(name), 1, &value[0]);
|
glUniform3fv(get_uniform_location_(name), 1, &value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_vec4(const std::string& name, const Vec4& value) const {
|
void Shader::set_vec4(const std::string &name, const Vec4 &value) const {
|
||||||
glUniform4fv(get_uniform_location_(name), 1, &value[0]);
|
glUniform4fv(get_uniform_location_(name), 1, &value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_mat3(const std::string& name, const Mat3& value) const {
|
void Shader::set_mat3(const std::string &name, const Mat3 &value) const {
|
||||||
glUniformMatrix3fv(get_uniform_location_(name), 1, GL_FALSE, &value[0][0]);
|
glUniformMatrix3fv(get_uniform_location_(name), 1, GL_FALSE, &value[0][0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::set_mat4(const std::string& name, const Mat4& value) const {
|
void Shader::set_mat4(const std::string &name, const Mat4 &value) const {
|
||||||
glUniformMatrix4fv(get_uniform_location_(name), 1, GL_FALSE, MathUtils::value_ptr(value));
|
glUniformMatrix4fv(get_uniform_location_(name), 1, GL_FALSE, MathUtils::value_ptr(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
int Shader::get_uniform_location_(const std::string& name) const {
|
int Shader::get_uniform_location_(const std::string &name) const {
|
||||||
auto it = uniform_cache_.find(name);
|
auto it = uniform_cache_.find(name);
|
||||||
if (it != uniform_cache_.end()) {
|
if (it != uniform_cache_.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
@ -154,9 +167,9 @@ int Shader::get_uniform_location_(const std::string& name) const {
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint Shader::compile_shader_(const std::string& source, uint type) {
|
uint Shader::compile_shader_(const std::string &source, uint type) {
|
||||||
uint shader = glCreateShader(type);
|
uint shader = glCreateShader(type);
|
||||||
const char* source_cstr = source.c_str();
|
const char *source_cstr = source.c_str();
|
||||||
glShaderSource(shader, 1, &source_cstr, nullptr);
|
glShaderSource(shader, 1, &source_cstr, nullptr);
|
||||||
glCompileShader(shader);
|
glCompileShader(shader);
|
||||||
|
|
||||||
|
|
@ -166,8 +179,8 @@ uint Shader::compile_shader_(const std::string& source, uint type) {
|
||||||
char info_log[512];
|
char info_log[512];
|
||||||
glGetShaderInfoLog(shader, 512, nullptr, info_log);
|
glGetShaderInfoLog(shader, 512, nullptr, info_log);
|
||||||
|
|
||||||
std::string type_str = (type == GL_VERTEX_SHADER) ? "VERTEX" :
|
std::string type_str = (type == GL_VERTEX_SHADER) ? "VERTEX" : (type == GL_FRAGMENT_SHADER) ? "FRAGMENT"
|
||||||
(type == GL_FRAGMENT_SHADER) ? "FRAGMENT" : "COMPUTE";
|
: "COMPUTE";
|
||||||
ARE_LOG_ERROR("Shader compilation failed (" + type_str + "): " + std::string(info_log));
|
ARE_LOG_ERROR("Shader compilation failed (" + type_str + "): " + std::string(info_log));
|
||||||
|
|
||||||
glDeleteShader(shader);
|
glDeleteShader(shader);
|
||||||
|
|
@ -177,7 +190,7 @@ uint Shader::compile_shader_(const std::string& source, uint type) {
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::link_program_(const uint* shaders, uint count) {
|
bool Shader::link_program_(const uint *shaders, uint count) {
|
||||||
handle_ = glCreateProgram();
|
handle_ = glCreateProgram();
|
||||||
|
|
||||||
for (uint i = 0; i < count; ++i) {
|
for (uint i = 0; i < count; ++i) {
|
||||||
|
|
@ -201,7 +214,7 @@ bool Shader::link_program_(const uint* shaders, uint count) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Shader::read_file_(const std::string& path) {
|
std::string Shader::read_file_(const std::string &path) {
|
||||||
std::ifstream file(path);
|
std::ifstream file(path);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
ARE_LOG_ERROR("Failed to open file: " + path);
|
ARE_LOG_ERROR("Failed to open file: " + path);
|
||||||
|
|
@ -213,4 +226,50 @@ std::string Shader::read_file_(const std::string& path) {
|
||||||
return buffer.str();
|
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 <path>
|
||||||
|
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
|
} // namespace are
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue