0
$\begingroup$

I have a forward path tracer. Essentially, it looks flat:

enter image description here

This is the backward path tracer in comparison:

enter image description here

My question is: How do I get pseudorandom lighting like with the backward path tracer, when the eye ray always hits a light in forward path tracing?

Here is my code for the forward path tracer:

float trace_path_forward2(const vec3 eye, const vec3 direction, const float hue, const float eta)
{
    const vec3 mask = hsv2rgb(vec3(hue, 1.0, 1.0));

    const vec3 light_o = get_random_light_pos(50, eye, direction, hue, eta);//getRandomPointOnTriangle(A.pos, B.pos, C.pos);
    const vec3 light_d = RandomUnitVector(prng_state);//vec3(0);//cosWeightedRandomHemisphereDirection(get_normal_by_light_index(tri_index), prng_state);


    const float energy = 1.0;

    int step_count = 0;
    vec3 step_locations[max_bounces + 2];
    vec3 step_directions[max_bounces + 2];

    step_locations[step_count] = light_o;
    step_directions[step_count] = light_d;
    step_count++;

    if(false == is_clear_line_of_sight(eye, light_o))
    {   
        vec3 o = light_o;
        vec3 d = light_d;

        for(int i = 0; i < max_bounces; i++)
        {
            traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o, 0.001, d, 10000.0, 0);

            if(rayPayload.dist == -1)
                return 0.0;

            const vec3 hitPos = o + d * rayPayload.dist;

            o = hitPos + rayPayload.normal * 0.01;
            d = cosWeightedRandomHemisphereDirection(rayPayload.normal, prng_state);
            
            step_locations[step_count] = o;
            step_directions[step_count] = d;
            step_count++;

            if(true == is_clear_line_of_sight(eye, step_locations[step_count - 1]))
                break;
        }
    }

    step_locations[step_count] = eye;
    step_directions[step_count] = direction;
    step_count++;

    

    // Reverse the path
    uint start = 0;
    uint end = step_count - 1;

    while(start < end) 
    { 
        vec3 temp = step_locations[start];  
        step_locations[start] = step_locations[end]; 
        step_locations[end] = temp; 

        temp = step_directions[start];  
        step_directions[start] = step_directions[end]; 
        step_directions[end] = temp; 

        start++;
        end--; 
    }

    
    for(int i = 1; i < step_count - 1; i++)
    {
        step_directions[i] = (step_locations[i + 1] - step_locations[i]);
    }
    



    float ret_colour = 0;
    float local_colour = energy;
    float total = 0;

    for(int i = 0; i < step_count - 1; i++)
    {
        vec3 step_o = step_locations[i];
        vec3 step_d = step_directions[i];



        traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, step_o, 0.001, step_d, 10000.0, 0);

        local_colour *= (rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);

        total += mask.r;
        total += mask.g;
        total += mask.b;

        if(i == step_count - 2 || (rayPayload.color.r > 1.0 || rayPayload.color.g > 1.0 || rayPayload.color.b > 1.0))
        {
            ret_colour += local_colour;
            break;
        }
    }

    return ret_colour / total;
}

The code for the backward path tracer is:

float trace_path_backward(const int steps, const vec3 origin, const vec3 direction, const float hue, const float eta)
{
    vec3 o = origin;
    vec3 d = direction;

    const float energy = 1;
    const float caustic_energy = 1;//energy;

    float ret_colour = 0;
    float local_colour = energy;
    float total = 0;

    bool doing_refraction_caustic = false;

    const vec3 mask = hsv2rgb(vec3(hue, 1.0, 1.0));

    for(int i = 0; i < steps; i++)
    {
        const float tmin = 0.001;
        const float tmax = 10000.0;

        traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o, tmin, d, tmax, 0);

        total += mask.r;
        total += mask.g;
        total += mask.b;

        if(doing_refraction_caustic)
        {
            local_colour += caustic_energy*(rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);
            doing_refraction_caustic = false;
        }
        else
        {
            local_colour *= (rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);
        }

        // If hit the sky
        if(rayPayload.dist == -1.0)
        {
            ret_colour += local_colour;
            break;
        }

        // If this is simply the final step
        // then don't throw away perfectly 
        // good data
        if(i == steps - 1)
        {
            ret_colour += local_colour;
            break;
        }

        // Hit a light
        if(rayPayload.color.r > 1
        || rayPayload.color.g > 1
        || rayPayload.color.b > 1)
        {
            ret_colour += local_colour;
            break;
        }

        vec3 hitPos = o + d * rayPayload.dist;

        if(stepAndOutputRNGFloat(prng_state) <= rayPayload.opacity)
        {
            RayPayload r = rayPayload;
            
            if(rayPayload.subsurface > 0)
            {
                vec3 o_subsurface = hitPos - rayPayload.normal * 0.001 + RandomUnitVector(prng_state)*0.001;
                vec3 d_subsurface = RandomUnitVector(prng_state);

                traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o_subsurface, tmin, d_subsurface, tmax, 0);

                total += mask.r;
                total += mask.g;
                total += mask.b;

                local_colour += rayPayload.subsurface*(rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);
            }

            if(rayPayload.reflector > 0)
            {
                vec3 o_reflect = hitPos + rayPayload.normal * 0.01;
                vec3 d_reflect = reflect(d, rayPayload.normal);

                traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o_reflect, tmin, d_reflect, tmax, 0);

                total += mask.r;
                total += mask.g;
                total += mask.b;

                local_colour += rayPayload.reflector*(rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);
            }

            rayPayload = r;

            vec3 o_scatter = hitPos + rayPayload.normal * 0.01;
            vec3 d_scatter = cosWeightedRandomHemisphereDirection(rayPayload.normal, prng_state);

            o = o_scatter;
            d = d_scatter;
        }
        else
        {
            doing_refraction_caustic = true;

            vec3 o_transparent = vec3(0.0);
            vec3 d_transparent = vec3(0.0);

            // Incoming
            if(dot(d, rayPayload.normal) <= 0.0)
            {
                o_transparent = hitPos.xyz - rayPayload.normal * 0.01f;
                d_transparent = refract(d, rayPayload.normal, eta);
            }
            else // Outgoing
            {
                vec3 temp_dir = refract(d, -rayPayload.normal, 1.0 / eta);

                if(temp_dir != vec3(0.0))
                {
                    o_transparent = hitPos.xyz + rayPayload.normal * 0.01f;
                    d_transparent = mix(temp_dir, RandomUnitVector(prng_state), rayPayload.subsurface);//   temp_dir;
                }
                else
                {
                    // Total internal reflection
                    o_transparent = hitPos.xyz - rayPayload.normal * 0.01f;
                    d_transparent = mix(reflect(d, -rayPayload.normal), RandomUnitVector(prng_state), rayPayload.subsurface);//reflect(d, -rayPayload.normal);
                }
            }

            o = o_transparent;
            d = normalize(d_transparent);
        }
    }

    return ret_colour / total;
}

Any comments or questions are appreciated. I think that I'm missing something that might be obvious to another person. If there's anything that I can add to this question, please let me know.

$\endgroup$
2
  • 2
    $\begingroup$ I don't understand why in your forward path tracer that: you will not simulate light bouncing when your emitter is in the direct line of sight if(false == is_clear_line_of_sight(eye, light_o)) ..., and even if the light pos is not in the direct line of sight, you will stop the bounce once that position after bouncing is in the direct line of sight. Normally, a forward path tracer is implemented almost in the same way as the backward one: you simulate light bounces and splat the result (light rasterize them) on to the screen. $\endgroup$
    – Enigmatisms
    Commented Mar 7 at 17:22
  • $\begingroup$ Thank you for your input. This is what I meant. So, basically, I need to do ray-image plane intersection? $\endgroup$ Commented Mar 7 at 18:49

1 Answer 1

0
$\begingroup$

I swear that I've been struggling with this code for months, and after a hiatus, I have now mostly solved the problem. I just need to add in reflections and refractions.

Instead of getting line of sight between the eye and some points, I am now getting the line of sight with the first hitpos generated by tracing the ray from the eye into the scene. I am also not overwriting the directions taken by the rays. Thank you so much Enigmatisms!

enter image description here

float trace_path_forward2(const vec3 eye, const vec3 direction, const float hue, const float eta)
{
    const vec3 mask = hsv2rgb(vec3(hue, 1.0, 1.0));

    const vec3 light_o = get_random_light_pos(50, eye, direction, hue, eta);
    const vec3 light_d = RandomUnitVector(prng_state);

    const float energy = 1.0;

    int step_count = 0;
    vec3 step_locations[max_bounces + 3];
    vec3 step_directions[max_bounces + 3];

    traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, eye, 0.001, direction, 10000.0, 0);

    if(rayPayload.dist == -1)
        return 0.0;

    const vec3 first_hit_pos = eye + direction * rayPayload.dist;

    step_locations[step_count] = light_o;
    step_directions[step_count] = light_d;
    step_count++;

    if(false == is_clear_line_of_sight(first_hit_pos, light_o))
    {   
        vec3 o = light_o;
        vec3 d = light_d;

        for(int i = 0; i < max_bounces; i++)
        {
            traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, o, 0.001, d, 10000.0, 0);

            if(rayPayload.dist == -1)
                return 0.0;

            const vec3 hitPos = o + d * rayPayload.dist;

            o = hitPos + rayPayload.normal * 0.01;
            d = cosWeightedRandomHemisphereDirection(rayPayload.normal, prng_state);
            
            step_locations[step_count] = o;
            step_directions[step_count] = d;
            step_count++;

            if(true == is_clear_line_of_sight(first_hit_pos, step_locations[step_count - 1]))
                break;
        }
    }




    step_locations[step_count] = first_hit_pos;
    step_directions[step_count] = light_d;
    step_count++;

    step_locations[step_count] = eye;
    step_directions[step_count] = direction;
    step_count++;






    // Reverse the path
    uint start = 0;
    uint end = step_count - 1;

    while(start < end) 
    { 
        vec3 temp = step_locations[start];  
        step_locations[start] = step_locations[end]; 
        step_locations[end] = temp; 

        temp = step_directions[start];
        step_directions[start] = step_directions[end]; 
        step_directions[end] = temp; 

        start++;
        end--; 
    }


    float ret_colour = 0;
    float local_colour = energy;
    float total = 0;

    for(int i = 0; i < step_count - 1; i++)
    {
        vec3 step_o = step_locations[i];
        vec3 step_d = step_directions[i];

        traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, step_o, 0.001, step_d, 10000.0, 0);

        local_colour *= (rayPayload.color.r*mask.r + rayPayload.color.g*mask.g + rayPayload.color.b*mask.b);

        total += mask.r;
        total += mask.g;
        total += mask.b;

        if(i == step_count - 2 || 
        (rayPayload.color.r > 1.0 || 
        rayPayload.color.g > 1.0 || 
        rayPayload.color.b > 1.0))
        {
            ret_colour += local_colour;
            break;
        }
    }

    return ret_colour / total;
}
$\endgroup$
4
  • 1
    $\begingroup$ You see, the global illumination is not correct. Maybe it's because that you break too early from the ray bouncing (first for loop). Can you explain what does the function is_clear_line_of_sight do in your code, in detail? I am confused by the logic here. $\endgroup$
    – Enigmatisms
    Commented Mar 8 at 2:14
  • $\begingroup$ You're right. I was mislead into thinking that it's working in general. I appreciate your efforts. The function traces a ray from point a to point b, to determine if there is anything occluding b from a. $\endgroup$ Commented Mar 8 at 16:34
  • $\begingroup$ ... but it goes to show that splatting is absolutely unnecessary! That's neat. $\endgroup$ Commented Mar 8 at 16:35
  • $\begingroup$ I don't think splatting is unnecessary. It bares the same idea of next event estimation: if the light source is small, we make a shadow connection to a point on the light source. Now in this reversed settings, camera is indeed small and for non-lens-based cameras, you will have zero probability for a ray to hit the camera. So for each vertex on the traced path, a shadow connection to the camera should be made and evaluate this direct component (how much radiance can the camera receive through this shadow connection, if the connection is not occluded). $\endgroup$
    – Enigmatisms
    Commented Mar 9 at 2:34

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.