I am trying to add GGX and PBR textures to my GPU ray tracer. The result is very strange, it's getting darker for each frame.
float ao = texAO.sample(textureSampler, hitRecord.uv).r;
float3 albedo = texAlbedo.sample(textureSampler, hitRecord.uv).rgb;
albedo = pow(albedo, 2.2); //albedo = float3(1.0);
float metallic = texMetallic.sample(textureSampler, hitRecord.uv).r;
float3 tNormal = texNormal.sample(textureSampler, hitRecord.uv).xyz;
tNormal = normalize(tNormal * 2 - 1);
float roughness = texRoughness.sample(textureSampler, hitRecord.uv).r;
roughness = max(roughness, 0.001);
float3 nx, ny;
CoordinateSystem(normal, nx, ny);
float3x3 stw = { nx, ny, normal };
normal = stw * tNormal;
CoordinateSystem(normal, nx, ny);
stw = { nx, ny, normal };
float3 wi, wh;
float3 wo = transpose(stw) * (ray.direction);
auto pSpecular = 1/(2-metallic);
auto r0 = randomF(seed);
auto r1 = randomF(seed);
if (randomF(seed) > pSpecular) {
float sinTheta = sqrt(r0);
float cosTheta = sqrt(1-r0);
float phi = 2.0 * M_PI_F * r1;
float x = sinTheta * cos(phi);
float y = sinTheta * sin(phi);
float z = cosTheta;
wi = { x, y, z };
wh = normalize(wo + wi); // not specular
} else {
auto a1 = roughness;
auto a2 = a1 * a1;
auto theta = acos(sqrt((1-r0) / ((a2-1)*r0+1)));
auto phi = 2 * M_PI_F * r1;
auto x = sin(theta) * cos(phi);
auto y = sin(theta) * sin(phi);
auto z = cos(theta) * 1;
//wo = transpose(stw) * (ray.direction);
wh = normalize( {x, y, z} );
wi = reflect(wo, wh);
}
if (wi.z <= 0 || dot(wh, wi) <= 0) { return false; }
auto D = NDF(wh, roughness);
auto G = GX(wo, wi, roughness);
auto F = FX(wi, wh, albedo, metallic);
float denominator = 4.0f * abs(wo.z * wi.z);
auto specular = D * G * F / max(FLT_MIN, denominator);
auto diffuse = albedo / M_PI_F;
auto kS = F;
auto kD = (1 - metallic) * (1.0f - kS);
ray = Ray(hitRecord.p, stw * wi);
scatRecord.attenuation = (kD * diffuse + specular) * ao;
//auto pdf = PDF(wh, roughness);
//scatRecord.attenuation /= abs(pdf);
return true;
// Normal Distribution Function
float NDF(const thread float3& h, float roughness) {
// GGX / Trowbridge-Reitz
float a1 = roughness;
float a2 = a1 * a1;
float NoH = abs(h.z);
float d = (NoH * a2 - NoH) * NoH + 1;
return a2 / (d * d * M_PI_F);
}
// Geometry Function
float GX(const thread float3& wo, const thread float3& wi, float roughness) {
// Schlick, remap roughness and k
if (wo.z >= 0 || wi.z <= 0)
return 0;
float k = pow(roughness + 1, 2) / 8.f;
k = pow(roughness, 4);
float G1_wo = wo.z / (wo.z*(1 - k) + k);
float G1_wi = wi.z / (wi.z*(1 - k) + k);
return G1_wo * G1_wi;
}
// Fresnel
float3 FX(const thread float3& wi, const thread float3& h, const thread float3 albedo, float metallic) {
// Schlick’s approximation
// use a Spherical Gaussian approximation to replace the power.
// slightly more efficient to calculate and the difference is imperceptible
float3 F0 = mix(float3(0.04f), albedo, metallic);
float HoWi = dot(h, wi);
return F0 + (float3(1.0f) - F0) * pow(2.0f, (-5.55473f * HoWi - 6.98316f) * HoWi);
}