Skip to content

Commit

Permalink
Add Use Color and Use HDR properties to LightmapGI
Browse files Browse the repository at this point in the history
Colored lightmaps and HDR lightmaps can be disabled to reduce file size,
which is useful for mobile/web platforms.

File size comparison in a given 3D scene:

- HDR enabled, color enabled (default): 9.80 MB
- HDR enabled, color disabled: 3.32 MB (3× smaller)
- HDR disabled, color enabled: 902 KB (11× smaller)
- HDR disabled, color disabled: 471 KB (21× smaller)
  • Loading branch information
Calinou committed Jul 12, 2024
1 parent 97b8ad1 commit 316627a
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 10 deletions.
6 changes: 6 additions & 0 deletions doc/classes/LightmapGI.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,15 @@
<member name="texel_scale" type="float" setter="set_texel_scale" getter="get_texel_scale" default="1.0">
Scales the lightmap texel density of all meshes for the current bake. This is a multiplier that builds upon the existing lightmap texel size defined in each imported 3D scene, along with the per-mesh density multiplier (which is designed to be used when the same mesh is used at different scales). Lower values will result in faster bake times.
</member>
<member name="use_color" type="bool" setter="set_use_color" getter="is_using_color" default="true">
Store full color values in the lightmap textures. When disabled, lightmap textures will store a single brightness channel. This can be disabled to reduce file size and memory usage if the scene contains mostly grayscale lights, or if you don't mind losing color information in indirect lighting.
</member>
<member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true">
If [code]true[/code], uses a GPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image.
</member>
<member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="true">
If [code]true[/code], stores the lightmap textures in a high dynamic range format (EXR). If [code]false[/code], stores the lightmap texture in a low dynamic range WebP image. This can be set to [code]false[/code] to reduce file size and memory usage, but light values over 1.0 will be clamped and you may see banding caused by the reduced precision.
</member>
<member name="use_texture_for_bounces" type="bool" setter="set_use_texture_for_bounces" getter="is_using_texture_for_bounces" default="true">
If [code]true[/code], a texture with the lighting information will be generated to speed up the generation of indirect lighting at the cost of some accuracy. The geometry might exhibit extra light leak artifacts when using low resolution lightmaps or UVs that stretch the lightmap significantly across surfaces. Leave [member use_texture_for_bounces] at its default value of [code]true[/code] if unsure.
[b]Note:[/b] [member use_texture_for_bounces] only has an effect if [member bounces] is set to a value greater than or equal to [code]1[/code].
Expand Down
28 changes: 28 additions & 0 deletions doc/classes/LightmapGIData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,40 @@
Returns the [NodePath] of the baked object at index [param user_idx].
</description>
</method>
<method name="is_using_color" qualifiers="const">
<return type="bool" />
<description>
If [code]true[/code], lightmaps were baked with color enabled. See also [member LightmapGI.use_color].
</description>
</method>
<method name="is_using_hdr" qualifiers="const">
<return type="bool" />
<description>
If [code]true[/code], lightmaps were baked with high dynamic range enabled. See also [member LightmapGI.use_hdr].
</description>
</method>
<method name="is_using_spherical_harmonics" qualifiers="const">
<return type="bool" />
<description>
If [code]true[/code], lightmaps were baked with directional information. See also [member LightmapGI.directional].
</description>
</method>
<method name="set_use_color">
<return type="void" />
<param index="0" name="use_color" type="bool" />
<description>
If [param use_color] is [code]true[/code], tells the engine to treat the lightmap data as if it was baked with color enabled.
[b]Note:[/b] Changing this value on already baked lightmaps will not cause them to be baked again. This means the material appearance will look incorrect until lightmaps are baked again, in which case the value set here is discarded as the entire [LightmapGIData] resource is replaced by the lightmapper.
</description>
</method>
<method name="set_use_hdr">
<return type="void" />
<param index="0" name="use_hdr" type="bool" />
<description>
If [param use_hdr] is [code]true[/code], tells the engine to treat the lightmap data as if it was baked with high dynamic range enabled.
[b]Note:[/b] Changing this value on already baked lightmaps will not cause them to be baked again. This means the material appearance will look incorrect until lightmaps are baked again, in which case the value set here is discarded as the entire [LightmapGIData] resource is replaced by the lightmapper.
</description>
</method>
<method name="set_uses_spherical_harmonics">
<return type="void" />
<param index="0" name="uses_spherical_harmonics" type="bool" />
Expand Down
89 changes: 79 additions & 10 deletions scene/3d/lightmap_gi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
return uses_spherical_harmonics;
}

void LightmapGIData::set_use_hdr(bool p_enable) {
use_hdr = p_enable;
}

bool LightmapGIData::is_using_hdr() const {
return use_hdr;
}

void LightmapGIData::set_use_color(bool p_enable) {
use_color = p_enable;
}

bool LightmapGIData::is_using_color() const {
return use_color;
}

void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
if (p_points.size()) {
int pc = p_points.size();
Expand Down Expand Up @@ -255,6 +271,12 @@ void LightmapGIData::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);

ClassDB::bind_method(D_METHOD("set_use_hdr", "use_hdr"), &LightmapGIData::set_use_hdr);
ClassDB::bind_method(D_METHOD("is_using_hdr"), &LightmapGIData::is_using_hdr);

ClassDB::bind_method(D_METHOD("set_use_color", "use_color"), &LightmapGIData::set_use_color);
ClassDB::bind_method(D_METHOD("is_using_color"), &LightmapGIData::is_using_color);

ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
Expand All @@ -265,6 +287,8 @@ void LightmapGIData::_bind_methods() {

ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_NO_EDITOR), "set_lightmap_textures", "get_lightmap_textures");
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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_use_hdr", "is_using_hdr");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_use_color", "is_using_color");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");

Expand Down Expand Up @@ -1120,18 +1144,18 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
images.set(i, lightmapper->get_bake_texture(i));
}

int slice_count = images.size();
int slice_width = images[0]->get_width();
int slice_height = images[0]->get_height();
const int slice_count = images.size();
const int slice_width = images[0]->get_width();
const int slice_height = images[0]->get_height();

int slices_per_texture = Image::MAX_HEIGHT / slice_height;
int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
const int slices_per_texture = Image::MAX_HEIGHT / slice_height;
const int texture_count = Math::ceil(slice_count / (float)slices_per_texture);

textures.resize(texture_count);

String base_path = p_image_data_path.get_basename();
const String base_path = p_image_data_path.get_basename();

int last_count = slice_count % slices_per_texture;
const int last_count = slice_count % slices_per_texture;
for (int i = 0; i < texture_count; i++) {
int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;

Expand All @@ -1141,7 +1165,10 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
}

String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr";
// Use OpenEXR for HDR lightmaps (required, as PNG does not support the required format).
// Use (lossless) WebP for LDR lightmaps.
const String extension = use_hdr ? ".exr" : ".webp";
const String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + extension : base_path + extension;

Ref<ConfigFile> config;
config.instantiate();
Expand All @@ -1154,7 +1181,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
config->set_value("remap", "type", "CompressedTexture2DArray");
if (!config->has_section_key("params", "compress/mode")) {
// User may want another compression, so leave it be, but default to VRAM uncompressed.
config->set_value("params", "compress/mode", 3);
config->set_value("params", "compress/mode", 3); // COMPRESS_VRAM_COMPRESSED
}
config->set_value("params", "compress/channel_pack", 1);
config->set_value("params", "mipmaps/generate", false);
Expand All @@ -1163,7 +1190,23 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa

config->save(texture_path + ".import");

Error err = texture_image->save_exr(texture_path, false);
if (!use_color) {
// Convert to grayscale to reduce file size.
if (use_hdr) {
texture_image->convert(Image::FORMAT_RH);
} else {
// Convert to low dynamic range to further reduce file size.
texture_image->convert(Image::FORMAT_L8);
}
}

Error err;
if (use_hdr) {
err = texture_image->save_exr(texture_path, !use_color);
} else {
err = texture_image->save_webp(texture_path);
}

ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
ResourceLoader::import(texture_path);
Ref<TextureLayered> t = ResourceLoader::load(texture_path); // If already loaded, it will be updated on refocus?
Expand All @@ -1185,6 +1228,8 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa

gi_data->set_lightmap_textures(textures);
gi_data->set_uses_spherical_harmonics(directional);
gi_data->set_use_hdr(use_hdr);
gi_data->set_use_color(use_color);

for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
Dictionary d = lightmapper->get_bake_mesh_userdata(i);
Expand Down Expand Up @@ -1458,6 +1503,22 @@ int LightmapGI::get_denoiser_range() const {
return denoiser_range;
}

void LightmapGI::set_use_hdr(bool p_enable) {
use_hdr = p_enable;
}

bool LightmapGI::is_using_hdr() const {
return use_hdr;
}

void LightmapGI::set_use_color(bool p_enable) {
use_color = p_enable;
}

bool LightmapGI::is_using_color() const {
return use_color;
}

void LightmapGI::set_directional(bool p_enable) {
directional = p_enable;
}
Expand Down Expand Up @@ -1652,6 +1713,12 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_denoiser_range", "denoiser_range"), &LightmapGI::set_denoiser_range);
ClassDB::bind_method(D_METHOD("get_denoiser_range"), &LightmapGI::get_denoiser_range);

ClassDB::bind_method(D_METHOD("set_use_hdr", "use_denoiser"), &LightmapGI::set_use_hdr);
ClassDB::bind_method(D_METHOD("is_using_hdr"), &LightmapGI::is_using_hdr);

ClassDB::bind_method(D_METHOD("set_use_color", "use_denoiser"), &LightmapGI::set_use_color);
ClassDB::bind_method(D_METHOD("is_using_color"), &LightmapGI::is_using_color);

ClassDB::bind_method(D_METHOD("set_interior", "enable"), &LightmapGI::set_interior);
ClassDB::bind_method(D_METHOD("is_interior"), &LightmapGI::is_interior);

Expand All @@ -1676,6 +1743,8 @@ void LightmapGI::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "denoiser_strength", PROPERTY_HINT_RANGE, "0.001,0.2,0.001,or_greater"), "set_denoiser_strength", "get_denoiser_strength");
ADD_PROPERTY(PropertyInfo(Variant::INT, "denoiser_range", PROPERTY_HINT_RANGE, "1,20"), "set_denoiser_range", "get_denoiser_range");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_hdr"), "set_use_hdr", "is_using_hdr");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_color"), "set_use_color", "is_using_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texel_scale", PROPERTY_HINT_RANGE, "0.01,100.0,0.01"), "set_texel_scale", "get_texel_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size");
Expand Down
18 changes: 18 additions & 0 deletions scene/3d/lightmap_gi.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class LightmapGIData : public Resource {
bool uses_spherical_harmonics = false;
bool interior = false;

// Mirrors LightmapGI properties (set before each bake). Required to save images correctly.
bool use_hdr = true;
bool use_color = true;

RID lightmap;
AABB bounds;
float baked_exposure = 1.0;
Expand Down Expand Up @@ -92,6 +96,12 @@ class LightmapGIData : public Resource {
void set_uses_spherical_harmonics(bool p_enable);
bool is_using_spherical_harmonics() const;

void set_use_hdr(bool p_enable);
bool is_using_hdr() const;

void set_use_color(bool p_enable);
bool is_using_color() const;

bool is_interior() const;
float get_baked_exposure() const;

Expand Down Expand Up @@ -159,6 +169,8 @@ class LightmapGI : public VisualInstance3D {
int denoiser_range = 10;
int bounces = 3;
float bounce_indirect_energy = 1.0;
bool use_hdr = true;
bool use_color = true;
float bias = 0.0005;
float texel_scale = 1.0;
int max_texture_size = 16384;
Expand Down Expand Up @@ -260,6 +272,12 @@ class LightmapGI : public VisualInstance3D {
void set_denoiser_range(int p_denoiser_range);
int get_denoiser_range() const;

void set_use_hdr(bool p_enable);
bool is_using_hdr() const;

void set_use_color(bool p_enable);
bool is_using_color() const;

void set_directional(bool p_enable);
bool is_directional() const;

Expand Down

0 comments on commit 316627a

Please sign in to comment.