203 lines
7.4 KiB
C
203 lines
7.4 KiB
C
#include "Rendering/Renderer.h"
|
|
#include "Algorithm/PathTracing.h"
|
|
|
|
static inline void create_target_if_required(aov_flags_t aov_flags, aov_flags_t target_flag, render_target_t** render_target, uint32_t width, uint32_t height)
|
|
{
|
|
render_target_t* temp = NULL;
|
|
if (has_flag(aov_flags, target_flag))
|
|
{
|
|
temp = (render_target_t*)malloc(sizeof(render_target_t));
|
|
if (temp == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
render_target_init(width, height, temp);
|
|
}
|
|
|
|
*render_target = temp;
|
|
}
|
|
|
|
bool renderer_aov_target_init(render_job_t* job, aov_flags_t aov_flags)
|
|
{
|
|
job->aov_target = (render_target_t**)malloc(sizeof(render_target_t*) * MAX_AOV_TARGET);
|
|
if (job->aov_target == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
create_target_if_required(aov_flags, AOV_BEAUTY, &job->aov_target[AOV_BEAUTY_INDEX], job->config->width, job->config->height);
|
|
create_target_if_required(aov_flags, AOV_AlBEDO, &job->aov_target[AOV_AlBEDO_INDEX], job->config->width, job->config->height);
|
|
create_target_if_required(aov_flags, AOV_NORMAL, &job->aov_target[AOV_NORMAL_INDEX], job->config->width, job->config->height);
|
|
create_target_if_required(aov_flags, AOV_DEPTH, &job->aov_target[AOV_DEPTH_INDEX], job->config->width, job->config->height);
|
|
create_target_if_required(aov_flags, AOV_POSITION, &job->aov_target[AOV_POSITION_INDEX], job->config->width, job->config->height);
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void ensure_camera_aspect_ratio(camera_t* camera, const rendering_config_t* config)
|
|
{
|
|
float aspect_ratio = (float)config->width / config->height;
|
|
if (fabsf((float)config->width / config->height - camera->size_x / camera->size_y) > 0.001f)
|
|
{
|
|
camera->size_y = camera->size_x / aspect_ratio;
|
|
camera->fov_y = 2.0f * (float)atan(camera->size_x / (2.0f * camera->focal_length * aspect_ratio));
|
|
}
|
|
}
|
|
|
|
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 inline uint16_t get_sample_count(uint16_t sample_count, aov_index_t index)
|
|
{
|
|
switch (index)
|
|
{
|
|
case AOV_BEAUTY_INDEX:
|
|
return sample_count;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s coord, uint32_t x, uint32_t y, aov_flags_t aov_flags, aov_output_t* pixel_output)
|
|
{
|
|
aov_output_t accumulated_color = {0};
|
|
|
|
uint32_t pixel_id = y * config->width + x;
|
|
uint16_t sample_count = config->sample_count;
|
|
float inv_sample = 1.0f / (float)sample_count;
|
|
|
|
vec3s camera_right = quat_get_right(scene->camera.rotation);
|
|
vec3s camera_up = quat_get_up(scene->camera.rotation);
|
|
|
|
for (uint16_t k = 0; k < sample_count; k++)
|
|
{
|
|
// TODO: Hash it
|
|
uint32_t sobol_idx = pixel_id * config->sample_count + (k + 1);
|
|
|
|
// Apply AA
|
|
float du = sobol_sample(sobol_idx, PRNG_FILTER_U);
|
|
float dv = sobol_sample(sobol_idx, PRNG_FILTER_V);
|
|
vec2s position_ndc = compute_ndc((float)x + du, (float)y + dv, 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 * 0.5f;
|
|
float sensor_offset_y = screen_y * scene->camera.size_y * 0.5f;
|
|
|
|
vec3s image_plane_point = coord;
|
|
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(camera_right, sensor_offset_x));
|
|
image_plane_point = glms_vec3_add(image_plane_point, glms_vec3_scale(camera_up, sensor_offset_y));
|
|
|
|
// Calculate initial spread angle for ray differentials
|
|
float pixel_height = scene->camera.size_y / (float)config->height;
|
|
float spread_angle = atanf(pixel_height / scene->camera.focal_length);
|
|
|
|
ray_t ray = ray_create(scene->camera.position, glms_vec3_normalize(glms_vec3_sub(image_plane_point, scene->camera.position)), 0.0f, spread_angle);
|
|
|
|
aov_output_t aov_output = {0};
|
|
if (has_flag(aov_flags, AOV_BEAUTY))
|
|
{
|
|
aov_output.beauty = path_trace(scene, ray, sobol_idx, config->max_depth);
|
|
}
|
|
|
|
if (aov_flags != AOV_BEAUTY)
|
|
{
|
|
render_aov(scene, ray, sobol_idx, config->max_depth, &aov_output);
|
|
}
|
|
|
|
accumulate_aov(&accumulated_color, &aov_output, inv_sample);
|
|
}
|
|
|
|
*pixel_output = accumulated_color;
|
|
}
|
|
|
|
static inline void update_aov_pixel_if_exist(render_target_t** target, vec4s color, uint32_t x, uint32_t y)
|
|
{
|
|
if (*target == NULL || (*target)->buffer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
render_target_set_pixel(*target, x, y, color);
|
|
}
|
|
|
|
static inline void update_aov(render_target_t** target, const aov_output_t* aov, uint32_t x, uint32_t y)
|
|
{
|
|
update_aov_pixel_if_exist(&target[AOV_BEAUTY_INDEX], aov->beauty, x, y);
|
|
update_aov_pixel_if_exist(&target[AOV_AlBEDO_INDEX], aov->albedo, x, y);
|
|
update_aov_pixel_if_exist(&target[AOV_NORMAL_INDEX], aov->normal, x, y);
|
|
update_aov_pixel_if_exist(&target[AOV_DEPTH_INDEX], (vec4s){aov->depth, aov->depth, aov->depth, 1.0f}, x, y);
|
|
update_aov_pixel_if_exist(&target[AOV_POSITION_INDEX], aov->position, x, y);
|
|
}
|
|
|
|
// TODO: Progressive rendering
|
|
void renderer_start(render_job_t* job)
|
|
{
|
|
ensure_camera_aspect_ratio(&job->scene->camera, job->config);
|
|
|
|
uint32_t tile_count_x = (job->config->width + job->config->bucket_size - 1) / job->config->bucket_size;
|
|
uint32_t tile_count_y = (job->config->height + job->config->bucket_size - 1) / job->config->bucket_size;
|
|
uint32_t tile_count = tile_count_x * tile_count_y;
|
|
|
|
vec3s coord = glms_vec3_add(job->scene->camera.position, glms_vec3_scale(quat_get_forward(job->scene->camera.rotation), job->scene->camera.focal_length));
|
|
|
|
int64_t x, y, tile_index; // OpenMP requires these to be declared outside the parallel region.
|
|
#pragma omp parallel for schedule(dynamic, 1) default(none) \
|
|
shared(tile_count_x, tile_count_y, tile_count, coord, job) \
|
|
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 * job->config->bucket_size;
|
|
uint32_t tile_y_0 = (uint32_t)tile_index / tile_count_x * job->config->bucket_size;
|
|
uint32_t tile_x_1 = (uint32_t)fmin(tile_x_0 + job->config->bucket_size, job->config->width);
|
|
uint32_t tile_y_1 = (uint32_t)fmin(tile_y_0 + job->config->bucket_size, job->config->height);
|
|
|
|
for (y = tile_y_0; y < tile_y_1; y++)
|
|
{
|
|
for (x = tile_x_0; x < tile_x_1; x++)
|
|
{
|
|
if (job->is_done)
|
|
{
|
|
goto tile_done;
|
|
}
|
|
|
|
aov_output_t pixel_output = {0};
|
|
render_pixel(job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, job->aov_flags, &pixel_output);
|
|
update_aov(job->aov_target, &pixel_output, (uint32_t)x, (uint32_t)y);
|
|
}
|
|
}
|
|
tile_done:;
|
|
}
|
|
|
|
// TODO: A-Trous denoising
|
|
|
|
job->is_done = true;
|
|
}
|
|
|
|
void render_job_free(render_job_t* job)
|
|
{
|
|
if (job == NULL || job->aov_target == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < MAX_AOV_TARGET; i++)
|
|
{
|
|
if (job->aov_target[i] != NULL)
|
|
{
|
|
render_target_free(job->aov_target[i]);
|
|
free(job->aov_target[i]);
|
|
}
|
|
}
|
|
}
|