aboutsummaryrefslogtreecommitdiffstats
path: root/shaders/compute.comp
diff options
context:
space:
mode:
Diffstat (limited to 'shaders/compute.comp')
-rw-r--r--shaders/compute.comp198
1 files changed, 152 insertions, 46 deletions
diff --git a/shaders/compute.comp b/shaders/compute.comp
index 0d5c525..48c4f7c 100644
--- a/shaders/compute.comp
+++ b/shaders/compute.comp
@@ -19,24 +19,39 @@ struct ray {
float max_t;
};
-const float PI = 3.141592653589793;
+const float PI = 3.141592653589793f;
+const float TWO_PI = PI * 2.0f;
const uint DIFFUSE = 1;
const uint SPECULAR = 2;
+struct tex {
+ uint texture_id;
+ uint sampler_id;
+ uint[2] padding;
+};
+
struct material {
vec4 base_color;
+ // bindless indices
+ tex base_color_texture;
+ tex metallic_roughness_texture;
float metalness;
float roughness;
- uint[2] padding;
+ float[2] padding;
};
struct hit_info {
- float t;
vec3 point;
+ vec3 barycentrics;
vec3 geometry_normal;
vec3 shading_normal;
+ vec3 color;
+ vec2 metalness_roughness;
+ vec2 uv;
material mat;
+ float t;
+ uint id;
};
struct sphere {
@@ -47,14 +62,16 @@ struct sphere {
};
struct vertex {
- vec4 position;
- vec4 normal;
+ vec3 position;
+ vec3 normal;
+ vec2 uv;
};
struct triangle {
vertex v1;
vertex v2;
vertex v3;
+ material mat;
};
struct camera {
@@ -102,6 +119,8 @@ layout(buffer_reference) readonly buffer nodes_array {
bvh_node[] nodes;
};
+layout(set = 0, binding = 0) uniform sampler samplers[];
+layout(set = 0, binding = 1) uniform texture2D textures[];
layout(set = 0, binding = 2, rgba32f) uniform image2D images[];
layout(push_constant) uniform buffers {
@@ -243,7 +262,8 @@ bool hit_sphere(sphere s, ray r, out hit_info info) {
return true;
}
-bool hit_triangle(triangle tri, ray r, out hit_info info) {
+bool hit_triangle(uint id, ray r, out hit_info info) {
+ triangle tri = bufs.geometry.triangles[id];
vec3 v2v1 = tri.v2.position.xyz - tri.v1.position.xyz;
vec3 v3v1 = tri.v3.position.xyz - tri.v1.position.xyz;
@@ -260,7 +280,10 @@ bool hit_triangle(triangle tri, ray r, out hit_info info) {
}
info.t = t;
+ info.barycentrics = vec3(1 - u - v, u, v);
info.geometry_normal = normalize(normal);
+ info.id = id;
+ info.mat = tri.mat;
return true;
}
@@ -278,7 +301,7 @@ bool hit_node(ray r, out hit_info info) {
// r.max_t = temp_info.t;
// hit = true;
// }
- if (hit_triangle(bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id], r, temp_info)) {
+ if (hit_triangle(bufs.bvh.nodes[id].primitive_id, r, temp_info)) {
info = temp_info;
r.max_t = temp_info.t;
hit = true;
@@ -295,6 +318,51 @@ bool hit_node(ray r, out hit_info info) {
return hit;
}
+
+
+// Samples a microfacet normal for the GGX distribution using VNDF method.
+// Source: "Sampling the GGX Distribution of Visible Normals" by Heitz
+// See also https://hal.inria.fr/hal-00996995v1/document and http://jcgt.org/published/0007/04/01/
+// Random variables 'u' must be in <0;1) interval
+// PDF is 'G1(NdotV) * D'
+vec3 sample_GGXVNDF(vec3 Ve, vec2 alpha2D, uint seed) {
+ // Section 3.2: transforming the view direction to the hemisphere configuration
+ vec3 Vh = normalize(vec3(alpha2D.x * Ve.x, alpha2D.y * Ve.y, Ve.z));
+
+ // Section 4.1: orthonormal basis (with special case if cross product is zero)
+ float lensq = Vh.x * Vh.x + Vh.y * Vh.y;
+ vec3 T1 = lensq > 0.0f ? vec3(-Vh.y, Vh.x, 0.0f) * inversesqrt(lensq) : vec3(1.0f, 0.0f, 0.0f);
+ vec3 T2 = cross(Vh, T1);
+
+ // Section 4.2: parameterization of the projected area
+ float r = sqrt(rand(seed));
+ float phi = TWO_PI * rand(seed);
+ float t1 = r * cos(phi);
+ float t2 = r * sin(phi);
+ float s = 0.5f * (1.0f + Vh.z);
+ t2 = mix(sqrt(1.0f - t1 * t1), t2, s);
+
+ // Section 4.3: reprojection onto hemisphere
+ vec3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0f, 1.0f - t1 * t1 - t2 * t2)) * Vh;
+
+ // Section 3.4: transforming the normal back to the ellipsoid configuration
+ return normalize(vec3(alpha2D.x * Nh.x, alpha2D.y * Nh.y, max(0.0f, Nh.z)));
+}
+
+// Smith G1 term (masking function) further optimized for GGX distribution (by substituting G_a into G1_GGX)
+float Smith_G1_GGX(float alpha, float NdotS, float alpha_squared, float NdotS_squared) {
+ return 2.0f / (sqrt(((alpha_squared * (1.0f - NdotS_squared)) + NdotS_squared) / NdotS_squared) + 1.0f);
+}
+
+// Weight for the reflection ray sampled from GGX distribution using VNDF method
+float specular_sample_weight_GGXVNDF(float alpha, float alpha_squared, float NdotL, float NdotV, float HdotL, float NdotH) {
+#if USE_HEIGHT_CORRELATED_G2
+ return Smith_G2_Over_G1_Height_Correlated(alpha, alpha_squared, NdotL, NdotV);
+#else
+ return Smith_G1_GGX(alpha, NdotL, alpha_squared, NdotL * NdotL);
+#endif
+}
+
vec3 base_color_to_specular_F0(vec3 base_color, float metalness) {
return mix(vec3(MIN_DIELECTRICS_F0), base_color, metalness);
}
@@ -304,10 +372,11 @@ vec3 base_color_to_diffuse_reflectance(vec3 base_color, float metalness) {
}
// Schlick's approximation to Fresnel term
-// f90 should be 1.0, except for the trick used by Schuler (see 'shadowedF90' function)
+// f90 should be 1.0, except for the trick used by Schuler (see 'shadowed_F90' function)
vec3 eval_fresnel_schlick(vec3 f0, float f90, float NdotS) {
return f0 + (f90 - f0) * pow(1.0f - NdotS, 5.0f);
}
+
vec3 eval_fresnel(vec3 f0, float f90, float NdotS) {
// Default is Schlick's approximation
return eval_fresnel_schlick(f0, f90, NdotS);
@@ -326,11 +395,11 @@ float shadowed_F90(vec3 F0) {
return min(1.f, t * luminance(F0));
}
-float get_brdf_probability(material mat, vec3 view, vec3 shading_normal) {
+float get_brdf_probability(hit_info info, vec3 view, vec3 shading_normal) {
// Evaluate Fresnel term using the shading normal
// Note: we use the shading normal instead of the microfacet normal (half-vector) for Fresnel term here. That's suboptimal for rough surfaces at grazing angles, but half-vector is yet unknown at this point
- vec3 specular_F0 = vec3(luminance(base_color_to_specular_F0(mat.base_color.rgb, mat.metalness)));
- float diffuse_reflectance = luminance(base_color_to_diffuse_reflectance(mat.base_color.rgb, mat.metalness));
+ vec3 specular_F0 = vec3(luminance(base_color_to_specular_F0(info.color, info.metalness_roughness.x)));
+ float diffuse_reflectance = luminance(base_color_to_diffuse_reflectance(info.color, info.metalness_roughness.x));
float fresnel = clamp(luminance(eval_fresnel(specular_F0, shadowed_F90(specular_F0), max(0.0f, dot(view, shading_normal)))), 0.0, 1.0);
// Approximate relative contribution of BRDFs using the Fresnel term
@@ -355,65 +424,102 @@ vec3 ray_color(ray r, uint seed) {
vec3 v = -r.direction;
+
+
+ // TODO: Interpolation function
+ triangle tri = bufs.geometry.triangles[info.id];
+ info.shading_normal = normalize(tri.v1.normal * info.barycentrics.x + tri.v2.normal * info.barycentrics.y + tri.v3.normal * info.barycentrics.z);
+ info.uv = tri.v1.uv * info.barycentrics.x + tri.v2.uv * info.barycentrics.y + tri.v3.uv * info.barycentrics.z;
+
+ info.color = texture(
+ sampler2D(
+ textures[nonuniformEXT(info.mat.base_color_texture.texture_id)],
+ samplers[nonuniformEXT(info.mat.base_color_texture.sampler_id)]
+ ), info.uv
+ ).xyz * info.mat.base_color.xyz;
+ info.metalness_roughness = texture(
+ sampler2D(
+ textures[nonuniformEXT(info.mat.metallic_roughness_texture.texture_id)],
+ samplers[nonuniformEXT(info.mat.metallic_roughness_texture.sampler_id)]
+ ), info.uv
+ ).xy * vec2(info.mat.metalness, info.mat.roughness);
+
+
+
if (dot(info.geometry_normal, v) < 0.f) info.geometry_normal = -info.geometry_normal;
- // if (dot(info.geometry_normal, info.shading_normal) < 0.0f) info.shading_normal = -info.shading_normal;
+ if (dot(info.geometry_normal, info.shading_normal) < 0.0f) info.shading_normal = -info.shading_normal;
uint brdf_type;
- // if (info.mat.metalness == 1.f && info.mat.metalness == 0.f) {
- // brdf_type = SPECULAR;
- // } else {
- // float brdf_probability = get_brdf_probability(info.mat, -r.direction, info.shading_normal);
- // if (rand(seed) < brdf_probability) {
- // brdf_type = SPECULAR;
- // throughput /= brdf_probability;
- // } else {
- // brdf_type = DIFFUSE;
- // throughput /= (1.0 - brdf_probability);
- // }
- // }
-
- brdf_type = DIFFUSE;
+ if (info.mat.metalness == 1.f && info.mat.metalness == 0.f) {
+ brdf_type = SPECULAR;
+ } else {
+ float brdf_probability = get_brdf_probability(info, -r.direction, info.shading_normal);
+ if (rand(seed) < brdf_probability) {
+ brdf_type = SPECULAR;
+ throughput /= brdf_probability;
+ } else {
+ brdf_type = DIFFUSE;
+ throughput /= (1.0 - brdf_probability);
+ }
+ }
// Ignore ray coming from below the hemisphere
- // if (dot(info.shading_normal, v) <= 0.f) break;
+ if (dot(info.shading_normal, v) <= 0.f) break;
// Move to tangent space
- vec4 rotation_to_z = rotation_to_z_axis(info.geometry_normal);
+ vec4 rotation_to_z = rotation_to_z_axis(info.shading_normal);
vec3 view_local = rotate_point(rotation_to_z, -r.direction);
vec3 normal_local = vec3(0.0, 0.0, 1.0);
vec3 ray_dir_local;
- vec3 brdf_weight;
+ vec3 sample_weight;
- if (brdf_type == DIFFUSE) {
+ if (brdf_type == DIFFUSE) { // Lambertian diffuse
ray_dir_local = sample_hemisphere(seed);
-
- float NdotL = dot(normal_local, ray_dir_local);
- float pdf = NdotL / PI;
- // vec3 brdf = base_color_to_diffuse_reflectance(info.mat.base_color.xyz, info.mat.metalness) / PI;
- vec3 brdf = (vec3(1.0) * NdotL) / PI;
- brdf_weight = brdf / pdf;
+ sample_weight = base_color_to_diffuse_reflectance(info.color, info.metalness_roughness.x);
} else if (brdf_type == SPECULAR) {
- // alpha = info.mat.roughness * info.mat.roughness;
+ float alpha = info.metalness_roughness.y * info.metalness_roughness.y;
+ float alpha_squared = alpha * alpha;
- // vec3 half_local;
- // if (alpha == 0.0f) {
- // // Fast path for zero roughness (perfect reflection), also prevents NaNs appearing due to divisions by zeroes
- // half_local = vec3(0.0f, 0.0f, 1.0f);
- // } else {
- // // For non-zero roughness, this calls VNDF sampling for GG-X distribution or Walter's sampling for Beckmann distribution
- // half_local = sampleSpecularHalfVector(view_local, vec2(alpha));
- // }
+ vec3 half_local;
+ if (alpha == 0.0f) {
+ // Fast path for zero roughness (perfect reflection), also prevents NaNs appearing due to divisions by zeroes
+ half_local = vec3(0.0f, 0.0f, 1.0f);
+ } else {
+ // For non-zero roughness, this calls VNDF sampling for GG-X distribution or Walter's sampling for Beckmann distribution
+ half_local = sample_GGXVNDF(view_local, vec2(alpha), seed);
+ }
+
+ // Reflect view direction to obtain light vector
+ vec3 light_local = reflect(-view_local, half_local);
+
+ vec3 specular_F0 = base_color_to_diffuse_reflectance(info.color, info.metalness_roughness.x);
+
+ // Note: HdotL is same as HdotV here
+ // Clamp dot products here to small value to prevent numerical instability.
+ float HdotL = clamp(dot(half_local, light_local), 0.00001f, 1.0f);
+ float NdotL = clamp(dot(normal_local, light_local), 0.00001f, 1.0f);
+ float NdotV = clamp(dot(normal_local, view_local), 0.00001f, 1.0f);
+ float NdotH = clamp(dot(normal_local, half_local), 0.00001f, 1.0f);
+ vec3 f = eval_fresnel(specular_F0, shadowed_F90(specular_F0), HdotL);
+
+ sample_weight = f * specular_sample_weight_GGXVNDF(alpha, alpha_squared, NdotL, NdotV, HdotL, NdotH);
+
+ ray_dir_local = light_local;
}
- throughput *= brdf_weight;
+ // Prevent tracing direction with no contribution
+ if (luminance(sample_weight) == 0.0f) break;
// Move to global space
vec4 rotation_from_z = invert_rotation(rotation_to_z);
vec3 ray_dir = rotate_point(rotation_from_z, ray_dir_local);
+ // Prevent tracing direction "under" the hemisphere (behind the triangle)
if (dot(info.geometry_normal, ray_dir) <= 0.0f) break;
+ throughput *= sample_weight;
+
r = ray(info.point, ray_dir, 0.001f, 1e15);
// Russian Roulette