I' m playing with nori and trying to sample BRDFs consisting of a diffuse and a glossy microfacet reflectance lobe:

where
k_d is diffuse reflectance of type Color3f
k_s equals 1 - max(k_d)
w_i is incident solid angle
w_o is outgoing solid angle
h_r is microsurface normal(half normal)
n is macrosurface normal
Implement BRDF term and PDF are straightforward:
Code: Select all
/// Evaluate the BRDF for the given pair of directions
Color3f eval(const BSDFQueryRecord &bRec) const {
if (bRec.measure != ESolidAngle
|| Frame::cosTheta(bRec.wi) <= 0
|| Frame::cosTheta(bRec.wo) <= 0)
return Color3f(0.0f);
Color3f diffuse = m_kd * INV_PI;
// Evaluate half vector
Vector3f m = h_r(bRec);
// Evaluate microfacet distribution fucntion (Beckmann)
float d = beckmann_d(m);
// Evaluate Fresnel term f
float f = fresnel(bRec.wi, m);
// Evaluate Shadowing-Masking Function G
float g = smith_g(bRec.wi, bRec.wo, m);
Color3f glossy = m_ks * d * f * g /
(4 * std::abs(Frame::cosTheta(bRec.wi) * Frame::cosTheta(bRec.wo)));
return diffuse + glossy;
}
/// Evaluate the sampling density of sample() wrt. solid angles
float pdf(const BSDFQueryRecord &bRec) const {
if (bRec.measure != ESolidAngle
|| Frame::cosTheta(bRec.wi) <= 0
|| Frame::cosTheta(bRec.wo) <= 0)
return 0.0f;
Vector3f m = h_r(bRec);
float d = beckmann_d(m);
// po(o)
return m_kd.maxCoeff() * INV_PI * Frame::cosTheta(bRec.wo) +
m_ks * d * std::abs(Frame::cosTheta(m)) / (4.f * std::abs(m.dot(bRec.wi)));
}
Code: Select all
Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const
{
if (Frame::cosTheta(bRec.wi) <= 0)
return Color3f(0.0f);
bRec.measure = ESolidAngle;
if (sample.y() < m_ks)
{
// Generate samples of Beckmann distribution
// Equations (28)(29) in [Wal07]
float alpha = alpha_b(bRec.wi);
float tan_theta_2_m = - alpha * alpha * std::log(sample.x()), phi_m = 2 * M_PI * sample.y() / m_ks,
cos_theta_m = 1.f / std::sqrt(tan_theta_2_m + 1), sin_theta_m = std::sqrt(tan_theta_2_m) * cos_theta_m;
// [Wal05] (5)
Vector3f m(sin_theta_m * std::cos(phi_m), sin_theta_m * std::sin(phi_m), cos_theta_m);
// Consider reflection only [Wal07] (39)
bRec.wo = 2 * std::abs(bRec.wi.dot(m)) * m - bRec.wi;
}
else
{
/* Warp a uniformly distributed sample on [0,1]^2
to a direction on a cosine-weighted hemisphere */
bRec.wo = squareToCosineHemisphere(Point2f(sample.x(), (sample.y() - m_ks) / m_kd.maxCoeff()));
}
float _pdf = this->pdf(bRec);
if (_pdf > 0.f)
{
return this->eval(bRec) * Frame::cosTheta(bRec.wo) / _pdf;
}
return Color3f(0.0f);
}
And for each grid cubature method is used to integrate the above pdf() to get a corresponding ground truth frequency.
But I found deviation between these freqencies tends to larger especially when incident wi towards to grazing angle:

I think diffuse part is not well sampled since it is dominated at this time.
So I change to the below method, which is similar to select between reflection and refraction by using Fresnel term in [Wal07]:
Code: Select all
/// Sample the BRDF
Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const {
if (Frame::cosTheta(bRec.wi) <= 0)
return Color3f(0.0f);
bRec.measure = ESolidAngle;
// Note m_ks + m_kd.maxCoeff() = 1.
if (sample.y() < m_ks)
{
// Generate samples of Beckmann distribution
// Equations (28)(29) in [Wal07]
float alpha = alpha_b(bRec.wi);
float tan_theta_2_m = - alpha * alpha * std::log(sample.x()), phi_m = 2 * M_PI * sample.y() / m_ks,
cos_theta_m = 1.f / std::sqrt(tan_theta_2_m + 1), sin_theta_m = std::sqrt(tan_theta_2_m) * cos_theta_m;
// [Wal05] (5)
Vector3f m(sin_theta_m * std::cos(phi_m), sin_theta_m * std::sin(phi_m), cos_theta_m);
// Equation (39) in [Wal07]
bRec.wo = 2 * std::abs(bRec.wi.dot(m)) * m - bRec.wi;
float f = fresnel(bRec.wi, m), g = smith_g(bRec.wi, bRec.wo, m);
return std::abs(bRec.wi.dot(m)) * f * g / (std::abs(Frame::cosTheta(bRec.wi) * Frame::cosTheta(m)));
}
else
{
/* Warp a uniformly distributed sample on [0,1]^2
to a direction on a cosine-weighted hemisphere */
bRec.wo = squareToCosineHemisphere(Point2f(sample.x(), (sample.y() - m_ks) / m_kd.maxCoeff()));
return m_kd;
}
}

It seems that the first way of mixing BRDFs is not plausible, and its underlying magic is the balance heuristic mutiple importance sampling below[Laf96], am I right?

Any suggestion is very appreciated.

References:
[Laf96] Mathematical Models and Monte Carlo Algorithms for Physically Based Rendering
[Wal07] Microfacet Models for Refraction through Rough Surfaces
Cheers,
Guo