Fixed genergy conservation problem in specular lobe

This commit is contained in:
2025-12-31 03:12:29 +09:00
parent 84b2504a6f
commit 5c988108ef
5 changed files with 228 additions and 86 deletions

View File

@@ -1,6 +1,8 @@
#include "Rendering/Renderer.h"
#include "Algorithm/PathTracing.h"
#include <string.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;
@@ -65,6 +67,47 @@ static inline bool aov_needs_lighting_samples(aov_flags_t flags)
return has_flag(flags, AOV_BEAUTY) || has_flag(flags, AOV_DIRECT) || has_flag(flags, AOV_INDIRECT);
}
static inline vec4s running_average_vec4(vec4s prev_avg, vec4s sample, uint32_t prev_count)
{
float n = (float)prev_count;
float inv = 1.0f / (n + 1.0f);
vec4s out;
out.x = (prev_avg.x * n + sample.x) * inv;
out.y = (prev_avg.y * n + sample.y) * inv;
out.z = (prev_avg.z * n + sample.z) * inv;
out.w = 1.0f;
return out;
}
static inline void clear_render_target(render_target_t* target)
{
if (target == NULL || target->buffer == NULL)
{
return;
}
size_t pixel_count = (size_t)target->width * target->height;
memset(target->buffer, 0, pixel_count * sizeof(vec4s));
for (size_t i = 0; i < pixel_count; ++i)
{
target->buffer[i].w = 1.0f;
}
}
static inline void clear_aov_targets(render_job_t* job)
{
if (job == NULL || job->aov_target == NULL)
{
return;
}
for (uint8_t i = 0; i < MAX_AOV_TARGET; ++i)
{
clear_render_target(job->aov_target[i]);
}
}
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};
@@ -83,8 +126,8 @@ static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s
uint32_t pos_hash = hash_uint32(pixel_id);
// Apply AA
float du = sobol_sample_scrambled(sobol_idx, PRNG_LENS_U, pos_hash);
float dv = sobol_sample_scrambled(sobol_idx, PRNG_LENS_V, pos_hash);
float du = sobol_sample_scrambled(sobol_idx, PRNG_FILTER_U, pos_hash);
float dv = sobol_sample_scrambled(sobol_idx, PRNG_FILTER_V, pos_hash);
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;
@@ -111,6 +154,48 @@ static void render_pixel(const rendering_config_t* config, scene_t* scene, vec3s
*pixel_output = accumulated_color;
}
static void render_pixel_one_sample(const rendering_config_t* config,
scene_t* scene,
vec3s coord,
uint32_t x,
uint32_t y,
uint32_t sample_index,
aov_flags_t aov_flags,
aov_output_t* pixel_output)
{
uint32_t pixel_id = y * config->width + x;
uint32_t sobol_idx = pixel_id * config->sample_count + (sample_index + 1);
uint32_t pos_hash = hash_uint32(pixel_id);
vec3s camera_right = quat_get_right(scene->camera.rotation);
vec3s camera_up = quat_get_up(scene->camera.rotation);
float du = sobol_sample_scrambled(sobol_idx, PRNG_FILTER_U, pos_hash);
float dv = sobol_sample_scrambled(sobol_idx, PRNG_FILTER_V, pos_hash);
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));
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 out = {0};
path_trace_aov(scene, ray, sobol_idx, config->max_depth, aov_flags, &out);
*pixel_output = out;
}
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)
@@ -133,48 +218,123 @@ static inline void update_aov(render_target_t** target, const aov_output_t* aov,
update_aov_pixel_if_exist(&target[AOV_INDIRECT_INDEX], aov->indirect, 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;
// Reset progressive state whenever we (re)start.
job->progressive_sample_index = 0;
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++)
if (job->rendering_mode == RENDER_PROGRESSIVE)
{
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);
// Progressive mode: accumulate 1 spp per pass until sample_count or stop requested.
clear_aov_targets(job);
job->is_done = false;
for (y = tile_y_0; y < tile_y_1; y++)
uint32_t width = job->config->width;
uint32_t height = job->config->height;
for (uint32_t s = 0; s < job->config->sample_count; ++s)
{
for (x = tile_x_0; x < tile_x_1; x++)
if (job->is_done)
{
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);
break;
}
int64_t x, y;
#pragma omp parallel for schedule(dynamic, 1) default(none) \
shared(job, coord, width, height, s) \
private(x, y)
for (y = 0; y < (int64_t)height; ++y)
{
for (x = 0; x < (int64_t)width; ++x)
{
if (job->is_done)
{
continue;
}
aov_output_t pixel = {0};
render_pixel_one_sample(job->config, job->scene, coord, (uint32_t)x, (uint32_t)y, s, job->aov_flags, &pixel);
// Accumulate lighting AOVs; write non-stochastic AOVs once.
if (has_flag(job->aov_flags, AOV_BEAUTY))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_BEAUTY_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.beauty, s);
render_target_set_pixel(job->aov_target[AOV_BEAUTY_INDEX], (uint32_t)x, (uint32_t)y, avg);
}
if (s == 0)
{
update_aov_pixel_if_exist(&job->aov_target[AOV_AlBEDO_INDEX], pixel.albedo, (uint32_t)x, (uint32_t)y);
update_aov_pixel_if_exist(&job->aov_target[AOV_NORMAL_INDEX], pixel.normal, (uint32_t)x, (uint32_t)y);
update_aov_pixel_if_exist(&job->aov_target[AOV_DEPTH_INDEX], (vec4s){pixel.depth, pixel.depth, pixel.depth, 1.0f}, (uint32_t)x, (uint32_t)y);
update_aov_pixel_if_exist(&job->aov_target[AOV_POSITION_INDEX], pixel.position, (uint32_t)x, (uint32_t)y);
}
if (has_flag(job->aov_flags, AOV_DIRECT))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_DIRECT_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.direct, s);
render_target_set_pixel(job->aov_target[AOV_DIRECT_INDEX], (uint32_t)x, (uint32_t)y, avg);
}
if (has_flag(job->aov_flags, AOV_INDIRECT))
{
vec4s prev = render_target_get_pixel(job->aov_target[AOV_INDIRECT_INDEX], (uint32_t)x, (uint32_t)y);
vec4s avg = running_average_vec4(prev, pixel.indirect, s);
render_target_set_pixel(job->aov_target[AOV_INDIRECT_INDEX], (uint32_t)x, (uint32_t)y, avg);
}
}
}
job->progressive_sample_index = s + 1;
}
tile_done:;
job->is_done = true;
return;
}
else
{
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;
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:;
}
job->is_done = true;
}
// TODO: A-Trous denoising
job->is_done = true;
}
void render_job_free(render_job_t* job)