Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose runtime baking functionality in LightmapGI #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/classes/LightmapGI.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@
<tutorials>
<link title="Using Lightmap global illumination">$DOCS_URL/tutorials/3d/global_illumination/using_lightmap_gi.html</link>
</tutorials>
<methods>
<method name="bake">
<return type="int" enum="LightmapGI.BakeError" />
<param index="0" name="from_node" type="Node" />
<param index="1" name="image_data_path" type="String" default="&quot;&quot;" />
<description>
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.
[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.
[b]Additional Note:[/b] Baking lightmaps from a headless editor instance is not supported. If you attempt to bake lightmaps in this manner, the images returned will be null.
</description>
</method>
</methods>
<members>
<member name="bias" type="float" setter="set_bias" getter="get_bias" default="0.0005">
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.
Expand Down
4 changes: 2 additions & 2 deletions editor/plugins/lightmap_gi_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) {

if (err == LightmapGI::BAKE_ERROR_OK) {
if (get_tree()->get_edited_scene_root() == lightmap) {
err = lightmap->bake(lightmap, p_file, bake_func_step);
err = lightmap->_bake(lightmap, p_file, bake_func_step);
} else {
err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step);
err = lightmap->_bake(lightmap->get_parent(), p_file, bake_func_step);
}
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion modules/lightmapper_rd/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def can_build(env, platform):
return env.editor_build and platform not in ["android", "ios"]
return (env.editor_build or env["module_lightmapper_rd_enabled"]) and platform not in ["android", "ios"]


def configure(env):
Expand Down
39 changes: 27 additions & 12 deletions modules/lightmapper_rd/lightmapper_rd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
#include "core/math/geometry_2d.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#endif
#include "servers/rendering/rendering_device_binds.h"

#if defined(VULKAN_ENABLED)
Expand Down Expand Up @@ -881,6 +883,7 @@ Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
return img;
}

#ifdef TOOLS_ENABLED
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) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);

Expand Down Expand Up @@ -946,6 +949,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
}
return BAKE_OK;
}
#endif

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, 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) {
RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams));
Expand Down Expand Up @@ -1024,22 +1028,28 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
}

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_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) {
#ifdef TOOLS_ENABLED
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");

if (p_use_denoiser && denoiser == 1) {
// OIDN (external).
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);

if (da->dir_exists(oidn_path)) {
if (OS::get_singleton()->get_name() == "Windows") {
oidn_path = oidn_path.path_join("oidnDenoise.exe");
} else {
oidn_path = oidn_path.path_join("oidnDenoise");
// TODO: Implement oidnDenoise for non-editor
String oidn_path;
if (Engine::get_singleton()->is_editor_hint()) {
oidn_path = p_use_denoiser ? EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path") : Variant();

if (p_use_denoiser && denoiser == 1) {
// OIDN (external).
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);

if (da->dir_exists(oidn_path)) {
if (OS::get_singleton()->get_name() == "Windows") {
oidn_path = oidn_path.path_join("oidnDenoise.exe");
} else {
oidn_path = oidn_path.path_join("oidnDenoise");
}
}
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.");
}
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.");
}
#endif

if (p_step_function) {
p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
Expand Down Expand Up @@ -1849,6 +1859,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d

{
BakeError error;
#ifdef TOOLS_ENABLED
if (denoiser == 1) {
// OIDN (external).
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
Expand All @@ -1857,6 +1868,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
SWAP(light_accum_tex, light_accum_tex2);
error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function, p_bake_userdata);
}
#else
SWAP(light_accum_tex, light_accum_tex2);
Comment on lines +1871 to +1872
Copy link
Member

@Spartan322 Spartan322 Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should put a warning for if denoiser somehow equals 1 that the oidn denoiser can only be used in tool builds and that it will only use the JNLM denoiser.

error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function, p_bake_userdata);
#endif
if (unlikely(error != BAKE_OK)) {
return error;
}
Expand Down
2 changes: 1 addition & 1 deletion modules/xatlas_unwrap/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def can_build(env, platform):
return env.editor_build and platform not in ["android", "ios"]
return (env.editor_build or env["module_xatlas_unwrap_enabled"]) and platform not in ["android", "ios"]


def configure(env):
Expand Down
100 changes: 70 additions & 30 deletions scene/3d/lightmap_gi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "lightmap_gi.h"

#include "core/config/engine.h"
#include "core/config/project_settings.h"
#include "core/io/config_file.h"
#include "core/math/delaunay_3d.h"
Expand All @@ -39,6 +40,7 @@
#include "scene/resources/environment.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/sky.h"
#include "scene/resources/texture.h"

void LightmapGIData::add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance) {
User user;
Expand Down Expand Up @@ -740,7 +742,17 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
}
}

LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
bool LightmapGI::_dummy_bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) {
// No reporting needed, but baking logic is identical
return true;
}

LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path) {
// is dummy bake func needed?
return _bake(p_from_node, p_image_data_path, _dummy_bake_func_step, nullptr);
}

LightmapGI::BakeError LightmapGI::_bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
if (p_image_data_path.is_empty()) {
if (get_light_data().is_null()) {
return BAKE_ERROR_NO_SAVE_PATH;
Expand Down Expand Up @@ -1083,14 +1095,34 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
}

if (env.is_valid()) {
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64));
environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse();

Sky::RadianceSize old_radiance_size = Sky::RADIANCE_SIZE_MAX;
if (!Engine::get_singleton()->is_editor_hint()) {
Ref<Sky> sky = env->get_sky();
if (sky.is_valid()) {
old_radiance_size = sky->get_radiance_size();
sky->set_radiance_size(Sky::RADIANCE_SIZE_128);
}
}
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 128));
if (old_radiance_size != Sky::RADIANCE_SIZE_MAX) { // If it's not max, it's been set and needs resetting
Ref<Sky> sky = env->get_sky();
if (sky.is_valid()) {
sky->set_radiance_size(old_radiance_size);
}
}
}
}
} break;
case ENVIRONMENT_MODE_CUSTOM_SKY: {
if (environment_custom_sky.is_valid()) {
environment_image = RS::get_singleton()->sky_bake_panorama(environment_custom_sky->get_rid(), environment_custom_energy, true, Size2i(128, 64));
Sky::RadianceSize old_radiance_size = environment_custom_sky->get_radiance_size();
if (!Engine::get_singleton()->is_editor_hint()) {
environment_custom_sky->set_radiance_size(Sky::RADIANCE_SIZE_128);
}
environment_image = RS::get_singleton()->sky_bake_panorama(environment_custom_sky->get_rid(), environment_custom_energy, true, Size2i(128, 128));
environment_custom_sky->set_radiance_size(old_radiance_size);
}

} break;
Expand Down Expand Up @@ -1156,34 +1188,42 @@ 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";

Ref<ConfigFile> config;
config.instantiate();

if (FileAccess::exists(texture_path + ".import")) {
config->load(texture_path + ".import");
}
if (Engine::get_singleton()->is_editor_hint()) {
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr";
Ref<ConfigFile> config;
config.instantiate();

config->set_value("remap", "importer", "2d_array_texture");
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("remap", "importer", "2d_array_texture");
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/channel_pack", 1);
config->set_value("params", "mipmaps/generate", false);
config->set_value("params", "slices/horizontal", 1);
config->set_value("params", "slices/vertical", texture_slice_count);

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

Error err = texture_image->save_exr(texture_path, false);
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?
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
textures[i] = t;
} else {
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".res" : base_path + ".res";
Ref<Texture2DArray> texs;
texs.instantiate();
texs->create_from_images(images);

Error err = ResourceSaver::save(texs, texture_path);
ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
Ref<TextureLayered> t = ResourceLoader::load(texture_path);
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
textures[i] = t;
}
config->set_value("params", "compress/channel_pack", 1);
config->set_value("params", "mipmaps/generate", false);
config->set_value("params", "slices/horizontal", 1);
config->set_value("params", "slices/vertical", texture_slice_count);

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

Error err = texture_image->save_exr(texture_path, false);
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?
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
textures[i] = t;
}
}

Expand Down Expand Up @@ -1686,7 +1726,7 @@ void LightmapGI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_camera_attributes", "camera_attributes"), &LightmapGI::set_camera_attributes);
ClassDB::bind_method(D_METHOD("get_camera_attributes"), &LightmapGI::get_camera_attributes);

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

ADD_GROUP("Tweaks", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality");
Expand Down
6 changes: 5 additions & 1 deletion scene/3d/lightmap_gi.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,11 @@ class LightmapGI : public VisualInstance3D {

AABB get_aabb() const override;

BakeError bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr);
static bool _dummy_bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh);

BakeError bake(Node *p_from_node, String p_image_data_path = "");

BakeError _bake(Node *p_from_node, String p_image_data_path = "", Lightmapper::BakeStepFunc p_bake_step = nullptr, void *p_bake_userdata = nullptr);

virtual PackedStringArray get_configuration_warnings() const override;

Expand Down
Loading