diff --git a/ref/vk/shaders/bounce.comp b/ref/vk/shaders/bounce.comp index 60b3823a..f40c354b 100644 --- a/ref/vk/shaders/bounce.comp +++ b/ref/vk/shaders/bounce.comp @@ -95,92 +95,13 @@ bool getHit(vec3 origin, vec3 direction, inout RayPayloadPrimary payload) { } const int INDIRECT_SCALE = 2; -#define BRDF_TYPE_NONE 0 -#define BRDF_TYPE_DIFFUSE 1 -#define BRDF_TYPE_SPECULAR 2 -int pickBounceDirection(MaterialProperties material, vec3 view, vec3 geometry_normal, vec3 shading_normal, /*float alpha, */out vec3 out_direction, inout vec3 inout_throughput) { -#if 1 - // Idiotic sampling, super noisy, bad distribution, etc etc - // But we need to start somewhere - const int brdf_type = (rand01() > .5) ? BRDF_TYPE_DIFFUSE : BRDF_TYPE_SPECULAR; - - if (any(isnan(geometry_normal))) { - inout_throughput = vec3(1.,0.,1.); - return brdf_type; - } - - // Very bad distribution, don't do this. - out_direction = vec3(rand01(), rand01(), rand01()) * 2. - 1.; - out_direction *= sign(dot(out_direction, shading_normal)); - - if (dot(out_direction, out_direction) < 1e-5 || dot(out_direction, geometry_normal) <= 0.) - return BRDF_TYPE_NONE; - //out_direction = reflect(-view, shading_normal); - out_direction = normalize(out_direction); - - vec3 diffuse, specular; - evalSplitBRDF(shading_normal, out_direction, view, material, diffuse, specular); - - /* - if (any(isnan(diffuse))) { - inout_throughput = vec3(0., 1., 0.); - return brdf_type; - } - */ - - inout_throughput *= (brdf_type == BRDF_TYPE_DIFFUSE) ? diffuse : specular; - - const float throughput_threshold = 1e-3; - if (dot(inout_throughput, inout_throughput) < throughput_threshold) - return BRDF_TYPE_NONE; - - // FIXME better sampling - return brdf_type; -#else - int brdf_type = BRDF_TYPE_DIFFUSE; - // FIXME address translucency properly - const float alpha = (base_a.a); - if (1. > alpha && rand01() > alpha) { - brdf_type = BRDF_TYPE_SPECULAR; - // TODO: when not sampling randomly: throughput *= (1. - base_a.a); - bounce_direction = normalize(refract(direction, geometry_normal, .95)); - geometry_normal = -geometry_normal; - //throughput /= base_a.rgb; - } else { - if (material.metalness == 1.0f && material.roughness == 0.0f) { - // Fast path for mirrors - brdf_type = BRDF_TYPE_SPECULAR; - } else { - // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) - const float brdf_probability = getBrdfProbability(material, -direction, shading_normal); - if (rand01() < brdf_probability) { - brdf_type = BRDF_TYPE_SPECULAR; - throughput /= brdf_probability; - } - } - - const vec2 u = vec2(rand01(), rand01()); - vec3 brdf_weight = vec3(0.); - if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) - return false; - throughput *= brdf_weight; - } - - const float throughput_threshold = 1e-3; - if (dot(throughput, throughput) < throughput_threshold) - return false; - - return true; -#endif -} - struct MaterialEx { MaterialProperties prop; vec3 geometry_normal, shading_normal; //TODO float alpha; }; -int max_bounces = 2; +int max_bounces = 1; void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, inout vec3 diffuse, inout vec3 specular) { const float ray_normal_fudge = .01; @@ -189,7 +110,9 @@ void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, inout vec3 diffuse //const int max_bounces = 2; for (int i = 0; i < max_bounces; ++i) { - const int brdf_type = pickBounceDirection(mat.prop, -direction, mat.geometry_normal, mat.shading_normal/* TODO, mat.base_color_a.a*/, bounce_direction, throughput); + // TODO blue noise + const vec2 rnd = vec2(rand01(), rand01()); + const int brdf_type = brdfGetSample(rnd, mat.prop, -direction, mat.geometry_normal, mat.shading_normal/* TODO, mat.base_color_a.a*/, bounce_direction, throughput); if (brdf_type == BRDF_TYPE_NONE) return; @@ -248,8 +171,8 @@ void computeBounces(MaterialEx mat, vec3 pos, vec3 direction, inout vec3 diffuse specular += throughput * lighting; if (any(isnan(throughput))) { - diffuse = vec3(1., 0., 0.); - specular = vec3(0., 0., 1.); + diffuse = 10.*vec3(1., 0., 0.); + specular = 10.*vec3(0., 0., 1.); } else { //diffuse = throughput; } @@ -300,7 +223,7 @@ void main() { mat.geometry_normal = geometry_normal; mat.shading_normal = shading_normal; - max_bounces = (pix.x > res.x / 2.) ? 2 : 1; + //max_bounces = (pix.x > res.x / 2.) ? 2 : 1; computeBounces(mat, pos_t.xyz, direction, diffuse, specular); } diff --git a/ref/vk/shaders/brdf.glsl b/ref/vk/shaders/brdf.glsl index 03057ae6..7441a8c4 100644 --- a/ref/vk/shaders/brdf.glsl +++ b/ref/vk/shaders/brdf.glsl @@ -69,6 +69,46 @@ float ggxV(float a2, float l_dot_n, float h_dot_l, float n_dot_v, float h_dot_v) // return mix(base, layer, fr) //} +void brdfComputeGltfModel(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 out_diffuse, out vec3 out_specular) { + const float alpha = material.roughness * material.roughness; + const float a2 = alpha * alpha; + const vec3 H = normalize(L + V); + const float h_dot_v = dot(H, V); + const float h_dot_n = dot(H, N); + const float l_dot_n = dot(L, N); + const float h_dot_l = dot(H, L); + const float n_dot_v = dot(N, V); + + /* Original for when the color is known already. + const vec3 diffuse_color = mix(material.base_color, vec3(0.), material.metalness); + const vec3 f0 = mix(vec3(.04), material.base_color, material.metalness); + const vec3 fresnel = f0 + (vec3(1.) - f0) * pow(1. - abs(h_dot_v), 5.); + */ + + // Use white for diffuse color here, as compositing happens way later in denoiser/smesitel + const vec3 diffuse_color = mix(vec3(1.), vec3(0.), material.metalness); + + // Specular does get the real color, as its contribution is light-direction-dependent + const vec3 f0 = mix(vec3(.04), material.base_color, material.metalness); + const float fresnel_factor = pow(1. - abs(h_dot_v), 5.); + const vec3 fresnel = vec3(1.) * fresnel_factor + f0 * (1. - fresnel_factor); + + // This is taken directly from glTF 2.0 spec. It seems incorrect to me: it should not include the base_color twice. + // Note: here diffuse_color doesn't include base_color as it is mixed later, but we can clearly see that + // base_color is still visible in the direct_diff term, which it shouldn't be. See E357 ~37:00 + // TODO make a PR against glTF spec with the correct derivation. + //out_diffuse = (vec3(1.) - fresnel) * kOneOverPi * diffuse_color * l_dot_n; + + // This is the correctly derived diffuse term that doesn't include the base_color twice + out_diffuse = diffuse_color * kOneOverPi * .96 * (1. - fresnel_factor); + + //if (isnan(l_dot_n)) + if (any(isnan(N))) + out_diffuse = 10.*vec3(1., 1., 0.); + + out_specular = fresnel * ggxD(a2, h_dot_n) * ggxV(a2, l_dot_n, h_dot_l, n_dot_v, h_dot_v); +} + void evalSplitBRDF(vec3 N, vec3 L, vec3 V, MaterialProperties material, out vec3 out_diffuse, out vec3 out_specular) { out_diffuse = vec3(0.); out_specular = vec3(0.); @@ -116,43 +156,10 @@ material = f_diffuse + f_specular #ifdef BRDF_COMPARE if (g_mat_gltf2) { #endif - const float alpha = material.roughness * material.roughness; - const float a2 = alpha * alpha; - const vec3 H = normalize(L + V); - const float h_dot_v = dot(H, V); - const float h_dot_n = dot(H, N); + brdfComputeGltfModel(N, L, V, material, out_diffuse, out_specular); const float l_dot_n = dot(L, N); - const float h_dot_l = dot(H, L); - const float n_dot_v = dot(N, V); - - /* Original for when the color is known already. - const vec3 diffuse_color = mix(material.base_color, vec3(0.), material.metalness); - const vec3 f0 = mix(vec3(.04), material.base_color, material.metalness); - const vec3 fresnel = f0 + (vec3(1.) - f0) * pow(1. - abs(h_dot_v), 5.); - */ - - // Use white for diffuse color here, as compositing happens way later in denoiser/smesitel - const vec3 diffuse_color = mix(vec3(1.), vec3(0.), material.metalness); - - // Specular does get the real color, as its contribution is light-direction-dependent - const vec3 f0 = mix(vec3(.04), material.base_color, material.metalness); - const float fresnel_factor = pow(1. - abs(h_dot_v), 5.); - const vec3 fresnel = vec3(1.) * fresnel_factor + f0 * (1. - fresnel_factor); - - // This is taken directly from glTF 2.0 spec. It seems incorrect to me: it should not include the base_color twice. - // Note: here diffuse_color doesn't include base_color as it is mixed later, but we can clearly see that - // base_color is still visible in the direct_diff term, which it shouldn't be. See E357 ~37:00 - // TODO make a PR against glTF spec with the correct derivation. - //out_diffuse = (vec3(1.) - fresnel) * kOneOverPi * diffuse_color * l_dot_n; - - // This is the correctly derived diffuse term that doesn't include the base_color twice - out_diffuse = l_dot_n * diffuse_color * kOneOverPi * .96 * (1. - fresnel_factor); - - //if (isnan(l_dot_n)) - if (any(isnan(N))) - out_diffuse = 10.*vec3(1., 1., 0.); - - out_specular = l_dot_n * fresnel * ggxD(a2, h_dot_n) * ggxV(a2, l_dot_n, h_dot_l, n_dot_v, h_dot_v); + out_diffuse *= l_dot_n; + out_specular *= l_dot_n; #ifdef BRDF_COMPARE } else { // Prepare data needed for BRDF evaluation - unpack material properties and evaluate commonly used terms (e.g. Fresnel, NdotL, ...) @@ -177,5 +184,133 @@ if (g_mat_gltf2) { #endif } +vec3 sampleCosineHemisphereAroundVectorUnnormalized(vec2 rnd, vec3 n) { + // Ray Tracing Gems, ยง16.6.2 + const float a = 1. - 2. * rnd.x; + const float b = sqrt(1. - a * a); + const float phi = 2. * kPi * rnd.y; + + // TODO const float pdf = a / kPi; + return n + vec3(b * cos(phi), b * sin(phi), a); +} + +// brdf.h +// Calculates rotation quaternion from input vector to the vector (0, 0, 1) +// Input vector must be normalized! +vec4 getRotationToZAxis(vec3 v) { + // Handle special case when input is exact or near opposite of (0, 0, 1) + if (v.z < -0.99999f) return vec4(1.0f, 0.0f, 0.0f, 0.0f); + + return normalize(vec4(v.y, -v.x, 0.0f, 1.0f + v.z)); +} +// Returns the quaternion with inverted rotation +vec4 invertRotation(vec4 q) +{ + return vec4(-q.x, -q.y, -q.z, q.w); +} +// Optimized point rotation using quaternion +// Source: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion +vec3 rotatePoint(vec4 q, vec3 v) { + const vec3 qAxis = vec3(q.x, q.y, q.z); + return 2.0f * dot(qAxis, v) * qAxis + (q.w * q.w - dot(qAxis, qAxis)) * v + 2.0f * q.w * cross(qAxis, v); +} +// Samples a direction within a hemisphere oriented along +Z axis with a cosine-weighted distribution +// Source: "Sampling Transformations Zoo" in Ray Tracing Gems by Shirley et al. +vec3 sampleHemisphere(vec2 u, out float pdf) { + float a = sqrt(u.x); + float b = 2. * kPi * u.y; + + vec3 result = vec3( + a * cos(b), + a * sin(b), + sqrt(1.0f - u.x)); + + pdf = result.z * kOneOverPi; + + return result; +} +vec3 sampleCosineHemisphereAroundVectorUnnormalized2(vec2 rnd, vec3 n) { + const vec4 qRotationToZ = getRotationToZAxis(n); + float pdf; + return rotatePoint(invertRotation(qRotationToZ), sampleHemisphere(rnd, pdf)); +} + +#define BRDF_TYPE_NONE 0 +#define BRDF_TYPE_DIFFUSE 1 +#define BRDF_TYPE_SPECULAR 2 + +int brdfGetSample(vec2 rnd, MaterialProperties material, vec3 view, vec3 geometry_normal, vec3 shading_normal, /*float alpha, */out vec3 out_direction, inout vec3 inout_throughput) { +#if 1 + // Idiotic sampling, super noisy, bad distribution, etc etc + // But we need to start somewhere + // TODO pick diffuse-vs-specular based on expected contribution + const int brdf_type = BRDF_TYPE_DIFFUSE;// (rand01() > .5) ? BRDF_TYPE_DIFFUSE : BRDF_TYPE_SPECULAR; + + if (any(isnan(geometry_normal))) { + inout_throughput = 10.*vec3(1.,0.,1.); + return brdf_type; + } + + out_direction = sampleCosineHemisphereAroundVectorUnnormalized2(rnd, shading_normal); + if (dot(out_direction, out_direction) < 1e-5 || dot(out_direction, geometry_normal) <= 0.) + return BRDF_TYPE_NONE; + + out_direction = normalize(out_direction); + + vec3 diffuse = vec3(0.), specular = vec3(0.); + brdfComputeGltfModel(shading_normal, out_direction, view, material, diffuse, specular); + + /* + if (any(isnan(diffuse))) { + inout_throughput = vec3(0., 1., 0.); + return brdf_type; + } + */ + + inout_throughput *= (brdf_type == BRDF_TYPE_DIFFUSE) ? diffuse : specular; + + const float throughput_threshold = 1e-3; + if (dot(inout_throughput, inout_throughput) < throughput_threshold) + return BRDF_TYPE_NONE; + + // FIXME better sampling + return brdf_type; +#else + int brdf_type = BRDF_TYPE_DIFFUSE; + // FIXME address translucency properly + const float alpha = (base_a.a); + if (1. > alpha && rand01() > alpha) { + brdf_type = BRDF_TYPE_SPECULAR; + // TODO: when not sampling randomly: throughput *= (1. - base_a.a); + bounce_direction = normalize(refract(direction, geometry_normal, .95)); + geometry_normal = -geometry_normal; + //throughput /= base_a.rgb; + } else { + if (material.metalness == 1.0f && material.roughness == 0.0f) { + // Fast path for mirrors + brdf_type = BRDF_TYPE_SPECULAR; + } else { + // Decide whether to sample diffuse or specular BRDF (based on Fresnel term) + const float brdf_probability = getBrdfProbability(material, -direction, shading_normal); + if (rand01() < brdf_probability) { + brdf_type = BRDF_TYPE_SPECULAR; + throughput /= brdf_probability; + } + } + + const vec2 u = vec2(rand01(), rand01()); + vec3 brdf_weight = vec3(0.); + if (!evalIndirectCombinedBRDF(u, shading_normal, geometry_normal, -direction, material, brdf_type, bounce_direction, brdf_weight)) + return false; + throughput *= brdf_weight; + } + + const float throughput_threshold = 1e-3; + if (dot(throughput, throughput) < throughput_threshold) + return false; + + return true; +#endif +} #endif //ifndef BRDF_GLSL_INCLUDED