Search code examples
c++math3drenderingraytracing

Path tracing: how to ensure the new direction vector is a valid direction vector with respect to a BSDF?


Given the BSDF function and the Normal vector of the intersection point in world space, how can I generate a new direction vector wi that is valid? Does the method for generating valid wis change based on the BSDF?

Here's an example of what I'm thinking to do for ideal diffuse material the BSDF: I generate a new direction vector wi as points on a unit hemisphere as follow and then compute the dot product of the produced vector with the Normal vector. If the dot product result is positive the direction vector wi is valid. Otherwise I negate wi as suggested here.

Here's how I get a random wi:

float theta = 2 * M_PI * uniform01(generator);
float phi = acos(uniform01(generator));
float x = sin(phi) * cos(theta);
float y = sin(phi) * sin(theta);
float z = cos(phi);
Vector3f wi(x, y, z);

if (dot(wi, Normal) > 0){
    return wi;
}
else{
    return -wi;
}

However, this doesn't seem to be the right approach based on a conversation I had with someone recently. Apparently the new direction vector produced this way is somehow not in the right space (not sure whether it was world or object space) and could only work if my material is ideal diffuse. So I will have to apply some transformations in order to be able to get the right wi. Is this correct? If so, can someone provide a solution that includes doing such transformation? Also, is there a general way to ensure all of my produced wis are valid with respect to the BSDF (not just ideal diffuse)?


Solution

  • You are generating your wi in tangent space, with z pointing along the normal. It is neither world nor object space, and you will have to transform into world space or do all your calculations in tangent space (or shading space, they're both the same).

    What you should be doing, as it will make your life much easier when doing other calculations, is to transform your wo to tangent space, and do all calculations in it. Over here, you would choose z to be your normal, and generate x and y vectors orthogonal to it.

    A function for generating the coordinate system like this would be:

    void GenerateCoordinateSystem(const Vector& normalized, Vector& outFirst, Vector& outSecond)
    {
        if (std::abs(normalized.x) > std::abs(normalized.y))
        {
            outFirst = Vector(-normalized.z, 0, normalized.x) /
                std::sqrt(normalized.x * normalized.x + normalized.z * normalized.z);
        }
        else
        {
            outFirst = Vector(0, normalized.z, -normalized.y) /
                std::sqrt(normalized.z * normalized.z + normalized.y * normalized.y);
        }
        outSecond = Cross(normalized, outFirst);
    }
    

    Where normalized is the normal (z vector) at the point, and outFirst and outSecond are your x and y vectors respectively.

    Now that you have your tangent space vectors, you transform into them by (wo is in object space):

    Vector x, y;
    GenerateCoordinateSystem(normal, x, y);
    Vector tangentWo = Vector(Dot(wo, x), Dot(wo, y), Dot(wo, normal));
    

    You would then generate your wi as you do above. Then, to get wi in object space, you would:

    Vector objWi = wi.X * x + wi.Y * y + wi.Z * normal;
    

    If you want them in world space, you would obviously have to multiply them by the object's transformation matrix.

    Uniform hemisphere sampling does ensure that your wi is valid for any BSDF, however, you have to ensure that the pdf for the BSDF takes into account the distribution.