Merge pull request #46054 from JFonS/cpu_lightmapper_disk

[3.2] Reduce lightmaps file size.
This commit is contained in:
Rémi Verschelde 2021-02-16 13:37:09 +01:00 committed by GitHub
commit 77438f7a45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 263 additions and 101 deletions

View file

@ -78,9 +78,15 @@
<member name="quality" type="int" setter="set_bake_quality" getter="get_bake_quality" enum="BakedLightmap.BakeQuality" default="1"> <member name="quality" type="int" setter="set_bake_quality" getter="get_bake_quality" enum="BakedLightmap.BakeQuality" default="1">
Determines the amount of samples per texel used in indrect light baking. The amount of samples for each quality level can be configured in the project settings. Determines the amount of samples per texel used in indrect light baking. The amount of samples for each quality level can be configured in the project settings.
</member> </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. Can be disabled to reduce disk usage if the scene contains only white lights or 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"> <member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true">
When enabled, a lightmap denoiser will be used to reduce the noise inherent to Monte Carlo based global illumination. When enabled, a lightmap denoiser will be used to reduce the noise inherent to Monte Carlo based global illumination.
</member> </member>
<member name="use_hdr" type="bool" setter="set_use_hdr" getter="is_using_hdr" default="true">
Store the lightmap textures in a Hight Dynamic Range format (EXR). Can be disabled to reduce disk usage, but light values over 1.0 will be clamped and you may see banding caused by the reduced precision.
</member>
</members> </members>
<constants> <constants>
<constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality"> <constant name="BAKE_QUALITY_LOW" value="0" enum="BakeQuality">

View file

@ -23,6 +23,12 @@
<description> <description>
</description> </description>
</method> </method>
<method name="clear_data">
<return type="void">
</return>
<description>
</description>
</method>
<method name="clear_users"> <method name="clear_users">
<return type="void"> <return type="void">
</return> </return>

View file

@ -108,6 +108,9 @@ env_thirdparty = env_oidn.Clone()
env_thirdparty.disable_warnings() env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources)
if env["platform"] == "windows" and not env.msvc:
env_thirdparty.Append(CPPFLAGS=["-mstackrealign"])
weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza" weights_in_path = thirdparty_dir + "weights/rtlightmap_hdr.tza"
weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp" weights_out_path = thirdparty_dir + "weights/rtlightmap_hdr.gen.cpp"

View file

@ -43,9 +43,7 @@ void *oidn_denoiser_init() {
bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) { bool oidn_denoise(void *deviceptr, float *p_floats, int p_width, int p_height) {
OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr; OIDNDeviceImpl *device = (OIDNDeviceImpl *)deviceptr;
OIDNFilter filter = oidnNewFilter(device, "RTLightmap"); OIDNFilter filter = oidnNewFilter(device, "RTLightmap");
void *input_buffer = memalloc(p_width * p_height * 3 * sizeof(float)); oidnSetSharedFilterImage(filter, "color", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
copymem(input_buffer, p_floats, p_width * p_height * 3 * sizeof(float));
oidnSetSharedFilterImage(filter, "color", input_buffer, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0); oidnSetSharedFilterImage(filter, "output", (void *)p_floats, OIDN_FORMAT_FLOAT3, p_width, p_height, 0, 0, 0);
oidnSetFilter1b(filter, "hdr", true); oidnSetFilter1b(filter, "hdr", true);
oidnCommitFilter(filter); oidnCommitFilter(filter);

View file

@ -1369,6 +1369,7 @@ LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use
if (parameters.environment_panorama.is_valid()) { if (parameters.environment_panorama.is_valid()) {
parameters.environment_panorama->lock(); parameters.environment_panorama->lock();
} }
for (unsigned int i = 0; i < mesh_instances.size(); i++) { for (unsigned int i = 0; i < mesh_instances.size(); i++) {
if (!mesh_instances[i].generate_lightmap) { if (!mesh_instances[i].generate_lightmap) {
@ -1389,6 +1390,7 @@ LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use
} }
} }
} }
if (parameters.environment_panorama.is_valid()) { if (parameters.environment_panorama.is_valid()) {
parameters.environment_panorama->unlock(); parameters.environment_panorama->unlock();
} }

View file

@ -6,6 +6,9 @@ Import("env_modules")
env_tinyexr = env_modules.Clone() env_tinyexr = env_modules.Clone()
# Thirdparty source files # Thirdparty source files
thirdparty_obj = []
# Not unbundled for now as they are not commonly available as shared library # Not unbundled for now as they are not commonly available as shared library
thirdparty_dir = "#thirdparty/tinyexr/" thirdparty_dir = "#thirdparty/tinyexr/"
thirdparty_sources = [ thirdparty_sources = [
@ -20,7 +23,15 @@ env_tinyexr.Append(CPPDEFINES=["TINYEXR_USE_THREAD"])
env_thirdparty = env_tinyexr.Clone() env_thirdparty = env_tinyexr.Clone()
env_thirdparty.disable_warnings() env_thirdparty.disable_warnings()
env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
env.modules_sources += thirdparty_obj
# Godot's own source files # Godot source files
env_tinyexr.add_source_files(env.modules_sources, "*.cpp")
module_obj = []
env_tinyexr.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
env.Depends(module_obj, thirdparty_obj)

View file

@ -42,7 +42,8 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT); ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
src_image.resize(src_image_len); src_image.resize(src_image_len);
PoolVector<uint8_t>::Write w = src_image.write(); PoolVector<uint8_t>::Write img_write = src_image.write();
uint8_t *w = img_write.ptr();
f->get_buffer(&w[0], src_image_len); f->get_buffer(&w[0], src_image_len);
@ -56,36 +57,37 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
EXRVersion exr_version; EXRVersion exr_version;
EXRImage exr_image; EXRImage exr_image;
EXRHeader exr_header; EXRHeader exr_header;
const char *err = NULL; const char *err = nullptr;
InitEXRHeader(&exr_header); InitEXRHeader(&exr_header);
int ret = ParseEXRVersionFromMemory(&exr_version, w.ptr(), src_image_len); int ret = ParseEXRVersionFromMemory(&exr_version, w, src_image_len);
if (ret != TINYEXR_SUCCESS) { if (ret != TINYEXR_SUCCESS) {
return ERR_FILE_CORRUPT; return ERR_FILE_CORRUPT;
} }
ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, w.ptr(), src_image_len, &err); ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, w, src_image_len, &err);
if (ret != TINYEXR_SUCCESS) { if (ret != TINYEXR_SUCCESS) {
if (err) { if (err) {
ERR_PRINTS(String(err)); ERR_PRINT(String(err));
} }
return ERR_FILE_CORRUPT; return ERR_FILE_CORRUPT;
} }
// Read HALF channel as FLOAT. (GH-13490) // Read HALF channel as FLOAT. (GH-13490)
bool use_float16 = false;
for (int i = 0; i < exr_header.num_channels; i++) { for (int i = 0; i < exr_header.num_channels; i++) {
if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
use_float16 = true;
exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
} }
} }
InitEXRImage(&exr_image); InitEXRImage(&exr_image);
ret = LoadEXRImageFromMemory(&exr_image, &exr_header, w.ptr(), src_image_len, &err); ret = LoadEXRImageFromMemory(&exr_image, &exr_header, w, src_image_len, &err);
if (ret != TINYEXR_SUCCESS) { if (ret != TINYEXR_SUCCESS) {
if (err) { if (err) {
ERR_PRINTS(String(err)); ERR_PRINT(String(err));
} }
return ERR_FILE_CORRUPT; return ERR_FILE_CORRUPT;
} }
@ -104,33 +106,10 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
idxB = c; idxB = c;
} else if (strcmp(exr_header.channels[c].name, "A") == 0) { } else if (strcmp(exr_header.channels[c].name, "A") == 0) {
idxA = c; idxA = c;
} } else if (strcmp(exr_header.channels[c].name, "Y") == 0) {
} idxR = c;
idxG = c;
if (exr_header.num_channels == 1) { idxB = c;
// Grayscale channel only.
idxR = 0;
idxG = 0;
idxB = 0;
idxA = 0;
} else {
// Assume RGB(A)
if (idxR == -1) {
ERR_PRINT("TinyEXR: R channel not found.");
// @todo { free exr_image }
return ERR_FILE_CORRUPT;
}
if (idxG == -1) {
ERR_PRINT("TinyEXR: G channel not found.");
// @todo { free exr_image }
return ERR_FILE_CORRUPT;
}
if (idxB == -1) {
ERR_PRINT("TinyEXR: B channel not found.");
// @todo { free exr_image }
return ERR_FILE_CORRUPT;
} }
} }
@ -140,16 +119,27 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
Image::Format format; Image::Format format;
int output_channels = 0; int output_channels = 0;
int channel_size = use_float16 ? 2 : 4;
if (idxA != -1) { if (idxA != -1) {
imgdata.resize(exr_image.width * exr_image.height * 4 * channel_size); //RGBA
imgdata.resize(exr_image.width * exr_image.height * 8); //RGBA16 format = use_float16 ? Image::FORMAT_RGBAH : Image::FORMAT_RGBAF;
format = Image::FORMAT_RGBAH;
output_channels = 4; output_channels = 4;
} else { } else if (idxB != -1) {
ERR_FAIL_COND_V(idxG == -1, ERR_FILE_CORRUPT);
imgdata.resize(exr_image.width * exr_image.height * 6); //RGB16 ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
format = Image::FORMAT_RGBH; imgdata.resize(exr_image.width * exr_image.height * 3 * channel_size); //RGB
format = use_float16 ? Image::FORMAT_RGBH : Image::FORMAT_RGBF;
output_channels = 3; output_channels = 3;
} else if (idxG != -1) {
ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
imgdata.resize(exr_image.width * exr_image.height * 2 * channel_size); //RG
format = use_float16 ? Image::FORMAT_RGH : Image::FORMAT_RGF;
output_channels = 2;
} else {
ERR_FAIL_COND_V(idxR == -1, ERR_FILE_CORRUPT);
imgdata.resize(exr_image.width * exr_image.height * 1 * channel_size); //R
format = use_float16 ? Image::FORMAT_RH : Image::FORMAT_RF;
output_channels = 1;
} }
EXRTile single_image_tile; EXRTile single_image_tile;
@ -179,54 +169,115 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
exr_tiles = exr_image.tiles; exr_tiles = exr_image.tiles;
} }
//print_line("reading format: " + Image::get_format_name(format));
{ {
PoolVector<uint8_t>::Write wd = imgdata.write();
uint16_t *iw = (uint16_t *)wd.ptr(); PoolVector<uint8_t>::Write imgdata_write = imgdata.write();
uint8_t *wd = imgdata_write.ptr();
uint16_t *iw16 = (uint16_t *)wd;
float *iw32 = (float *)wd;
// Assume `out_rgba` have enough memory allocated. // Assume `out_rgba` have enough memory allocated.
for (int tile_index = 0; tile_index < num_tiles; tile_index++) { for (int tile_index = 0; tile_index < num_tiles; tile_index++) {
const EXRTile &tile = exr_tiles[tile_index]; const EXRTile &tile = exr_tiles[tile_index];
int tw = tile.width; int tw = tile.width;
int th = tile.height; int th = tile.height;
const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]); const float *r_channel_start = reinterpret_cast<const float *>(tile.images[idxR]);
const float *g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]); const float *g_channel_start = nullptr;
const float *b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]); const float *b_channel_start = nullptr;
const float *a_channel_start = NULL; const float *a_channel_start = nullptr;
if (idxG != -1) {
g_channel_start = reinterpret_cast<const float *>(tile.images[idxG]);
}
if (idxB != -1) {
b_channel_start = reinterpret_cast<const float *>(tile.images[idxB]);
}
if (idxA != -1) { if (idxA != -1) {
a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]); a_channel_start = reinterpret_cast<const float *>(tile.images[idxA]);
} }
uint16_t *first_row_w = iw + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels; uint16_t *first_row_w16 = iw16 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels;
float *first_row_w32 = iw32 + (tile.offset_y * tile_height * exr_image.width + tile.offset_x * tile_width) * output_channels;
for (int y = 0; y < th; y++) { for (int y = 0; y < th; y++) {
const float *r_channel = r_channel_start + y * tile_width; const float *r_channel = r_channel_start + y * tile_width;
const float *g_channel = g_channel_start + y * tile_width; const float *g_channel = nullptr;
const float *b_channel = b_channel_start + y * tile_width; const float *b_channel = nullptr;
const float *a_channel = NULL; const float *a_channel = nullptr;
if (g_channel_start) {
g_channel = g_channel_start + y * tile_width;
}
if (b_channel_start) {
b_channel = b_channel_start + y * tile_width;
}
if (a_channel_start) { if (a_channel_start) {
a_channel = a_channel_start + y * tile_width; a_channel = a_channel_start + y * tile_width;
} }
uint16_t *row_w = first_row_w + (y * exr_image.width * output_channels); if (use_float16) {
uint16_t *row_w = first_row_w16 + (y * exr_image.width * output_channels);
for (int x = 0; x < tw; x++) { for (int x = 0; x < tw; x++) {
Color color;
color.r = *r_channel++;
if (g_channel) {
color.g = *g_channel++;
}
if (b_channel) {
color.b = *b_channel++;
}
if (a_channel) {
color.a = *a_channel++;
}
Color color(*r_channel++, *g_channel++, *b_channel++); if (p_force_linear) {
color = color.to_linear();
}
if (p_force_linear) *row_w++ = Math::make_half_float(color.r);
color = color.to_linear(); if (g_channel) {
*row_w++ = Math::make_half_float(color.g);
}
if (b_channel) {
*row_w++ = Math::make_half_float(color.b);
}
if (a_channel) {
*row_w++ = Math::make_half_float(color.a);
}
}
} else {
float *row_w = first_row_w32 + (y * exr_image.width * output_channels);
*row_w++ = Math::make_half_float(color.r); for (int x = 0; x < tw; x++) {
*row_w++ = Math::make_half_float(color.g); Color color;
*row_w++ = Math::make_half_float(color.b); color.r = *r_channel++;
if (g_channel) {
color.g = *g_channel++;
}
if (b_channel) {
color.b = *b_channel++;
}
if (a_channel) {
color.a = *a_channel++;
}
if (idxA != -1) { if (p_force_linear) {
*row_w++ = Math::make_half_float(*a_channel++); color = color.to_linear();
}
*row_w++ = color.r;
if (g_channel) {
*row_w++ = color.g;
}
if (b_channel) {
*row_w++ = color.b;
}
if (a_channel) {
*row_w++ = color.a;
}
} }
} }
} }
@ -235,7 +286,7 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
p_image->create(exr_image.width, exr_image.height, false, format, imgdata); p_image->create(exr_image.width, exr_image.height, false, format, imgdata);
w.release(); img_write.release();
FreeEXRHeader(&exr_header); FreeEXRHeader(&exr_header);
FreeEXRImage(&exr_image); FreeEXRImage(&exr_image);
@ -244,7 +295,6 @@ Error ImageLoaderTinyEXR::load_image(Ref<Image> p_image, FileAccess *f, bool p_f
} }
void ImageLoaderTinyEXR::get_recognized_extensions(List<String> *p_extensions) const { void ImageLoaderTinyEXR::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("exr"); p_extensions->push_back("exr");
} }

View file

@ -34,7 +34,6 @@
#include "core/io/image_loader.h" #include "core/io/image_loader.h"
class ImageLoaderTinyEXR : public ImageFormatLoader { class ImageLoaderTinyEXR : public ImageFormatLoader {
public: public:
virtual Error load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale); virtual Error load_image(Ref<Image> p_image, FileAccess *f, bool p_force_linear, float p_scale);
virtual void get_recognized_extensions(List<String> *p_extensions) const; virtual void get_recognized_extensions(List<String> *p_extensions) const;

View file

@ -58,7 +58,8 @@ static bool is_supported_format(Image::Format p_format) {
enum SrcPixelType { enum SrcPixelType {
SRC_FLOAT, SRC_FLOAT,
SRC_HALF, SRC_HALF,
SRC_BYTE SRC_BYTE,
SRC_UNSUPPORTED
}; };
static SrcPixelType get_source_pixel_type(Image::Format p_format) { static SrcPixelType get_source_pixel_type(Image::Format p_format) {
@ -79,7 +80,7 @@ static SrcPixelType get_source_pixel_type(Image::Format p_format) {
case Image::FORMAT_RGBA8: case Image::FORMAT_RGBA8:
return SRC_BYTE; return SRC_BYTE;
default: default:
CRASH_NOW(); return SRC_UNSUPPORTED;
} }
} }
@ -101,7 +102,7 @@ static int get_target_pixel_type(Image::Format p_format) {
case Image::FORMAT_RGBA8: case Image::FORMAT_RGBA8:
return TINYEXR_PIXELTYPE_HALF; return TINYEXR_PIXELTYPE_HALF;
default: default:
CRASH_NOW(); return -1;
} }
} }
@ -112,7 +113,7 @@ static int get_pixel_type_size(int p_pixel_type) {
case TINYEXR_PIXELTYPE_FLOAT: case TINYEXR_PIXELTYPE_FLOAT:
return 4; return 4;
} }
CRASH_NOW(); return -1;
} }
static int get_channel_count(Image::Format p_format) { static int get_channel_count(Image::Format p_format) {
@ -134,12 +135,11 @@ static int get_channel_count(Image::Format p_format) {
case Image::FORMAT_RGBA8: case Image::FORMAT_RGBA8:
return 4; return 4;
default: default:
CRASH_NOW(); return -1;
} }
} }
Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) { Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) {
Image::Format format = p_img->get_format(); Image::Format format = p_img->get_format();
if (!is_supported_format(format)) { if (!is_supported_format(format)) {
@ -173,11 +173,15 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
}; };
int channel_count = get_channel_count(format); int channel_count = get_channel_count(format);
ERR_FAIL_COND_V(channel_count < 0, ERR_UNAVAILABLE);
ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER);
int target_pixel_type = get_target_pixel_type(format); int target_pixel_type = get_target_pixel_type(format);
ERR_FAIL_COND_V(target_pixel_type < 0, ERR_UNAVAILABLE);
int target_pixel_type_size = get_pixel_type_size(target_pixel_type); int target_pixel_type_size = get_pixel_type_size(target_pixel_type);
ERR_FAIL_COND_V(target_pixel_type_size < 0, ERR_UNAVAILABLE);
SrcPixelType src_pixel_type = get_source_pixel_type(format); SrcPixelType src_pixel_type = get_source_pixel_type(format);
ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, ERR_UNAVAILABLE);
const int pixel_count = p_img->get_width() * p_img->get_height(); const int pixel_count = p_img->get_width() * p_img->get_height();
const int *channel_mapping = channel_mappings[channel_count - 1]; const int *channel_mapping = channel_mappings[channel_count - 1];
@ -187,7 +191,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
PoolByteArray::Read src_r = src_data.read(); PoolByteArray::Read src_r = src_data.read();
for (int channel_index = 0; channel_index < channel_count; ++channel_index) { for (int channel_index = 0; channel_index < channel_count; ++channel_index) {
// De-interleave channels // De-interleave channels
PoolByteArray &dst = channels[channel_index]; PoolByteArray &dst = channels[channel_index];
@ -196,7 +199,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
PoolByteArray::Write dst_w = dst.write(); PoolByteArray::Write dst_w = dst.write();
if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) { if (src_pixel_type == SRC_FLOAT && target_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
// Note: we don't save mipmaps // Note: we don't save mipmaps
CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size); CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
@ -208,7 +210,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
} }
} else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) { } else if (src_pixel_type == SRC_HALF && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size); CRASH_COND(src_data.size() < pixel_count * channel_count * target_pixel_type_size);
const uint16_t *src_rp = (uint16_t *)src_r.ptr(); const uint16_t *src_rp = (uint16_t *)src_r.ptr();
@ -219,7 +220,6 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
} }
} else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) { } else if (src_pixel_type == SRC_BYTE && target_pixel_type == TINYEXR_PIXELTYPE_HALF) {
CRASH_COND(src_data.size() < pixel_count * channel_count); CRASH_COND(src_data.size() < pixel_count * channel_count);
const uint8_t *src_rp = (uint8_t *)src_r.ptr(); const uint8_t *src_rp = (uint8_t *)src_r.ptr();
@ -262,13 +262,21 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
header.channels = channel_infos; header.channels = channel_infos;
header.pixel_types = pixel_types; header.pixel_types = pixel_types;
header.requested_pixel_types = requested_pixel_types; header.requested_pixel_types = requested_pixel_types;
header.compression_type = TINYEXR_COMPRESSIONTYPE_PIZ;
CharString utf8_filename = p_path.utf8(); unsigned char *mem = nullptr;
const char *err; const char *err = nullptr;
int ret = SaveEXRImageToFile(&image, &header, utf8_filename.ptr(), &err);
if (ret != TINYEXR_SUCCESS) { size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err);
if (bytes == 0) {
print_error(String("Saving EXR failed. Error: {0}").format(varray(err))); print_error(String("Saving EXR failed. Error: {0}").format(varray(err)));
return ERR_FILE_CANT_WRITE; return ERR_FILE_CANT_WRITE;
} else {
FileAccessRef ref = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_V(!ref, ERR_FILE_CANT_WRITE);
ref->store_buffer(mem, bytes);
free(mem);
} }
return OK; return OK;

View file

@ -516,6 +516,55 @@ void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightm
} }
} }
void BakedLightmap::_save_image(String &r_base_path, Ref<Image> r_img, bool p_use_srgb) {
if (use_hdr) {
r_base_path += ".exr";
} else {
r_base_path += ".png";
}
String relative_path = r_base_path;
if (relative_path.begins_with("res://")) {
relative_path = relative_path.substr(6, relative_path.length());
}
bool hdr_grayscale = use_hdr && !use_color;
if (p_use_srgb || hdr_grayscale) {
r_img->lock();
for (int i = 0; i < r_img->get_height(); i++) {
for (int j = 0; j < r_img->get_width(); j++) {
Color c = r_img->get_pixel(j, i);
if (hdr_grayscale) {
c = Color(c.get_v(), 0.0f, 0.0f);
}
if (p_use_srgb) {
c = c.to_srgb();
}
r_img->set_pixel(j, i, c);
}
}
r_img->unlock();
}
if (!use_color) {
if (use_hdr) {
r_img->convert(Image::FORMAT_RH);
} else {
r_img->convert(Image::FORMAT_L8);
}
}
if (use_hdr) {
r_img->save_exr(relative_path, !use_color);
} else {
r_img->save_png(relative_path);
}
}
bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) { bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) {
BakeStepUD *bsud = (BakeStepUD *)ud; BakeStepUD *bsud = (BakeStepUD *)ud;
bool ret = false; bool ret = false;
@ -893,6 +942,8 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
images.push_back(lightmapper->get_bake_texture(i)); images.push_back(lightmapper->get_bake_texture(i));
} }
bool use_srgb = use_color && !use_hdr;
if (gen_atlas) { if (gen_atlas) {
Ref<Image> large_image; Ref<Image> large_image;
@ -906,13 +957,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
String base_path = p_data_save_path.get_basename(); String base_path = p_data_save_path.get_basename();
if (ResourceLoader::import) { if (ResourceLoader::import) {
_save_image(base_path, large_image, use_srgb);
base_path += ".exr";
String relative_path = base_path;
if (relative_path.begins_with("res://")) {
relative_path = relative_path.substr(6, relative_path.length());
}
large_image->save_exr(relative_path, false);
Ref<ConfigFile> config; Ref<ConfigFile> config;
config.instance(); config.instance();
@ -928,7 +973,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
config->set_value("params", "flags/repeat", false); config->set_value("params", "flags/repeat", false);
config->set_value("params", "flags/filter", true); config->set_value("params", "flags/filter", true);
config->set_value("params", "flags/mipmaps", false); config->set_value("params", "flags/mipmaps", false);
config->set_value("params", "flags/srgb", false); config->set_value("params", "flags/srgb", use_srgb);
config->set_value("params", "slices/horizontal", 1); config->set_value("params", "slices/horizontal", 1);
config->set_value("params", "slices/vertical", images.size()); config->set_value("params", "slices/vertical", images.size());
config->save(base_path + ".import"); config->save(base_path + ".import");
@ -987,12 +1032,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
if (ResourceLoader::import) { if (ResourceLoader::import) {
base_path += ".exr"; _save_image(base_path, images[i], use_srgb);
String relative_path = base_path;
if (relative_path.begins_with("res://")) {
relative_path = relative_path.substr(6, relative_path.length());
}
images[i]->save_exr(relative_path, false);
Ref<ConfigFile> config; Ref<ConfigFile> config;
config.instance(); config.instance();
@ -1008,7 +1048,7 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
config->set_value("params", "flags/repeat", false); config->set_value("params", "flags/repeat", false);
config->set_value("params", "flags/filter", true); config->set_value("params", "flags/filter", true);
config->set_value("params", "flags/mipmaps", false); config->set_value("params", "flags/mipmaps", false);
config->set_value("params", "flags/srgb", false); config->set_value("params", "flags/srgb", use_srgb);
config->save(base_path + ".import"); config->save(base_path + ".import");
@ -1309,6 +1349,26 @@ bool BakedLightmap::is_using_denoiser() const {
return use_denoiser; return use_denoiser;
} }
void BakedLightmap::set_use_hdr(bool p_enable) {
use_hdr = p_enable;
}
bool BakedLightmap::is_using_hdr() const {
return use_hdr;
}
void BakedLightmap::set_use_color(bool p_enable) {
use_color = p_enable;
}
bool BakedLightmap::is_using_color() const {
return use_color;
}
void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) { void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) {
environment_mode = p_mode; environment_mode = p_mode;
_change_notify(); _change_notify();
@ -1428,6 +1488,12 @@ void BakedLightmap::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser); ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser);
ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser); ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser);
ClassDB::bind_method(D_METHOD("set_use_hdr", "use_denoiser"), &BakedLightmap::set_use_hdr);
ClassDB::bind_method(D_METHOD("is_using_hdr"), &BakedLightmap::is_using_hdr);
ClassDB::bind_method(D_METHOD("set_use_color", "use_denoiser"), &BakedLightmap::set_use_color);
ClassDB::bind_method(D_METHOD("is_using_color"), &BakedLightmap::is_using_color);
ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas); ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas);
ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled); ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled);
@ -1463,6 +1529,8 @@ void BakedLightmap::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality"); ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
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::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit");
@ -1528,6 +1596,8 @@ BakedLightmap::BakedLightmap() {
environment_custom_energy = 1.0; environment_custom_energy = 1.0;
use_denoiser = true; use_denoiser = true;
use_hdr = true;
use_color = true;
bias = 0.005; bias = 0.005;
generate_atlas = true; generate_atlas = true;

View file

@ -161,6 +161,8 @@ private:
bool capture_enabled; bool capture_enabled;
int bounces; int bounces;
bool use_denoiser; bool use_denoiser;
bool use_hdr;
bool use_color;
EnvironmentMode environment_mode; EnvironmentMode environment_mode;
Ref<Sky> environment_custom_sky; Ref<Sky> environment_custom_sky;
@ -186,6 +188,7 @@ private:
Vector2i _compute_lightmap_size(const MeshesFound &p_mesh); Vector2i _compute_lightmap_size(const MeshesFound &p_mesh);
static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh); static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh);
void _save_image(String &r_base_path, Ref<Image> p_img, bool p_use_srgb);
protected: protected:
static void _bind_methods(); static void _bind_methods();
@ -247,6 +250,12 @@ public:
void set_use_denoiser(bool p_enable); void set_use_denoiser(bool p_enable);
bool is_using_denoiser() const; bool is_using_denoiser() 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_bounces(int p_bounces); void set_bounces(int p_bounces);
int get_bounces() const; int get_bounces() const;