diff options
| author | ladevieq <ladevie.quentin@gmail.com> | 2021-09-26 23:57:26 +0200 |
|---|---|---|
| committer | ladevieq <ladevie.quentin@gmail.com> | 2021-09-26 23:57:26 +0200 |
| commit | c5119193ffaedf4ac94fecfb434a3292a4746259 (patch) | |
| tree | 398031c5d1e920fec21e53feacd1d55ba8a79dad /shaders/include | |
| parent | bbba71cfe5a9545f1a87c70eb3cab3b6b08b1b25 (diff) | |
Refactor gltf loading and triangles attributes
Diffstat (limited to 'shaders/include')
| -rw-r--r-- | shaders/include/brdf.h | 99 | ||||
| -rw-r--r-- | shaders/include/color_utils.h | 26 | ||||
| -rw-r--r-- | shaders/include/math.h | 45 | ||||
| -rw-r--r-- | shaders/include/rand.h | 12 | ||||
| -rw-r--r-- | shaders/include/ray.h | 155 |
5 files changed, 337 insertions, 0 deletions
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;
+}
|
