mix BRDFs correctly back

Board: Home Board index Raytracing General Development

(L) [2012/09/27] [tby guo] [mix BRDFs correctly] Wayback!

Hi guys,
I' m playing with [LINK https://github.com/wjakob/nori nori] and trying to sample BRDFs consisting of a diffuse and a glossy microfacet reflectance lobe:
[IMG #1 Image]
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: [LINK # 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)));
   }

At first like PBRT and Mitsuba render, monte caro weights/samples are generated as:
Code: [LINK # 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);
   }

For validation, these resulting NONZERO samples are binned into 180*360 grids according to their direction, to get a empirical freqency.
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:
[IMG #2 Image]
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: [LINK # 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;
      }
   }

Now the deviation is alleviated:
[IMG #3 Image]
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?
[IMG #4 Image]
Any suggestion is very appreciated. [SMILEY ;)]
References:
[Laf96] Mathematical Models and Monte Carlo Algorithms for Physically Based Rendering
[Wal07] Microfacet Models for Refraction through Rough Surfaces
Cheers,
Guo
[IMG #1]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/f_s_zps77480a3f.png
[IMG #2]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/old_zpsd6a2bf3f.png
[IMG #3]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/new_zps1db1cc81.png
[IMG #4]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/b_h_zps2e4a3a4c.png
(L) [2012/10/17] [tby guo] [mix BRDFs correctly] Wayback!

OK, let me show off a result by path racing with next event estimate:
[IMG #1 Image]
256 samples are generated for each pixel.
The above eval() and pdf() are used in luminaire sampling, while sample() is used in BRDF sampling.
However, since they are different estimates, I am not sure it is reasonable this way.
Any suggestions? [SMILEY :shock:]
[IMG #1]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/1_zps196335a1.png
(L) [2012/10/17] [tby graphicsMan] [mix BRDFs correctly] Wayback!

I can't be 100% sure, but by visual inspection, your MIS is not being done correctly.  You're simply calling pdf(bRec), but you also need to factor in the probability of the two choices.  In other words, you need the two separate pdfs(bRec), and you need to combine them into a mixed pdf to normalize.  You need something like eval(bRec) * cos / (m_ks*pdfBeck(bRec) + (1-m_ks)*pdfCos(bRec)).
  Brian
(L) [2012/10/24] [tby guo] [mix BRDFs correctly] Wayback!

>> graphicsMan wrote:I can't be 100% sure, but by visual inspection, your MIS is not being done correctly.  You're simply calling pdf(bRec), but you also need to factor in the probability of the two choices.  In other words, you need the two separate pdfs(bRec), and you need to combine them into a mixed pdf to normalize.  You need something like eval(bRec) * cos / (m_ks*pdfBeck(bRec) + (1-m_ks)*pdfCos(bRec)).
  Brian
Hi Brian,
Thanks for your suggestion, variance seems to be lower in this way. And I think mixing samples of two estimates should be reasonable since they converge to the same expectation. But still I cannot bypass the t-test in nori render. [SMILEY :?]
Anyway, here is an eye candy [SMILEY :D]
[IMG #1 Image]
Cheers,
Guo
[IMG #1]:Not scraped: https://web.archive.org/web/20161005163329im_/http://i1339.photobucket.com/albums/o713/guozhou/ajax-path-envmap_zps920c6ea3.png

back