#include "Algorithm/PathTracing.h" #include "Algorithm/RayIntersection.h" #include "Algorithm/BSDF.h" #include "Algorithm/Sobol.h" #include "Lighting/LightEvaluation.h" // TODO: Implement a faster methods like BVH, KD-Tree or uniform grid acceleration // TODO: Split the diffuse and specular into different Monte Carlo, so we can decide the sample count for each one vec3s path_trace(const scene_t* scene, const ray_t ray, const uint32_t sample_index, const int max_depth) { const triangle_collection_t* triangles = &scene->triangles; const material_collection_t* materials = &scene->materials; const light_collection_t* lights = &scene->lights; vec3s accumulated_color = glms_vec3_zero(); vec3s throughput = glms_vec3_one(); ray_t active_ray = ray; vec3s prev_normal = glms_vec3_zero(); float pdf_bsdf = 1.0f; // Even though pdf_bsdf should be avaliable after the first bounce. For seafty, we set it to 1.0f for the first iteration. sobol_state_t sobol_state = {sample_index, 0}; int depth = 0; while (depth < max_depth) { hit_result_t closest_hit = ray_intersect_closest(triangles, active_ray); if (!closest_hit.hit) { vec3s sky_light = evaluate_bsdf_sky(scene, NULL, &sobol_state); if (depth > 0) { // Have to multiply the weight since we evaluate the sky at each bounce float pdf_nee = pdf_cosine_weighted_hemisphere(prev_normal, active_ray.direction); float weight = power_heuristic(pdf_bsdf, pdf_nee); sky_light = glms_vec3_scale(sky_light, weight); } accumulated_color = glms_vec3_add(accumulated_color, sky_light); // TODO: Skybox break; } // Add the emission of the hit material to the accumulated color material_t* hit_material = &materials->buffer[triangles->buffer[closest_hit.triangle_id].material_id]; vec3s emission = hit_material->emission; accumulated_color = glms_vec3_add(accumulated_color, glms_vec3_mul(throughput, emission)); light_shading_context_t light_context = { .normal = closest_hit.normal, .hit_point = closest_hit.point, .wo = active_ray.direction, .throughput = throughput, .triangles = triangles, .material = hit_material }; // Running the light loop. // TODO: Implementing other light types. for (uint32_t i = 0; i < lights->directional_light_count; i++) { vec3s l = evaluate_bsdf_directional(lights->directional_lights[i], &light_context, &sobol_state); accumulated_color = glms_vec3_add(accumulated_color, l); } vec3s sky_light = evaluate_bsdf_sky(scene, &light_context, &sobol_state); accumulated_color = glms_vec3_add(accumulated_color, sky_light); // Bounce and prepare for the next iteration vec3s wo = glms_vec3_negate(active_ray.direction); // We need to negate the direction of the incoming ray vec3s wi = sample_material_bsdf(hit_material, closest_hit.normal, wo, &sobol_state, &pdf_bsdf); if (pdf_bsdf <= 0.0f) { break; } shading_context_t shading_context = { .normal = closest_hit.normal, .wi = wi, .wo = wo }; vec3s bsdf = evaluate_material_bsdf(hit_material, &shading_context); float cos_theta = fmaxf(0.0f, glms_vec3_dot(wi, closest_hit.normal)); throughput = glms_vec3_mul(throughput, glms_vec3_scale(bsdf, cos_theta / pdf_bsdf)); // We do Russian roulette to decide whether to continue tracing or terminate the path if (depth > 2) { float q = fminf(glms_vec3_max(throughput), 0.95f); if (random_float() > q) { break; // Terminate the path } // Keep the energy of the path by scaling the throughput throughput = glms_vec3_scale(throughput, 1.0f / q); } active_ray.origin = glms_vec3_add(closest_hit.point, glms_vec3_scale(closest_hit.normal, FLT_EPSILON)); active_ray.direction = wi; prev_normal = closest_hit.normal; depth++; } return accumulated_color; }