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$ Commented Mar 7, 2024 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, 2024 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$ Commented Mar 8, 2024 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, 2024 at 16:34
  • $\begingroup$ ... but it goes to show that splatting is absolutely unnecessary! That's neat. $\endgroup$ Commented Mar 8, 2024 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$ Commented Mar 9, 2024 at 2:34

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.