summaryrefslogtreecommitdiffstats
path: root/modules/lightmapper_rd
diff options
context:
space:
mode:
Diffstat (limited to 'modules/lightmapper_rd')
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp105
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.h10
-rw-r--r--modules/lightmapper_rd/lm_compute.glsl202
3 files changed, 245 insertions, 72 deletions
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 835fb3e59d..8ba6f9e2ba 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -44,6 +44,9 @@
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_context_driver_vulkan.h"
#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES
@@ -226,21 +229,21 @@ void LightmapperRD::_sort_triangle_clusters(uint32_t p_cluster_size, uint32_t p_
}
}
-Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) {
+Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_size, int p_denoiser_range, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata) {
Vector<Size2i> sizes;
for (int m_i = 0; m_i < mesh_instances.size(); m_i++) {
MeshInstance &mi = mesh_instances.write[m_i];
Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height());
sizes.push_back(s);
- atlas_size = atlas_size.max(s + Size2i(2, 2));
+ atlas_size = atlas_size.max(s + Size2i(2, 2).maxi(p_denoiser_range));
}
int max = nearest_power_of_2_templated(atlas_size.width);
max = MAX(max, nearest_power_of_2_templated(atlas_size.height));
if (max > p_max_texture_size) {
- return BAKE_ERROR_LIGHTMAP_TOO_SMALL;
+ return BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE;
}
if (p_step_function) {
@@ -254,19 +257,27 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
int best_atlas_memory = 0x7FFFFFFF;
Vector<Vector3i> best_atlas_offsets;
- //determine best texture array atlas size by bruteforce fitting
+ // Determine best texture array atlas size by bruteforce fitting.
while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) {
Vector<Vector2i> source_sizes;
Vector<int> source_indices;
source_sizes.resize(sizes.size());
source_indices.resize(sizes.size());
for (int i = 0; i < source_indices.size(); i++) {
- source_sizes.write[i] = sizes[i] + Vector2i(2, 2); // Add padding between lightmaps
+ source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps.
source_indices.write[i] = i;
}
Vector<Vector3i> atlas_offsets;
atlas_offsets.resize(source_sizes.size());
+ // Ensure the sizes can all fit into a single atlas layer.
+ // This should always happen, and this check is only in place to prevent an infinite loop.
+ for (int i = 0; i < source_sizes.size(); i++) {
+ if (source_sizes[i] > atlas_size) {
+ return BAKE_ERROR_ATLAS_TOO_SMALL;
+ }
+ }
+
int slices = 0;
while (source_sizes.size() > 0) {
@@ -428,6 +439,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
SWAP(edge.a, edge.b);
SWAP(edge.na, edge.nb);
SWAP(uv2.a, uv2.b);
+ SWAP(uv2.indices.x, uv2.indices.y);
SWAP(edge_indices.x, edge_indices.y);
}
@@ -706,7 +718,7 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x);
raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y);
- RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS);
//draw opaque
rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
@@ -781,6 +793,35 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
return BAKE_OK;
}
+LightmapperRD::BakeError LightmapperRD::_pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) {
+ Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex);
+
+ RID compute_shader_pack = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("pack_coeffs"));
+ ERR_FAIL_COND_V(compute_shader_pack.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
+ RID compute_shader_pack_pipeline = rd->compute_pipeline_create(compute_shader_pack);
+
+ RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_pack, 1);
+
+ RD::ComputeListID compute_list = rd->compute_list_begin();
+ rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_pack_pipeline);
+ rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0);
+ rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1);
+ push_constant.region_ofs[0] = 0;
+ push_constant.region_ofs[1] = 0;
+ Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size
+
+ for (int i = 0; i < atlas_slices; i++) {
+ push_constant.atlas_slice = i;
+ rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant));
+ rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z);
+ //no barrier, let them run all together
+ }
+ rd->compute_list_end();
+ rd->free(compute_shader_pack);
+
+ return BAKE_OK;
+}
+
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
@@ -906,7 +947,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
return BAKE_OK;
}
-LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) {
+LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata) {
RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
DenoiseParams denoise_params;
denoise_params.spatial_bandwidth = 5.0f;
@@ -914,6 +955,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
denoise_params.albedo_bandwidth = 1.0f;
denoise_params.normal_bandwidth = 0.1f;
denoise_params.filter_strength = 10.0f;
+ denoise_params.half_search_window = p_denoiser_range;
p_rd->buffer_update(denoise_params_buffer, 0, sizeof(DenoiseParams), &denoise_params);
Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(p_source_light_tex, p_dest_light_tex);
@@ -968,6 +1010,11 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
p_rd->sync();
}
}
+ if (p_step_function) {
+ int percent = (s + 1) * 100 / p_atlas_slices;
+ float p = float(s) / p_atlas_slices * 0.1;
+ p_step_function(0.8 + p, vformat(RTR("Denoising %d%%"), percent), p_bake_userdata, false);
+ }
}
p_rd->free(compute_shader_denoise);
@@ -976,7 +1023,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
return BAKE_OK;
}
-LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
+LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
@@ -1008,7 +1055,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
Vector<Ref<Image>> albedo_images;
Vector<Ref<Image>> emission_images;
- BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata);
+ BakeError bake_error = _blit_meshes_into_atlas(p_max_texture_size, p_denoiser_range, albedo_images, emission_images, bounds, atlas_size, atlas_slices, p_step_function, p_bake_userdata);
if (bake_error != BAKE_OK) {
return bake_error;
}
@@ -1028,10 +1075,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
if (rd == nullptr) {
#if defined(RD_ENABLED)
-#if defined(VULKAN_ENABLED)
- rcd = memnew(RenderingContextDriverVulkan);
+#if defined(METAL_ENABLED)
+ rcd = memnew(RenderingContextDriverMetal);
rd = memnew(RenderingDevice);
#endif
+#if defined(VULKAN_ENABLED)
+ if (rcd == nullptr) {
+ rcd = memnew(RenderingContextDriverVulkan);
+ rd = memnew(RenderingDevice);
+ }
+#endif
#endif
if (rcd != nullptr && rd != nullptr) {
err = rcd->initialize();
@@ -1571,12 +1624,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
img->save_exr("res://2_light_primary_" + itos(i) + ".exr", false);
}
+
+ if (p_bake_sh) {
+ for (int i = 0; i < atlas_slices * 4; i++) {
+ Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
+ Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
+ img->save_exr("res://2_light_primary_accum_" + itos(i) + ".exr", false);
+ }
+ }
#endif
/* SECONDARY (indirect) LIGHT PASS(ES) */
- if (p_step_function) {
- p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true);
- }
if (p_bounces > 0) {
Vector<RD::Uniform> uniforms;
@@ -1640,6 +1698,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
rd->submit();
rd->sync();
+ if (p_step_function) {
+ p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true);
+ }
+
int count = 0;
for (int s = 0; s < atlas_slices; s++) {
push_constant.atlas_slice = s;
@@ -1692,7 +1754,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
light_probe_buffer = rd->storage_buffer_create(sizeof(float) * 4 * 9 * probe_positions.size());
if (p_step_function) {
- p_step_function(0.7, RTR("Baking lightprobes"), p_bake_userdata, true);
+ p_step_function(0.7, RTR("Baking light probes"), p_bake_userdata, true);
}
Vector<RD::Uniform> uniforms;
@@ -1793,7 +1855,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
} else {
// JNLM (built-in).
SWAP(light_accum_tex, light_accum_tex2);
- error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, atlas_size, atlas_slices, p_bake_sh, p_step_function);
+ error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function, p_bake_userdata);
}
if (unlikely(error != BAKE_OK)) {
return error;
@@ -1908,7 +1970,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
seams_push_constant.slice = uint32_t(i * subslices + k);
seams_push_constant.debug = debug;
- RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
+ // Store the current subslice in the breadcrumb.
+ RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS | seams_push_constant.slice);
rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0);
rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1);
@@ -1968,6 +2031,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}
}
+ if (p_bake_sh) {
+ SWAP(light_accum_tex, light_accum_tex2);
+ BakeError error = _pack_l1(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices);
+ if (unlikely(error != BAKE_OK)) {
+ return error;
+ }
+ }
+
#ifdef DEBUG_TEXTURES
for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) {
diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h
index 5414048ddc..f43da39670 100644
--- a/modules/lightmapper_rd/lightmapper_rd.h
+++ b/modules/lightmapper_rd/lightmapper_rd.h
@@ -262,16 +262,18 @@ class LightmapperRD : public Lightmapper {
float albedo_bandwidth;
float normal_bandwidth;
+ int half_search_window;
float filter_strength;
- float pad[3];
+ float pad[2];
};
- BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata);
+ BakeError _blit_meshes_into_atlas(int p_max_texture_size, int p_denoiser_range, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata);
void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, uint32_t p_cluster_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &lights_buffer, RID &r_triangle_indices_buffer, RID &r_cluster_indices_buffer, RID &r_cluster_aabbs_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata);
void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform);
BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
- BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function);
+ BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata);
+ BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
Ref<Image> _read_pfm(const String &p_name);
@@ -283,7 +285,7 @@ public:
virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override;
virtual void add_probe(const Vector3 &p_position) override;
- virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
+ virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override;
int get_bake_texture_count() const override;
Ref<Image> get_bake_texture(int p_index) const override;
diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl
index 1d088450e9..2c85fff6f3 100644
--- a/modules/lightmapper_rd/lm_compute.glsl
+++ b/modules/lightmapper_rd/lm_compute.glsl
@@ -6,6 +6,7 @@ dilate = "#define MODE_DILATE";
unocclude = "#define MODE_UNOCCLUDE";
light_probes = "#define MODE_LIGHT_PROBES";
denoise = "#define MODE_DENOISE";
+pack_coeffs = "#define MODE_PACK_L1_COEFFS";
#[compute]
@@ -63,7 +64,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
layout(set = 1, binding = 5) uniform texture2D environment;
#endif
-#if defined(MODE_DILATE) || defined(MODE_DENOISE)
+#if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS)
layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light;
layout(set = 1, binding = 1) uniform texture2DArray source_light;
#endif
@@ -76,6 +77,7 @@ layout(set = 1, binding = 3) uniform DenoiseParams {
float albedo_bandwidth;
float normal_bandwidth;
+ int half_search_window;
float filter_strength;
}
denoise_params;
@@ -358,7 +360,36 @@ float get_omni_attenuation(float distance, float inv_range, float decay) {
return nd * pow(max(distance, 0.0001), -decay);
}
-void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) {
+const int AA_SAMPLES = 16;
+
+const vec2 halton_map[AA_SAMPLES] = vec2[](
+ vec2(0.5, 0.33333333),
+ vec2(0.25, 0.66666667),
+ vec2(0.75, 0.11111111),
+ vec2(0.125, 0.44444444),
+ vec2(0.625, 0.77777778),
+ vec2(0.375, 0.22222222),
+ vec2(0.875, 0.55555556),
+ vec2(0.0625, 0.88888889),
+ vec2(0.5625, 0.03703704),
+ vec2(0.3125, 0.37037037),
+ vec2(0.8125, 0.7037037),
+ vec2(0.1875, 0.14814815),
+ vec2(0.6875, 0.48148148),
+ vec2(0.4375, 0.81481481),
+ vec2(0.9375, 0.25925926),
+ vec2(0.03125, 0.59259259));
+
+vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) {
+ const float golden_angle = 2.4;
+
+ float r = sqrt(p_i + 0.5) / p_sample_count_sqrt;
+ float theta = p_i * golden_angle + p_rotation;
+
+ return vec2(cos(theta), sin(theta)) * r;
+}
+
+void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size) {
r_light = vec3(0.0f);
vec3 light_pos;
@@ -406,46 +437,70 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool
}
float penumbra = 0.0;
- if ((light_data.size > 0.0) && p_soft_shadowing) {
+ if (p_soft_shadowing) {
+ const bool use_soft_shadows = (light_data.size > 0.0);
+ const uint ray_count = AA_SAMPLES;
+ const uint total_ray_count = use_soft_shadows ? params.ray_count : ray_count;
+ const uint shadowing_rays_check_penumbra_denom = 2;
+ const uint shadowing_ray_count = max(1, params.ray_count / ray_count);
+ const float shadowing_ray_count_sqrt = sqrt(float(total_ray_count));
+
+ // Setup tangent pass to calculate AA samples over the current texel.
+ vec3 aux = p_normal.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
+ vec3 tangent = normalize(cross(p_normal, aux));
+ vec3 bitan = normalize(cross(p_normal, tangent));
+
+ // Setup light tangent pass to calculate samples over disk aligned towards the light
vec3 light_to_point = -r_light_dir;
- vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
- vec3 light_to_point_tan = normalize(cross(light_to_point, aux));
+ vec3 light_aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
+ vec3 light_to_point_tan = normalize(cross(light_to_point, light_aux));
vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan));
- const uint shadowing_rays_check_penumbra_denom = 2;
- uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1;
-
uint hits = 0;
- vec3 light_disk_to_point = light_to_point;
- for (uint j = 0; j < shadowing_ray_count; j++) {
- // Optimization:
- // Once already traced an important proportion of rays, if all are hits or misses,
- // assume we're not in the penumbra so we can infer the rest would have the same result
- if (p_soft_shadowing) {
- if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) {
- if (hits == j) {
- // Assume totally lit
- hits = shadowing_ray_count;
- break;
- } else if (hits == 0) {
- // Assume totally dark
- hits = 0;
- break;
+ for (uint i = 0; i < ray_count; i++) {
+ // Create a random sample within the texel.
+ vec2 disk_sample = (halton_map[i] - vec2(0.5)) * p_texel_size * light_data.shadow_blur;
+ // Align the sample to world space.
+ vec3 disk_aligned = (disk_sample.x * tangent + disk_sample.y * bitan);
+ vec3 origin = p_position - disk_aligned;
+ vec3 light_dir = normalize(light_pos - origin);
+
+ if (use_soft_shadows) {
+ uint soft_shadow_hits = 0;
+ for (uint j = 0; j < shadowing_ray_count; j++) {
+ // Optimization:
+ // Once already traced an important proportion of rays, if all are hits or misses,
+ // assume we're not in the penumbra so we can infer the rest would have the same result.
+ if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) {
+ if (soft_shadow_hits == j) {
+ // Assume totally lit
+ soft_shadow_hits = shadowing_ray_count;
+ break;
+ } else if (soft_shadow_hits == 0) {
+ // Assume totally dark
+ soft_shadow_hits = 0;
+ break;
+ }
}
- }
- }
-
- float r = randomize(r_noise);
- float a = randomize(r_noise) * 2.0 * PI;
- vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur;
- light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan);
- if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) {
- hits++;
+ float a = randomize(r_noise) * 2.0 * PI;
+ float vogel_index = float(total_ray_count - 1 - (i * shadowing_ray_count + j)); // Start from (total_ray_count - 1) so we check the outer points first.
+ vec2 light_disk_sample = (get_vogel_disk(vogel_index, a, shadowing_ray_count_sqrt)) * soft_shadowing_disk_size * light_data.shadow_blur;
+ vec3 light_disk_to_point = normalize(light_to_point + light_disk_sample.x * light_to_point_tan + light_disk_sample.y * light_to_point_bitan);
+ // Offset the ray origin for AA, offset the light position for soft shadows.
+ if (trace_ray_any_hit(origin - light_disk_to_point * (bake_params.bias + length(disk_sample)), p_position - light_disk_to_point * dist) == RAY_MISS) {
+ soft_shadow_hits++;
+ }
+ }
+ hits += soft_shadow_hits;
+ } else {
+ // Offset the ray origin based on the disk. Also increase the bias for further samples to avoid bleeding.
+ if (trace_ray_any_hit(origin + light_dir * (bake_params.bias + length(disk_sample)), light_pos) == RAY_MISS) {
+ hits++;
+ }
}
}
-
- penumbra = float(hits) / float(shadowing_ray_count);
+ penumbra = float(hits) / float(total_ray_count);
} else {
if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) {
penumbra = 1.0;
@@ -469,7 +524,7 @@ vec3 trace_environment_color(vec3 ray_dir) {
return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb;
}
-vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) {
+vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, float p_texel_size) {
// The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested.
vec3 position = p_position;
vec3 ray_dir = p_ray_dir;
@@ -501,7 +556,7 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) {
for (uint i = 0; i < bake_params.light_count; i++) {
vec3 light;
vec3 light_dir;
- trace_direct_light(position, normal, i, false, light, light_dir, r_noise);
+ trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size);
direct_light += light * lights.data[i].indirect_energy;
}
@@ -565,6 +620,14 @@ void main() {
return; //empty texel, no process
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+ vec4 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0).xyzw;
+
+ if (neighbor_position.w < 0.001) {
+ // Empty texel, try again.
+ neighbor_position.xyz = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0).xyz;
+ }
+ float texel_size_world_space = distance(position, neighbor_position.xyz);
+
vec3 light_for_texture = vec3(0.0);
vec3 light_for_bounces = vec3(0.0);
@@ -581,21 +644,26 @@ void main() {
for (uint i = 0; i < bake_params.light_count; i++) {
vec3 light;
vec3 light_dir;
- trace_direct_light(position, normal, i, true, light, light_dir, noise);
+ trace_direct_light(position, normal, i, true, light, light_dir, noise, texel_size_world_space);
if (lights.data[i].static_bake) {
light_for_texture += light;
#ifdef USE_SH_LIGHTMAPS
+ // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor.
+ // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25
+ // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0
+ // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader.
+
float c[4] = float[](
- 0.282095, //l0
- 0.488603 * light_dir.y, //l1n1
- 0.488603 * light_dir.z, //l1n0
- 0.488603 * light_dir.x //l1p1
+ 0.25, //l0
+ light_dir.y, //l1n1
+ light_dir.z, //l1n0
+ light_dir.x //l1p1
);
for (uint j = 0; j < 4; j++) {
- sh_accum[j].rgb += light * c[j] * 8.0;
+ sh_accum[j].rgb += light * c[j] * bake_params.exposure_normalization;
}
#endif
}
@@ -639,21 +707,29 @@ void main() {
}
vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
+ int neighbor_offset = atlas_pos.x < bake_params.atlas_size.x - 1 ? 1 : -1;
+ vec3 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(neighbor_offset, 0), params.atlas_slice), 0).xyz;
+ float texel_size_world_space = distance(position, neighbor_position);
uint noise = random_seed(ivec3(params.ray_from, atlas_pos));
for (uint i = params.ray_from; i < params.ray_to; i++) {
vec3 ray_dir = generate_ray_dir_from_normal(normal, noise);
- vec3 light = trace_indirect_light(position, ray_dir, noise);
+ vec3 light = trace_indirect_light(position, ray_dir, noise, texel_size_world_space);
#ifdef USE_SH_LIGHTMAPS
+ // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor.
+ // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25
+ // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0
+ // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader.
+
float c[4] = float[](
- 0.282095, //l0
- 0.488603 * ray_dir.y, //l1n1
- 0.488603 * ray_dir.z, //l1n0
- 0.488603 * ray_dir.x //l1p1
+ 0.25, //l0
+ ray_dir.y, //l1n1
+ ray_dir.z, //l1n0
+ ray_dir.x //l1p1
);
for (uint j = 0; j < 4; j++) {
- sh_accum[j].rgb += light * c[j] * 8.0;
+ sh_accum[j].rgb += light * c[j];
}
#else
light_accum += light;
@@ -736,7 +812,7 @@ void main() {
uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */));
for (uint i = params.ray_from; i < params.ray_to; i++) {
vec3 ray_dir = generate_sphere_uniform_direction(noise);
- vec3 light = trace_indirect_light(position, ray_dir, noise);
+ vec3 light = trace_indirect_light(position, ray_dir, noise, 0.0);
float c[9] = float[](
0.282095, //l0
@@ -849,10 +925,10 @@ void main() {
// Half the size of the patch window around each pixel that is weighted to compute the denoised pixel.
// A value of 1 represents a 3x3 window, a value of 2 a 5x5 window, etc.
- const int HALF_PATCH_WINDOW = 4;
+ const int HALF_PATCH_WINDOW = 3;
// Half the size of the search window around each pixel that is denoised and weighted to compute the denoised pixel.
- const int HALF_SEARCH_WINDOW = 10;
+ const int HALF_SEARCH_WINDOW = denoise_params.half_search_window;
// For all of the following sigma values, smaller values will give less weight to pixels that have a bigger distance
// in the feature being evaluated. Therefore, smaller values are likely to cause more noise to appear, but will also
@@ -962,4 +1038,28 @@ void main() {
imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a));
}
#endif
+
+#ifdef MODE_PACK_L1_COEFFS
+ vec4 base_coeff = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4), 0);
+
+ for (int i = 1; i < 4; i++) {
+ vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4 + i), 0);
+
+ if (abs(base_coeff.r) > 0.0) {
+ c.r /= (base_coeff.r * 8);
+ }
+
+ if (abs(base_coeff.g) > 0.0) {
+ c.g /= (base_coeff.g * 8);
+ }
+
+ if (abs(base_coeff.b) > 0.0) {
+ c.b /= (base_coeff.b * 8);
+ }
+
+ c.rgb += vec3(0.5);
+ c.rgb = clamp(c.rgb, vec3(0.0), vec3(1.0));
+ imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), c);
+ }
+#endif
}