#define FLIP_Y #include "Rendering/Scene.h" #include "Algorithm/PathTracing.h" scene_t scene_create(const uint64_t triangle_count, const uint8_t material_count, const uint32_t punctual_light_count) { scene_t scene = {0}; scene.triangles = triangle_collection_create(triangle_count); scene.materials = material_collection_create(material_count); scene.lights = light_collection_create(punctual_light_count, 16); // NOTE: We just fixed the max directional light count to 16. scene.camera = camera_create( (vec3s){0.0f, 0.0f, 5.0f}, (vec3s){0.0f, 0.0f, -1.0f}, (vec3s){0.0f, 1.0f, 0.0f}, 0.025f, 0.036f, 16.0f / 9.0f ); return scene; } static inline void ensure_camera_aspect_ratio(camera_t* camera, const rendering_config_t config) { if (fabsf((float)config.width / config.height - camera->aspect_ratio) > FLT_EPSILON) { *camera = camera_create( camera->position, camera->forward, camera->up, camera->focal_length, camera->size_x, (float)config.width / (float)config.height ); } } static inline vec2s compute_ndc(float x, float y, uint32_t width, uint32_t height) { return (vec2s){ .x = x / (float)width, #ifdef FLIP_Y .y = 1.0f - y / (float)height #else .y = y / (float)height #endif }; } static void screne_render_pixel(scene_t* scene, const rendering_config_t config, const vec3s coord, const uint32_t x, const uint32_t y, vec4s* pixel_color) { vec4s accumulated_color = glms_vec4_zero(); *pixel_color = accumulated_color; uint32_t pixel_id = y * config.width + x; for (uint16_t k = 0; k < config.sample_count; k++) { vec2s position_ndc = compute_ndc((float)x, (float)y, config.width, config.height); float screen_x = position_ndc.x * 2.0f - 1.0f; float screen_y = position_ndc.y * 2.0f - 1.0f; float sensor_offset_x = screen_x * scene->camera.size_x / 2.0f; float sensor_offset_y = screen_y * scene->camera.size_y / 2.0f; vec3s image_plane_point = coord; image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(scene->camera.right, sensor_offset_x)); image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(scene->camera.up, sensor_offset_y)); ray_t ray = { .origin = scene->camera.position, .direction = glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position)) }; // TODO: Hash it uint32_t sobol_idx = pixel_id * config.sample_count + (k + 1); vec3s out_color = path_trace(scene, ray, sobol_idx, config.max_depth); // TODO: Handle alpha vec4s color = {.x = out_color.x, .y = out_color.y, .z = out_color.z, .w = 1.0f}; accumulated_color = glms_vec4_add(accumulated_color, color); } *pixel_color = glms_vec4_scale(accumulated_color, 1.0f / (float)config.sample_count); } bool scene_render_tile(scene_t* scene, rendering_context_t* ctx, render_target_t* render_target, const rendering_config_t config, const uint32_t tile_index, tile_t* tile_out) { if (ctx->is_done) { return false; } if(!ctx->is_init) { ensure_camera_aspect_ratio(&scene->camera, config); ctx->tile_count_x = (config.width + config.bucket_size - 1) / config.bucket_size; ctx->tile_count_y = (config.height + config.bucket_size - 1) / config.bucket_size; ctx->coord = glms_vec3_add(scene->camera.position, glms_vec3_scale(scene->camera.forward, scene->camera.focal_length)); ctx->is_init = true; } if (tile_index >= ctx->tile_count_x * ctx->tile_count_y) { return false; } uint32_t tile_x_0 = tile_index % ctx->tile_count_x * config.bucket_size; uint32_t tile_y_0 = tile_index / ctx->tile_count_x * config.bucket_size; uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + config.bucket_size, config.width); uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + config.bucket_size, config.height); tile_out->x = tile_x_0; tile_out->y = tile_y_0; tile_out->width = tile_x_1 - tile_x_0; tile_out->height = tile_y_1 - tile_y_0; int64_t x, y; // OpenMP requires these to be declared outside the parallel region. Also, they need to be signed integers. To avoid overflow, we need to use int64_t rendering_config_t config_copy = config; // Have to copy it, otherwise OpenMP will cause it become invalid. Not sure if this is a bug or not. #pragma omp parallel for schedule(dynamic, 1) default(none) \ shared(tile_x_0, tile_x_1, tile_y_0, tile_y_1, config_copy) \ private(x, y) for (y = tile_y_0; y < tile_y_1; y++) { for (x = tile_x_0; x < tile_x_1; x++) { vec4s pixel_color; screne_render_pixel(scene, config_copy, ctx->coord, (uint32_t)x, (uint32_t)y, &pixel_color); render_target_set_pixel(render_target, (uint32_t)x, (uint32_t)y, pixel_color); } } if (tile_index == ctx->tile_count_x * ctx->tile_count_y - 1) { ctx->is_done = true; } return true; } render_target_t scene_render(scene_t* scene, const rendering_config_t config) { ensure_camera_aspect_ratio(&scene->camera, config); // The actual float buffer inside the render target is on the heap, copy return shoudl be fine. render_target_t render_target = render_target_create(config.width, config.height); uint32_t tile_count_x = (config.width + config.bucket_size - 1) / config.bucket_size; uint32_t tile_count_y = (config.height + config.bucket_size - 1) / config.bucket_size; uint32_t tile_count = tile_count_x * tile_count_y; float inv_sample = 1.0f / config.sample_count; vec3s coord = glms_vec3_add(scene->camera.position, glms_vec3_scale(scene->camera.forward, scene->camera.focal_length)); int64_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region. Also, they need to be signed integers. To avoid overflow, we need to use int64_t rendering_config_t config_copy = config; #pragma omp parallel for schedule(dynamic, 1) default(none) \ shared(tile_count_x, tile_count_y, tile_count, config_copy, coord, inv_sample, render_target) \ private(x, y, tile_index) for (tile_index = 0; tile_index < tile_count; tile_index++) { uint32_t tile_x_0 = (uint32_t)tile_index % tile_count_x * config.bucket_size; uint32_t tile_y_0 = (uint32_t)tile_index / tile_count_x * config.bucket_size; uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + config.bucket_size, config.width); uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + config.bucket_size, config.height); for (y = tile_y_0; y < tile_y_1; y++) { for (x = tile_x_0; x < tile_x_1; x++) { vec4s pixel_color; screne_render_pixel(scene, config_copy, coord, (uint32_t)x, (uint32_t)y, &pixel_color); render_target_set_pixel(&render_target, (uint32_t)x, (uint32_t)y, pixel_color); } } } return render_target; } void scene_free(scene_t* scene) { triangle_collection_free(&scene->triangles); material_collection_free(&scene->materials); light_collection_free(&scene->lights); }