From b774a454be0727be98bbb66cda828cb9675f09f3 Mon Sep 17 00:00:00 2001 From: abalfoort Date: Mon, 2 Jun 2025 09:34:02 +0200 Subject: [PATCH 1/3] Implement ReSTIR sampling Code from Sirtsu55: https://github.com/Sirtsu55/Q2RTX/commit/33d91e32f0a7bb194280e787e4ef4fc43aeb031a --- baseq2/q2rtx.menu | 1 + cmake/compileShaders.cmake | 1 + doc/client.md | 7 + inc/refresh/models.h | 1 + src/refresh/vkpt/bsp_mesh.c | 4 + src/refresh/vkpt/main.c | 135 +++--- src/refresh/vkpt/path_tracer.c | 11 +- src/refresh/vkpt/shader/constants.h | 6 +- src/refresh/vkpt/shader/direct_lighting.rgen | 366 ++++++++++++++-- src/refresh/vkpt/shader/global_textures.h | 62 +-- src/refresh/vkpt/shader/global_ubo.h | 6 +- .../vkpt/shader/indirect_lighting.rgen | 8 +- src/refresh/vkpt/shader/light_lists.h | 370 +++++++++-------- src/refresh/vkpt/shader/path_tracer_rgen.h | 117 +++--- src/refresh/vkpt/shader/restir.h | 393 ++++++++++++++++++ src/refresh/vkpt/shader/vertex_buffer.h | 2 + src/refresh/vkpt/transparency.c | 13 +- src/refresh/vkpt/vertex_buffer.c | 2 +- src/refresh/vkpt/vkpt.h | 11 +- 19 files changed, 1152 insertions(+), 364 deletions(-) create mode 100644 src/refresh/vkpt/shader/restir.h diff --git a/baseq2/q2rtx.menu b/baseq2/q2rtx.menu index 50c12dc59..5f72a0629 100644 --- a/baseq2/q2rtx.menu +++ b/baseq2/q2rtx.menu @@ -147,6 +147,7 @@ begin video toggle "AMD FSR 1.0" flt_fsr_enable range --status "lower is sharper" "AMD FSR 1.0 sharpness" flt_fsr_sharpness 0 2 0.01 pairs "global illumination" pt_num_bounce_rays low 0.5 medium 1 high 2 + pairs "direct light sampling" pt_restir "RIS light" 0 "ReSTIR, high quality" 1 "ReSTIR, half" 2 "ReSTIR, quarter" 3 pairs "reflection/refraction depth" pt_reflect_refract off 0 1 1 2 2 4 4 8 8 toggle --status "turn some monitors in the game into security camera views" \ "security cameras" pt_cameras diff --git a/cmake/compileShaders.cmake b/cmake/compileShaders.cmake index 544bcb650..dd10070d6 100644 --- a/cmake/compileShaders.cmake +++ b/cmake/compileShaders.cmake @@ -16,6 +16,7 @@ set(SHADER_SOURCE_DEPENDENCIES ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/precomputed_sky.glsl ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/precomputed_sky_params.h ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/projection.glsl + ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/restir.h ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/sky.h ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/tiny_encryption_algorithm.h ${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/tone_mapping_utils.glsl diff --git a/doc/client.md b/doc/client.md index 237b7ae89..10deaa5f3 100644 --- a/doc/client.md +++ b/doc/client.md @@ -930,6 +930,13 @@ Selects the projection to use for rendering. Default value is 0. #### `pt_reflect_refract` Number of reflection or refraction bounces to trace. Default value is 2. +#### `pt_restir` +Switch for experimental direct light sampling algorithms. Default value is 1. +- 0 — RIS light sampling. +- 1 — ReSTIR, high quality. +- 2 — ReSTIR El-Cheapo, uses half of the shadow rays. +- 3 — ReSTIR El-Very-Cheapo, uses one quarter of the shadow rays. + #### `pt_roughness_override` Global override for roughness of all materials. Negative values mean there is no override. Default value is -1. diff --git a/inc/refresh/models.h b/inc/refresh/models.h index d9becfc57..ac34125fd 100644 --- a/inc/refresh/models.h +++ b/inc/refresh/models.h @@ -118,6 +118,7 @@ typedef struct light_poly_s { int cluster; int style; float emissive_factor; + uint32_t type; } light_poly_t; typedef struct maliasmesh_s maliasmesh_t; diff --git a/src/refresh/vkpt/bsp_mesh.c b/src/refresh/vkpt/bsp_mesh.c index 7117a6c43..292c3c234 100644 --- a/src/refresh/vkpt/bsp_mesh.c +++ b/src/refresh/vkpt/bsp_mesh.c @@ -959,6 +959,7 @@ collect_one_light_poly_entire_texture(bsp_t *bsp, mface_t *surf, mtexinfo_t *tex light.material = texinfo->material; light.style = light_style; + light.type = DYNLIGHT_POLYGON; if(!get_triangle_off_center(light.positions, light.off_center, NULL, 1.f)) continue; @@ -1114,6 +1115,7 @@ collect_one_light_poly(bsp_t *bsp, mface_t *surf, mtexinfo_t *texinfo, int model light->material = texinfo->material; light->style = light_style; light->emissive_factor = emissive_factor; + light->type = DYNLIGHT_POLYGON; VectorCopy(instance_positions[0], light->positions + 0); VectorCopy(instance_positions[i1], light->positions + 3); VectorCopy(instance_positions[i2], light->positions + 6); @@ -1328,6 +1330,7 @@ collect_sky_and_lava_light_polys(bsp_mesh_t *wm, bsp_t* bsp) } light.style = 0; + light.type = DYNLIGHT_POLYGON; if (!get_triangle_off_center(light.positions, light.off_center, NULL, 1.f)) continue; @@ -1821,6 +1824,7 @@ bsp_mesh_create_custom_sky_prims(uint32_t* prim_ctr, bsp_mesh_t* wm, const bsp_t light->material = 0; light->style = 0; light->cluster = cluster; + light->type = DYNLIGHT_POLYGON; ++*prim_ctr; diff --git a/src/refresh/vkpt/main.c b/src/refresh/vkpt/main.c index 5cc1eaf64..ed196df02 100644 --- a/src/refresh/vkpt/main.c +++ b/src/refresh/vkpt/main.c @@ -1666,20 +1666,14 @@ destroy_vulkan(void) return 0; } -typedef struct entity_hash_s { - unsigned int mesh : 8; - unsigned int model : 9; - unsigned int entity : 14; - unsigned int bsp : 1; -} entity_hash_t; - static int entity_frame_num = 0; static uint32_t model_entity_ids[2][MAX_MODEL_INSTANCES]; static int model_entity_id_count[2]; +static int light_entity_ids[2][MAX_MODEL_LIGHTS]; +static int light_entity_id_count[2]; static int iqm_matrix_count[2]; static ModelInstance model_instances_prev[MAX_MODEL_INSTANCES]; -#define MAX_MODEL_LIGHTS 16384 static int num_model_lights = 0; static light_poly_t model_lights[MAX_MODEL_LIGHTS]; @@ -1808,47 +1802,51 @@ static void fill_model_instance(ModelInstance* instance, const entity_t* entity, instance->material |= MATERIAL_FLAG_LIGHT; } -static void add_dlight_spot(const dlight_t* light, DynLightData* dynlight_data) +static void +add_dlights(const dlight_t* dlights, int num_dlights, light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, int* light_entity_ids) { - // Copy spot data - VectorCopy(light->spot.direction, dynlight_data->spot_direction); - switch(light->spot.emission_profile) + for (int i = 0; i < num_dlights; i++) { - case DLIGHT_SPOT_EMISSION_PROFILE_FALLOFF: - dynlight_data->type |= DYNLIGHT_SPOT_EMISSION_PROFILE_FALLOFF << 16; - dynlight_data->spot_data = floatToHalf(light->spot.cos_total_width) | (floatToHalf(light->spot.cos_falloff_start) << 16); - break; - case DLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE: - dynlight_data->type |= DYNLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE << 16; - dynlight_data->spot_data = floatToHalf(light->spot.total_width) | (light->spot.texture << 16); - break; - } -} + if (*num_lights >= max_lights) + return; -static void -add_dlights(const dlight_t* lights, int num_lights, QVKUniformBuffer_t* ubo) -{ - ubo->num_dyn_lights = 0; + const dlight_t* dlight = dlights + i; + light_poly_t* light = light_list + *num_lights; - for (int i = 0; i < num_lights; i++) - { - const dlight_t* light = lights + i; - - DynLightData* dynlight_data = ubo->dyn_light_data + ubo->num_dyn_lights; - VectorCopy(light->origin, dynlight_data->center); - VectorScale(light->color, light->intensity / 25.f, dynlight_data->color); - dynlight_data->radius = light->radius; - switch(light->light_type) { - case DLIGHT_SPHERE: - dynlight_data->type = DYNLIGHT_SPHERE; - break; - case DLIGHT_SPOT: - dynlight_data->type = DYNLIGHT_SPOT; - add_dlight_spot(light, dynlight_data); - break; - } + light->cluster = BSP_PointLeaf(bsp->nodes, dlight->origin)->cluster; + + entity_hash_t hash; + hash.entity = i + 1; //entity ID + hash.mesh = 0xAA; + + if(light->cluster >= 0) + { + //Super wasteful but we want to have all lights in the same list. + VectorCopy(dlight->origin, light->positions + 0); + VectorScale(dlight->color, dlight->intensity / 25.f, light->color); + light->positions[3] = dlight->radius; + light->material = NULL; + light->style = 0; + + switch(dlight->light_type) { + case DLIGHT_SPHERE: + light->type = DYNLIGHT_SPHERE; + hash.model = 0xFE; + break; + case DLIGHT_SPOT: + light->type = DYNLIGHT_SPOT; + // Copy spot data + VectorCopy(dlight->spot.direction, light->positions + 6); + light->positions[4] = dlight->spot.cos_total_width; + light->positions[5] = dlight->spot.cos_falloff_start; + hash.model = 0xFD; + break; + } + + light_entity_ids[(*num_lights)] = *(uint32_t*)&hash; + (*num_lights)++; - ubo->num_dyn_lights++; + } } } @@ -1860,7 +1858,7 @@ static inline void transform_point(const float* p, const float* matrix, float* r VectorCopy(transformed, result); // vec4 -> vec3 } -static void instance_model_lights(int num_light_polys, const light_poly_t* light_polys, const float* transform) +static void instance_model_lights(int num_light_polys, const light_poly_t* light_polys, const float* transform, entity_hash_t hash) { for (int nlight = 0; nlight < num_light_polys; nlight++) { @@ -1890,10 +1888,15 @@ static void instance_model_lights(int num_light_polys, const light_poly_t* light VectorCopy(src_light->color, dst_light->color); dst_light->material = src_light->material; dst_light->style = src_light->style; + dst_light->type = DYNLIGHT_POLYGON; + + hash.mesh = nlight; //More a light index than a mesh + light_entity_ids[entity_frame_num][num_model_lights] = *(uint32_t*)&hash; num_model_lights++; } } + static const mat4 g_identity_transform = { { 1.f, 0.f, 0.f, 0.f }, { 0.f, 1.f, 0.f, 0.f }, @@ -1973,7 +1976,7 @@ static void process_bsp_entity(const entity_t* entity, int* instance_count) mi->render_buffer_idx = VERTEX_BUFFER_WORLD; mi->render_prim_offset = model->geometry.prim_offsets[0]; - instance_model_lights(model->num_light_polys, model->light_polys, transform); + instance_model_lights(model->num_light_polys, model->light_polys, transform, hash); if (model->geometry.accel) { @@ -2166,7 +2169,13 @@ static void process_regular_entity( mult_matrix_vector(end, transform, offset2); VectorSet(color, 0.25f, 0.5f, 0.07f); - vkpt_build_cylinder_light(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, begin, end, color, 1.5f); + entity_hash_t hash; + hash.entity = entity->id; + hash.model = entity->model; + hash.mesh = 0; + hash.bsp = 0; + + vkpt_build_cylinder_light(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, begin, end, color, 1.5f, hash, light_entity_ids[entity_frame_num]); } *instance_count = current_instance_index; @@ -2241,7 +2250,13 @@ prepare_entities(EntityUploadInfo* upload_info) else create_entity_matrix(transform, (entity_t*)entity); - instance_model_lights(model->num_light_polys, model->light_polys, transform); + entity_hash_t hash; + hash.entity = i + 1; + hash.model = ~entity->model; + hash.mesh = 0; + hash.bsp = 0; + + instance_model_lights(model->num_light_polys, model->light_polys, transform, hash); } } } @@ -2318,6 +2333,8 @@ prepare_entities(EntityUploadInfo* upload_info) memset(instance_buffer->model_current_to_prev, -1, sizeof(instance_buffer->model_current_to_prev)); memset(instance_buffer->model_prev_to_current, -1, sizeof(instance_buffer->model_prev_to_current)); + + memset(instance_buffer->mlight_prev_to_current, ~0u, sizeof(instance_buffer->mlight_prev_to_current)); model_entity_id_count[entity_frame_num] = model_instance_idx; for(int i = 0; i < model_entity_id_count[entity_frame_num]; i++) { @@ -2937,8 +2954,6 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c VectorCopy(sky_matrix[0], ubo->environment_rotation_matrix[0]); VectorCopy(sky_matrix[1], ubo->environment_rotation_matrix[1]); VectorCopy(sky_matrix[2], ubo->environment_rotation_matrix[2]); - - add_dlights(vkpt_refdef.fd->dlights, vkpt_refdef.fd->num_dlights, ubo); if (wm->num_cameras > 0) { @@ -2955,6 +2970,21 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c ubo->num_cameras = wm->num_cameras; } +static void +update_mlight_prev_to_current() +{ + light_entity_id_count[entity_frame_num] = num_model_lights; + for(int i = 0; i < light_entity_id_count[entity_frame_num]; i++) { + entity_hash_t hash = *(entity_hash_t*)&light_entity_ids[entity_frame_num][i]; + if(hash.entity == 0u) continue; + for(int j = 0; j < light_entity_id_count[!entity_frame_num]; j++) { + if(light_entity_ids[entity_frame_num][i] == light_entity_ids[!entity_frame_num][j]) { + vkpt_refdef.uniform_instance_buffer.mlight_prev_to_current[j] = i; + break; + } + } + } +} /* renders the map ingame */ void @@ -3043,9 +3073,12 @@ R_RenderFrame_RTX(refdef_t *fd) vkpt_pt_instance_model_blas(&vkpt_refdef.bsp_mesh_world.geom_sky, g_identity_transform, VERTEX_BUFFER_WORLD, -1, 0); vkpt_pt_instance_model_blas(&vkpt_refdef.bsp_mesh_world.geom_custom_sky, g_identity_transform, VERTEX_BUFFER_WORLD, -1, 0); - vkpt_build_beam_lights(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, fd->entities, fd->num_entities, prev_adapted_luminance); + vkpt_build_beam_lights(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, fd->entities, fd->num_entities, prev_adapted_luminance, light_entity_ids[entity_frame_num], &num_model_lights); + add_dlights(vkpt_refdef.fd->dlights, vkpt_refdef.fd->num_dlights, model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, light_entity_ids[entity_frame_num]); } + update_mlight_prev_to_current(); + vkpt_vertex_buffer_ensure_primbuf_size(upload_info.num_prims); QVKUniformBuffer_t *ubo = &vkpt_refdef.uniform_buffer; diff --git a/src/refresh/vkpt/path_tracer.c b/src/refresh/vkpt/path_tracer.c index 13fd88faa..2ba3c6a42 100644 --- a/src/refresh/vkpt/path_tracer.c +++ b/src/refresh/vkpt/path_tracer.c @@ -104,7 +104,7 @@ cvar_t* cvar_pt_enable_sprites = NULL; extern cvar_t *cvar_pt_caustics; extern cvar_t *cvar_pt_reflect_refract; - +extern cvar_t *cvar_pt_restir; typedef struct QvkGeometryInstance_s { float transform[12]; @@ -1032,7 +1032,7 @@ VkResult vkpt_pt_trace_primary_rays(VkCommandBuffer cmd_buf) { int frame_idx = qvk.frame_counter & 1; - + BUFFER_BARRIER(cmd_buf, .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, @@ -1115,6 +1115,8 @@ vkpt_pt_trace_reflections(VkCommandBuffer cmd_buf, int bounce) VkResult vkpt_pt_trace_lighting(VkCommandBuffer cmd_buf, float num_bounce_rays) { + int frame_idx = qvk.frame_counter & 1; + BEGIN_PERF_MARKER(cmd_buf, PROFILER_DIRECT_LIGHTING); for (int i = 0; i < qvk.device_count; i++) @@ -1139,6 +1141,11 @@ vkpt_pt_trace_lighting(VkCommandBuffer cmd_buf, float num_bounce_rays) BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_COLOR_HF]); BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_COLOR_SPEC]); + if(cvar_pt_restir->value != 0) { + BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_RESTIR_A + frame_idx]); + BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_RESTIR_ID_A + frame_idx]); + } + BUFFER_BARRIER(cmd_buf, .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, diff --git a/src/refresh/vkpt/shader/constants.h b/src/refresh/vkpt/shader/constants.h index e8c13b6c7..172de9932 100644 --- a/src/refresh/vkpt/shader/constants.h +++ b/src/refresh/vkpt/shader/constants.h @@ -114,6 +114,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MAX_TLAS_INSTANCES (MAX_MODEL_INSTANCES + MAX_RESERVED_INSTANCES) #define MAX_LIGHT_SOURCES 32 #define MAX_LIGHT_STYLES 64 +#define MAX_MODEL_LIGHTS 16384 #define TLAS_INDEX_GEOMETRY 0 #define TLAS_INDEX_EFFECTS 1 @@ -162,8 +163,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif // Dynamic light types -#define DYNLIGHT_SPHERE 0 -#define DYNLIGHT_SPOT 1 +#define DYNLIGHT_POLYGON 0 +#define DYNLIGHT_SPHERE 1 +#define DYNLIGHT_SPOT 2 // // Spotlight styles (emission profiles) diff --git a/src/refresh/vkpt/shader/direct_lighting.rgen b/src/refresh/vkpt/shader/direct_lighting.rgen index 6261ecaaa..9fb7520ed 100644 --- a/src/refresh/vkpt/shader/direct_lighting.rgen +++ b/src/refresh/vkpt/shader/direct_lighting.rgen @@ -1,6 +1,7 @@ /* Copyright (C) 2018 Christoph Schied Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved. +Copyright (C) 2022 Jorge Gustavo Martinez This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,11 +31,313 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma optionNV(unroll all) #define ENABLE_SHADOW_CAUSTICS -#include "path_tracer_rgen.h" +#include "restir.h" #include "projection.glsl" layout(constant_id = 0) const uint spec_enable_caustics = 0; +void +direct_lighting_restir(ivec2 ipos, bool is_odd_checkerboard, out vec3 high_freq, out vec3 o_specular) +{ + high_freq = vec3(0); + o_specular = vec3(0); + + rng_seed = texelFetch(TEX_ASVGF_RNG_SEED_A, ipos, 0).r; + + vec3 direct_diffuse, direct_specular; + float vis; + + Reservoir r, prev_r, spatial_r; + prev_r.W = 0; + + vec4 position_material = texelFetch(TEX_PT_SHADING_POSITION, ipos, 0); + + vec3 position = position_material.xyz; + uint material_id = floatBitsToUint(position_material.w); + + // don't compute lighting for invalid surfaces + if(material_id == 0) + { + imageStore(IMG_PT_RESTIR_ID_A, ipos, uvec4(RESTIR_INVALID_ID)); + return; + } + + vec4 view_direction = texelFetch(TEX_PT_VIEW_DIRECTION, ipos, 0); + vec3 normal = decode_normal(texelFetch(TEX_PT_NORMAL_A, ipos, 0).x); + vec3 geo_normal = decode_normal(texelFetch(TEX_PT_GEO_NORMAL_A, ipos, 0).x); + vec4 primary_base_color = texelFetch(TEX_PT_BASE_COLOR_A, ipos, 0); + float primary_specular_factor = primary_base_color.a; + vec2 metal_rough = texelFetch(TEX_PT_METALLIC_A, ipos, 0).xy; + float primary_metallic = metal_rough.x; + float primary_roughness = metal_rough.y; + uint cluster_idx = texelFetch(TEX_PT_CLUSTER_A, ipos, 0).x; + if(cluster_idx == 0xffff) cluster_idx = ~0u; // because the image is uint16 + float view_depth = texelFetch(TEX_PT_VIEW_DEPTH_A, ipos, 0).x; + vec4 motion = texelFetch(TEX_PT_MOTION, ipos, 0); + + bool primary_is_weapon = (material_id & MATERIAL_FLAG_WEAPON) != 0; + int primary_medium = int((material_id & MATERIAL_LIGHT_STYLE_MASK) >> MATERIAL_LIGHT_STYLE_SHIFT); + + bool use_shadow_rays = true; + ivec2 ipos_r = ipos; + + if(global_ubo.pt_restir == 2) + { + ipos_r.y >>= 1; + use_shadow_rays = (ipos.x & 1) == (ipos.y & 1); + } + else if(global_ubo.pt_restir == 3) + { + ipos_r >>= 1; + use_shadow_rays = (ipos.x & 1) == 0 && (ipos.y & 1) == 0; + } + + int shadow_cull_mask = SHADOW_RAY_CULL_MASK; + + if(global_ubo.first_person_model != 0 && !primary_is_weapon) + shadow_cull_mask |= AS_FLAG_VIEWER_MODELS; + else + shadow_cull_mask |= AS_FLAG_VIEWER_WEAPON; + + float direct_specular_weight = smoothstep( + global_ubo.pt_direct_roughness_threshold - 0.02, + global_ubo.pt_direct_roughness_threshold + 0.02, + primary_roughness); + + vec3 primary_albedo, primary_base_reflectivity; + get_reflectivity(primary_base_color.rgb, primary_metallic, primary_albedo, primary_base_reflectivity); + + float alpha = square(primary_roughness); + float phong_exp = RoughnessSquareToSpecPower(alpha); + float phong_scale = min(100, 1 / (M_PI * square(alpha))); + float phong_weight = clamp(primary_specular_factor * luminance(primary_base_reflectivity) / (luminance(primary_base_reflectivity) + luminance(primary_albedo)), 0, 0.9); + + int field_left = 0; + int field_right = global_ubo.prev_width / 2; + if(ipos.x >= global_ubo.width / 2) + { + field_left = field_right; + field_right = global_ubo.prev_width; + } + + //Do temporal + ivec2 pos_prev = ivec2(((vec2(ipos) + vec2(0.5)) * vec2(global_ubo.inv_width * 2, global_ubo.inv_height) + motion.xy) * vec2(global_ubo.prev_width * 0.5, global_ubo.prev_height)); + ivec2 pos_prev_r = pos_prev; + + if(global_ubo.pt_restir == 2) + { + pos_prev.x = (pos_prev.x & ~1) + (pos_prev.y & 1); + pos_prev_r = pos_prev; + pos_prev_r.y >>= 1; + } + else if(global_ubo.pt_restir == 3) + { + pos_prev &= ~1; + pos_prev_r >>= 1; + } + + if (!(pos_prev.x < field_left || pos_prev.x >= field_right || pos_prev.y < 0 || pos_prev.y >= global_ubo.height)) + { + uint prev_idx = texelFetch(TEX_PT_RESTIR_ID_B, pos_prev_r, 0).x; + + prev_idx = get_light_current_idx(prev_idx); + + if(prev_idx != RESTIR_INVALID_ID) + { + float depth_prev = texelFetch(TEX_PT_VIEW_DEPTH_B, pos_prev, 0).x; + float dist_depth = abs(depth_prev - view_depth) / abs(view_depth); + + if(dist_depth < 0.1) + { + + vec3 normal_prev; + float dot_normals; + + if(use_shadow_rays) + { + normal_prev = decode_normal(texelFetch(TEX_PT_NORMAL_B, pos_prev, 0).x); + dot_normals = dot(normal_prev, normal); + } + else + { + dot_normals = 1.0f; + } + if(dot_normals > 0.9) + { + uvec4 packed_reservoir = texelFetch(TEX_PT_RESTIR_B, pos_prev_r, 0); + unpack_reservoir(packed_reservoir, prev_idx, prev_r); + prev_r.p_hat = get_unshadowed_path_contrib(prev_r.y, position, normal, view_direction.xyz, phong_exp, phong_scale, phong_weight, prev_r.y_pos); + } + } + } + } + + //Sample lights + if(use_shadow_rays) + { + get_direct_illumination_restir( + position, + normal, + cluster_idx, + view_direction.xyz, + phong_exp, + phong_scale, + phong_weight, + 0, + prev_r, + r); + } + else + { + r = prev_r; + } + + //Spatio-temporal + if(global_ubo.pt_restir_spatial != 0) + { + float rng = get_rng(RNG_RESTIR_SP_LIGHT_SELECTION(0)); + uint processed = 0; + ivec2 sample_pos; + Reservoir st_r; + init_reservoir(spatial_r); + + vec2 rng_pos = vec2(get_rng(RNG_RESTIR_SPATIAL_X(0)), get_rng(RNG_RESTIR_SPATIAL_Y(0))); + int distance = use_shadow_rays ? RESTIR_SPACIAL_DISTANCE : 2; + int samples = min(RESTIR_SPACIAL_SAMPLES, int(global_ubo.pt_restir_spatial) * 2); + ivec2 pos; + #pragma unroll + for(int i=0; i < samples ; i++) + { + pos = ivec2(ceil(sample_disk(rng_pos) * distance)); + + if(i == 4) distance *= 2; + + rng_pos = rng_pos * 10 - floor(rng_pos * 10); + + sample_pos.x = clamp(pos_prev.x + pos.x, field_left, field_right - 1); + sample_pos.y = clamp(pos_prev.y + pos.y, 0, int(global_ubo.height - 1)); + ivec2 sample_pos_r = sample_pos; + + if(global_ubo.pt_restir == 2) + { + sample_pos.x = min((sample_pos.x & ~1) + (sample_pos.y & 1), field_right - 1); + sample_pos_r = sample_pos; + sample_pos_r.y >>= 1; + } + else if(global_ubo.pt_restir == 3) + { + sample_pos &= ~1; + sample_pos_r >>= 1; + } + + uint str_idx = texelFetch(TEX_PT_RESTIR_ID_B, sample_pos_r, 0).x; + str_idx = get_light_current_idx(str_idx); + + if(str_idx == RESTIR_INVALID_ID) continue; + + float depth_prev = texelFetch(TEX_PT_VIEW_DEPTH_B, sample_pos, 0).x; + float dist_depth = abs(depth_prev - view_depth) / abs(view_depth); + + if(dist_depth < 0.1) + { + + vec3 normal_prev; + float dot_normals; + + if(use_shadow_rays) + { + normal_prev = decode_normal(texelFetch(TEX_PT_NORMAL_B, sample_pos, 0).x); + dot_normals = dot(normal_prev, normal); + } + else + { + dot_normals = 1.0f; + } + + if(dot_normals > 0.9) + { + uvec4 packed_reservoir = texelFetch(TEX_PT_RESTIR_B, sample_pos_r, 0); + unpack_reservoir(packed_reservoir, str_idx, st_r); + + float p_hat = get_unshadowed_path_contrib(st_r.y, position, normal, view_direction.xyz, phong_exp, phong_scale, phong_weight, st_r.y_pos); + if(p_hat <= 0.0) { + continue; + } + + //Combine reservoirs + update_reservoir(st_r.y, p_hat * st_r.W * st_r.M, st_r.y_pos, p_hat, rng, spatial_r); + spatial_r.M += st_r.M - 1; + processed ++; + if(global_ubo.pt_restir_spatial == processed) break; + } + } + } + + if(spatial_r.w_sum != 0) + { + //Combine with the other reservoir + if(r.W != 0) + { + update_reservoir(spatial_r.y, spatial_r.w_sum, spatial_r.y_pos, spatial_r.p_hat, rng, r); + r.M += spatial_r.M - 1; + r.W = r.w_sum / (r.p_hat * r.M); + } + else + { + spatial_r.W = (spatial_r.w_sum ) / (spatial_r.p_hat * spatial_r.M); + r = spatial_r; + } + } + } + + if(r.W != 0.0f) + { + process_selected_light_restir( + r.y, + r.y_pos, + r.W, + position, + normal, + geo_normal, + shadow_cull_mask, + view_direction.xyz, + primary_base_reflectivity, + primary_specular_factor, + primary_roughness, + primary_medium, + spec_enable_caustics != 0, + direct_specular_weight, + phong_exp, + phong_scale, + phong_weight, + use_shadow_rays, + cluster_idx, + direct_diffuse, + direct_specular, + vis); + + high_freq += direct_diffuse; + o_specular += direct_specular; + if(vis == 0.0f) r.W = 0.0f; + } + + //Normalize reservoir + float m_clamp = global_ubo.pt_restir != 3 ? float(RESTIR_M_CLAMP) : float(RESTIR_M_VC_CLAMP); + r.w_sum = (m_clamp * r.w_sum) / r.M; + + o_specular = demodulate_specular(primary_base_reflectivity, o_specular); + + high_freq = clamp_output(high_freq); + o_specular = clamp_output(o_specular); + + if(use_shadow_rays) + { + imageStore(IMG_PT_RESTIR_ID_A, ipos_r, uvec4(r.W == 0.0f ? RESTIR_INVALID_ID : r.y)); + imageStore(IMG_PT_RESTIR_A, ipos_r, uvec4(pack_reservoir(r))); + } +} + + void direct_lighting(ivec2 ipos, bool is_odd_checkerboard, out vec3 high_freq, out vec3 o_specular) { @@ -45,7 +348,7 @@ direct_lighting(ivec2 ipos, bool is_odd_checkerboard, out vec3 high_freq, out ve vec4 position_material = texelFetch(TEX_PT_SHADING_POSITION, ipos, 0); - + vec3 position = position_material.xyz; uint material_id = floatBitsToUint(position_material.w); @@ -56,10 +359,10 @@ direct_lighting(ivec2 ipos, bool is_odd_checkerboard, out vec3 high_freq, out ve vec4 view_direction = texelFetch(TEX_PT_VIEW_DIRECTION, ipos, 0); vec3 normal = decode_normal(texelFetch(TEX_PT_NORMAL_A, ipos, 0).x); vec3 geo_normal = decode_normal(texelFetch(TEX_PT_GEO_NORMAL_A, ipos, 0).x); - vec4 primary_base_color = texelFetch(TEX_PT_BASE_COLOR_A, ipos, 0); - float primary_specular_factor = primary_base_color.a; - vec2 metal_rough = texelFetch(TEX_PT_METALLIC_A, ipos, 0).xy; - float primary_metallic = metal_rough.x; + vec4 primary_base_color = texelFetch(TEX_PT_BASE_COLOR_A, ipos, 0); + float primary_specular_factor = primary_base_color.a; + vec2 metal_rough = texelFetch(TEX_PT_METALLIC_A, ipos, 0).xy; + float primary_metallic = metal_rough.x; float primary_roughness = metal_rough.y; uint cluster_idx = texelFetch(TEX_PT_CLUSTER_A, ipos, 0).x; if(cluster_idx == 0xffff) cluster_idx = ~0u; // because the image is uint16 @@ -86,29 +389,29 @@ direct_lighting(ivec2 ipos, bool is_odd_checkerboard, out vec3 high_freq, out ve vec3 direct_diffuse, direct_specular; get_direct_illumination( - position, - normal, - geo_normal, - cluster_idx, - material_id, - shadow_cull_mask, - view_direction.xyz, - primary_albedo, - primary_base_reflectivity, - primary_specular_factor, - primary_roughness, - primary_medium, - spec_enable_caustics != 0, - direct_specular_weight, - global_ubo.pt_direct_polygon_lights > 0, - global_ubo.pt_direct_dyn_lights > 0, - is_gradient, - 0, - direct_diffuse, - direct_specular); - - high_freq += direct_diffuse; - o_specular += direct_specular; + position, + normal, + geo_normal, + cluster_idx, + material_id, + shadow_cull_mask, + view_direction.xyz, + primary_albedo, + primary_base_reflectivity, + primary_specular_factor, + primary_roughness, + primary_medium, + spec_enable_caustics != 0, + direct_specular_weight, + global_ubo.pt_direct_polygon_lights > 0, + global_ubo.pt_direct_dyn_lights > 0, + is_gradient, + 0, + direct_diffuse, + direct_specular); + + high_freq += direct_diffuse; + o_specular += direct_specular; if(global_ubo.pt_direct_sun_light != 0) { @@ -149,7 +452,10 @@ main() bool is_odd_checkerboard = (rt_LaunchID.z != 0) || (push_constants.gpu_index == 1); vec3 high_freq, specular; - direct_lighting(ipos, is_odd_checkerboard, high_freq, specular); + if(global_ubo.pt_restir > 0) + direct_lighting_restir(ipos, is_odd_checkerboard, high_freq, specular); + else + direct_lighting(ipos, is_odd_checkerboard, high_freq, specular); high_freq *= STORAGE_SCALE_HF; specular *= STORAGE_SCALE_SPEC; diff --git a/src/refresh/vkpt/shader/global_textures.h b/src/refresh/vkpt/shader/global_textures.h index 2452b7f90..036bf8032 100644 --- a/src/refresh/vkpt/shader/global_textures.h +++ b/src/refresh/vkpt/shader/global_textures.h @@ -109,6 +109,10 @@ with this program; if not, write to the Free Software Foundation, Inc., IMG_DO(ASVGF_HIST_COLOR_LF_COCG_B,NUM_IMAGES_BASE + 27, R16G16_SFLOAT, rg16f, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ IMG_DO(ASVGF_GRAD_SMPL_POS_A, NUM_IMAGES_BASE + 28, R32_UINT, r32ui, IMG_WIDTH_GRAD_MGPU, IMG_HEIGHT_GRAD) \ IMG_DO(ASVGF_GRAD_SMPL_POS_B, NUM_IMAGES_BASE + 29, R32_UINT, r32ui, IMG_WIDTH_GRAD_MGPU, IMG_HEIGHT_GRAD) \ + IMG_DO(PT_RESTIR_ID_A, NUM_IMAGES_BASE + 30, R16_UINT, r16ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_ID_B, NUM_IMAGES_BASE + 31, R16_UINT, r16ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_A, NUM_IMAGES_BASE + 32, R32G32_UINT, rg32ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_B, NUM_IMAGES_BASE + 33, R32G32_UINT, rg32ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ #define LIST_IMAGES_B_A \ IMG_DO(PT_VISBUF_PRIM_B, NUM_IMAGES_BASE + 0, R32G32_UINT, rg32ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ @@ -141,8 +145,12 @@ with this program; if not, write to the Free Software Foundation, Inc., IMG_DO(ASVGF_HIST_COLOR_LF_COCG_A,NUM_IMAGES_BASE + 27, R16G16_SFLOAT, rg16f, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ IMG_DO(ASVGF_GRAD_SMPL_POS_B, NUM_IMAGES_BASE + 28, R32_UINT, r32ui, IMG_WIDTH_GRAD_MGPU, IMG_HEIGHT_GRAD) \ IMG_DO(ASVGF_GRAD_SMPL_POS_A, NUM_IMAGES_BASE + 29, R32_UINT, r32ui, IMG_WIDTH_GRAD_MGPU, IMG_HEIGHT_GRAD) \ + IMG_DO(PT_RESTIR_ID_B, NUM_IMAGES_BASE + 30, R16_UINT, r16ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_ID_A, NUM_IMAGES_BASE + 31, R16_UINT, r16ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_B, NUM_IMAGES_BASE + 32, R32G32_UINT, rg32ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ + IMG_DO(PT_RESTIR_A, NUM_IMAGES_BASE + 33, R32G32_UINT, rg32ui, IMG_WIDTH_MGPU, IMG_HEIGHT ) \ -#define NUM_IMAGES (NUM_IMAGES_BASE + 30) /* this really sucks but I don't know how to fix it +#define NUM_IMAGES (NUM_IMAGES_BASE + 34) /* this really sucks but I don't know how to fix it counting with enum does not work in GLSL */ // todo: make naming consistent! @@ -195,31 +203,33 @@ layout( binding = GLOBAL_TEXTURES_TEX_ARR_BINDING_IDX ) uniform sampler2D global_texture_descriptors[]; -#define SAMPLER_r16ui usampler2D -#define SAMPLER_r32ui usampler2D -#define SAMPLER_rg32ui usampler2D -#define SAMPLER_r32i isampler2D -#define SAMPLER_r32f sampler2D -#define SAMPLER_rg32f sampler2D -#define SAMPLER_rg16f sampler2D -#define SAMPLER_rgba32f sampler2D -#define SAMPLER_rgba16f sampler2D -#define SAMPLER_rgba8 sampler2D -#define SAMPLER_r8 sampler2D -#define SAMPLER_rg8 sampler2D - -#define IMAGE_r16ui uimage2D -#define IMAGE_r32ui uimage2D -#define IMAGE_rg32ui uimage2D -#define IMAGE_r32i iimage2D -#define IMAGE_r32f image2D -#define IMAGE_rg32f image2D -#define IMAGE_rg16f image2D -#define IMAGE_rgba32f image2D -#define IMAGE_rgba16f image2D -#define IMAGE_rgba8 image2D -#define IMAGE_r8 image2D -#define IMAGE_rg8 image2D +#define SAMPLER_r16ui usampler2D +#define SAMPLER_r32ui usampler2D +#define SAMPLER_rg32ui usampler2D +#define SAMPLER_rgba32ui usampler2D +#define SAMPLER_r32i isampler2D +#define SAMPLER_r32f sampler2D +#define SAMPLER_rg32f sampler2D +#define SAMPLER_rg16f sampler2D +#define SAMPLER_rgba32f sampler2D +#define SAMPLER_rgba16f sampler2D +#define SAMPLER_rgba8 sampler2D +#define SAMPLER_r8 sampler2D +#define SAMPLER_rg8 sampler2D + +#define IMAGE_r16ui uimage2D +#define IMAGE_r32ui uimage2D +#define IMAGE_rg32ui uimage2D +#define IMAGE_rgba32ui uimage2D +#define IMAGE_r32i iimage2D +#define IMAGE_r32f image2D +#define IMAGE_rg32f image2D +#define IMAGE_rg16f image2D +#define IMAGE_rgba32f image2D +#define IMAGE_rgba16f image2D +#define IMAGE_rgba8 image2D +#define IMAGE_r8 image2D +#define IMAGE_rg8 image2D /* framebuffer images */ #define IMG_DO(_name, _binding, _vkformat, _glslformat, _w, _h) \ diff --git a/src/refresh/vkpt/shader/global_ubo.h b/src/refresh/vkpt/shader/global_ubo.h index 1b4f14058..5c89ec7ec 100644 --- a/src/refresh/vkpt/shader/global_ubo.h +++ b/src/refresh/vkpt/shader/global_ubo.h @@ -92,6 +92,9 @@ with this program; if not, write to the Free Software Foundation, Inc., UBO_CVAR_DO(pt_particle_softness, 0.7) /* particle softness */ \ UBO_CVAR_DO(pt_particle_brightness, 100) /* particle brightness */ \ UBO_CVAR_DO(pt_reflect_refract, 2) /* number of reflection or refraction bounces: 0, 1 or 2 */ \ + UBO_CVAR_DO(pt_restir, 1) /* switch for using RIS or ReSTIR, 0 or 1 */ \ + UBO_CVAR_DO(pt_restir_spatial, 1) /* ReSTIR spatial samples */ \ + UBO_CVAR_DO(pt_restir_max_w, 12.0) /* ReSTIR max weight clamp */ \ UBO_CVAR_DO(pt_roughness_override, -1) /* overrides roughness of all materials if non-negative, [0..1] */ \ UBO_CVAR_DO(pt_specular_anti_flicker, 2) /* fade factor for rough reflections of surfaces far away, [0..inf) */ \ UBO_CVAR_DO(pt_specular_mis, 1) /* enables the use of MIS between specular direct lighting and BRDF specular rays */ \ @@ -162,7 +165,7 @@ with this program; if not, write to the Free Software Foundation, Inc., GLOBAL_UBO_VAR_LIST_DO(int, planet_albedo_map) \ GLOBAL_UBO_VAR_LIST_DO(int, planet_normal_map) \ \ - GLOBAL_UBO_VAR_LIST_DO(int, num_dyn_lights) \ + GLOBAL_UBO_VAR_LIST_DO(int, padding1) \ GLOBAL_UBO_VAR_LIST_DO(int , num_static_lights) \ GLOBAL_UBO_VAR_LIST_DO(uint, num_static_primitives) \ GLOBAL_UBO_VAR_LIST_DO(int, cluster_debug_index) \ @@ -300,6 +303,7 @@ BEGIN_SHADER_STRUCT( InstanceBuffer ) uint model_current_to_prev [MAX_MODEL_INSTANCES]; uint model_prev_to_current [MAX_MODEL_INSTANCES]; ModelInstance model_instances [MAX_MODEL_INSTANCES]; + uint mlight_prev_to_current [MAX_MODEL_LIGHTS]; uint tlas_instance_prim_offsets[MAX_TLAS_INSTANCES]; int tlas_instance_model_indices[MAX_TLAS_INSTANCES]; } diff --git a/src/refresh/vkpt/shader/indirect_lighting.rgen b/src/refresh/vkpt/shader/indirect_lighting.rgen index 917f8ca28..1884d389a 100644 --- a/src/refresh/vkpt/shader/indirect_lighting.rgen +++ b/src/refresh/vkpt/shader/indirect_lighting.rgen @@ -294,7 +294,13 @@ indirect_lighting( if (global_ubo.pt_specular_mis != 0) { mat3 projected_positions = project_triangle(triangle.positions, position); - float pdfw = get_spherical_triangle_pdfw(projected_positions); + + vec3 bary = get_hit_barycentric(ray_payload_geometry); + vec3 pos_on_triangle = triangle.positions * bary; + pos_on_triangle -= position; + pos_on_triangle = normalize(pos_on_triangle); + + float pdfw = get_triangle_pdfw(projected_positions, pos_on_triangle); direct_specular_weight *= get_specular_sampled_lighting_weight(primary_roughness, normal, -view_direction.xyz, bounce_direction, pdfw); diff --git a/src/refresh/vkpt/shader/light_lists.h b/src/refresh/vkpt/shader/light_lists.h index c555f96e8..4fb7f2c05 100644 --- a/src/refresh/vkpt/shader/light_lists.h +++ b/src/refresh/vkpt/shader/light_lists.h @@ -37,7 +37,7 @@ project_triangle(mat3 positions, vec3 p) } float -spherical_tri_area(mat3 positions, vec3 p, vec3 n, vec3 V, float phong_exp, float phong_scale, float phong_weight) +projected_tri_area(mat3 positions, vec3 p, vec3 n, vec3 V, float phong_exp, float phong_scale, float phong_weight) { positions[0] = positions[0] - p; positions[1] = positions[1] - p; @@ -53,29 +53,95 @@ spherical_tri_area(mat3 positions, vec3 p, vec3 n, vec3 V, float phong_exp, floa float specular = phong(n, L, V, phong_exp) * phong_scale; float brdf = mix(1.0, specular, phong_weight); - // Project triangle to unit sphere - vec3 A = normalize(positions[0]); - vec3 B = normalize(positions[1]); - vec3 C = normalize(positions[2]); + positions[0] = normalize(positions[0]); + positions[1] = normalize(positions[1]); + positions[2] = normalize(positions[2]); - // Area of spherical triangle - float area = 2 * atan(abs(dot(A, cross(B, C))), 1 + dot(A, B) + dot(B, C) + dot(A, C)); - float pa = max(area - 1e-5, 0.); + vec3 a = cross(positions[1] - positions[0], positions[2] - positions[0]); + float pa = max(length(a) - 1e-5, 0.); return pa * brdf; } -float get_spherical_triangle_pdfw(mat3 positions) +float +projected_sphere_area(mat3 positions, vec3 p, vec3 n, vec3 V, float phong_exp, float phong_scale, float phong_weight) { - // Project triangle to unit sphere - vec3 A = normalize(positions[0]); - vec3 B = normalize(positions[1]); - vec3 C = normalize(positions[2]); + vec3 position = positions[0] - p; + float sphere_radius = positions[1].x; + float dist = length(position); + float rdist = 1.0 / dist; + vec3 L = position * rdist; + + if (dot(n, L) <= 0) + return 0.0; + + float specular = phong(n, L, V, phong_exp) * phong_scale; + float brdf = mix(1.0, specular, phong_weight); + + float irradiance = 2 * (1 - sqrt(max(0, 1 - square(sphere_radius * rdist)))); + irradiance = min(irradiance, 2 * M_PI); //max solid angle + + return irradiance * brdf; +} + +float +projected_spotlight_area(mat3 positions, vec3 p, vec3 n, vec3 V, float phong_exp, float phong_scale, float phong_weight) +{ + vec3 position = positions[0] - p; + float sphere_radius = positions[1].x; + const float cosTotalWidth = positions[1].y; + const float cosFalloffStart = positions[1].z; + + float dist = length(position); + float rdist = 1.0 / dist; + vec3 L = position * rdist; + + if (dot(n, L) <= 0) + return 0.0; + + float specular = phong(n, L, V, phong_exp) * phong_scale; + float brdf = mix(1.0, specular, phong_weight); - // Area of spherical triangle - float area = 2 * atan(abs(dot(A, cross(B, C))), 1 + dot(A, B) + dot(B, C) + dot(A, C)); + float cosTheta = dot(-L, positions[2]); // cosine of angle to spot direction + float falloff; + if(cosTheta < cosTotalWidth) + falloff = 0; + else if (cosTheta > cosFalloffStart) + falloff = 1; + else { + float delta = (cosTheta - cosTotalWidth) / (cosFalloffStart - cosTotalWidth); + falloff = (delta * delta) * (delta * delta); + } + + float irradiance = 2 * falloff * square(rdist); - // Since the solid angle is distributed uniformly, the PDF wrt to solid angle is simply: - return 1 / area; + irradiance = min(irradiance, 2 * M_PI); //max solid angle + + return irradiance * brdf; +} + +float pdf_area_to_solid_angle(float pdfA, float distance_, float cos_theta) +{ + return pdfA * square(distance_) / cos_theta; +} + +float get_triangle_pdfw(mat3 positions, vec3 sample_pos) +{ + vec3 normal = cross(positions[1] - positions[0], positions[2] - positions[0]); + float normal_length = length(normal); + float sample_pos_distance = length(sample_pos); + + // The samples should be more or less on the unit sphere. If they are much closer than + // 1 unit away, this means the projected light is very large, and the surface is likely + // on the light itself. + float clamped_sample_pos_distance = max(sample_pos_distance, 0.1); + + if (normal_length > 0 && sample_pos_distance > 0) + { + float cos_theta = -dot(normal / normal_length, sample_pos / sample_pos_distance); + return pdf_area_to_solid_angle(2.0 / normal_length, clamped_sample_pos_distance, cos_theta); + } + + return 0; } /* Sample a triangle, projected to a unit sphere. @@ -100,59 +166,88 @@ sample_projected_triangle(vec3 pt, mat3 positions, vec2 rnd, out vec3 light_norm // Distance of triangle to origin float o = dot(light_normal, positions[0]); - // Project triangle to unit sphere - vec3 A = normalize(positions[0]); - vec3 B = normalize(positions[1]); - vec3 C = normalize(positions[2]); - // Planes passing through two vertices and origin. They'll be used to obtain the angles. - vec3 cross_BC = cross(B, C); - vec3 norm_AB = normalize(cross(A, B)); - vec3 norm_BC = normalize(cross_BC); - vec3 norm_CA = normalize(cross(C, A)); - // Side of spherical triangle - float cos_c = dot(A, B); - // Angles at vertices - float cos_alpha = dot(norm_AB, -norm_CA); - - // Area of spherical triangle. From: "On the Measure of Solid Angles", F. Eriksson, 1990. - float area = 2 * atan(abs(dot(A, cross_BC)), 1 + cos_c + dot(B, C) + dot(A, C)); - - // Use one random variable to select the new area. - float new_area = rnd.x * area; - - float sin_alpha = sqrt(1 - cos_alpha * cos_alpha); // = sin(acos(cos_alpha)) - float sin_new_area = sin(new_area); - float cos_new_area = cos(new_area); - // Save the sine and cosine of the angle phi. - float p = sin_new_area * cos_alpha - cos_new_area * sin_alpha; - float q = cos_new_area * cos_alpha + sin_new_area * sin_alpha; - - // Compute the pair (u, v) that determines new_beta. - float u = q - cos_alpha; - float v = p + sin_alpha * cos_c; - - // Let cos_b be the cosine of the new edge length new_b. - float cos_b = clamp(((v * q - u * p) * cos_alpha - v) / ((v * p + u * q) * sin_alpha), -1, 1); - - // Compute the third vertex of the sub-triangle. - vec3 new_C = cos_b * A + sqrt(1 - cos_b * cos_b) * normalize(C - dot(C, A) * A); - - // Use the other random variable to select cos(phi). - float z = 1 - rnd.y * (1 - dot(new_C, B)); - - // Construct the corresponding point on the sphere. - vec3 direction = z * B + sqrt(1 - z * z) * normalize(new_C - dot(new_C, B) * B); - // ...which is also the direction! - - // Line-plane intersection + positions[0] = normalize(positions[0]); + positions[1] = normalize(positions[1]); + positions[2] = normalize(positions[2]); + + vec3 direction = positions * sample_triangle(rnd); + float dl = length(direction); + + // n (p + d * t - p[i]) == 0 + // -n (p - pi) / n d == o / n d == t vec3 lo = direction * (o / dot(light_normal, direction)); - // Since the solid angle is distributed uniformly, the PDF wrt to solid angle is simply: - pdfw = 1 / area; + pdfw = get_triangle_pdfw(positions, direction); return pt + lo; } +vec3 +sample_projected_sphere(vec3 p, mat3 positions, vec2 rnd, out vec3 light_normal, out float pdfw) +{ + vec3 light_center = positions[0]; + vec3 position = light_center - p; + float sphere_radius = positions[1].x; + float dist = length(position); + float rdist = 1.0 / dist; + vec3 L = position * rdist; + + float projected_area = 2 * (1 - sqrt(max(0, 1 - square(sphere_radius * rdist)))); + projected_area = min(projected_area, 2 * M_PI); //max solid angle + pdfw = 1.0 / projected_area; + + mat3 onb = construct_ONB_frisvad(L); + vec3 diskpt; + diskpt.xy = sample_disk(rnd); + diskpt.z = sqrt(max(0, 1 - diskpt.x * diskpt.x - diskpt.y * diskpt.y)); + + vec3 position_light = light_center + (onb[0] * diskpt.x + onb[2] * diskpt.y - L * diskpt.z) * sphere_radius; + + light_normal = normalize(position_light - light_center); + + return position_light; +} + +vec3 +sample_projected_spotlight(vec3 p, mat3 positions, vec2 rnd, out vec3 light_normal, out float pdfw) +{ + vec3 light_center = positions[0]; + float emitter_radius = positions[1].x; + const float cosTotalWidth = positions[1].y; + const float cosFalloffStart = positions[1].z; + + mat3 onb = construct_ONB_frisvad(positions[2]); + // Emit light from a small disk around the origin + vec2 diskpt = sample_disk(rnd); + vec3 position_light = light_center + (onb[0] * diskpt.x + onb[2] * diskpt.y) * emitter_radius; + + vec3 c = position_light - p; + float dist = length(c); + float rdist = 1.0 / dist; + vec3 L = c * rdist; + + // Direction from emission point to surface, in a basis where +Y is the spot direction + vec3 L_l = -L * onb; + float cosTheta = L_l.y; // cosine of angle to spot direction + float falloff; + if(cosTheta < cosTotalWidth) + falloff = 0; + else if (cosTheta > cosFalloffStart) + falloff = 1; + else { + float delta = (cosTheta - cosTotalWidth) / (cosFalloffStart - cosTotalWidth); + falloff = (delta * delta) * (delta * delta); + } + + float projected_area = 2 * falloff * square(rdist); + projected_area = min(projected_area, 2 * M_PI); //max solid angle + pdfw = 1.0 / projected_area; + + light_normal = normalize(positions[2]); + + return position_light; +} + uint get_light_stats_addr(uint cluster, uint light, uint side) { uint addr = cluster; @@ -163,13 +258,13 @@ uint get_light_stats_addr(uint cluster, uint light, uint side) } void -sample_polygonal_lights( +sample_lights( uint list_idx, vec3 p, vec3 n, vec3 gn, - vec3 V, - float phong_exp, + vec3 V, + float phong_exp, float phong_scale, float phong_weight, bool is_gradient, @@ -232,7 +327,18 @@ sample_polygonal_lights( LightPolygon light = get_light_polygon(current_idx); - float m = spherical_tri_area(light.positions, p, n, V, phong_exp, phong_scale, phong_weight); + float m = 0.0f; + switch(uint(light.type)){ + case DYNLIGHT_POLYGON: + m = projected_tri_area(light.positions, p, n, V, phong_exp, phong_scale, phong_weight); + break; + case DYNLIGHT_SPHERE: + m = projected_sphere_area(light.positions, p, n, V, phong_exp, phong_scale, phong_weight); + break; + case DYNLIGHT_SPOT: + m = projected_spotlight_area(light.positions, p, n, V, phong_exp, phong_scale, phong_weight); + break; + } float light_lum = luminance(light.color); @@ -315,7 +421,18 @@ sample_polygonal_lights( LightPolygon light = get_light_polygon(current_idx); vec3 light_normal; - position_light = sample_projected_triangle(p, light.positions, rng.yz, light_normal, pdfw); + + switch(uint(light.type)){ + case DYNLIGHT_POLYGON: + position_light = sample_projected_triangle(p, light.positions, rng.yz, light_normal, pdfw); + break; + case DYNLIGHT_SPHERE: + position_light = sample_projected_sphere(p, light.positions, rng.yz, light_normal, pdfw); + break; + case DYNLIGHT_SPOT: + position_light = sample_projected_spotlight(p, light.positions, rng.yz, light_normal, pdfw); + break; + } vec3 L = normalize(position_light - p); @@ -345,119 +462,4 @@ sample_polygonal_lights( light_color /= pdf; } -float -compute_dynlight_sphere(uint light_idx, vec3 light_center, vec3 p, out vec3 position_light, vec3 rng) -{ - vec3 c = light_center - p; - float dist = length(c); - float rdist = 1.0 / dist; - vec3 L = c * rdist; - - float sphere_radius = global_ubo.dyn_light_data[light_idx].radius; - float irradiance = 2 * (1 - sqrt(max(0, 1 - square(sphere_radius * rdist)))); - - mat3 onb = construct_ONB_frisvad(L); - vec3 diskpt; - diskpt.xy = sample_disk(rng.yz); - diskpt.z = sqrt(max(0, 1 - diskpt.x * diskpt.x - diskpt.y * diskpt.y)); - - position_light = light_center + (onb[0] * diskpt.x + onb[2] * diskpt.y - L * diskpt.z) * sphere_radius; - - return irradiance; -} - -float -compute_dynlight_spot(uint light_idx, uint spot_style, vec3 light_center, vec3 p, out vec3 position_light, vec3 rng) -{ - mat3 onb = construct_ONB_frisvad(global_ubo.dyn_light_data[light_idx].spot_direction); - // Emit light from a small disk around the origin - float emitter_radius = global_ubo.dyn_light_data[light_idx].radius; - vec2 diskpt = sample_disk(rng.yz); - position_light = light_center + (onb[0] * diskpt.x + onb[2] * diskpt.y) * emitter_radius; - - vec3 c = position_light - p; - float dist = length(c); - float rdist = 1.0 / dist; - vec3 L = c * rdist; - - // Direction from emission point to surface, in a basis where +Y is the spot direction - vec3 L_l = -L * onb; - float cosTheta = L_l.y; // cosine of angle to spot direction - float falloff; - - if(spot_style == DYNLIGHT_SPOT_EMISSION_PROFILE_FALLOFF) { - const vec2 spot_falloff = unpackHalf2x16(global_ubo.dyn_light_data[light_idx].spot_data); - const float cosTotalWidth = spot_falloff.x; - const float cosFalloffStart = spot_falloff.y; - - if(cosTheta < cosTotalWidth) - falloff = 0; - else if (cosTheta > cosFalloffStart) - falloff = 1; - else { - float delta = (cosTheta - cosTotalWidth) / (cosFalloffStart - cosTotalWidth); - falloff = (delta * delta) * (delta * delta); - } - } else if(spot_style == DYNLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE) { - const uint spot_data = global_ubo.dyn_light_data[light_idx].spot_data; - const float theta = acos(cosTheta); - const float totalWidth = unpackHalf2x16(spot_data).x; - const uint texture_num = spot_data >> 16; - - if (cosTheta >= 0) { - // Use the angle directly as texture coordinate for better angular resolution next to the center of the beam - float tc = clamp(theta / totalWidth, 0, 1); - falloff = global_texture(texture_num, vec2(tc, 0)).r; - } else - falloff = 0; - } - - float irradiance = 2 * falloff * square(rdist); - - return irradiance; -} - -void -sample_dynamic_lights( - vec3 p, - vec3 n, - vec3 gn, - float max_solid_angle, - out vec3 position_light, - out vec3 light_color, - vec3 rng) -{ - position_light = vec3(0); - light_color = vec3(0); - - if(global_ubo.num_dyn_lights == 0) - return; - - float random_light = rng.x * global_ubo.num_dyn_lights; - uint light_idx = min(global_ubo.num_dyn_lights - 1, uint(random_light)); - - vec3 light_center = global_ubo.dyn_light_data[light_idx].center; - - light_color = global_ubo.dyn_light_data[light_idx].color; - - uint light_type = global_ubo.dyn_light_data[light_idx].type & 0xffff; - uint light_style = global_ubo.dyn_light_data[light_idx].type >> 16; - - float irradiance; - if(light_type == DYNLIGHT_SPHERE) { - irradiance = compute_dynlight_sphere(light_idx, light_center, p, position_light, rng); - } else { - irradiance = compute_dynlight_spot(light_idx, light_style, light_center, p, position_light, rng); - } - irradiance = min(irradiance, max_solid_angle); - irradiance *= float(global_ubo.num_dyn_lights); // 1 / pdf - - light_color *= irradiance; - - if(dot(position_light - p, gn) <= 0) - light_color = vec3(0); -} - #endif /*_LIGHT_LISTS_*/ - -// vim: shiftwidth=4 noexpandtab tabstop=4 cindent diff --git a/src/refresh/vkpt/shader/path_tracer_rgen.h b/src/refresh/vkpt/shader/path_tracer_rgen.h index 21cdac6a8..1f3f896d2 100644 --- a/src/refresh/vkpt/shader/path_tracer_rgen.h +++ b/src/refresh/vkpt/shader/path_tracer_rgen.h @@ -52,15 +52,18 @@ uniform accelerationStructureEXT topLevelAS[TLAS_COUNT]; #define RNG_PRIMARY_APERTURE_X 2 #define RNG_PRIMARY_APERTURE_Y 3 -#define RNG_NEE_LIGHT_SELECTION(bounce) (4 + 0 + 9 * bounce) -#define RNG_NEE_TRI_X(bounce) (4 + 1 + 9 * bounce) -#define RNG_NEE_TRI_Y(bounce) (4 + 2 + 9 * bounce) -#define RNG_NEE_LIGHT_TYPE(bounce) (4 + 3 + 9 * bounce) -#define RNG_BRDF_X(bounce) (4 + 4 + 9 * bounce) -#define RNG_BRDF_Y(bounce) (4 + 5 + 9 * bounce) -#define RNG_BRDF_FRESNEL(bounce) (4 + 6 + 9 * bounce) -#define RNG_SUNLIGHT_X(bounce) (4 + 7 + 9 * bounce) -#define RNG_SUNLIGHT_Y(bounce) (4 + 8 + 9 * bounce) +#define RNG_NEE_LIGHT_SELECTION(bounce) (4 + 0 + 12 * bounce) +#define RNG_NEE_TRI_X(bounce) (4 + 1 + 12 * bounce) +#define RNG_NEE_TRI_Y(bounce) (4 + 2 + 12 * bounce) +#define RNG_NEE_LIGHT_TYPE(bounce) (4 + 3 + 12 * bounce) +#define RNG_BRDF_X(bounce) (4 + 4 + 12 * bounce) +#define RNG_BRDF_Y(bounce) (4 + 5 + 12 * bounce) +#define RNG_BRDF_FRESNEL(bounce) (4 + 6 + 12 * bounce) +#define RNG_SUNLIGHT_X(bounce) (4 + 7 + 12 * bounce) +#define RNG_SUNLIGHT_Y(bounce) (4 + 8 + 12 * bounce) +#define RNG_RESTIR_SP_LIGHT_SELECTION(bounce) (4 + 9 + 12 * bounce) +#define RNG_RESTIR_SPATIAL_X(bounce) (4 + 10 + 12 * bounce) +#define RNG_RESTIR_SPATIAL_Y(bounce) (4 + 11 + 12 * bounce) #define PRIMARY_RAY_CULL_MASK (AS_FLAG_OPAQUE | AS_FLAG_TRANSPARENT | AS_FLAG_VIEWER_WEAPON | AS_FLAG_SKY) #define REFLECTION_RAY_CULL_MASK (AS_FLAG_OPAQUE | AS_FLAG_SKY) @@ -656,6 +659,28 @@ get_specular_sampled_lighting_weight(float roughness, vec3 N, vec3 V, vec3 L, fl return clamp(pdfw / (pdfw + ggxVndfPdf), 0, 1); } +float get_unshadowed_env_path_contrib( + vec3 normal, + vec3 view_direction, + float phong_exp, + float phong_scale, + float phong_weight, + vec2 rng) +{ + vec3 direction = global_ubo.sun_direction; + float NoL = dot(direction , normal); + if(NoL <= 0.0001) return 0.0; + + float specular = phong(normal, direction, view_direction, phong_exp) * phong_scale; + float m = mix(1.0, specular, phong_weight); + + float light_lum = sun_color_ubo.sun_luminance;// / global_ubo.sun_solid_angle; + + m *= abs(light_lum); // abs because sky lights have negative color + + return m; +} + void get_direct_illumination( vec3 position, @@ -682,19 +707,17 @@ get_direct_illumination( diffuse = vec3(0); specular = vec3(0); - vec3 pos_on_light_polygonal; - vec3 pos_on_light_dynamic; + vec3 pos_on_light; - vec3 contrib_polygonal = vec3(0); - vec3 contrib_dynamic = vec3(0); + vec3 contrib = vec3(0); float alpha = square(roughness); float phong_exp = RoughnessSquareToSpecPower(alpha); float phong_scale = min(100, 1 / (M_PI * square(alpha))); float phong_weight = clamp(specular_factor * luminance(base_reflectivity) / (luminance(base_reflectivity) + luminance(albedo)), 0, 0.9); - int polygonal_light_index = -1; - float polygonal_light_pdfw = 0; + int light_index = -1; + float light_pdfw = 0; bool polygonal_light_is_sky = false; vec3 rng = vec3( @@ -703,9 +726,9 @@ get_direct_illumination( get_rng(RNG_NEE_TRI_Y(bounce))); /* polygonal light illumination */ - if(enable_polygonal) + if(enable_polygonal || enable_dynamic) { - sample_polygonal_lights( + sample_lights( cluster_idx, position, normal, @@ -715,51 +738,24 @@ get_direct_illumination( phong_scale, phong_weight, is_gradient, - pos_on_light_polygonal, - contrib_polygonal, - polygonal_light_index, - polygonal_light_pdfw, + pos_on_light, + contrib, + light_index, + light_pdfw, polygonal_light_is_sky, rng); } bool is_polygonal = true; - float vis = 1; - - /* dynamic light illumination */ - if(enable_dynamic) - { - // Limit the solid angle of sphere lights for indirect lighting - // in order to kill some fireflies in locations with many sphere lights. - // Example: green wall-lamp corridor in the "train" map. - float max_solid_angle = (bounce == 0) ? 2 * M_PI : 0.02; - - sample_dynamic_lights( - position, - normal, - geo_normal, - max_solid_angle, - pos_on_light_dynamic, - contrib_dynamic, - rng); - } - - float spec_polygonal = phong(normal, normalize(pos_on_light_polygonal - position), view_direction, phong_exp) * phong_scale; - float spec_dynamic = phong(normal, normalize(pos_on_light_dynamic - position), view_direction, phong_exp) * phong_scale; + float vis = 1.0; - float l_polygonal = luminance(abs(contrib_polygonal)) * mix(1, spec_polygonal, phong_weight); - float l_dynamic = luminance(abs(contrib_dynamic)) * mix(1, spec_dynamic, phong_weight); - float l_sum = l_polygonal + l_dynamic; + float spec_polygonal = phong(normal, normalize(pos_on_light - position), view_direction, phong_exp) * phong_scale; - bool null_light = (l_sum == 0); + float l_polygonal = luminance(abs(contrib)) * mix(1, spec_polygonal, phong_weight); - float w = null_light ? 0.5 : l_polygonal / (l_polygonal + l_dynamic); + bool null_light = (l_polygonal == 0); - float rng2 = get_rng(RNG_NEE_LIGHT_TYPE(bounce)); - is_polygonal = (rng2 < w); - vis = is_polygonal ? (1 / w) : (1 / (1 - w)); - vec3 pos_on_light = null_light ? position : (is_polygonal ? pos_on_light_polygonal : pos_on_light_dynamic); - vec3 contrib = is_polygonal ? contrib_polygonal : contrib_dynamic; + pos_on_light = null_light ? position : pos_on_light; Ray shadow_ray = get_shadow_ray(position - view_direction * 0.01, pos_on_light, 0); @@ -771,7 +767,7 @@ get_direct_illumination( } #endif - /* + /* Accumulate light shadowing statistics to guide importance sampling on the next frame. Inspired by paper called "Adaptive Shadow Testing for Ray Tracing" by G. Ward, EUROGRAPHICS 1994. @@ -787,13 +783,12 @@ get_direct_illumination( polygon lights do not have polygonal indices, and it would be difficult to map them between frames. */ - if(global_ubo.pt_light_stats != 0 - && is_polygonal + if(global_ubo.pt_light_stats != 0 && !null_light - && polygonal_light_index >= 0 - && polygonal_light_index < global_ubo.num_static_lights) + && light_index >= 0 + && light_index < global_ubo.num_static_lights) { - uint addr = get_light_stats_addr(cluster_idx, polygonal_light_index, get_primary_direction(normal)); + uint addr = get_light_stats_addr(cluster_idx, light_index, get_primary_direction(normal)); // Offset 0 is unshadowed rays, // Offset 1 is shadowed rays @@ -811,7 +806,7 @@ get_direct_illumination( vec3 L = pos_on_light - position; L = normalize(L); - if(is_polygonal && direct_specular_weight > 0 && polygonal_light_is_sky && global_ubo.pt_specular_mis != 0) + if(direct_specular_weight > 0 && polygonal_light_is_sky && global_ubo.pt_specular_mis != 0) { // MIS with direct specular and indirect specular. // Only applied to sky lights, for two reasons: @@ -819,7 +814,7 @@ get_direct_illumination( // 2) Non-sky lights are usually away from walls, so the direct sampling issue is not as pronounced. direct_specular_weight *= get_specular_sampled_lighting_weight(roughness, - normal, -view_direction, L, polygonal_light_pdfw); + normal, -view_direction, L, light_pdfw); } vec3 F = vec3(0); diff --git a/src/refresh/vkpt/shader/restir.h b/src/refresh/vkpt/shader/restir.h new file mode 100644 index 000000000..f69494d6b --- /dev/null +++ b/src/refresh/vkpt/shader/restir.h @@ -0,0 +1,393 @@ +/* +Copyright (C) 2018 Christoph Schied +Copyright (C) 2018 Tobias Zirr +Copyright (C) 2019, NVIDIA CORPORATION. All rights reserved. +Copyright (C) 2022 Jorge Gustavo Martinez + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +// ========================================================================== // +// This rgen shader computes direct lighting for the first opaque surface. +// The parameters of that surface are loaded from the G-buffer, stored there +// previously by the `primary_rays.rgen` and `reflect_refract.rgen` shaders. +// +// See `path_tracer.h` for an overview of the path tracer. +// ========================================================================== // + +#ifndef _RESTIR_H_ +#define _RESTIR_H_ + +#include "path_tracer_rgen.h" + +#define RESTIR_INVALID_ID 0xFFFF +#define RESTIR_ENV_ID 0xFFFE + +#define RESTIR_SPACIAL_DISTANCE 32 +#define RESTIR_SPACIAL_SAMPLES 8 + +#define RESTIR_SAMPLING_M 4 +#define RESTIR_M_CLAMP 32 +#define RESTIR_M_VC_CLAMP 16 + +struct Reservoir +{ + uint y; + uint M; + float w_sum; + float W; + float p_hat; + vec2 y_pos; + vec3 normal; +}; + +void +init_reservoir(inout Reservoir r) +{ + r.y = RESTIR_INVALID_ID; + r.M = 0; + r.w_sum = 0.0; + r.W = 0.0; + r.p_hat = 0.0; + r.y_pos = vec2(0.0); +} + +bool +update_reservoir(uint xi, float wi, vec2 xi_pos, float p_hat, inout float rng, inout Reservoir r) +{ + r.w_sum += wi; + r.M++; + float p_s = (wi/r.w_sum); + if(rng < p_s) + { + r.y = xi; + r.y_pos = xi_pos; + r.p_hat = p_hat; + rng /= p_s; + return true; + } + else + { + rng = (rng - p_s) / (1.0f - p_s); + return false; + } +} + +uvec4 +pack_reservoir(Reservoir r) +{ + uvec4 vec; + r.W = r.y == RESTIR_INVALID_ID ? 0.0 : r.W; + vec.x = packHalf2x16(vec2(r.W, r.w_sum)); + vec.y = packHalf2x16(r.y_pos); + return vec; +} + +void +unpack_reservoir(uvec4 packed, uint light_idx, out Reservoir r) +{ + r.y = light_idx; + r.M = light_idx == RESTIR_INVALID_ID ? 0 : (global_ubo.pt_restir != 3 ? RESTIR_M_CLAMP : RESTIR_M_VC_CLAMP); + vec2 val = unpackHalf2x16(packed.x); + r.W = val.x; + if(isnan(r.W) || isinf(r.W) || r.y == RESTIR_INVALID_ID) r.W = 0.0; + r.w_sum = val.y; + r.y_pos = unpackHalf2x16(packed.y); + r.p_hat = 0.0; +} + +// Functions + +uint +get_light_current_idx(uint index) +{ + if (index < global_ubo.num_static_lights || index == RESTIR_INVALID_ID || index == RESTIR_ENV_ID) + { + return index; + } + else + { + uint light_id_curr = instance_buffer.mlight_prev_to_current[index - global_ubo.num_static_lights]; + if(light_id_curr != ~0u) return light_id_curr + global_ubo.num_static_lights; + else + { + return RESTIR_INVALID_ID; + } + } +} + + +float +get_unshadowed_path_contrib( + uint light_idx, + vec3 position, + vec3 normal, + vec3 view_direction, + float phong_exp, + float phong_scale, + float phong_weight, + vec2 rng) +{ + if(light_idx == RESTIR_ENV_ID) return get_unshadowed_env_path_contrib(normal,view_direction, phong_exp, phong_scale, phong_weight, rng); + LightPolygon light = get_light_polygon(light_idx); + + float m = 0.0f; + switch(uint(light.type)) + { + case DYNLIGHT_POLYGON: + m = projected_tri_area(light.positions, position, normal, view_direction, phong_exp, phong_scale, phong_weight); + break; + case DYNLIGHT_SPHERE: + m = projected_sphere_area(light.positions, position, normal, view_direction, phong_exp, phong_scale, phong_weight); + break; + case DYNLIGHT_SPOT: + m = projected_spotlight_area(light.positions, position, normal, view_direction, phong_exp, phong_scale, phong_weight); + break; + } + + float light_lum = luminance(light.color); + + // Apply light style scaling. + light_lum *= light.light_style_scale; + + if(light_lum < 0 && global_ubo.environment_type == ENVIRONMENT_DYNAMIC) + { + // Set limits on sky luminance to avoid oversampling the sky in shadowed areas, or undersampling at dusk and dawn. + // Note: the log -> linear conversion of the cvars happens on the CPU, in main.c + m *= clamp(sun_color_ubo.sky_luminance, global_ubo.pt_min_log_sky_luminance, global_ubo.pt_max_log_sky_luminance); + } + else + m *= abs(light_lum); // abs because sky lights have negative color + + return m; +} + + +void +process_selected_light_restir( + uint light_idx, + vec2 light_position, + float weight, + vec3 position, + vec3 normal, + vec3 geo_normal, + int shadow_cull_mask, + vec3 view_direction, + vec3 base_reflectivity, + float specular_factor, + float roughness, + int surface_medium, + bool enable_caustics, + float direct_specular_weight, + float phong_exp, + float phong_scale, + float phong_weight, + bool check_vis, + uint cluster_idx, + out vec3 diffuse, + out vec3 specular, + out float vis) +{ + float polygonal_light_pdfw = 0; + vec3 contrib_polygonal = vec3(0); + vec3 L, pos_on_light_polygonal; + bool polygonal_light_is_sky = false; + diffuse = vec3(0); + specular = vec3(0); + vis = 1.0; + + if(light_idx != RESTIR_ENV_ID) + { + LightPolygon light = get_light_polygon(light_idx); + + vec3 light_normal; + + switch(uint(light.type)) + { + case DYNLIGHT_POLYGON: + pos_on_light_polygonal = sample_projected_triangle(position, light.positions, light_position , light_normal, polygonal_light_pdfw); + break; + case DYNLIGHT_SPHERE: + pos_on_light_polygonal = sample_projected_sphere(position, light.positions, light_position , light_normal, polygonal_light_pdfw); + break; + case DYNLIGHT_SPOT: + pos_on_light_polygonal = sample_projected_spotlight(position, light.positions, light_position , light_normal, polygonal_light_pdfw); + break; + } + + L = normalize(pos_on_light_polygonal - position); + + if(dot(L, geo_normal) <= 0) + polygonal_light_pdfw = 0; + + if(polygonal_light_pdfw > 0){ + float LdotNL = max(0, -dot(light_normal, L)); + float spotlight = sqrt(LdotNL); + float inv_pdfw = 1.0 / polygonal_light_pdfw; + + if(light.color.r >= 0) + { + contrib_polygonal = light.color * (inv_pdfw * spotlight * light.light_style_scale); + } + else + { + contrib_polygonal = env_map(L, true) * inv_pdfw * global_ubo.pt_env_scale; + polygonal_light_is_sky = true; + } + } + + } + else + { + vec2 disk = sample_disk(light_position); + disk.xy *= global_ubo.sun_tan_half_angle; + L = normalize(global_ubo.sun_direction + global_ubo.sun_tangent * disk.x + global_ubo.sun_bitangent * disk.y); + polygonal_light_pdfw = global_ubo.sun_solid_angle; + pos_on_light_polygonal = position + L * 10000; + contrib_polygonal = env_map(L, false) * polygonal_light_pdfw * global_ubo.pt_env_scale; + } + + contrib_polygonal *= min(weight, global_ubo.pt_restir_max_w); + + float spec_polygonal = phong(normal, L, view_direction, phong_exp) * phong_scale; + + float l_polygonal = luminance(abs(contrib_polygonal)) * mix(1, spec_polygonal, phong_weight); + + bool null_light = (l_polygonal == 0); + + Ray shadow_ray = get_shadow_ray(position - view_direction * 0.01, pos_on_light_polygonal, 0); + + if(check_vis) vis *= trace_shadow_ray(shadow_ray, null_light ? 0 : shadow_cull_mask); + + #ifdef ENABLE_SHADOW_CAUSTICS + if(enable_caustics) + { + contrib_polygonal *= trace_caustic_ray(shadow_ray, surface_medium); + } + #endif + + if(null_light) + { + vis = 0.0f; + return; + } + + vec3 radiance = vis * contrib_polygonal; + + if(direct_specular_weight > 0 && polygonal_light_is_sky && global_ubo.pt_specular_mis != 0) + { + // MIS with direct specular and indirect specular. + // Only applied to sky lights, for two reasons: + // 1) Non-sky lights are trimmed to match the light texture, and indirect rays don't see that; + // 2) Non-sky lights are usually away from walls, so the direct sampling issue is not as pronounced. + + direct_specular_weight *= get_specular_sampled_lighting_weight(roughness, + normal, -view_direction, L, polygonal_light_pdfw); + } + + vec3 F = vec3(0); + + if(vis > 0 && direct_specular_weight > 0) + { + vec3 specular_brdf = GGX_times_NdotL(view_direction, L, + normal, roughness, base_reflectivity, 0.0, specular_factor, F); + specular = radiance * specular_brdf * direct_specular_weight; + } + + float NdotL = max(0, dot(normal, L)); + + float diffuse_brdf = NdotL / M_PI; + diffuse = radiance * diffuse_brdf * (vec3(1.0) - F); +} + + +void +get_direct_illumination_restir( + vec3 position, + vec3 normal, + uint cluster_idx, + vec3 view_direction, + float phong_exp, + float phong_scale, + float phong_weight, + int bounce, + Reservoir prev_r, + out Reservoir reservoir) +{ + init_reservoir(reservoir); + + if(cluster_idx == ~0u) + return; + + vec3 contrib_polygonal = vec3(0); + + float rng, p_hat; + + uint list_start = light_buffer.light_list_offsets[cluster_idx]; + uint list_end = light_buffer.light_list_offsets[cluster_idx + 1]; + + rng = get_rng(RNG_NEE_LIGHT_SELECTION(bounce)); + + uint add_sun = (global_ubo.sun_visible != 0) && ((cluster_idx == ~0u) || (light_buffer.sky_visibility[cluster_idx >> 5] & (1 << (cluster_idx & 31))) != 0) ? 1 : 0; + + uint sun_idx = add_sun > 0 ? list_end : -1; + list_end += add_sun; + float list_size = float(list_end - list_start); + float partitions = ceil(list_size / float(RESTIR_SAMPLING_M)); + float inv_pdf = list_size; + float rng_part = rng * partitions; + float fpart = min(floor(rng_part), partitions-1); + + list_start += int(fpart); + int stride = int(partitions); + rng = rng_part - floor(rng_part); + + uint current_idx, current_light_idx; + + vec2 rng2 = vec2( + get_rng(RNG_NEE_TRI_X(bounce)), + get_rng(RNG_NEE_TRI_Y(bounce))); + + float samples = 1.; + + #pragma unroll + for(uint i = 0, n_idx = list_start; i < RESTIR_SAMPLING_M; i++, n_idx += stride) + { + if (n_idx >= list_end) + break; + + current_light_idx = n_idx != sun_idx ? light_buffer.light_list_lights[n_idx] : RESTIR_ENV_ID; + + if(current_light_idx == ~0u) continue; + + p_hat = get_unshadowed_path_contrib(current_light_idx, position, normal, view_direction, phong_exp, phong_scale, phong_weight, rng2); + if(p_hat > 0)update_reservoir(current_light_idx, p_hat * inv_pdf, rng2, p_hat, rng, reservoir); + } + + reservoir.M = RESTIR_SAMPLING_M; + + //Combine with temporal + if(prev_r.W > 0.0 && prev_r.y != RESTIR_INVALID_ID && prev_r.p_hat > 0) + { + update_reservoir(prev_r.y, prev_r.p_hat * prev_r.W * prev_r.M ,prev_r.y_pos, prev_r.p_hat, rng, reservoir); + reservoir.M += prev_r.M - 1; + } + + reservoir.W = reservoir.w_sum / (reservoir.p_hat * reservoir.M); + if(isnan(reservoir.W) || isinf(reservoir.W)) reservoir.W = 0.0; +} + + +#endif /*_RESTIR_H_*/ diff --git a/src/refresh/vkpt/shader/vertex_buffer.h b/src/refresh/vkpt/shader/vertex_buffer.h index 07cf1aaa1..7641967be 100644 --- a/src/refresh/vkpt/shader/vertex_buffer.h +++ b/src/refresh/vkpt/shader/vertex_buffer.h @@ -183,6 +183,7 @@ struct LightPolygon vec3 color; float light_style_scale; float prev_style_scale; + float type; }; // The buffers with primitive data, currently two of them: world and instanced. @@ -478,6 +479,7 @@ get_light_polygon(uint index) light.color = vec3(p0.w, p1.w, p2.w); light.light_style_scale = p3.x; light.prev_style_scale = p3.y; + light.type = p3.z; return light; } diff --git a/src/refresh/vkpt/transparency.c b/src/refresh/vkpt/transparency.c index 16b81113a..afc4cdf59 100644 --- a/src/refresh/vkpt/transparency.c +++ b/src/refresh/vkpt/transparency.c @@ -468,7 +468,7 @@ static int compare_beams(const void* _a, const void* _b) return 0; } -bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, vec3_t begin, vec3_t end, vec3_t color, float radius) +bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, vec3_t begin, vec3_t end, vec3_t color, float radius, entity_hash_t hash, int* light_entity_ids) { vec3_t dir, norm_dir; VectorSubtract(end, begin, dir); @@ -540,11 +540,14 @@ bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int ma light->cluster = BSP_PointLeaf(bsp->nodes, light->off_center)->cluster; light->material = NULL; light->style = 0; + light->type = DYNLIGHT_POLYGON; VectorCopy(color, light->color); if (light->cluster >= 0) { + hash.mesh = tri; + light_entity_ids[(*num_lights)] = *(uint32_t*)&hash; (*num_lights)++; } } @@ -552,7 +555,7 @@ bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int ma return true; } -void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, entity_t* entities, int num_entites, float adapted_luminance) +void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, entity_t* entities, int num_entites, float adapted_luminance, int* light_entity_ids, int* num_light_entities) { const float hdr_factor = cvar_pt_beam_lights->value * adapted_luminance * 20.f; @@ -584,6 +587,10 @@ void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_l const entity_t* beam = beams[i]; + entity_hash_t hash; + hash.entity = (beams[i] - entities) + 1; //entity ID + hash.model = RF_BEAM; + // Adjust beam width. Default "narrow" beams have a width of 4, "fat" beams have 16. if (beam->frame == 0) continue; @@ -606,7 +613,7 @@ void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_l vec3_t color; cast_u32_to_f32_color(beam->skinnum, &beam->rgba, color, hdr_factor); - vkpt_build_cylinder_light(light_list, num_lights, max_lights, bsp, begin, end, color, beam_radius); + vkpt_build_cylinder_light(light_list, num_lights, max_lights, bsp, begin, end, color, beam_radius, hash, light_entity_ids); } } diff --git a/src/refresh/vkpt/vertex_buffer.c b/src/refresh/vkpt/vertex_buffer.c index 5ae49977a..ff84b4e42 100644 --- a/src/refresh/vkpt/vertex_buffer.c +++ b/src/refresh/vkpt/vertex_buffer.c @@ -635,7 +635,7 @@ copy_light(const light_poly_t* light, float* vblight, const float* sky_radiance) vblight[12] = style_scale; vblight[13] = prev_style; - vblight[14] = 0.f; + vblight[14] = light->type; vblight[15] = 0.f; } diff --git a/src/refresh/vkpt/vkpt.h b/src/refresh/vkpt/vkpt.h index f43508c8f..ff7ea61d4 100644 --- a/src/refresh/vkpt/vkpt.h +++ b/src/refresh/vkpt/vkpt.h @@ -460,6 +460,13 @@ typedef struct sun_light_s { bool visible; } sun_light_t; +typedef struct entity_hash_s { + unsigned int mesh : 8; + unsigned int model : 9; + unsigned int entity : 14; + unsigned int bsp : 1; +} entity_hash_t; + void mult_matrix_matrix(mat4_t p, const mat4_t a, const mat4_t b); void mult_matrix_vector(vec4_t v, const mat4_t a, const vec4_t b); void create_entity_matrix(mat4_t matrix, entity_t *e); @@ -765,8 +772,8 @@ VkBufferView get_transparency_beam_color_buffer_view(void); VkBufferView get_transparency_sprite_info_buffer_view(void); VkBufferView get_transparency_beam_intersect_buffer_view(void); void get_transparency_counts(int* particle_num, int* beam_num, int* sprite_num); -void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, entity_t* entities, int num_entites, float adapted_luminance); -bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, vec3_t begin, vec3_t end, vec3_t color, float radius); +void vkpt_build_beam_lights(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, entity_t* entities, int num_entites, float adapted_luminance, int* light_entity_ids, int* num_light_entities); +bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, vec3_t begin, vec3_t end, vec3_t color, float radius, entity_hash_t hash, int* light_entity_ids); bool get_triangle_off_center(const float* positions, float* center, float* anti_center, float offset); VkResult vkpt_initialize_god_rays(void); From d7d040d562c5719dad5fdae1bcad363d94533612 Mon Sep 17 00:00:00 2001 From: abalfoort Date: Mon, 2 Jun 2025 13:38:46 +0200 Subject: [PATCH 2/3] Fix build warnings --- src/refresh/vkpt/main.c | 18 ++++++++++++++---- src/refresh/vkpt/transparency.c | 6 +++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/refresh/vkpt/main.c b/src/refresh/vkpt/main.c index ed196df02..ab83b808d 100644 --- a/src/refresh/vkpt/main.c +++ b/src/refresh/vkpt/main.c @@ -1843,7 +1843,10 @@ add_dlights(const dlight_t* dlights, int num_dlights, light_poly_t* light_list, break; } - light_entity_ids[(*num_lights)] = *(uint32_t*)&hash; + uint32_t tmp; + memcpy(&tmp, &hash, sizeof(hash)); + light_entity_ids[(*num_lights)] = tmp; + (*num_lights)++; } @@ -1891,7 +1894,10 @@ static void instance_model_lights(int num_light_polys, const light_poly_t* light dst_light->type = DYNLIGHT_POLYGON; hash.mesh = nlight; //More a light index than a mesh - light_entity_ids[entity_frame_num][num_model_lights] = *(uint32_t*)&hash; + + uint32_t tmp; + memcpy(&tmp, &hash, sizeof(hash)); + light_entity_ids[entity_frame_num][num_model_lights] = tmp; num_model_lights++; } @@ -2971,11 +2977,15 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c } static void -update_mlight_prev_to_current() +update_mlight_prev_to_current(void) { light_entity_id_count[entity_frame_num] = num_model_lights; for(int i = 0; i < light_entity_id_count[entity_frame_num]; i++) { - entity_hash_t hash = *(entity_hash_t*)&light_entity_ids[entity_frame_num][i]; + + entity_hash_t tmp; + memcpy(&tmp, &light_entity_ids[entity_frame_num][i], sizeof(light_entity_ids[entity_frame_num][i])); + entity_hash_t hash = tmp; + if(hash.entity == 0u) continue; for(int j = 0; j < light_entity_id_count[!entity_frame_num]; j++) { if(light_entity_ids[entity_frame_num][i] == light_entity_ids[!entity_frame_num][j]) { diff --git a/src/refresh/vkpt/transparency.c b/src/refresh/vkpt/transparency.c index afc4cdf59..ee59c4186 100644 --- a/src/refresh/vkpt/transparency.c +++ b/src/refresh/vkpt/transparency.c @@ -547,7 +547,11 @@ bool vkpt_build_cylinder_light(light_poly_t* light_list, int* num_lights, int ma if (light->cluster >= 0) { hash.mesh = tri; - light_entity_ids[(*num_lights)] = *(uint32_t*)&hash; + + uint32_t tmp; + memcpy(&tmp, &hash, sizeof(hash)); + light_entity_ids[(*num_lights)] = tmp; + (*num_lights)++; } } From 1ce4325cd9c6108b19de57b9d2ae407e63c38e5f Mon Sep 17 00:00:00 2001 From: abalfoort Date: Tue, 10 Jun 2025 09:15:28 +0200 Subject: [PATCH 3/3] Restore PR #266 --- src/refresh/vkpt/shader/light_lists.h | 58 ++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/refresh/vkpt/shader/light_lists.h b/src/refresh/vkpt/shader/light_lists.h index 4fb7f2c05..97563bf23 100644 --- a/src/refresh/vkpt/shader/light_lists.h +++ b/src/refresh/vkpt/shader/light_lists.h @@ -166,18 +166,56 @@ sample_projected_triangle(vec3 pt, mat3 positions, vec2 rnd, out vec3 light_norm // Distance of triangle to origin float o = dot(light_normal, positions[0]); - positions[0] = normalize(positions[0]); - positions[1] = normalize(positions[1]); - positions[2] = normalize(positions[2]); - - vec3 direction = positions * sample_triangle(rnd); - float dl = length(direction); - - // n (p + d * t - p[i]) == 0 - // -n (p - pi) / n d == o / n d == t + // Project triangle to unit sphere + vec3 A = normalize(positions[0]); + vec3 B = normalize(positions[1]); + vec3 C = normalize(positions[2]); + // Planes passing through two vertices and origin. They'll be used to obtain the angles. + vec3 norm_AB = normalize(cross(A, B)); + vec3 norm_BC = normalize(cross(B, C)); + vec3 norm_CA = normalize(cross(C, A)); + // Side of spherical triangle + float cos_c = dot(A, B); + // Angles at vertices + float cos_alpha = dot(norm_AB, -norm_CA); + float cos_beta = dot(norm_BC, -norm_AB); + float cos_gamma = dot(norm_CA, -norm_BC); + + // Area of spherical triangle + float area = acos(cos_alpha) + acos(cos_beta) + acos(cos_gamma) - M_PI; + + // Use one random variable to select the new area. + float new_area = rnd.x * area; + + float sin_alpha = sqrt(1 - cos_alpha * cos_alpha); // = sin(acos(cos_alpha)) + float sin_new_area = sin(new_area); + float cos_new_area = cos(new_area); + // Save the sine and cosine of the angle phi. + float p = sin_new_area * cos_alpha - cos_new_area * sin_alpha; + float q = cos_new_area * cos_alpha + sin_new_area * sin_alpha; + + // Compute the pair (u, v) that determines new_beta. + float u = q - cos_alpha; + float v = p + sin_alpha * cos_c; + + // Let cos_b be the cosine of the new edge length new_b. + float cos_b = ((v * q - u * p) * cos_alpha - v) / ((v * p + u * q) * sin_alpha); + + // Compute the third vertex of the sub-triangle. + vec3 new_C = cos_b * A + sqrt(1 - cos_b * cos_b) * normalize(C - dot(C, A) * A); + + // Use the other random variable to select cos(phi). + float z = 1 - rnd.y * (1 - dot(new_C, B)); + + // Construct the corresponding point on the sphere. + vec3 direction = z * B + sqrt(1 - z * z) * normalize(new_C - dot(new_C, B) * B); + // ...which is also the direction! + + // Line-plane intersection vec3 lo = direction * (o / dot(light_normal, direction)); - pdfw = get_triangle_pdfw(positions, direction); + // Since the solid angle is distributed uniformly, the PDF wrt to solid angle is simply: + pdfw = 1 / area; return pt + lo; }