Skip to content

Commit bb285fe

Browse files
Expose Lightmap Baking
1 parent 3a5a9c1 commit bb285fe

7 files changed

Lines changed: 150 additions & 57 deletions

File tree

doc/classes/LightmapGI.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@
1515
<tutorials>
1616
<link title="Using Lightmap global illumination">$DOCS_URL/tutorials/3d/global_illumination/using_lightmap_gi.html</link>
1717
</tutorials>
18+
<methods>
19+
<method name="bake">
20+
<return type="int" enum="LightmapGI.BakeError" />
21+
<param index="0" name="from_node" type="Node" />
22+
<param index="1" name="image_data_path" type="String" default="&quot;&quot;" />
23+
<description>
24+
Bakes lightmaps (requires meshes to have UV2 unwrapped) for [param from_node] and its children to [param image_data_path]. [param image_data_path] must end with an [code].exr[/code] or [code].lmbake[/code] file extension. If [param from_node] is [code]null[/code], lightmaps are baked from the [LightmapGI] node's parent. Baking lightmaps can take from a few seconds to several dozen minutes depending on the GPU speed and quality settings chosen.
25+
[b]Note:[/b] [method bake] only works within the editor, and when running a project from the editor. [method bake] will do nothing when called in a project exported in either debug or release mode. This limitation is in place to reduce the binary size of exported projects. You can [url=$DOCS_URL/contributing/development/compiling/index.html]compile custom export templates[/url] with the [code]module_lightmapper_rd_enabled=yes module_xatlas_unwrap_enabled=yes[/code] SCons options to remove this limitation.
26+
[b]Additional Note:[/b] Baking lightmaps from a headless editor instance is not supported. If you attempt to bake lightmaps in this manner, baking will fail.
27+
</description>
28+
</method>
29+
</methods>
1830
<members>
1931
<member name="bias" type="float" setter="set_bias" getter="get_bias" default="0.0005">
2032
The bias to use when computing shadows. Increasing [member bias] can fix shadow acne on the resulting baked lightmap, but can introduce peter-panning (shadows not connecting to their casters). Real-time [Light3D] shadows are not affected by this [member bias] property.

editor/scene/3d/lightmap_gi_editor_plugin.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {
7676

7777
if (err == LightmapGI::BAKE_ERROR_OK) {
7878
if (get_tree()->get_edited_scene_root() == lightmap) {
79-
err = lightmap->bake(lightmap, p_file, bake_func_step);
79+
err = lightmap->_bake(lightmap, p_file, bake_func_step);
8080
} else {
81-
err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
81+
err = lightmap->_bake(lightmap->get_parent(), p_file, bake_func_step);
8282
}
8383
}
8484
} else {

modules/lightmapper_rd/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
def can_build(env, platform):
2-
return env.editor_build
2+
# return env.editor_build
3+
return (env.editor_build or env["module_lightmapper_rd_enabled"]) and platform not in ["android", "ios"]
34

45

56
def configure(env):

modules/lightmapper_rd/lightmapper_rd.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
#include "core/config/project_settings.h"
4747
#include "core/io/dir_access.h"
4848
#include "core/math/geometry_2d.h"
49+
#ifdef TOOLS_ENABLED
4950
#include "editor/file_system/editor_paths.h"
5051
#include "editor/settings/editor_settings.h"
52+
#endif
5153
#include "servers/rendering/rendering_device_binds.h"
5254
#include "servers/rendering/rendering_server_globals.h"
5355

@@ -913,6 +915,7 @@ Ref<Image> LightmapperRD::_read_pfm(const String &p_name, bool p_shadowmask) {
913915
return img;
914916
}
915917

918+
#ifdef TOOLS_ENABLED
916919
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, bool p_shadowmask, const String &p_exe) {
917920
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
918921

@@ -978,6 +981,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
978981
}
979982
return BAKE_OK;
980983
}
984+
#endif
981985

982986
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, RID p_unocclude_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) {
983987
RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
@@ -1066,22 +1070,28 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
10661070
}
10671071

10681072
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_bake_shadowmask, 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, float p_supersampling_factor) {
1073+
#ifdef TOOLS_ENABLED
10691074
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
1070-
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
1071-
1072-
if (p_use_denoiser && denoiser == 1) {
1073-
// OIDN (external).
1074-
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1075-
1076-
if (da->dir_exists(oidn_path)) {
1077-
if (OS::get_singleton()->get_name() == "Windows") {
1078-
oidn_path = oidn_path.path_join("oidnDenoise.exe");
1079-
} else {
1080-
oidn_path = oidn_path.path_join("oidnDenoise");
1075+
/// @todo Implement oidnDenoise for non-editor
1076+
String oidn_path;
1077+
if (Engine::get_singleton()->is_editor_hint()) {
1078+
oidn_path = p_use_denoiser ? EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path") : Variant();
1079+
1080+
if (p_use_denoiser && denoiser == 1) {
1081+
// OIDN (external).
1082+
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
1083+
1084+
if (da->dir_exists(oidn_path)) {
1085+
if (OS::get_singleton()->get_name() == "Windows") {
1086+
oidn_path = oidn_path.path_join("oidnDenoise.exe");
1087+
} else {
1088+
oidn_path = oidn_path.path_join("oidnDenoise");
1089+
}
10811090
}
1091+
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, "OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings.");
10821092
}
1083-
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, "OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings.");
10841093
}
1094+
#endif
10851095

10861096
if (p_step_function) {
10871097
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
@@ -2093,6 +2103,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
20932103

20942104
{
20952105
BakeError error;
2106+
#ifdef TOOLS_ENABLED
20962107
if (denoiser == 1) {
20972108
// OIDN (external).
20982109
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, false, oidn_path);
@@ -2116,6 +2127,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
21162127
SWAP(shadowmask_tex, shadowmask_tex2);
21172128
error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, shadowmask_tex2, normal_tex, shadowmask_tex, unocclude_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, false, p_step_function, p_bake_userdata);
21182129
}
2130+
#else
2131+
SWAP(shadowmask_tex, shadowmask_tex2);
2132+
error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, shadowmask_tex2, normal_tex, shadowmask_tex, unocclude_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, false, p_step_function, p_bake_userdata);
2133+
#endif
2134+
21192135
if (unlikely(error != BAKE_OK)) {
21202136
return error;
21212137
}

modules/xatlas_unwrap/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
def can_build(env, platform):
2-
return env.editor_build
2+
# return env.editor_build
3+
return (env.editor_build or env["module_xatlas_unwrap_enabled"]) and platform not in ["android", "ios"]
34

45

56
def configure(env):

scene/3d/lightmap_gi.cpp

Lines changed: 99 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
#include "lightmap_gi.h"
4040

41+
#include "core/config/engine.h"
4142
#include "core/config/project_settings.h"
4243
#include "core/io/config_file.h"
4344
#include "core/math/delaunay_3d.h"
@@ -48,6 +49,7 @@
4849
#include "scene/resources/environment.h"
4950
#include "scene/resources/image_texture.h"
5051
#include "scene/resources/sky.h"
52+
#include "scene/resources/texture.h"
5153

5254
#include "modules/modules_enabled.gen.h" // For lightmapper_rd.
5355

@@ -840,57 +842,88 @@ LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Li
840842
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
841843
}
842844

843-
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
844-
const String config_path = atlas_path + ".import";
845+
if (Engine::get_singleton()->is_editor_hint()) {
846+
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
847+
const String config_path = atlas_path + ".import";
845848

846-
Ref<ConfigFile> config;
847-
config.instantiate();
848-
849-
// Load an import configuration if present.
850-
if (FileAccess::exists(config_path)) {
851-
config->load(config_path);
852-
}
849+
Ref<ConfigFile> config;
850+
config.instantiate();
851+
// Load an import configuration if present.
852+
if (FileAccess::exists(config_path)) {
853+
config->load(config_path);
854+
}
853855

854-
config->set_value("remap", "importer", "2d_array_texture");
855-
config->set_value("remap", "type", "CompressedTexture2DArray");
856-
if (!config->has_section_key("params", "compress/mode")) {
857-
// Do not override an existing compression mode.
858-
config->set_value("params", "compress/mode", 2);
859-
}
860-
config->set_value("params", "compress/channel_pack", 1);
861-
config->set_value("params", "mipmaps/generate", false);
862-
config->set_value("params", "slices/horizontal", 1);
863-
config->set_value("params", "slices/vertical", texture_slice_count);
856+
config->set_value("remap", "importer", "2d_array_texture");
857+
config->set_value("remap", "type", "CompressedTexture2DArray");
858+
if (!config->has_section_key("params", "compress/mode")) {
859+
// Do not override an existing compression mode.
860+
config->set_value("params", "compress/mode", 3);
861+
}
862+
config->set_value("params", "compress/channel_pack", 1);
863+
config->set_value("params", "mipmaps/generate", false);
864+
config->set_value("params", "slices/horizontal", 1);
865+
config->set_value("params", "slices/vertical", texture_slice_count);
864866

865-
config->save(config_path);
867+
config->save(config_path);
866868

867-
if (supersampling_enabled) {
868-
texture_image->resize(texture_image->get_width() / supersampling_factor, texture_image->get_height() / supersampling_factor, Image::INTERPOLATE_TRILINEAR);
869-
}
869+
if (supersampling_enabled) {
870+
texture_image->resize(texture_image->get_width() / supersampling_factor, texture_image->get_height() / supersampling_factor, Image::INTERPOLATE_TRILINEAR);
871+
}
870872

871-
// Save the file.
872-
Error save_err;
873-
if (p_is_shadowmask) {
874-
save_err = texture_image->save_png(atlas_path);
875-
} else {
876-
save_err = texture_image->save_exr(atlas_path, false);
877-
}
873+
// Save the file.
874+
Error save_err;
875+
if (p_is_shadowmask) {
876+
save_err = texture_image->save_png(atlas_path);
877+
} else {
878+
save_err = texture_image->save_exr(atlas_path, false);
879+
}
878880

879-
ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
881+
ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
880882

881-
// Reimport the file.
882-
ResourceLoader::import(atlas_path);
883-
Ref<TextureLayered> t = ResourceLoader::load(atlas_path); // If already loaded, it will be updated on refocus?
884-
ERR_FAIL_COND_V(t.is_null(), LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
883+
// Reimport the file.
884+
Error import_err = ResourceLoader::import(atlas_path);
885+
ERR_FAIL_COND_V(import_err != OK, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
886+
Ref<TextureLayered> t = ResourceLoader::load(atlas_path); // If already loaded, it will be updated on refocus?
887+
ERR_FAIL_COND_V(t.is_null(), LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
885888

886-
// Store the atlas in the array.
887-
r_textures[i] = t;
889+
// Store the atlas in the array.
890+
r_textures[i] = t;
891+
} else {
892+
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
893+
if (supersampling_enabled) {
894+
texture_image->resize(texture_image->get_width() / supersampling_factor, texture_image->get_height() / supersampling_factor, Image::INTERPOLATE_TRILINEAR);
895+
}
896+
Vector<Ref<Image>> slice_images;
897+
slice_images.resize(texture_slice_count);
898+
for (int j = 0; j < texture_slice_count; j++) {
899+
slice_images.set(j, images[i * slices_per_texture + j]);
900+
}
901+
Ref<Texture2DArray> texs;
902+
texs.instantiate();
903+
texs->create_from_images(slice_images);
904+
905+
Error err = ResourceSaver::save(texs, atlas_path);
906+
ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
907+
Ref<TextureLayered> t = ResourceLoader::load(atlas_path);
908+
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
909+
r_textures[i] = t;
910+
}
888911
}
889912

890913
return LightmapGI::BAKE_ERROR_OK;
891914
}
892915

893-
LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
916+
bool LightmapGI::_dummy_bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) {
917+
// No reporting needed, but baking logic is identical
918+
return true;
919+
}
920+
921+
LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path) {
922+
// is dummy bake func needed?
923+
return _bake(p_from_node, p_image_data_path, _dummy_bake_func_step, nullptr);
924+
}
925+
926+
LightmapGI::BakeError LightmapGI::_bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
894927
if (p_image_data_path.is_empty()) {
895928
if (get_light_data().is_null()) {
896929
return BAKE_ERROR_NO_SAVE_PATH;
@@ -1240,14 +1273,39 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
12401273
}
12411274

12421275
if (env.is_valid()) {
1243-
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64));
1276+
// environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64));
12441277
environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse();
1278+
1279+
Sky::RadianceSize old_radiance_size = Sky::RADIANCE_SIZE_MAX;
1280+
if (!Engine::get_singleton()->is_editor_hint()) {
1281+
Ref<Sky> sky = env->get_sky();
1282+
if (sky.is_valid()) {
1283+
old_radiance_size = sky->get_radiance_size();
1284+
sky->set_radiance_size(Sky::RADIANCE_SIZE_128);
1285+
}
1286+
}
1287+
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 128));
1288+
if (old_radiance_size != Sky::RADIANCE_SIZE_MAX) { // If it's not max, it's been set and needs resetting
1289+
Ref<Sky> sky = env->get_sky();
1290+
if (sky.is_valid()) {
1291+
sky->set_radiance_size(old_radiance_size);
1292+
}
1293+
}
12451294
}
12461295
}
12471296
} break;
12481297
case ENVIRONMENT_MODE_CUSTOM_SKY: {
12491298
if (environment_custom_sky.is_valid()) {
1250-
environment_image = RS::get_singleton()->sky_bake_panorama(environment_custom_sky->get_rid(), environment_custom_energy, true, Size2i(128, 64));
1299+
// environment_image = RS::get_singleton()->sky_bake_panorama(environment_custom_sky->get_rid(), environment_custom_energy, true, Size2i(128, 64));
1300+
Sky::RadianceSize old_radiance_size = Sky::RADIANCE_SIZE_MAX;
1301+
if (!Engine::get_singleton()->is_editor_hint()) {
1302+
old_radiance_size = environment_custom_sky->get_radiance_size();
1303+
environment_custom_sky->set_radiance_size(Sky::RADIANCE_SIZE_128);
1304+
}
1305+
environment_image = RS::get_singleton()->sky_bake_panorama(environment_custom_sky->get_rid(), environment_custom_energy, true, Size2i(128, 128));
1306+
if (old_radiance_size != Sky::RADIANCE_SIZE_MAX) {
1307+
environment_custom_sky->set_radiance_size(old_radiance_size);
1308+
}
12511309
}
12521310

12531311
} break;
@@ -1899,6 +1957,7 @@ void LightmapGI::_bind_methods() {
18991957
ClassDB::bind_method(D_METHOD("get_camera_attributes"), &LightmapGI::get_camera_attributes);
19001958

19011959
// ClassDB::bind_method(D_METHOD("bake", "from_node"), &LightmapGI::bake, DEFVAL(Variant()));
1960+
ClassDB::bind_method(D_METHOD("bake", "from_node", "image_data_path"), &LightmapGI::bake, DEFVAL(""));
19021961

19031962
ADD_GROUP("Tweaks", "");
19041963
ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");

scene/3d/lightmap_gi.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,11 @@ class LightmapGI : public VisualInstance3D {
359359

360360
AABB get_aabb() const override;
361361

362-
BakeError bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr);
362+
static bool _dummy_bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh);
363+
364+
BakeError bake(Node *p_from_node, String p_image_data_path = "");
365+
366+
BakeError _bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr);
363367

364368
virtual PackedStringArray get_configuration_warnings() const override;
365369

0 commit comments

Comments
 (0)