Skip to content

Commit 1843954

Browse files
committed
LightmapGI: Clean up and improve lightmap atlas storage
1 parent 92e51fc commit 1843954

File tree

2 files changed

+99
-75
lines changed

2 files changed

+99
-75
lines changed

scene/3d/lightmap_gi.cpp

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ Array LightmapGIData::_get_user_data() const {
9898
}
9999

100100
void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_data) {
101-
light_textures = p_data;
101+
storage_light_textures = p_data;
102102
if (p_data.is_empty()) {
103-
light_texture = Ref<TextureLayered>();
103+
combined_light_texture = Ref<TextureLayered>();
104104
_reset_lightmap_textures();
105105
return;
106106
}
107107

108108
if (p_data.size() == 1) {
109-
light_texture = p_data[0];
109+
combined_light_texture = p_data[0];
110110
} else {
111111
Vector<Ref<Image>> images;
112112
for (int i = 0; i < p_data.size(); i++) {
@@ -121,13 +121,13 @@ void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_d
121121
combined_texture.instantiate();
122122

123123
combined_texture->create_from_images(images);
124-
light_texture = combined_texture;
124+
combined_light_texture = combined_texture;
125125
}
126126
_reset_lightmap_textures();
127127
}
128128

129129
TypedArray<TextureLayered> LightmapGIData::get_lightmap_textures() const {
130-
return light_textures;
130+
return storage_light_textures;
131131
}
132132

133133
RID LightmapGIData::get_rid() const {
@@ -139,7 +139,7 @@ void LightmapGIData::clear() {
139139
}
140140

141141
void LightmapGIData::_reset_lightmap_textures() {
142-
RS::get_singleton()->lightmap_set_textures(lightmap, light_texture.is_valid() ? light_texture->get_rid() : RID(), uses_spherical_harmonics);
142+
RS::get_singleton()->lightmap_set_textures(lightmap, combined_light_texture.is_valid() ? combined_light_texture->get_rid() : RID(), uses_spherical_harmonics);
143143
}
144144

145145
void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) {
@@ -238,18 +238,18 @@ void LightmapGIData::set_light_texture(const Ref<TextureLayered> &p_light_textur
238238
}
239239

240240
Ref<TextureLayered> LightmapGIData::get_light_texture() const {
241-
if (light_textures.is_empty()) {
241+
if (storage_light_textures.is_empty()) {
242242
return Ref<TextureLayered>();
243243
}
244-
return light_textures.get(0);
244+
return storage_light_textures.get(0);
245245
}
246246

247247
void LightmapGIData::_set_light_textures_data(const Array &p_data) {
248248
set_lightmap_textures(p_data);
249249
}
250250

251251
Array LightmapGIData::_get_light_textures_data() const {
252-
return Array(light_textures);
252+
return Array(storage_light_textures);
253253
}
254254
#endif
255255

@@ -274,7 +274,7 @@ void LightmapGIData::_bind_methods() {
274274
ClassDB::bind_method(D_METHOD("_set_probe_data", "data"), &LightmapGIData::_set_probe_data);
275275
ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
276276

277-
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_NO_EDITOR), "set_lightmap_textures", "get_lightmap_textures");
277+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_lightmap_textures", "get_lightmap_textures");
278278
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
279279
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
280280
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
@@ -287,8 +287,8 @@ void LightmapGIData::_bind_methods() {
287287
ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data);
288288
ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data);
289289

290-
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture");
291-
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
290+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_NONE), "set_light_texture", "get_light_texture");
291+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
292292
#endif
293293
}
294294

@@ -740,6 +740,74 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
740740
}
741741
}
742742

743+
LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_compress) const {
744+
Vector<Ref<Image>> images;
745+
images.resize(p_lightmapper->get_bake_texture_count());
746+
747+
for (int i = 0; i < images.size(); i++) {
748+
images.set(i, p_lightmapper->get_bake_texture(i));
749+
}
750+
751+
const int slice_count = images.size();
752+
const int slice_width = images[0]->get_width();
753+
const int slice_height = images[0]->get_height();
754+
755+
const int slices_per_texture = Image::MAX_HEIGHT / slice_height;
756+
const int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
757+
const int last_count = slice_count % slices_per_texture;
758+
759+
r_textures.resize(texture_count);
760+
761+
for (int i = 0; i < texture_count; i++) {
762+
const int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
763+
764+
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
765+
766+
for (int j = 0; j < texture_slice_count; j++) {
767+
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
768+
}
769+
770+
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + ".exr";
771+
const String config_path = atlas_path + ".import";
772+
773+
Ref<ConfigFile> config;
774+
config.instantiate();
775+
776+
// Load an import configuration if present.
777+
if (FileAccess::exists(config_path)) {
778+
config->load(config_path);
779+
}
780+
781+
config->set_value("remap", "importer", "2d_array_texture");
782+
config->set_value("remap", "type", "CompressedTexture2DArray");
783+
if (!config->has_section_key("params", "compress/mode")) {
784+
// Do not override an existing compression mode.
785+
config->set_value("params", "compress/mode", p_compress ? 2 : 3);
786+
}
787+
config->set_value("params", "compress/channel_pack", 1);
788+
config->set_value("params", "mipmaps/generate", false);
789+
config->set_value("params", "slices/horizontal", 1);
790+
config->set_value("params", "slices/vertical", texture_slice_count);
791+
792+
config->save(config_path);
793+
794+
// Save the file.
795+
Error save_err = texture_image->save_exr(atlas_path, false);
796+
797+
ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
798+
799+
// Reimport the file.
800+
ResourceLoader::import(atlas_path);
801+
Ref<TextureLayered> t = ResourceLoader::load(atlas_path); // If already loaded, it will be updated on refocus?
802+
ERR_FAIL_COND_V(t.is_null(), LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
803+
804+
// Store the atlas in the array.
805+
r_textures[i] = t;
806+
}
807+
808+
return LightmapGI::BAKE_ERROR_OK;
809+
}
810+
743811
LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
744812
if (p_image_data_path.is_empty()) {
745813
if (get_light_data().is_null()) {
@@ -1127,80 +1195,30 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
11271195

11281196
// POSTBAKE: Save Textures.
11291197

1130-
TypedArray<TextureLayered> textures;
1131-
{
1132-
Vector<Ref<Image>> images;
1133-
images.resize(lightmapper->get_bake_texture_count());
1134-
for (int i = 0; i < images.size(); i++) {
1135-
images.set(i, lightmapper->get_bake_texture(i));
1136-
}
1137-
1138-
int slice_count = images.size();
1139-
int slice_width = images[0]->get_width();
1140-
int slice_height = images[0]->get_height();
1141-
1142-
int slices_per_texture = Image::MAX_HEIGHT / slice_height;
1143-
int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
1144-
1145-
textures.resize(texture_count);
1146-
1147-
String base_path = p_image_data_path.get_basename();
1148-
1149-
int last_count = slice_count % slices_per_texture;
1150-
for (int i = 0; i < texture_count; i++) {
1151-
int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
1152-
1153-
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
1154-
1155-
for (int j = 0; j < texture_slice_count; j++) {
1156-
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
1157-
}
1158-
1159-
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr";
1198+
TypedArray<TextureLayered> lightmap_textures;
11601199

1161-
Ref<ConfigFile> config;
1162-
config.instantiate();
1200+
const String texture_filename = p_image_data_path.get_basename();
11631201

1164-
if (FileAccess::exists(texture_path + ".import")) {
1165-
config->load(texture_path + ".import");
1166-
}
1202+
// Save the lightmap atlases.
1203+
BakeError save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename, lightmap_textures, false);
1204+
ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
11671205

1168-
config->set_value("remap", "importer", "2d_array_texture");
1169-
config->set_value("remap", "type", "CompressedTexture2DArray");
1170-
if (!config->has_section_key("params", "compress/mode")) {
1171-
// User may want another compression, so leave it be, but default to VRAM uncompressed.
1172-
config->set_value("params", "compress/mode", 3);
1173-
}
1174-
config->set_value("params", "compress/channel_pack", 1);
1175-
config->set_value("params", "mipmaps/generate", false);
1176-
config->set_value("params", "slices/horizontal", 1);
1177-
config->set_value("params", "slices/vertical", texture_slice_count);
1178-
1179-
config->save(texture_path + ".import");
1180-
1181-
Error err = texture_image->save_exr(texture_path, false);
1182-
ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
1183-
ResourceLoader::import(texture_path);
1184-
Ref<TextureLayered> t = ResourceLoader::load(texture_path); // If already loaded, it will be updated on refocus?
1185-
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
1186-
textures[i] = t;
1187-
}
1188-
}
1189-
1190-
/* POSTBAKE: Save Light Data */
1206+
// POSTBAKE: Save Light Data.
11911207

11921208
Ref<LightmapGIData> gi_data;
1209+
11931210
if (get_light_data().is_valid()) {
11941211
gi_data = get_light_data();
1195-
set_light_data(Ref<LightmapGIData>()); //clear
1212+
set_light_data(Ref<LightmapGIData>()); // Clear.
11961213
gi_data->clear();
1214+
11971215
} else {
11981216
gi_data.instantiate();
11991217
}
12001218

1201-
gi_data->set_lightmap_textures(textures);
1202-
gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
1219+
gi_data->set_lightmap_textures(lightmap_textures);
12031220
gi_data->set_uses_spherical_harmonics(directional);
1221+
gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
12041222

12051223
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
12061224
Dictionary d = lightmapper->get_bake_mesh_userdata(i);

scene/3d/lightmap_gi.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ class LightmapGIData : public Resource {
4343
GDCLASS(LightmapGIData, Resource);
4444
RES_BASE_EXTENSION("lmbake")
4545

46-
Ref<TextureLayered> light_texture;
47-
TypedArray<TextureLayered> light_textures;
46+
// The 'merged' texture atlases actually used by the renderer.
47+
Ref<TextureLayered> combined_light_texture;
48+
49+
// The temporary texture atlas arrays which are used for storage.
50+
// If a single atlas is too large, it's split and recombined during loading.
51+
TypedArray<TextureLayered> storage_light_textures;
4852

4953
bool uses_spherical_harmonics = false;
5054
bool interior = false;
@@ -245,6 +249,8 @@ class LightmapGI : public VisualInstance3D {
245249
void _plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cell_size, const Vector3 *p_triangle);
246250
void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds);
247251

252+
BakeError _save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_compress = false) const;
253+
248254
protected:
249255
void _validate_property(PropertyInfo &p_property) const;
250256
static void _bind_methods();

0 commit comments

Comments
 (0)