vk: rt: pick bounce direction by cosine-hemisphere sampling

Also:
- move sampling to brdf.glsl
- do only 1 bounce w/o comparison (for testing)
- do only diffuse (for testing)
This commit is contained in:
Ivan Avdeev 2024-01-05 20:06:01 -05:00
parent 0bc585eddc
commit 5906f72e0d
2 changed files with 178 additions and 120 deletions

View File

@ -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);
}

View File

@ -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