From c5119193ffaedf4ac94fecfb434a3292a4746259 Mon Sep 17 00:00:00 2001 From: ladevieq Date: Sun, 26 Sep 2021 23:57:26 +0200 Subject: Refactor gltf loading and triangles attributes --- shaders/compute.comp | 67 ++++++++++++++---- shaders/include/brdf.h | 99 +++++++++++++++++++++++++++ shaders/include/color_utils.h | 26 +++++++ shaders/include/math.h | 45 ++++++++++++ shaders/include/rand.h | 12 ++++ shaders/include/ray.h | 155 ++++++++++++++++++++++++++++++++++++++++++ shaders/tonemapping.comp | 4 +- shaders/ui.frag | 2 +- shaders/ui.vert | 2 +- 9 files changed, 393 insertions(+), 19 deletions(-) create mode 100644 shaders/include/brdf.h create mode 100644 shaders/include/color_utils.h create mode 100644 shaders/include/math.h create mode 100644 shaders/include/rand.h create mode 100644 shaders/include/ray.h (limited to 'shaders') diff --git a/shaders/compute.comp b/shaders/compute.comp index 015ef1e..2663592 100644 --- a/shaders/compute.comp +++ b/shaders/compute.comp @@ -6,12 +6,6 @@ #extension GL_EXT_buffer_reference : require #extension GL_EXT_nonuniform_qualifier : require -const float PI = 3.141592653589793f; -const float TWO_PI = PI * 2.0f; - -const uint DIFFUSE = 1; -const uint SPECULAR = 2; - #include "math.h" #include "color_utils.h" #include "rand.h" @@ -52,7 +46,6 @@ struct triangle { vertex v1; vertex v2; vertex v3; - material mat; }; struct camera { @@ -91,9 +84,24 @@ layout(buffer_reference) readonly buffer scene_metadata { int downscale_factor; }; -layout(buffer_reference) readonly buffer primitives_array { - // sphere[] spheres; - triangle triangles[]; +layout(buffer_reference) readonly buffer indices_array { + uint indices[]; +}; + +layout(buffer_reference) readonly buffer positions_array { + float positions[]; +}; + +layout(buffer_reference) readonly buffer normals_array { + float normals[]; +}; + +layout(buffer_reference) readonly buffer uvs_array { + float uvs[]; +}; + +layout(buffer_reference) readonly buffer materials_array { + material materials[]; }; layout(buffer_reference) readonly buffer nodes_array { @@ -106,8 +114,12 @@ layout(set = 0, binding = 2, rgba32f) uniform image2D images[]; layout(push_constant) uniform buffers { scene_metadata scene; - primitives_array geometry; nodes_array bvh; + indices_array indices_arr; + positions_array positions_arr; + normals_array normals_arr; + uvs_array uvs_arr; + materials_array materials_arr; uint output_image_index; uint accumulation_image_index; uint padding[2]; @@ -128,12 +140,36 @@ vec3 ray_color(ray r, uint seed) { - // TODO: Interpolation function - triangle tri = bufs.geometry.triangles[info.id]; + // TODO: Interpolation function & indices decoding function + uint id1 = bufs.indices_arr.indices[info.id * 3] & 0x00ffffff; + uint id2 = bufs.indices_arr.indices[info.id * 3 + 1] & 0x00ffffff; + uint id3 = bufs.indices_arr.indices[info.id * 3 + 2] & 0x00ffffff; + uint mat_id = (bufs.indices_arr.indices[info.id * 3] & 0xff000000) >> 8 + | (bufs.indices_arr.indices[info.id * 3 + 1] & 0xff000000) >> 16 + | (bufs.indices_arr.indices[info.id * 3 + 2] & 0xff000000) >> 24; + + vec3 p1 = vec3(bufs.positions_arr.positions[id1 * 3], bufs.positions_arr.positions[id1 * 3 + 1], bufs.positions_arr.positions[id1 * 3 + 2]); + vec3 p2 = vec3(bufs.positions_arr.positions[id2 * 3], bufs.positions_arr.positions[id2 * 3 + 1], bufs.positions_arr.positions[id2 * 3 + 2]); + vec3 p3 = vec3(bufs.positions_arr.positions[id3 * 3], bufs.positions_arr.positions[id3 * 3 + 1], bufs.positions_arr.positions[id3 * 3 + 2]); + + vec3 n1 = vec3(bufs.normals_arr.normals[id1 * 3], bufs.normals_arr.normals[id1 * 3 + 1], bufs.normals_arr.normals[id1 * 3 + 2]); + vec3 n2 = vec3(bufs.normals_arr.normals[id2 * 3], bufs.normals_arr.normals[id2 * 3 + 1], bufs.normals_arr.normals[id2 * 3 + 2]); + vec3 n3 = vec3(bufs.normals_arr.normals[id3 * 3], bufs.normals_arr.normals[id3 * 3 + 1], bufs.normals_arr.normals[id3 * 3 + 2]); + + vec2 uv1 = vec2(bufs.uvs_arr.uvs[id1 * 2], bufs.uvs_arr.uvs[id1 * 2 + 1]); + vec2 uv2 = vec2(bufs.uvs_arr.uvs[id2 * 2], bufs.uvs_arr.uvs[id2 * 2 + 1]); + vec2 uv3 = vec2(bufs.uvs_arr.uvs[id3 * 2], bufs.uvs_arr.uvs[id3 * 2 + 1]); + + vertex v1 = vertex(p1, n1, uv1); + vertex v2 = vertex(p2, n2, uv2); + vertex v3 = vertex(p3, n3, uv3); + + triangle tri = triangle(v1, v2, v3); + 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; - material mat = tri.mat; + material mat = bufs.materials_arr.materials[mat_id]; info.color = srgb_to_linear(texture( sampler2D( textures[nonuniformEXT(mat.base_color_texture.texture_id)], @@ -149,12 +185,13 @@ vec3 ray_color(ray r, uint seed) { + 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; uint brdf_type; - if (mat.metalness == 1.f && mat.metalness == 0.f) { + if (info.metalness_roughness.x == 1.f && info.metalness_roughness.y == 0.f) { brdf_type = SPECULAR; } else { float brdf_probability = get_brdf_probability(info.color, info.metalness_roughness.x, -r.direction, info.shading_normal); diff --git a/shaders/include/brdf.h b/shaders/include/brdf.h new file mode 100644 index 0000000..cafe289 --- /dev/null +++ b/shaders/include/brdf.h @@ -0,0 +1,99 @@ +// Specifies minimal reflectance for dielectrics (when metalness is zero) +// Nothing has lower reflectance than 2%, but we use 4% to have consistent results with UE4, Frostbite, et al. +#define MIN_DIELECTRICS_F0 0.04f + +const uint DIFFUSE = 1; +const uint SPECULAR = 2; + +// 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); +} + +vec3 base_color_to_diffuse_reflectance(vec3 base_color, float metalness) { + return base_color * (1.0f - metalness); +} + +// Schlick's approximation to Fresnel term +// 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); +} + +// Attenuates F90 for very low F0 values +// Source: "An efficient and Physically Plausible Real-Time Shading Model" in ShaderX7 by Schuler +// Also see section "Overbright highlights" in Hoffman's 2010 "Crafting Physically Motivated Shading Models for Game Development" for discussion +// IMPORTANT: Note that when F0 is calculated using metalness, it's value is never less than MIN_DIELECTRICS_F0, and therefore, +// this adjustment has no effect. To be effective, F0 must be authored separately, or calculated in different way. See main text for discussion. +float shadowed_F90(vec3 F0) { + // This scaler value is somewhat arbitrary, Schuler used 60 in his article. In here, we derive it from MIN_DIELECTRICS_F0 so + // that it takes effect for any reflectance lower than least reflective dielectrics + //const float t = 60.0f; + const float t = (1.f / MIN_DIELECTRICS_F0); + return min(1.f, t * luminance(F0)); +} + +float get_brdf_probability(vec3 color, float metalness, 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(color, metalness))); + float diffuse_reflectance = luminance(base_color_to_diffuse_reflectance(color, metalness)); + 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 + float specular = fresnel; + float diffuse = diffuse_reflectance * (1.0f - fresnel); //< If diffuse term is weighted by Fresnel, apply it here as well + + // Return probability of selecting specular BRDF over diffuse BRDF + float p = (specular / max(0.0001f, (specular + diffuse))); + + // Clamp probability to avoid undersampling of less prominent BRDF + return clamp(p, 0.1f, 0.9f); +} diff --git a/shaders/include/color_utils.h b/shaders/include/color_utils.h new file mode 100644 index 0000000..11c2d9e --- /dev/null +++ b/shaders/include/color_utils.h @@ -0,0 +1,26 @@ +vec3 linear_to_srgb(vec3 color) +{ + color = clamp(color, 0.0f, 1.0f); + + return mix( + pow(color, vec3(1.0f / 2.4f)) * 1.055f - 0.055f, + color * 12.92f, + less_than(color, 0.0031308f) + ); +} + +vec3 srgb_to_linear(vec3 color) +{ + color = clamp(color, 0.0f, 1.0f); + + return mix( + pow(((color + 0.055f) / 1.055f), vec3(2.4f)), + color / 12.92f, + less_than(color, 0.04045f) + ); +} + +float luminance(vec3 rgb) +{ + return dot(rgb, vec3(0.2126f, 0.7152f, 0.0722f)); +} diff --git a/shaders/include/math.h b/shaders/include/math.h new file mode 100644 index 0000000..cc4e2f4 --- /dev/null +++ b/shaders/include/math.h @@ -0,0 +1,45 @@ +const float PI = 3.141592653589793f; +const float TWO_PI = PI * 2.0f; + +vec3 less_than(vec3 vec, float value) +{ + return vec3( + (vec.x < value) ? 1.0f : 0.0f, + (vec.y < value) ? 1.0f : 0.0f, + (vec.z < value) ? 1.0f : 0.0f); +} + +vec3 sample_hemisphere(vec2 randoms) { + float radius = sqrt(randoms[0]); + float theta = 2.0 * PI * randoms[1]; + + return vec3(cos(theta) * radius, sin(theta) * radius, sqrt(1 - randoms[0])); +} + +vec2 disk_vec(vec2 randoms) { + float z = randoms[0] * 2.0 - 1.0; + float a = 2.0 * PI * randoms[1]; + float r = sqrt(1.0 - z * z); + + return vec2(cos(a) * r, sin(a) * r); +} + +// Calculates rotation quaternion from axis vector to the vector (0, 0, 1) +// Input vector must be normalized! +vec4 rotation_to_z_axis(vec3 axis) { + // Handle special case when axis is exact or near opposite of (0, 0, 1) + if (axis.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f); + + return normalize(vec4(axis.y, -axis.x, 0.0f, 1.0f + axis.z)); +} + +vec4 invert_rotation(vec4 q) { + return vec4(-q.xyz, q.w); +} + +// Optimized point rotation using quaternion +// Source: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion +vec3 rotate_point(vec4 q, vec3 v) { + const vec3 q_axis = q.xyz; + return 2.0f * dot(q_axis, v) * q_axis + (q.w * q.w - dot(q_axis, q_axis)) * v + 2.0f * q.w * cross(q_axis, v); +} diff --git a/shaders/include/rand.h b/shaders/include/rand.h new file mode 100644 index 0000000..e1f8b27 --- /dev/null +++ b/shaders/include/rand.h @@ -0,0 +1,12 @@ +uint pcg_hash(inout uint seed) +{ + uint state = seed; + seed *= 747796405u + 2891336453u; + uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; + return (word >> 22u) ^ word; +} + +float rand(inout uint seed) +{ + return float(pcg_hash(seed)) / 4294967296.0; +} diff --git a/shaders/include/ray.h b/shaders/include/ray.h new file mode 100644 index 0000000..38e5c16 --- /dev/null +++ b/shaders/include/ray.h @@ -0,0 +1,155 @@ +struct ray { + vec3 origin; + vec3 direction; + float min_t; + float max_t; +}; + +vec3 at(ray r, float t) { + return r.origin + r.direction * t; +} + +struct hit_info { + vec3 point; + vec3 barycentrics; + vec3 geometry_normal; + vec3 shading_normal; + vec3 color; + vec2 metalness_roughness; + vec2 uv; + float t; + uint id; +}; + +bool hit_aabb(vec3 minimum, vec3 maximum, ray r) { + const vec3 invdir = 1.0 / r.direction; + const vec3 f = (maximum.xyz - r.origin.xyz) * invdir; + const vec3 n = (minimum.xyz - r.origin.xyz) * invdir; + + const vec3 tmax = max(f, n); + const vec3 tmin = min(f, n); + + const float t1 = min(min(tmax.x, min(tmax.y, tmax.z)), r.max_t); + const float t0 = max(max(max(tmin.x, max(tmin.y, tmin.z)), r.min_t), 0.0f); + + return t1 >= t0; +} + +uint hit_aabbs(ray r) { + uint hit_count = 0; + int id = 0; + + while(id != -1) { + if (bufs.bvh.nodes[id].primitive_id != -1) { + // triangle tri = bufs.geometry.triangles[bufs.bvh.nodes[id].primitive_id]; + // vec3 minimum = min(tri.v1.position, min(tri.v2.position, tri.v3.position)).xyz; + // vec3 maximum = max(tri.v1.position, max(tri.v2.position, tri.v3.position)).xyz; + + // if (hit_aabb(minimum, maximum, r)) { + // hit_count++; + // } + id = bufs.bvh.nodes[id].next_id; + } else if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r)) { + hit_count++; + id++; + } else { + id = bufs.bvh.nodes[id].next_id; + } + } + + return hit_count; +} + +bool hit_sphere(sphere s, ray r, out hit_info info) { + vec3 origin_center = r.origin - s.position.xyz; + + float a = dot(r.direction, r.direction); + float half_b = dot(r.direction, origin_center); + float c = dot(origin_center, origin_center) - s.radius * s.radius; + float discriminant = half_b * half_b - a * c; + + if (discriminant < 0) { + return false; + } + + float sqrt_discriminant = sqrt(discriminant); + float root = (-half_b - sqrt_discriminant) / a; + if (root < r.min_t || root > r.max_t) { + root = (-half_b + sqrt_discriminant) / a; + + if (root < r.min_t || root > r.max_t) { + return false; + } + } + + info.t = root; + // info.point = at(r, info.t); + + // vec3 outward_normal = (info.point - s.position.xyz) / s.radius; + // bool front_face = dot(outward_normal, r.direction) < 0.0; + // info.normal = front_face ? outward_normal : -outward_normal; + + return true; +} + +bool hit_triangle(uint id, ray r, out hit_info info) { + uint id1 = bufs.indices_arr.indices[id * 3] & 0x00ffffff; + uint id2 = bufs.indices_arr.indices[id * 3 + 1] & 0x00ffffff; + uint id3 = bufs.indices_arr.indices[id * 3 + 2] & 0x00ffffff; + + vec3 p1 = vec3(bufs.positions_arr.positions[id1 * 3], bufs.positions_arr.positions[id1 * 3 + 1], bufs.positions_arr.positions[id1 * 3 + 2]); + vec3 p2 = vec3(bufs.positions_arr.positions[id2 * 3], bufs.positions_arr.positions[id2 * 3 + 1], bufs.positions_arr.positions[id2 * 3 + 2]); + vec3 p3 = vec3(bufs.positions_arr.positions[id3 * 3], bufs.positions_arr.positions[id3 * 3 + 1], bufs.positions_arr.positions[id3 * 3 + 2]); + + vec3 v2v1 = p2 - p1; + vec3 v3v1 = p3 - p1; + + vec3 originv1 = r.origin - p1; + vec3 normal = cross(v2v1, v3v1); + vec3 q = cross(originv1, r.direction); + float d = 1.0 / dot(r.direction, normal); + float u = d * dot(-q, v3v1); + float v = d * dot(q, v2v1); + float t = d * dot(-normal, originv1); + + if (u < 0.0 || u > 1.0 || v < 0.0 || u + v > 1.0 || t < r.min_t || t > r.max_t) { + return false; + } + + info.t = t; + info.barycentrics = vec3(1 - u - v, u, v); + info.geometry_normal = normalize(normal); + info.id = id; + + return true; +} + +bool hit_node(ray r, out hit_info info) { + hit_info temp_info; + bool hit = false; + int id = 0; + + while(id != -1) { + // Leafs + if (bufs.bvh.nodes[id].primitive_id != -1) { + // if (hit_sphere(bufs.geometry.spheres[bufs.bvh.nodes[id].primitive_id], r, temp_info)) { + // info = temp_info; + // r.max_t = temp_info.t; + // hit = true; + // } + if (hit_triangle(bufs.bvh.nodes[id].primitive_id, r, temp_info)) { + info = temp_info; + r.max_t = temp_info.t; + hit = true; + } + id = bufs.bvh.nodes[id].next_id; + } else if (hit_aabb(bufs.bvh.nodes[id].min, bufs.bvh.nodes[id].max, r)) { + id++; + } else { + id = bufs.bvh.nodes[id].next_id; + } + } + + info.point = at(r, info.t); + return hit; +} diff --git a/shaders/tonemapping.comp b/shaders/tonemapping.comp index 660327d..35a7a13 100644 --- a/shaders/tonemapping.comp +++ b/shaders/tonemapping.comp @@ -12,8 +12,8 @@ layout(set = 0, binding = 2, rgba32f) uniform image2D images[]; layout(push_constant) uniform constants { scene_metadata scene; - uint output_image_index; - uint accumulation_image_index; + layout(offset = 56) uint output_image_index; + layout(offset = 60) uint accumulation_image_index; } consts; void main() { diff --git a/shaders/ui.frag b/shaders/ui.frag index c4e0150..dd0fdd4 100644 --- a/shaders/ui.frag +++ b/shaders/ui.frag @@ -11,7 +11,7 @@ layout(set = 0, binding = 0) uniform sampler samplers[]; layout(set = 0, binding = 1) uniform texture2D textures[]; layout(push_constant) uniform constants { - layout(offset = 56) uint texture_index; + layout(offset = 96) uint texture_index; uint sampler_index; } consts; diff --git a/shaders/ui.vert b/shaders/ui.vert index dd71ef6..a9f0670 100644 --- a/shaders/ui.vert +++ b/shaders/ui.vert @@ -12,7 +12,7 @@ layout(buffer_reference) readonly buffer vertices { }; layout(push_constant) uniform constants { - layout(offset = 32) vertices vertex_buffer; + layout(offset = 64) vertices vertex_buffer; vec2 scale; vec2 translate; } consts; -- cgit v1.3