/*************************************************************************/ /* baked_lightmap.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "baked_lightmap.h" #include "core/io/config_file.h" #include "core/io/resource_saver.h" #include "core/math/math_defs.h" #include "core/os/dir_access.h" #include "core/os/os.h" #include "voxel_light_baker.h" void BakedLightmapData::set_bounds(const AABB &p_bounds) { bounds = p_bounds; VS::get_singleton()->lightmap_capture_set_bounds(baked_light, p_bounds); } AABB BakedLightmapData::get_bounds() const { return bounds; } void BakedLightmapData::set_octree(const PoolVector &p_octree) { VS::get_singleton()->lightmap_capture_set_octree(baked_light, p_octree); } PoolVector BakedLightmapData::get_octree() const { return VS::get_singleton()->lightmap_capture_get_octree(baked_light); } void BakedLightmapData::set_cell_space_transform(const Transform &p_xform) { cell_space_xform = p_xform; VS::get_singleton()->lightmap_capture_set_octree_cell_transform(baked_light, p_xform); } Transform BakedLightmapData::get_cell_space_transform() const { return cell_space_xform; } void BakedLightmapData::set_cell_subdiv(int p_cell_subdiv) { cell_subdiv = p_cell_subdiv; VS::get_singleton()->lightmap_capture_set_octree_cell_subdiv(baked_light, p_cell_subdiv); } int BakedLightmapData::get_cell_subdiv() const { return cell_subdiv; } void BakedLightmapData::set_energy(float p_energy) { energy = p_energy; VS::get_singleton()->lightmap_capture_set_energy(baked_light, energy); } float BakedLightmapData::get_energy() const { return energy; } void BakedLightmapData::add_user(const NodePath &p_path, const Ref &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance) { ERR_FAIL_COND_MSG(p_lightmap.is_null(), "It's not a reference to a valid Texture object."); ERR_FAIL_COND(p_lightmap_slice == -1 && !Object::cast_to(p_lightmap.ptr())); ERR_FAIL_COND(p_lightmap_slice != -1 && !Object::cast_to(p_lightmap.ptr())); User user; user.path = p_path; if (p_lightmap_slice == -1) { user.lightmap.single = p_lightmap; } else { user.lightmap.layered = p_lightmap; } user.lightmap_slice = p_lightmap_slice; user.lightmap_uv_rect = p_lightmap_uv_rect; user.instance_index = p_instance; users.push_back(user); } int BakedLightmapData::get_user_count() const { return users.size(); } NodePath BakedLightmapData::get_user_path(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), NodePath()); return users[p_user].path; } Ref BakedLightmapData::get_user_lightmap(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), Ref()); if (users[p_user].lightmap_slice == -1) { return users[p_user].lightmap.single; } else { return users[p_user].lightmap.layered; } } int BakedLightmapData::get_user_lightmap_slice(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), -1); return users[p_user].lightmap_slice; } Rect2 BakedLightmapData::get_user_lightmap_uv_rect(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), Rect2(0, 0, 1, 1)); return users[p_user].lightmap_uv_rect; } int BakedLightmapData::get_user_instance(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), -1); return users[p_user].instance_index; } void BakedLightmapData::clear_users() { users.clear(); } void BakedLightmapData::_set_user_data(const Array &p_data) { // Detect old lightmapper format if (p_data.size() % 3 == 0) { bool is_old_format = true; for (int i = 0; i < p_data.size(); i += 3) { is_old_format = is_old_format && p_data[i + 0].get_type() == Variant::NODE_PATH; is_old_format = is_old_format && p_data[i + 1].is_ref(); is_old_format = is_old_format && p_data[i + 2].get_type() == Variant::INT; if (!is_old_format) { break; } } if (is_old_format) { #ifdef DEBUG_ENABLED WARN_PRINTS("Geometry at path " + String(p_data[0]) + " is using old lightmapper data. Please re-bake."); #endif Array adapted_data; adapted_data.resize((p_data.size() / 3) * 5); for (int i = 0; i < p_data.size() / 3; i++) { adapted_data[i * 5 + 0] = p_data[i * 3 + 0]; adapted_data[i * 5 + 1] = p_data[i * 3 + 1]; adapted_data[i * 5 + 2] = -1; adapted_data[i * 5 + 3] = Rect2(0, 0, 1, 1); adapted_data[i * 5 + 4] = p_data[i * 3 + 2]; } _set_user_data(adapted_data); return; } } ERR_FAIL_COND((p_data.size() % 5) != 0); for (int i = 0; i < p_data.size(); i += 5) { add_user(p_data[i], p_data[i + 1], p_data[i + 2], p_data[i + 3], p_data[i + 4]); } } Array BakedLightmapData::_get_user_data() const { Array ret; for (int i = 0; i < users.size(); i++) { ret.push_back(users[i].path); ret.push_back(users[i].lightmap_slice == -1 ? Ref(users[i].lightmap.single) : Ref(users[i].lightmap.layered)); ret.push_back(users[i].lightmap_slice); ret.push_back(users[i].lightmap_uv_rect); ret.push_back(users[i].instance_index); } return ret; } RID BakedLightmapData::get_rid() const { return baked_light; } void BakedLightmapData::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_user_data", "data"), &BakedLightmapData::_set_user_data); ClassDB::bind_method(D_METHOD("_get_user_data"), &BakedLightmapData::_get_user_data); ClassDB::bind_method(D_METHOD("set_bounds", "bounds"), &BakedLightmapData::set_bounds); ClassDB::bind_method(D_METHOD("get_bounds"), &BakedLightmapData::get_bounds); ClassDB::bind_method(D_METHOD("set_cell_space_transform", "xform"), &BakedLightmapData::set_cell_space_transform); ClassDB::bind_method(D_METHOD("get_cell_space_transform"), &BakedLightmapData::get_cell_space_transform); ClassDB::bind_method(D_METHOD("set_cell_subdiv", "cell_subdiv"), &BakedLightmapData::set_cell_subdiv); ClassDB::bind_method(D_METHOD("get_cell_subdiv"), &BakedLightmapData::get_cell_subdiv); ClassDB::bind_method(D_METHOD("set_octree", "octree"), &BakedLightmapData::set_octree); ClassDB::bind_method(D_METHOD("get_octree"), &BakedLightmapData::get_octree); ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmapData::set_energy); ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmapData::get_energy); ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap", "lightmap_slice", "lightmap_uv_rect", "instance"), &BakedLightmapData::add_user); ClassDB::bind_method(D_METHOD("get_user_count"), &BakedLightmapData::get_user_count); ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &BakedLightmapData::get_user_path); ClassDB::bind_method(D_METHOD("get_user_lightmap", "user_idx"), &BakedLightmapData::get_user_lightmap); ClassDB::bind_method(D_METHOD("clear_users"), &BakedLightmapData::clear_users); ADD_PROPERTY(PropertyInfo(Variant::AABB, "bounds", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_bounds", "get_bounds"); ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "cell_space_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_space_transform", "get_cell_space_transform"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_subdiv", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_subdiv", "get_cell_subdiv"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy"); ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "octree", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_octree", "get_octree"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); } BakedLightmapData::BakedLightmapData() { baked_light = VS::get_singleton()->lightmap_capture_create(); energy = 1; cell_subdiv = 1; } BakedLightmapData::~BakedLightmapData() { VS::get_singleton()->free(baked_light); } /////////////////////////// Lightmapper::BakeStepFunc BakedLightmap::bake_step_function; Lightmapper::BakeStepFunc BakedLightmap::bake_substep_function; Size2i BakedLightmap::_compute_lightmap_size(const MeshesFound &p_mesh) { double area = 0; double uv_area = 0; for (int i = 0; i < p_mesh.mesh->get_surface_count(); i++) { Array arrays = p_mesh.mesh->surface_get_arrays(i); PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; PoolVector indices = arrays[Mesh::ARRAY_INDEX]; ERR_FAIL_COND_V(vertices.size() == 0, Vector2()); ERR_FAIL_COND_V(uv2.size() == 0, Vector2()); int vc = vertices.size(); PoolVector::Read vr = vertices.read(); PoolVector::Read u2r = uv2.read(); PoolVector::Read ir; int ic = 0; if (indices.size()) { ic = indices.size(); ir = indices.read(); } int faces = ic ? ic / 3 : vc / 3; for (int j = 0; j < faces; j++) { Vector3 vertex[3]; Vector2 uv[3]; for (int k = 0; k < 3; k++) { int idx = ic ? ir[j * 3 + k] : j * 3 + k; vertex[k] = p_mesh.xform.xform(vr[idx]); uv[k] = u2r[idx]; } Vector3 p1 = vertex[0]; Vector3 p2 = vertex[1]; Vector3 p3 = vertex[2]; double a = p1.distance_to(p2); double b = p2.distance_to(p3); double c = p3.distance_to(p1); double halfPerimeter = (a + b + c) / 2.0; area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c)); Vector2 uv_p1 = uv[0]; Vector2 uv_p2 = uv[1]; Vector2 uv_p3 = uv[2]; double uv_a = uv_p1.distance_to(uv_p2); double uv_b = uv_p2.distance_to(uv_p3); double uv_c = uv_p3.distance_to(uv_p1); double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0; uv_area += sqrt( uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c)); } } if (uv_area < 0.0001f) { uv_area = 1.0; } int pixels = Math::round(ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); int size = CLAMP(pixels, 2, 4096); return Vector2i(size, size); } void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, Vector &meshes, Vector &lights) { MeshInstance *mi = Object::cast_to(p_at_node); if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) { Ref mesh = mi->get_mesh(); if (mesh.is_valid()) { bool all_have_uv2_and_normal = true; bool surfaces_found = false; for (int i = 0; i < mesh->get_surface_count(); i++) { if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { continue; } if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_TEX_UV2)) { all_have_uv2_and_normal = false; break; } if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_NORMAL)) { all_have_uv2_and_normal = false; break; } surfaces_found = true; } if (surfaces_found && all_have_uv2_and_normal) { MeshesFound mf; mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF; mf.generate_lightmap = mi->get_generate_lightmap(); mf.xform = get_global_transform().affine_inverse() * mi->get_global_transform(); mf.node_path = get_path_to(mi); mf.subindex = -1; mf.mesh = mesh; static const int lightmap_scale[4] = { 1, 2, 4, 8 }; //GeometryInstance3D::LIGHTMAP_SCALE_MAX = { 1, 2, 4, 8 }; mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()]; Ref all_override = mi->get_material_override(); for (int i = 0; i < mesh->get_surface_count(); i++) { if (all_override.is_valid()) { mf.overrides.push_back(all_override); } else { mf.overrides.push_back(mi->get_surface_material(i)); } } meshes.push_back(mf); } } } Spatial *s = Object::cast_to(p_at_node); if (!mi && s) { Array bmeshes = p_at_node->call("get_bake_meshes"); if (bmeshes.size() && (bmeshes.size() & 1) == 0) { Transform xf = get_global_transform().affine_inverse() * s->get_global_transform(); for (int i = 0; i < bmeshes.size(); i += 2) { Ref mesh = bmeshes[i]; if (!mesh.is_valid()) { continue; } MeshesFound mf; Transform mesh_xf = bmeshes[i + 1]; mf.xform = xf * mesh_xf; mf.node_path = get_path_to(s); mf.subindex = i / 2; mf.lightmap_scale = 1; mf.mesh = mesh; meshes.push_back(mf); } } } Light *light = Object::cast_to(p_at_node); if (light && light->get_bake_mode() != Light::BAKE_DISABLED) { LightsFound lf; lf.xform = get_global_transform().affine_inverse() * light->get_global_transform(); lf.light = light; lights.push_back(lf); } for (int i = 0; i < p_at_node->get_child_count(); i++) { Node *child = p_at_node->get_child(i); if (!child->get_owner()) { continue; //maybe a helper } _find_meshes_and_lights(child, meshes, lights); } } void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightmapper::MeshData &r_mesh_data, Vector > &r_albedo_textures, Vector > &r_emission_textures) { for (int i = 0; i < p_found_mesh.mesh->get_surface_count(); ++i) { Ref mat = p_found_mesh.overrides[i]; if (mat.is_null()) { mat = p_found_mesh.mesh->surface_get_material(i); } Ref albedo_texture; Color albedo_add = Color(0, 0, 0, 0); Color albedo_mul = Color(1, 1, 1, 1); Ref emission_texture; Color emission_add = Color(0, 0, 0, 0); Color emission_mul = Color(1, 1, 1, 1); if (mat.is_valid()) { albedo_texture = mat->get_texture(SpatialMaterial::TEXTURE_ALBEDO); if (albedo_texture.is_valid()) { albedo_mul = mat->get_albedo(); } else { albedo_add = mat->get_albedo(); } emission_texture = mat->get_texture(SpatialMaterial::TEXTURE_EMISSION); Color emission_color = mat->get_emission(); float emission_energy = mat->get_emission_energy(); if (mat->get_emission_operator() == SpatialMaterial::EMISSION_OP_ADD) { emission_mul = Color(1, 1, 1) * emission_energy; emission_add = emission_color * emission_energy; } else { emission_mul = emission_color * emission_energy; emission_add = Color(0, 0, 0); } } Lightmapper::MeshData::TextureDef albedo; albedo.mul = albedo_mul; albedo.add = albedo_add; if (albedo_texture.is_valid()) { albedo.tex_rid = albedo_texture->get_rid(); r_albedo_textures.push_back(albedo_texture); } r_mesh_data.albedo.push_back(albedo); Lightmapper::MeshData::TextureDef emission; emission.mul = emission_mul; emission.add = emission_add; if (emission_texture.is_valid()) { emission.tex_rid = emission_texture->get_rid(); r_emission_textures.push_back(emission_texture); } r_mesh_data.emission.push_back(emission); } } bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) { BakeStepUD *bsud = (BakeStepUD *)ud; bool ret = false; if (bsud->func) { ret = bsud->func(bsud->from_percent + p_completion * (bsud->to_percent - bsud->from_percent), p_text, bsud->ud, p_refresh); } return ret; } BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_save_path) { bool no_save_path = false; if (p_data_save_path == "" && (get_light_data().is_null() || !get_light_data()->get_path().is_resource_file())) { no_save_path = true; } if (p_data_save_path == "") { if (get_light_data().is_null()) { no_save_path = true; } else { p_data_save_path = get_light_data()->get_path(); if (!p_data_save_path.is_resource_file()) { no_save_path = true; } } } if (no_save_path) { if (image_path == "") { return BAKE_ERROR_NO_SAVE_PATH; } else { p_data_save_path = image_path; } WARN_PRINT("Using the deprecated property \"image_path\" as a save path, consider providing a better save path via the \"data_save_path\" parameter"); p_data_save_path = image_path.plus_file("BakedLightmap.lmbake"); } { //check for valid save path DirAccessRef d = DirAccess::open(p_data_save_path.get_base_dir()); if (!d) { ERR_FAIL_V_MSG(BAKE_ERROR_NO_SAVE_PATH, "Invalid save path '" + p_data_save_path + "'."); } } if (bake_step_function) { bool cancelled = bake_step_function(0.0, TTR("Finding meshes and lights"), nullptr, true); if (cancelled) { return BAKE_ERROR_USER_ABORTED; } } Ref lightmapper = Lightmapper::create(); ERR_FAIL_COND_V(lightmapper.is_null(), BAKE_ERROR_NO_LIGHTMAPPER); Vector lights_found; Vector meshes_found; _find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), meshes_found, lights_found); if (meshes_found.size() == 0) { return BAKE_ERROR_NO_MESHES; } for (int m_i = 0; m_i < meshes_found.size(); m_i++) { if (bake_step_function) { float p = (float)(m_i) / meshes_found.size(); bool cancelled = bake_step_function(p * 0.05, vformat(TTR("Preparing geometry (%d/%d)"), m_i + 1, meshes_found.size()), nullptr, false); if (cancelled) { return BAKE_ERROR_USER_ABORTED; } } MeshesFound &mf = meshes_found.write[m_i]; Size2i lightmap_size = mf.mesh->get_lightmap_size_hint(); if (lightmap_size == Vector2i(0, 0)) { lightmap_size = _compute_lightmap_size(mf); } lightmap_size *= mf.lightmap_scale; Lightmapper::MeshData md; { Dictionary d; d["path"] = mf.node_path; if (mf.subindex >= 0) { d["subindex"] = mf.subindex; } d["cast_shadows"] = mf.cast_shadows; d["generate_lightmap"] = mf.generate_lightmap; d["node_name"] = mf.node_path.get_name(mf.node_path.get_name_count() - 1); md.userdata = d; } Basis normal_xform = mf.xform.basis.inverse().transposed(); for (int i = 0; i < mf.mesh->get_surface_count(); i++) { if (mf.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { continue; } Array a = mf.mesh->surface_get_arrays(i); Vector vertices = a[Mesh::ARRAY_VERTEX]; const Vector3 *vr = vertices.ptr(); Vector uv2 = a[Mesh::ARRAY_TEX_UV2]; const Vector2 *uv2r = nullptr; Vector uv = a[Mesh::ARRAY_TEX_UV]; const Vector2 *uvr = nullptr; Vector normals = a[Mesh::ARRAY_NORMAL]; const Vector3 *nr = nullptr; Vector index = a[Mesh::ARRAY_INDEX]; ERR_CONTINUE(uv2.size() == 0); ERR_CONTINUE(normals.size() == 0); if (!uv.empty()) { uvr = uv.ptr(); } uv2r = uv2.ptr(); nr = normals.ptr(); int facecount; const int *ir = nullptr; if (index.size()) { facecount = index.size() / 3; ir = index.ptr(); } else { facecount = vertices.size() / 3; } md.surface_facecounts.push_back(facecount); for (int j = 0; j < facecount; j++) { uint32_t vidx[3]; if (ir) { for (int k = 0; k < 3; k++) { vidx[k] = ir[j * 3 + k]; } } else { for (int k = 0; k < 3; k++) { vidx[k] = j * 3 + k; } } for (int k = 0; k < 3; k++) { Vector3 v = mf.xform.xform(vr[vidx[k]]); md.points.push_back(v); md.uv2.push_back(uv2r[vidx[k]]); md.normal.push_back(normal_xform.xform(nr[vidx[k]]).normalized()); if (uvr != nullptr) { md.uv.push_back(uvr[vidx[k]]); } } } } Vector > albedo_textures; Vector > emission_textures; _get_material_images(mf, md, albedo_textures, emission_textures); for (int j = 0; j < albedo_textures.size(); j++) { lightmapper->add_albedo_texture(albedo_textures[j]); } for (int j = 0; j < emission_textures.size(); j++) { lightmapper->add_emission_texture(emission_textures[j]); } lightmapper->add_mesh(md, lightmap_size); } for (int i = 0; i < lights_found.size(); i++) { Light *light = lights_found[i].light; Transform xf = lights_found[i].xform; if (Object::cast_to(light)) { DirectionalLight *l = Object::cast_to(light); lightmapper->add_directional_light(light->get_bake_mode() == Light::BAKE_ALL, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY)); } else if (Object::cast_to(light)) { OmniLight *l = Object::cast_to(light); lightmapper->add_omni_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION)); } else if (Object::cast_to(light)) { SpotLight *l = Object::cast_to(light); lightmapper->add_spot_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION), l->get_param(Light::PARAM_SPOT_ANGLE), l->get_param(Light::PARAM_SPOT_ATTENUATION)); } } Ref environment_image; Basis environment_xform; if (environment_mode != ENVIRONMENT_MODE_DISABLED) { if (bake_step_function) { bake_step_function(0.1, TTR("Preparing environment"), nullptr, true); } switch (environment_mode) { case ENVIRONMENT_MODE_DISABLED: { //nothing } break; case ENVIRONMENT_MODE_SCENE: { Ref world = get_world(); if (world.is_valid()) { Ref env = world->get_environment(); if (env.is_null()) { env = world->get_fallback_environment(); } if (env.is_valid()) { environment_image = _get_irradiance_map(env, Vector2i(128, 64)); environment_xform = get_global_transform().affine_inverse().basis * env->get_sky_orientation(); } } } break; case ENVIRONMENT_MODE_CUSTOM_SKY: { if (environment_custom_sky.is_valid()) { environment_image = _get_irradiance_from_sky(environment_custom_sky, Vector2i(128, 64)); print_line(vformat("env -> %s", environment_custom_sky_rotation_degrees)); environment_xform.set_euler(environment_custom_sky_rotation_degrees * Math_PI / 180.0); } } break; case ENVIRONMENT_MODE_CUSTOM_COLOR: { environment_image.instance(); environment_image->create(128, 64, false, Image::FORMAT_RGBF); Color c = environment_custom_color; c.r *= environment_custom_energy; c.g *= environment_custom_energy; c.b *= environment_custom_energy; for (int i = 0; i < 128; i++) { for (int j = 0; j < 64; j++) { environment_image->set_pixel(i, j, c); } } } break; } } BakeStepUD bsud; bsud.func = bake_step_function; bsud.ud = nullptr; bsud.from_percent = 0.1; bsud.to_percent = 0.9; Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, generate_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function); if (bake_err != Lightmapper::BAKE_OK) { switch (bake_err) { case Lightmapper::BAKE_ERROR_USER_ABORTED: { return BAKE_ERROR_USER_ABORTED; } case Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { return BAKE_ERROR_LIGHTMAP_SIZE; } case Lightmapper::BAKE_ERROR_NO_MESHES: { return BAKE_ERROR_NO_MESHES; } default: { } } return BAKE_ERROR_NO_LIGHTMAPPER; } Ref data; if (get_light_data().is_valid()) { data = get_light_data(); set_light_data(Ref()); //clear data->clear_users(); } else { data.instance(); } if (capture_enabled) { if (bake_step_function) { bool cancelled = bake_step_function(0.85, TTR("Generating capture"), nullptr, true); if (cancelled) { return BAKE_ERROR_USER_ABORTED; } } VoxelLightBaker voxel_baker; int bake_subdiv; int capture_subdiv; AABB bake_bounds; { bake_bounds = AABB(-extents, extents * 2.0); int subdiv = nearest_power_of_2_templated(int(bake_bounds.get_longest_axis_size() / capture_cell_size)); bake_bounds.size[bake_bounds.get_longest_axis_index()] = subdiv * capture_cell_size; bake_subdiv = nearest_shift(subdiv) + 1; capture_subdiv = bake_subdiv; float css = capture_cell_size; while (css < capture_cell_size && capture_subdiv > 2) { capture_subdiv--; css *= 2.0; } } voxel_baker.begin_bake(capture_subdiv + 1, bake_bounds); for (int mesh_id = 0; mesh_id < meshes_found.size(); mesh_id++) { MeshesFound &mf = meshes_found.write[mesh_id]; voxel_baker.plot_mesh(mf.xform, mf.mesh, mf.overrides, Ref()); } VoxelLightBaker::BakeQuality capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_HIGH; if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_LOW) { capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_LOW; } else if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_MEDIUM) { capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_MEDIUM; } voxel_baker.begin_bake_light(capt_quality, capture_propagation); for (int i = 0; i < lights_found.size(); i++) { LightsFound &lf = lights_found.write[i]; switch (lf.light->get_light_type()) { case VS::LIGHT_DIRECTIONAL: { voxel_baker.plot_light_directional(-lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_bake_mode() == Light::BAKE_ALL); } break; case VS::LIGHT_OMNI: { voxel_baker.plot_light_omni(lf.xform.origin, lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); } break; case VS::LIGHT_SPOT: { voxel_baker.plot_light_spot(lf.xform.origin, lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_param(Light::PARAM_SPOT_ANGLE), lf.light->get_param(Light::PARAM_SPOT_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); } break; } } voxel_baker.end_bake(); AABB bounds = AABB(-extents, extents * 2); data->set_cell_subdiv(capture_subdiv); data->set_bounds(bounds); data->set_octree(voxel_baker.create_capture_octree(capture_subdiv)); { float bake_bound_size = bake_bounds.get_longest_axis_size(); Transform to_bounds; to_bounds.basis.scale(Vector3(bake_bound_size, bake_bound_size, bake_bound_size)); to_bounds.origin = bounds.position; Transform to_grid; to_grid.basis.scale(Vector3(1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1))); Transform to_cell_space = to_grid * to_bounds.affine_inverse(); data->set_cell_space_transform(to_cell_space); } } if (bake_step_function) { bool cancelled = bake_step_function(0.9, TTR("Saving lightmaps"), nullptr, true); if (cancelled) { return BAKE_ERROR_USER_ABORTED; } } Vector > images; for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { images.push_back(lightmapper->get_bake_texture(i)); } if (generate_atlas) { Ref large_image; large_image.instance(); large_image->create(images[0]->get_width(), images[0]->get_height() * images.size(), false, images[0]->get_format()); for (int i = 0; i < images.size(); i++) { large_image->blit_rect(images[i], Rect2(0, 0, images[0]->get_width(), images[0]->get_height()), Point2(0, images[0]->get_height() * i)); } Ref texture; String base_path = p_data_save_path.get_basename(); if (ResourceLoader::import) { 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 config; config.instance(); if (FileAccess::exists(base_path + ".import")) { config->load(base_path + ".import"); } else { // Set only if settings don't exist, to keep user choice config->set_value("params", "compress/mode", 0); } config->set_value("remap", "importer", "texture_array"); config->set_value("remap", "type", "TextureArray"); config->set_value("params", "detect_3d", false); config->set_value("params", "flags/repeat", false); config->set_value("params", "flags/filter", true); config->set_value("params", "flags/mipmaps", false); config->set_value("params", "flags/srgb", false); config->set_value("params", "slices/horizontal", 1); config->set_value("params", "slices/vertical", images.size()); config->save(base_path + ".import"); ResourceLoader::import(base_path); texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? } else { base_path += ".texarr"; Ref tex; bool set_path = true; if (ResourceCache::has(base_path)) { tex = Ref((Resource *)ResourceCache::get(base_path)); set_path = false; } if (!tex.is_valid()) { tex.instance(); } tex->create(images[0]->get_width(), images[0]->get_height(), images.size(), images[0]->get_format(), Texture::FLAGS_DEFAULT); for (int i = 0; i < images.size(); i++) { tex->set_layer_data(images[i], i); } ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); if (set_path) { tex->set_path(base_path); } texture = tex; } for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { if (meshes_found[i].generate_lightmap) { Dictionary d = lightmapper->get_bake_mesh_userdata(i); NodePath np = d["path"]; int32_t subindex = -1; if (d.has("subindex")) { subindex = d["subindex"]; } Rect2 uv_rect = lightmapper->get_bake_mesh_uv_scale(i); int slice_index = lightmapper->get_bake_mesh_texture_slice(i); data->add_user(np, texture, slice_index, uv_rect, subindex); } } } else { for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { if (!meshes_found[i].generate_lightmap) { continue; } Ref texture; String base_path = p_data_save_path.get_base_dir().plus_file(images[i]->get_name()); if (ResourceLoader::import) { base_path += ".exr"; 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 config; config.instance(); if (FileAccess::exists(base_path + ".import")) { config->load(base_path + ".import"); } else { // Set only if settings don't exist, to keep user choice config->set_value("params", "compress/mode", 0); } config->set_value("remap", "importer", "texture"); config->set_value("remap", "type", "StreamTexture"); config->set_value("params", "detect_3d", false); config->set_value("params", "flags/repeat", false); config->set_value("params", "flags/filter", true); config->set_value("params", "flags/mipmaps", false); config->set_value("params", "flags/srgb", false); config->save(base_path + ".import"); ResourceLoader::import(base_path); texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? } else { base_path += ".tex"; Ref tex; bool set_path = true; if (ResourceCache::has(base_path)) { tex = Ref((Resource *)ResourceCache::get(base_path)); set_path = false; } if (!tex.is_valid()) { tex.instance(); } tex->create_from_image(images[i], Texture::FLAGS_DEFAULT); ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); if (set_path) { tex->set_path(base_path); } texture = tex; } Dictionary d = lightmapper->get_bake_mesh_userdata(i); NodePath np = d["path"]; int32_t subindex = -1; if (d.has("subindex")) { subindex = d["subindex"]; } Rect2 uv_rect = Rect2(0, 0, 1, 1); int slice_index = -1; data->add_user(np, texture, slice_index, uv_rect, subindex); } } if (bake_step_function) { bool cancelled = bake_step_function(1.0, TTR("Done"), nullptr, true); if (cancelled) { return BAKE_ERROR_USER_ABORTED; } } Error err = ResourceSaver::save(p_data_save_path, data); data->set_path(p_data_save_path); if (err != OK) { return BAKE_ERROR_CANT_CREATE_IMAGE; } set_light_data(data); return BAKE_ERROR_OK; } void BakedLightmap::set_capture_cell_size(float p_cell_size) { capture_cell_size = MAX(0.1, p_cell_size); } float BakedLightmap::get_capture_cell_size() const { return capture_cell_size; } void BakedLightmap::set_extents(const Vector3 &p_extents) { extents = p_extents; update_gizmo(); _change_notify("bake_extents"); } Vector3 BakedLightmap::get_extents() const { return extents; } void BakedLightmap::set_default_texels_per_unit(const float &p_bake_texels_per_unit) { default_texels_per_unit = MAX(0.0, p_bake_texels_per_unit); } float BakedLightmap::get_default_texels_per_unit() const { return default_texels_per_unit; } void BakedLightmap::set_capture_enabled(bool p_enable) { capture_enabled = p_enable; _change_notify(); } bool BakedLightmap::get_capture_enabled() const { return capture_enabled; } void BakedLightmap::_notification(int p_what) { if (p_what == NOTIFICATION_READY) { if (light_data.is_valid()) { _assign_lightmaps(); } request_ready(); //will need ready again if re-enters tree } if (p_what == NOTIFICATION_EXIT_TREE) { if (light_data.is_valid()) { _clear_lightmaps(); } } } void BakedLightmap::_assign_lightmaps() { ERR_FAIL_COND(!light_data.is_valid()); for (int i = 0; i < light_data->get_user_count(); i++) { Ref lightmap = light_data->get_user_lightmap(i); ERR_CONTINUE(!lightmap.is_valid()); ERR_CONTINUE(!Object::cast_to(lightmap.ptr()) && !Object::cast_to(lightmap.ptr())); Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_instance(i); if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } } void BakedLightmap::_clear_lightmaps() { ERR_FAIL_COND(!light_data.is_valid()); for (int i = 0; i < light_data->get_user_count(); i++) { Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_instance(i); if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); } } } Ref BakedLightmap::_get_irradiance_from_sky(Ref p_sky, Vector2i p_size) { if (p_sky.is_null()) { return Ref(); } Ref sky_image; Ref panorama = p_sky; if (panorama.is_valid()) { sky_image = panorama->get_panorama()->get_data(); } Ref procedural = p_sky; if (procedural.is_valid()) { sky_image = procedural->get_panorama(); } if (sky_image.is_null()) { return Ref(); } sky_image->convert(Image::FORMAT_RGBF); sky_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_CUBIC); return sky_image; } Ref BakedLightmap::_get_irradiance_map(Ref p_env, Vector2i p_size) { Environment::BGMode bg_mode = p_env->get_background(); switch (bg_mode) { case Environment::BG_SKY: { return _get_irradiance_from_sky(p_env->get_sky(), Vector2i(128, 64)); } case Environment::BG_CLEAR_COLOR: case Environment::BG_COLOR: { Color c = bg_mode == Environment::BG_CLEAR_COLOR ? Color(GLOBAL_GET("rendering/environment/default_clear_color")) : p_env->get_bg_color(); c.r *= p_env->get_bg_energy(); c.g *= p_env->get_bg_energy(); c.b *= p_env->get_bg_energy(); Ref ret; ret.instance(); ret->create(p_size.x, p_size.y, false, Image::FORMAT_RGBF); ret->fill(c); return ret; } default: { } } return Ref(); } void BakedLightmap::set_light_data(const Ref &p_data) { if (light_data.is_valid()) { if (is_inside_tree()) { _clear_lightmaps(); } set_base(RID()); } light_data = p_data; _change_notify(); if (light_data.is_valid()) { set_base(light_data->get_rid()); if (is_inside_tree()) { _assign_lightmaps(); } } } Ref BakedLightmap::get_light_data() const { return light_data; } void BakedLightmap::set_capture_propagation(float p_propagation) { capture_propagation = p_propagation; } float BakedLightmap::get_capture_propagation() const { return capture_propagation; } void BakedLightmap::set_capture_quality(BakeQuality p_quality) { capture_quality = p_quality; } BakedLightmap::BakeQuality BakedLightmap::get_capture_quality() const { return capture_quality; } void BakedLightmap::set_generate_atlas(bool p_enabled) { generate_atlas = p_enabled; } bool BakedLightmap::is_generate_atlas_enabled() const { return generate_atlas; } void BakedLightmap::set_max_atlas_size(int p_size) { ERR_FAIL_COND(p_size < 2048); max_atlas_size = p_size; } int BakedLightmap::get_max_atlas_size() const { return max_atlas_size; } void BakedLightmap::set_bake_quality(BakeQuality p_quality) { bake_quality = p_quality; _change_notify(); } BakedLightmap::BakeQuality BakedLightmap::get_bake_quality() const { return bake_quality; } #ifndef DISABLE_DEPRECATED void BakedLightmap::set_image_path(const String &p_path) { image_path = p_path; } String BakedLightmap::get_image_path() const { return image_path; } #endif void BakedLightmap::set_use_denoiser(bool p_enable) { use_denoiser = p_enable; } bool BakedLightmap::is_using_denoiser() const { return use_denoiser; } void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) { environment_mode = p_mode; _change_notify(); } BakedLightmap::EnvironmentMode BakedLightmap::get_environment_mode() const { return environment_mode; } void BakedLightmap::set_environment_custom_sky(const Ref &p_sky) { environment_custom_sky = p_sky; } Ref BakedLightmap::get_environment_custom_sky() const { return environment_custom_sky; } void BakedLightmap::set_environment_custom_sky_rotation_degrees(const Vector3 &p_rotation) { environment_custom_sky_rotation_degrees = p_rotation; } Vector3 BakedLightmap::get_environment_custom_sky_rotation_degrees() const { return environment_custom_sky_rotation_degrees; } void BakedLightmap::set_environment_custom_color(const Color &p_color) { environment_custom_color = p_color; } Color BakedLightmap::get_environment_custom_color() const { return environment_custom_color; } void BakedLightmap::set_environment_custom_energy(float p_energy) { environment_custom_energy = p_energy; } float BakedLightmap::get_environment_custom_energy() const { return environment_custom_energy; } void BakedLightmap::set_bounces(int p_bounces) { ERR_FAIL_COND(p_bounces < 0 || p_bounces > 16); bounces = p_bounces; } int BakedLightmap::get_bounces() const { return bounces; } void BakedLightmap::set_bias(float p_bias) { ERR_FAIL_COND(p_bias < 0.00001); bias = p_bias; } float BakedLightmap::get_bias() const { return bias; } AABB BakedLightmap::get_aabb() const { return AABB(-extents, extents * 2); } PoolVector BakedLightmap::get_faces(uint32_t p_usage_flags) const { return PoolVector(); } void BakedLightmap::_validate_property(PropertyInfo &property) const { if (property.name.begins_with("environment_custom_sky") && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { property.usage = 0; } if (property.name == "environment_custom_color" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR) { property.usage = 0; } if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { property.usage = 0; } if (property.name.begins_with("atlas") && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { property.usage = PROPERTY_USAGE_NOEDITOR; } if (property.name.begins_with("capture") && property.name != "capture_enabled" && !capture_enabled) { property.usage = 0; } } void BakedLightmap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_light_data", "data"), &BakedLightmap::set_light_data); ClassDB::bind_method(D_METHOD("get_light_data"), &BakedLightmap::get_light_data); ClassDB::bind_method(D_METHOD("set_bake_quality", "quality"), &BakedLightmap::set_bake_quality); ClassDB::bind_method(D_METHOD("get_bake_quality"), &BakedLightmap::get_bake_quality); ClassDB::bind_method(D_METHOD("set_bounces", "bounces"), &BakedLightmap::set_bounces); ClassDB::bind_method(D_METHOD("get_bounces"), &BakedLightmap::get_bounces); ClassDB::bind_method(D_METHOD("set_bias", "bias"), &BakedLightmap::set_bias); ClassDB::bind_method(D_METHOD("get_bias"), &BakedLightmap::get_bias); ClassDB::bind_method(D_METHOD("set_environment_mode", "mode"), &BakedLightmap::set_environment_mode); ClassDB::bind_method(D_METHOD("get_environment_mode"), &BakedLightmap::get_environment_mode); ClassDB::bind_method(D_METHOD("set_environment_custom_sky", "sky"), &BakedLightmap::set_environment_custom_sky); ClassDB::bind_method(D_METHOD("get_environment_custom_sky"), &BakedLightmap::get_environment_custom_sky); ClassDB::bind_method(D_METHOD("set_environment_custom_sky_rotation_degrees", "rotation"), &BakedLightmap::set_environment_custom_sky_rotation_degrees); ClassDB::bind_method(D_METHOD("get_environment_custom_sky_rotation_degrees"), &BakedLightmap::get_environment_custom_sky_rotation_degrees); ClassDB::bind_method(D_METHOD("set_environment_custom_color", "color"), &BakedLightmap::set_environment_custom_color); ClassDB::bind_method(D_METHOD("get_environment_custom_color"), &BakedLightmap::get_environment_custom_color); ClassDB::bind_method(D_METHOD("set_environment_custom_energy", "energy"), &BakedLightmap::set_environment_custom_energy); ClassDB::bind_method(D_METHOD("get_environment_custom_energy"), &BakedLightmap::get_environment_custom_energy); 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("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("set_max_atlas_size", "max_atlas_size"), &BakedLightmap::set_max_atlas_size); ClassDB::bind_method(D_METHOD("get_max_atlas_size"), &BakedLightmap::get_max_atlas_size); ClassDB::bind_method(D_METHOD("set_capture_quality", "capture_quality"), &BakedLightmap::set_capture_quality); ClassDB::bind_method(D_METHOD("get_capture_quality"), &BakedLightmap::get_capture_quality); ClassDB::bind_method(D_METHOD("set_extents", "extents"), &BakedLightmap::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &BakedLightmap::get_extents); ClassDB::bind_method(D_METHOD("set_default_texels_per_unit", "texels"), &BakedLightmap::set_default_texels_per_unit); ClassDB::bind_method(D_METHOD("get_default_texels_per_unit"), &BakedLightmap::get_default_texels_per_unit); ClassDB::bind_method(D_METHOD("set_capture_propagation", "propagation"), &BakedLightmap::set_capture_propagation); ClassDB::bind_method(D_METHOD("get_capture_propagation"), &BakedLightmap::get_capture_propagation); ClassDB::bind_method(D_METHOD("set_capture_enabled", "enabled"), &BakedLightmap::set_capture_enabled); ClassDB::bind_method(D_METHOD("get_capture_enabled"), &BakedLightmap::get_capture_enabled); ClassDB::bind_method(D_METHOD("set_capture_cell_size", "capture_cell_size"), &BakedLightmap::set_capture_cell_size); ClassDB::bind_method(D_METHOD("get_capture_cell_size"), &BakedLightmap::get_capture_cell_size); #ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_image_path", "image_path"), &BakedLightmap::set_image_path); ClassDB::bind_method(D_METHOD("get_image_path"), &BakedLightmap::get_image_path); #endif ClassDB::bind_method(D_METHOD("bake", "from_node", "data_save_path"), &BakedLightmap::bake, DEFVAL(Variant()), DEFVAL("")); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); ADD_GROUP("Tweaks", ""); 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::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); 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_GROUP("Atlas", "atlas_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "atlas_generate"), "set_generate_atlas", "is_generate_atlas_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "atlas_max_size"), "set_max_atlas_size", "get_max_atlas_size"); ADD_GROUP("Environment", "environment_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "environment_mode", PROPERTY_HINT_ENUM, "Disabled,Scene,Custom Sky,Custom Color"), "set_environment_mode", "get_environment_mode"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment_custom_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_environment_custom_sky", "get_environment_custom_sky"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "environment_custom_sky_rotation_degrees", PROPERTY_HINT_NONE), "set_environment_custom_sky_rotation_degrees", "get_environment_custom_sky_rotation_degrees"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "environment_custom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_custom_color", "get_environment_custom_color"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "environment_custom_energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_environment_custom_energy", "get_environment_custom_energy"); ADD_GROUP("Capture", "capture_"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_enabled"), "set_capture_enabled", "get_capture_enabled"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_cell_size", PROPERTY_HINT_RANGE, "0.25,2.0,0.05,or_greater"), "set_capture_cell_size", "get_capture_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "capture_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_capture_quality", "get_capture_quality"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_capture_propagation", "get_capture_propagation"); ADD_GROUP("Data", ""); #ifndef DISABLE_DEPRECATED ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_path", PROPERTY_HINT_DIR, "", 0), "set_image_path", "get_image_path"); #endif ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_data", PROPERTY_HINT_RESOURCE_TYPE, "BakedLightmapData"), "set_light_data", "get_light_data"); BIND_ENUM_CONSTANT(BAKE_QUALITY_LOW); BIND_ENUM_CONSTANT(BAKE_QUALITY_MEDIUM); BIND_ENUM_CONSTANT(BAKE_QUALITY_HIGH); BIND_ENUM_CONSTANT(BAKE_QUALITY_ULTRA); BIND_ENUM_CONSTANT(BAKE_ERROR_OK); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SAVE_PATH); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_MESHES); BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_SKY); BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_COLOR); } BakedLightmap::BakedLightmap() { extents = Vector3(10, 10, 10); default_texels_per_unit = 16.0f; bake_quality = BAKE_QUALITY_MEDIUM; capture_quality = BAKE_QUALITY_MEDIUM; capture_propagation = 1; capture_enabled = true; bounces = 3; image_path = ""; set_disable_scale(true); capture_cell_size = 0.5; environment_mode = ENVIRONMENT_MODE_DISABLED; environment_custom_color = Color(0.2, 0.7, 1.0); environment_custom_energy = 1.0; use_denoiser = true; bias = 0.005; generate_atlas = true; max_atlas_size = 4096; }