it's me again! :D

I have finally implemented area lights, but without modifying the emission value of the material, this is what it looks like with indirect light only, this is what it looks like with direct only and this is both direct+indirect!

Clearly there is something wrong going on with the direct light sampling.

This is the function for one light:

    float pdf, dist;
    glm::vec3 wi;
    Ray visibilityRay;
    auto li = light->li(sampler, hr, visibilityRay, wi, pdf, dist);
    if (scene->visibilityCheck(visibilityRay, EPS, dist - EPS, light))
        return glm::dot(hr.normal, wi) * material->brdf(hr, wi) * li / pdf;
    return BLACK;

In case of the area light, li is the following:

    glm::vec3 samplePoint, sampleNormal;
    shape->sample(sampler, samplePoint, sampleNormal, pdf);
    wi = (samplePoint - hr.point);
    dist = glm::length(wi);
    wi = glm::normalize(wi);
    vRay.origin = hr.point + EPS * wi;
    vRay.direction = wi;
    float cosT = glm::dot(sampleNormal, -wi);
    auto solidAngle = (cosT * this->area()) / (dist * dist);
    if(cosT > 0.0f) {
        return this->color * solidAngle;
    } else {
        return BLACK;

And I think the code to sample the spheres is correct

    glm::vec3 sampleUniformSphere(std::shared_ptr<Sampler> &sampler)
        float z = 1 - 2 * sampler->getSample();
        float r = sqrt(std::max(0.0f, 1.0f - z * z));
        float phi = 2 * PI * sampler->getSample();
        return glm::vec3(
            r * cos(phi),
            r * sin(phi),

    void Sphere::sample(std::shared_ptr<Sampler> &sampler, glm::vec3 &point, glm::vec3 &normal, float &pdf) const
        glm::vec3 local = sampleUniformSphere(sampler);
        normal = glm::normalize(local);
        point = m_obj2World.transformPoint(radius * local);
        pdf = 1.0f / area();

It looks like either the solid angle or the distance attenuation aren't working correctly. This is a Mitsuba3 render with roughly the same values.



1 Answer 1


I think, the problem lies in dividing the NEE radiance by PDF (inverse area) twice. Since NEE typically uniformly samples an emitter and then samples a point (via area product measure) on that emitter (if it is not infinitesimal), so first we will have: $$ L_e(x_i) / \text{PDF}(x_i) $$ as the exitant radiance at sampled emitter point $x_i$, and in your case, PDF will be 1 / area() and $L_e$ is just a constant vector this->color. Since this sampling is done via area product measure, the measure conversion (to solid angle measure) should be performed, which yields: $$ \frac{L_e(x_i)\cos(\theta_e)}{\Vert x_i - x_e \Vert^2\text{PDF}(x_i)} $$ So, this->color * solidAngle should be the formula above, which is correct. However, in the code that calls NEE method, you have:

 glm::dot(hr.normal, wi) * material->brdf(hr, wi) * li / pdf;

pdf here is, again, $\text{PDF}(x_i)$, which has already been in the denominator. The pdf for the denominator here, should be the pdf of sampling the emitter (say, if you have multiple glowing spheres in the scene, you want to choose one of them for this NEE).

I compared the rendering results in my own renderer:

(incorrect) divide PDF twice (correct) divide PDF once
enter image description here enter image description here

Image on the left is noticably brighter.

  • $\begingroup$ I understand now! Thank you so much for your explanation, it was very very very clear. $\endgroup$
    – Syrinxos
    Commented Apr 24 at 20:46

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.