Minor importance sampling tweaks and code clean-up (#342)
* cleanup importance sampling code * Add VNDF importance sampling -- work in progress. * minor code cleanup
This commit is contained in:
@@ -52,9 +52,9 @@ bool IBL::loadFromDirectory(const utils::Path& path) {
|
||||
shReader >> std::skipws;
|
||||
|
||||
std::string line;
|
||||
for (size_t i = 0; i < 9; i++) {
|
||||
for (float3& band : mBands) {
|
||||
std::getline(shReader, line);
|
||||
int n = sscanf(line.c_str(), "(%f,%f,%f)", &mBands[i].r, &mBands[i].g, &mBands[i].b);
|
||||
int n = sscanf(line.c_str(), "(%f,%f,%f)", &band.r, &band.g, &band.b); // NOLINT(cert-err34-c)
|
||||
if (n != 3) return false;
|
||||
}
|
||||
} else {
|
||||
@@ -62,11 +62,11 @@ bool IBL::loadFromDirectory(const utils::Path& path) {
|
||||
}
|
||||
|
||||
// Read mip-mapped cubemap
|
||||
if (!loadCubemapLevel(&mTexture, path, 0, "m0_")) return false;
|
||||
const std::string prefix = "m";
|
||||
if (!loadCubemapLevel(&mTexture, path, 0, prefix + "0_")) return false;
|
||||
size_t numLevels = mTexture->getLevels();
|
||||
for (size_t i = 1; i<numLevels; i++) {
|
||||
std::string levelPrefix = "m";
|
||||
levelPrefix += std::to_string(i) + "_";
|
||||
const std::string levelPrefix = prefix + std::to_string(i) + "_";
|
||||
if (!loadCubemapLevel(&mTexture, path, i, levelPrefix)) return false;
|
||||
}
|
||||
|
||||
@@ -106,15 +106,15 @@ bool IBL::loadCubemapLevel(filament::Texture** texture, const utils::Path& path,
|
||||
|
||||
size = (size_t)w;
|
||||
|
||||
if (levelPrefix != "") {
|
||||
if (!levelPrefix.empty()) {
|
||||
numLevels = (size_t)std::log2(size) + 1;
|
||||
}
|
||||
|
||||
if (level == 0) {
|
||||
*texture = Texture::Builder()
|
||||
.width(size)
|
||||
.height(size)
|
||||
.levels(numLevels)
|
||||
.width((uint32_t)size)
|
||||
.height((uint32_t)size)
|
||||
.levels((uint8_t)numLevels)
|
||||
.format(Texture::InternalFormat::RGBM)
|
||||
.sampler(Texture::Sampler::SAMPLER_CUBEMAP)
|
||||
.build(mEngine);
|
||||
|
||||
@@ -228,9 +228,78 @@ vec3 getReflectedVector(const PixelParams pixel, const vec3 n) {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#if IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
|
||||
|
||||
vec2 hammersley(uint index) {
|
||||
// Compute Hammersley sequence
|
||||
// TODO: these should come from uniforms
|
||||
// TODO: we should do this with logical bit operations
|
||||
const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT);
|
||||
const uint numSampleBits = uint(log2(float(numSamples)));
|
||||
const float invNumSamples = 1.0 / float(numSamples);
|
||||
uint i = uint(index);
|
||||
uint t = i;
|
||||
uint bits = 0u;
|
||||
for (uint j = 0u; j < numSampleBits; j++) {
|
||||
bits = bits * 2u + (t - (2u * (t / 2u)));
|
||||
t /= 2u;
|
||||
}
|
||||
return vec2(float(i), float(bits)) * invNumSamples;
|
||||
}
|
||||
|
||||
vec3 importanceSamplingNdfDggx(vec2 u, float linearRoughness) {
|
||||
// Importance sampling D_GGX
|
||||
float a2 = linearRoughness * linearRoughness;
|
||||
float phi = 2.0 * PI * u.x;
|
||||
float cosTheta2 = (1.0 - u.y) / (1.0 + (a2 - 1.0) * u.y);
|
||||
float cosTheta = sqrt(cosTheta2);
|
||||
float sinTheta = sqrt(1.0 - cosTheta2);
|
||||
return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
||||
}
|
||||
|
||||
vec3 importanceSamplingVNdfDggx(vec2 u, float linearRoughness, vec3 v) {
|
||||
// See: "A Simpler and Exact Sampling Routine for the GGX Distribution of Visible Normals", Eric Heitz
|
||||
float alpha = linearRoughness;
|
||||
|
||||
// stretch view
|
||||
v = normalize(vec3(alpha * v.x, alpha * v.y, v.z));
|
||||
|
||||
// orthonormal basis
|
||||
vec3 up = abs(v.z) < 0.9999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 t = normalize(cross(up, v));
|
||||
vec3 b = cross(t, v);
|
||||
|
||||
// sample point with polar coordinates (r, phi)
|
||||
float a = 1.0 / (1.0 + v.z);
|
||||
float r = sqrt(u.x);
|
||||
float phi = (u.y < a) ? u.y / a * PI : PI + (u.y - a) / (1.0 - a) * PI;
|
||||
float p1 = r * cos(phi);
|
||||
float p2 = r * sin(phi) * ((u.y < a) ? 1.0 : v.z);
|
||||
|
||||
// compute normal
|
||||
vec3 h = p1 * t + p2 * b + sqrt(max(0.0, 1.0 - p1*p1 - p2*p2)) * v;
|
||||
|
||||
// unstretch
|
||||
h = normalize(vec3(alpha * h.x, alpha * h.y, max(0.0, h.z)));
|
||||
return h;
|
||||
}
|
||||
|
||||
float prefilteredImportanceSampling(float ipdf) {
|
||||
// See: "Real-time Shading with Filtered Importance Sampling", Jaroslav Krivanek
|
||||
// Prefiltering doesn't work with anisotropy
|
||||
const float numSamples = float(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT);
|
||||
const float invNumSamples = 1.0 / float(numSamples);
|
||||
const float dim = float(1u << uint(IBL_MAX_MIP_LEVEL));
|
||||
const float omegaP = (4.0 * PI) / (6.0 * dim * dim);
|
||||
const float invOmegaP = 1.0 / omegaP;
|
||||
const float K = 4.0;
|
||||
float omegaS = invNumSamples * ipdf;
|
||||
float mipLevel = clamp(log2(K * omegaS * invOmegaP) * 0.5, 0.0, IBL_MAX_MIP_LEVEL);
|
||||
return mipLevel;
|
||||
}
|
||||
|
||||
vec3 isEvaluateIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) {
|
||||
// TODO: for a true anisotropic BRDF, we need a real tangent space
|
||||
vec3 up = abs(n.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
vec3 up = abs(n.z) < 0.9999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
|
||||
mat3 tangentToWorld;
|
||||
tangentToWorld[0] = normalize(cross(up, n));
|
||||
@@ -240,36 +309,13 @@ vec3 isEvaluateIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) {
|
||||
float linearRoughness = pixel.linearRoughness;
|
||||
float a2 = linearRoughness * linearRoughness;
|
||||
|
||||
const float dim = float(1 << uint(IBL_MAX_MIP_LEVEL));
|
||||
const float omegaP = (4.0 * PI) / (6.0 * dim * dim);
|
||||
const float invOmegaP = 1.0 / omegaP;
|
||||
const float K = 4.0;
|
||||
|
||||
// IMPORTANT: Keep numSample = 1 << numSampleBits
|
||||
const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT);
|
||||
const uint numSampleBits = uint(log2(float(numSamples)));
|
||||
const float invNumSamples = 1.0 / float(numSamples);
|
||||
|
||||
vec3 indirectSpecular = vec3(0.0);
|
||||
for (uint i = 0u; i < numSamples; i++) {
|
||||
// Compute Hammersley sequence
|
||||
// TODO: these should come from uniforms
|
||||
// TODO: we should do this with logical bit operations
|
||||
uint t = i;
|
||||
uint bits = 0u;
|
||||
for (uint j = 0u; j < numSampleBits; j++) {
|
||||
bits = bits * 2u + (t - (2u * (t / 2u)));
|
||||
t /= 2u;
|
||||
}
|
||||
vec2 u = vec2(float(i), float(bits)) * invNumSamples;
|
||||
|
||||
// Importance sampling D_GGX
|
||||
float phi = 2.0 * PI * u.x;
|
||||
float cosTheta2 = (1.0 - u.y) / (1.0 + (a2 - 1.0) * u.y);
|
||||
float cosTheta = sqrt(cosTheta2);
|
||||
float sinTheta = sqrt(1.0 - cosTheta2);
|
||||
|
||||
vec3 h = tangentToWorld * vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
|
||||
vec2 u = hammersley(i);
|
||||
vec3 h = tangentToWorld * importanceSamplingNdfDggx(u, linearRoughness);
|
||||
|
||||
// Since anisotropy doesn't work with prefiltering, we use the same "faux" anisotropy
|
||||
// we do when we use the prefiltered cubemap
|
||||
@@ -278,26 +324,23 @@ vec3 isEvaluateIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) {
|
||||
// Compute this sample's contribution to the brdf
|
||||
float NoL = dot(n, l);
|
||||
if (NoL > 0.0) {
|
||||
float NoH = dot(n, h);
|
||||
float LoH = max(dot(l, h), 0.0);
|
||||
float NoH = cosTheta;
|
||||
|
||||
// PDF inverse (we must use D_GGX() here, which is used to generate samples)
|
||||
float ipdf = (4.0 * LoH) / (D_GGX(linearRoughness, NoH, h) * NoH);
|
||||
|
||||
// See: "Real-time Shading with Filtered Importance Sampling", Jaroslav Krivanek
|
||||
// Prefiltering doesn't work with anisotropy
|
||||
float omegaS = invNumSamples * ipdf;
|
||||
float mipLevel = clamp(log2(K * omegaS * invOmegaP) * 0.5, 0.0, IBL_MAX_MIP_LEVEL);
|
||||
float mipLevel = prefilteredImportanceSampling(ipdf);
|
||||
|
||||
// we use texture() instead of textureLod() to take advantage of mipmapping
|
||||
vec3 L = decodeDataForIBL(texture(light_iblSpecular, l, mipLevel));
|
||||
|
||||
float D = distribution(linearRoughness, NoH, h);
|
||||
float V = visibility(pixel.roughness, linearRoughness, NoV, NoL, LoH);
|
||||
vec3 F = fresnel(pixel.f0, LoH);
|
||||
vec3 Fr = F * (D * V * NoL * ipdf * invNumSamples);
|
||||
|
||||
vec3 Fr = F * (D * V * ipdf * NoL);
|
||||
|
||||
// we use texture() instead of textureLod() to take advantage of mipmapping
|
||||
vec3 env = decodeDataForIBL(texture(light_iblSpecular, l, mipLevel));
|
||||
indirectSpecular += (Fr * env) * invNumSamples;
|
||||
indirectSpecular += (Fr * L);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user