diff options
Diffstat (limited to 'shaders/compute.comp')
| -rw-r--r-- | shaders/compute.comp | 198 |
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 |
