Skip to content

Commit 899e56d

Browse files
committed
Re-add optional OIDN denoise as an external executable.
1 parent 4b7cc99 commit 899e56d

File tree

7 files changed

+177
-3
lines changed

7 files changed

+177
-3
lines changed

doc/classes/EditorSettings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,10 @@
500500
If [code]true[/code], when saving a file, the editor will rename the old file to a different name, save a new file, then only remove the old file once the new file has been saved. This makes loss of data less likely to happen if the editor or operating system exits unexpectedly while saving (e.g. due to a crash or power outage).
501501
[b]Note:[/b] On Windows, this feature can interact negatively with certain antivirus programs. In this case, you may have to set this to [code]false[/code] to prevent file locking issues.
502502
</member>
503+
<member name="filesystem/tools/oidn/oidn_denoise_path" type="String" setter="" getter="">
504+
The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url].
505+
To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser].
506+
</member>
503507
<member name="interface/editor/accept_dialog_cancel_ok_buttons" type="int" setter="" getter="">
504508
How to position the Cancel and OK buttons in the editor's [AcceptDialog]s. Different platforms have different standard behaviors for this, which can be overridden using this setting. This is useful if you use Godot both on Windows and macOS/Linux and your Godot muscle memory is stronger than your OS specific one.
505509
- [b]Auto[/b] follows the platform convention: Cancel first on macOS and Linux, OK first on Windows.

doc/classes/LightmapGI.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
The [CameraAttributes] resource that specifies exposure levels to bake at. Auto-exposure and non exposure properties will be ignored. Exposure settings should be used to reduce the dynamic range present when baking. If exposure is too high, the [LightmapGI] will have banding artifacts or may have over-exposure artifacts.
2626
</member>
2727
<member name="denoiser_strength" type="float" setter="set_denoiser_strength" getter="get_denoiser_strength" default="0.1">
28-
The strength of denoising step applied to the generated lightmaps. Only effective if [member use_denoiser] is [code]true[/code].
28+
The strength of denoising step applied to the generated lightmaps. Only effective if [member use_denoiser] is [code]true[/code] and [member ProjectSettings.rendering/lightmapping/denoising/denoiser] is set to JNLM.
2929
</member>
3030
<member name="directional" type="bool" setter="set_directional" getter="is_directional" default="false">
3131
If [code]true[/code], bakes lightmaps to contain directional information as spherical harmonics. This results in more realistic lighting appearance, especially with normal mapped materials and for lights that have their direct light baked ([member Light3D.light_bake_mode] set to [constant Light3D.BAKE_STATIC]). The directional information is also used to provide rough reflections for static and dynamic objects. This has a small run-time performance cost as the shader has to perform more work to interpret the direction information from the lightmap. Directional lightmaps also take longer to bake and result in larger file sizes.

doc/classes/ProjectSettings.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,15 @@
24462446
<member name="rendering/lightmapping/bake_quality/ultra_quality_ray_count" type="int" setter="" getter="" default="1024">
24472447
The number of rays to use for baking lightmaps with [LightmapGI] when [member LightmapGI.quality] is [constant LightmapGI.BAKE_QUALITY_ULTRA].
24482448
</member>
2449+
<member name="rendering/lightmapping/denoising/denoiser" type="int" setter="" getter="" default="0">
2450+
Denoiser tool used for denoising lightmaps.
2451+
Using [url=https://www.openimagedenoise.org/]OpenImageDenoise[/url] (OIDN) requires configuring a path to an OIDN executable in the editor settings at [member EditorSettings.filesystem/tools/oidn/oidn_denoise_path]. OIDN can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]OpenImageDenoise's downloads page[/url].
2452+
OIDN will use GPU acceleration when available. Unlike JNLM which uses compute shaders for acceleration, OIDN uses vendor-specific acceleration methods. For GPU acceleration to be available, the following libraries must be installed on the system depending on your GPU:
2453+
- NVIDIA GPUs: CUDA libraries
2454+
- AMD GPUs: HIP libraries
2455+
- Intel GPUs: SYCL libraries
2456+
If no GPU acceleration is configured on the system, multi-threaded CPU-based denoising will be performed instead. This CPU-based denoising is significantly slower than the JNLM denoiser in most cases.
2457+
</member>
24492458
<member name="rendering/lightmapping/primitive_meshes/texel_size" type="float" setter="" getter="" default="0.2">
24502459
The texel_size that is used to calculate the [member Mesh.lightmap_size_hint] on [PrimitiveMesh] resources if [member PrimitiveMesh.add_uv2] is enabled.
24512460
</member>

editor/editor_settings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
519519
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
520520
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
521521

522+
// Tools (denoise)
523+
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/tools/oidn/oidn_denoise_path", "", "", PROPERTY_USAGE_DEFAULT)
524+
522525
/* Docks */
523526

524527
// SceneTree

modules/lightmapper_rd/lightmapper_rd.cpp

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
#include "lm_raster.glsl.gen.h"
3636

3737
#include "core/config/project_settings.h"
38+
#include "core/io/dir_access.h"
3839
#include "core/math/geometry_2d.h"
40+
#include "editor/editor_paths.h"
41+
#include "editor/editor_settings.h"
3942
#include "servers/rendering/rendering_device_binds.h"
4043

4144
//uncomment this if you want to see textures from all the process saved
@@ -671,6 +674,131 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
671674
return BAKE_OK;
672675
}
673676

677+
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
678+
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
679+
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
680+
img->convert(Image::FORMAT_RGBF);
681+
Vector<uint8_t> data_float = img->get_data();
682+
683+
Error err = OK;
684+
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::WRITE, &err);
685+
ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save PFN at path: '%s'.", p_name));
686+
file->store_line("PF");
687+
file->store_line(vformat("%d %d", img->get_width(), img->get_height()));
688+
#ifdef BIG_ENDIAN_ENABLED
689+
file->store_line("1.0");
690+
#else
691+
file->store_line("-1.0");
692+
#endif
693+
file->store_buffer(data_float);
694+
file->close();
695+
696+
return OK;
697+
}
698+
699+
Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
700+
Error err = OK;
701+
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
702+
ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
703+
ERR_FAIL_COND_V(file->get_line() != "PF", Ref<Image>());
704+
705+
Vector<String> new_size = file->get_line().split(" ");
706+
ERR_FAIL_COND_V(new_size.size() != 2, Ref<Image>());
707+
int new_width = new_size[0].to_int();
708+
int new_height = new_size[1].to_int();
709+
710+
float endian = file->get_line().to_float();
711+
Vector<uint8_t> new_data = file->get_buffer(file->get_length() - file->get_position());
712+
file->close();
713+
714+
#ifdef BIG_ENDIAN_ENABLED
715+
if (unlikely(endian < 0.0)) {
716+
uint32_t count = new_data.size() / 4;
717+
uint16_t *dst = (uint16_t *)new_data.ptrw();
718+
for (uint32_t j = 0; j < count; j++) {
719+
dst[j * 4] = BSWAP32(dst[j * 4]);
720+
}
721+
}
722+
#else
723+
if (unlikely(endian > 0.0)) {
724+
uint32_t count = new_data.size() / 4;
725+
uint16_t *dst = (uint16_t *)new_data.ptrw();
726+
for (uint32_t j = 0; j < count; j++) {
727+
dst[j * 4] = BSWAP32(dst[j * 4]);
728+
}
729+
}
730+
#endif
731+
Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
732+
img->convert(Image::FORMAT_RGBAH);
733+
return img;
734+
}
735+
736+
LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe) {
737+
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
738+
739+
for (int i = 0; i < p_atlas_slices; i++) {
740+
String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
741+
_store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in);
742+
743+
for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
744+
int index = i * (p_bake_sh ? 4 : 1) + j;
745+
String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
746+
String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
747+
748+
_store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in);
749+
750+
List<String> args;
751+
args.push_back("--device");
752+
args.push_back("default");
753+
754+
args.push_back("--filter");
755+
args.push_back("RTLightmap");
756+
757+
args.push_back("--hdr");
758+
args.push_back(fname_light_in);
759+
760+
args.push_back("--nrm");
761+
args.push_back(fname_norm_in);
762+
763+
args.push_back("--output");
764+
args.push_back(fname_out);
765+
766+
String str;
767+
int exitcode = 0;
768+
769+
Error err = OS::get_singleton()->execute(p_exe, args, &str, &exitcode, true);
770+
771+
da->remove(fname_light_in);
772+
773+
if (err != OK || exitcode != 0) {
774+
da->remove(fname_out);
775+
print_verbose(str);
776+
ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat(TTR("OIDN denoiser failed, return code: %d"), exitcode));
777+
}
778+
779+
Ref<Image> img = _read_pfm(fname_out);
780+
da->remove(fname_out);
781+
782+
ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
783+
784+
Vector<uint8_t> old_data = p_rd->texture_get_data(p_source_light_tex, index);
785+
Vector<uint8_t> new_data = img->get_data();
786+
img.unref(); // Avoid copy on write.
787+
788+
uint32_t count = old_data.size() / 2;
789+
const uint16_t *src = (const uint16_t *)old_data.ptr();
790+
uint16_t *dst = (uint16_t *)new_data.ptrw();
791+
for (uint32_t k = 0; k < count; k += 4) {
792+
dst[k + 3] = src[k + 3];
793+
}
794+
795+
p_rd->texture_update(p_dest_light_tex, index, new_data);
796+
}
797+
da->remove(fname_norm_in);
798+
}
799+
return BAKE_OK;
800+
}
801+
674802
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) {
675803
RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
676804
DenoiseParams denoise_params;
@@ -742,6 +870,23 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
742870
}
743871

744872
LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, 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) {
873+
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
874+
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
875+
876+
if (p_use_denoiser && denoiser == 1) {
877+
// OIDN (external).
878+
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
879+
880+
if (da->dir_exists(oidn_path)) {
881+
if (OS::get_singleton()->get_name() == "Windows") {
882+
oidn_path = oidn_path.path_join("oidnDenoise.exe");
883+
} else {
884+
oidn_path = oidn_path.path_join("oidnDenoise");
885+
}
886+
}
887+
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings."));
888+
}
889+
745890
if (p_step_function) {
746891
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
747892
}
@@ -1501,8 +1646,15 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
15011646
}
15021647

15031648
{
1504-
SWAP(light_accum_tex, light_accum_tex2);
1505-
BakeError 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);
1649+
BakeError error;
1650+
if (denoiser == 1) {
1651+
// OIDN (external).
1652+
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
1653+
} else {
1654+
// JNLM (built-in).
1655+
SWAP(light_accum_tex, light_accum_tex2);
1656+
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);
1657+
}
15061658
if (unlikely(error != BAKE_OK)) {
15071659
return error;
15081660
}

modules/lightmapper_rd/lightmapper_rd.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ class LightmapperRD : public Lightmapper {
246246
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);
247247
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);
248248

249+
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
250+
Ref<Image> _read_pfm(const String &p_name);
251+
BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe);
252+
249253
public:
250254
virtual void add_mesh(const MeshData &p_mesh) override;
251255
virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) override;

modules/lightmapper_rd/register_types.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
5858
GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512);
5959
GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048);
6060
GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64);
61+
62+
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/lightmapping/denoising/denoiser", PROPERTY_HINT_ENUM, "JNLM,OIDN"), 0);
6163
#ifndef _3D_DISABLED
6264
GDREGISTER_CLASS(LightmapperRD);
6365
Lightmapper::create_gpu = create_lightmapper_rd;

0 commit comments

Comments
 (0)