Say I have constructed a path, using bidirectional path tracing (BDPT), consisting of a light subpath $y$ with $s=2$ vertices and a camera subpath $z$ with $t=3$ camera vertices.

Now I want to compute the contribution (measurement contribution over sampling density) of the same path considered under the BDPT strategy with $s'=3$ and $t'=2$ light and camera vertices, respectively.

Doing this involves the computation of the density of sampling the direction $\omega_{y_{s-1}\to z_{t-1}}$. Assuming the path has positive luminance, can I expect this density to be positive?

I'm only considering "lambertian reflection" components of the involved BSDFs. Here's my example code in PBRT:

auto const omega1 = pbrt::Normalize(z[t - 1].p() - y[s - 1].p());
auto const omega2 = y[s - 1].si.wo;
auto const p = y[s - 1].si.bsdf->Pdf(omega1, omega2, lambertian_reflection);

auto const c =
    g(scene, z[t - 1], y[s - 1]) *                          // g(Z_{t-1} ↔ Y_{s-1})
    z[t - 1].f(y[s - 1], pbrt::TransportMode::Radiance) *   // f_s(Y_{s-1} → Z_{t-1} → Z_{t-2})
    y[s - 1].f(z[t - 1], pbrt::TransportMode::Importance);  // f_s(Y_{s-2} → Y_{s-1} → Z_{t-1})

I've encountered a situation where c is nonzero, but the density p is 0 since internally si.bsdf->Pdf checks whether omega1 and omega2 are on the same side of the hemisphere induced by the normal at y[s - 1] and they were not in that situation.

However, this is confusing. If they are not on the same side, shouldn't this imply that y[s - 2] and z[t - 1] are occluded from each other (and hence the geometry term g(scene, z[t - 1], y[s - 1]) should be $0$?). Am I missing something?

EDIT: Note that the density of sampling a path of length $k$ using the $(s,t)$-strategy is $q_{s,\:t}(x)=q_{0,\:t}(x_0,\ldots,x_{t-1})q_{s,\:0}(x_t,\ldots,x_k)$, where $$\begin{equation}\label{eq:path-tracing-strategy}\begin{split}q_{0,\:t}(z)=&\prod_{i=1}^kg(z_{i-1}\leftrightarrow z_i)\left.\begin{cases}p_0\left(z_0,\omega_{z_0\to z_1}\right)&\text{, if }q_0(z_0)>0\\0&\text{, otherwise}\end{cases}\right\}\\&\prod_{i=2}^kp\left(z_{i-1},\omega_{z_{i-1}\to z_{i-2}},\omega_{z_{i-1}\to z_i}\right)\end{split}\end{equation}$$ where $p_0$ is the density of sampling the inital camera ray (with respect to the path throughput measure as defined by Veach), $q_0$ is the marginal density of sampling the initial vertex and $p$ is the conditional density (with respect to projected solid angle) of sampling the third argument given the first and second argument. The form of $q_{s,\:0}$ is analogous.

EDIT 2: The relevant PBRT code:

Float BxDF::Pdf(const Vector3f &wo, const Vector3f &wi) const {
    return SameHemisphere(wo, wi) ? AbsCosTheta(wi) * InvPi : 0;

inline bool SameHemisphere(const Vector3f &w, const Vector3f &wp) {
    return w.z * wp.z > 0;

// BSDF Method Definitions
Spectrum BSDF::f(const Vector3f &woW, const Vector3f &wiW,
                 BxDFType flags) const {
    ProfilePhase pp(Prof::BSDFEvaluation);
    Vector3f wi = WorldToLocal(wiW), wo = WorldToLocal(woW);
    if (wo.z == 0) return 0.;
    bool reflect = Dot(wiW, ng) * Dot(woW, ng) > 0;
    Spectrum f(0.f);
    for (int i = 0; i < nBxDFs; ++i)
        if (bxdfs[i]->MatchesFlags(flags) &&
            ((reflect && (bxdfs[i]->type & BSDF_REFLECTION)) ||
             (!reflect && (bxdfs[i]->type & BSDF_TRANSMISSION))))
            f += bxdfs[i]->f(wo, wi);
    return f;

EDIT 3: Here is an example: PBRT is using a local coordinate system for shading where the $z$-axis is aligned with the shading normal. The following picture shows the local coordinates of the geometric normal at $y_1$, the direction wi from $y_1$ to $z_2$ and the direction wo from $y_1$ to $y_0$,

wi = (0.93675665128065100,0.34934790470253263,-0.021047987110668076)
wo = (0.37751541702974800,0.28880688322120657,0.87981401108922330)
ng (local) = (0.025315795417292675,-0.0035753744029505263,0.99967311017165450)

enter image description here

ng is slightly off from the shading normal ns = (0,0,1). With respect to ng, wi and wo are in the same hemisphere, but with respect to ns they are not.

  • $\begingroup$ A non-zero path can always be reinterpreted as being generated by another strategy (provided that both have the same support). Say you have path 1: $x_1, ..., x_N$, and you know that $x_1$ is generated with probability $p_1(x_1)$, $x_2$ with $p_2(x_2)$ etc. Then the probability of the path is $\Pi p_i(x_i)$. Now introduce a different strategy with probabilities $q_1(x_1), q_2(x_2), ...$, then the probability for the path being generated by the new strategy is $\Pi q_i(x_i)$. All you have to do is compute the new probabilities to reinterpret it. $\endgroup$
    – lightxbulb
    Mar 18, 2020 at 19:02
  • $\begingroup$ More precisely, the two primary estimators are: $\frac{f(x_1,...,x_N)}{\Pi_i p_i(x_i)}$ and $\frac{f(x_1,...,x_N)}{\Pi_i q_i(x_i)}$. In general you would prefer the one that yields a lower variance (that is, where the probability density better matches $f$). $\endgroup$
    – lightxbulb
    Mar 18, 2020 at 19:05
  • $\begingroup$ @lightxbulb Thank you for your comment. Everything you wrote is clear to me; see my edit. I think that the problem is that .si.bsdf->Pdf is converting the pased directions to local coordinates and then determines whether they lie on the same hemisphere via pbrt::SameHemisphere. In my example, the $z$-components have different signs and hence SameHemisphere returns false. On the other hand, in f the computation of bool reflect = Dot(wiW, ng) * Dot(woW, ng) > 0; involves the normal and this product is positive. $\endgroup$
    – 0xbadf00d
    Mar 18, 2020 at 19:35
  • $\begingroup$ @lightxbulb In my example c is not zero, but p is zero. And I'm unsure whether there is an error in my computations, since I'd actually expect that the density p of sampling the direction from $y_{s-1}$ to $z_{t-1}$ should be positive (assuming that the contribution under the strategy under which the path was constructed is nonvanishing). $\endgroup$
    – 0xbadf00d
    Mar 18, 2020 at 19:38
  • $\begingroup$ @lightxbulb Actually, I don't understand why the computation in SameHemisphere doesn't involve the surface normal (ng). Maybe it does implicitly, since the directions are transformed via WorldToLocal. $\endgroup$
    – 0xbadf00d
    Mar 18, 2020 at 20:15


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.