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:
parent
0bc585eddc
commit
5906f72e0d
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue