godot/scene/3d/baked_lightmap.cpp

1632 lines
56 KiB
C++

/*************************************************************************/
/* 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<uint8_t> &p_octree) {
VS::get_singleton()->lightmap_capture_set_octree(baked_light, p_octree);
}
PoolVector<uint8_t> 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::set_interior(bool p_interior) {
interior = p_interior;
VS::get_singleton()->lightmap_capture_set_interior(baked_light, interior);
}
bool BakedLightmapData::is_interior() const {
return interior;
}
void BakedLightmapData::add_user(const NodePath &p_path, const Ref<Resource> &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<Texture>(p_lightmap.ptr()));
ERR_FAIL_COND(p_lightmap_slice != -1 && !Object::cast_to<TextureLayered>(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<Resource> BakedLightmapData::get_user_lightmap(int p_user) const {
ERR_FAIL_INDEX_V(p_user, users.size(), Ref<Resource>());
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::clear_data() {
clear_users();
if (baked_light.is_valid()) {
VS::get_singleton()->free(baked_light);
}
baked_light = VS::get_singleton()->lightmap_capture_create();
}
void BakedLightmapData::_set_user_data(const Array &p_data) {
ERR_FAIL_COND(p_data.size() <= 0);
// 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_PRINT("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<Resource>(users[i].lightmap.single) : Ref<Resource>(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("set_interior", "interior"), &BakedLightmapData::set_interior);
ClassDB::bind_method(D_METHOD("is_interior"), &BakedLightmapData::is_interior);
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);
ClassDB::bind_method(D_METHOD("clear_data"), &BakedLightmapData::clear_data);
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::BOOL, "interior"), "set_interior", "is_interior");
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;
interior = false;
}
BakedLightmapData::~BakedLightmapData() {
VS::get_singleton()->free(baked_light);
}
///////////////////////////
Lightmapper::BakeStepFunc BakedLightmap::bake_step_function;
Lightmapper::BakeStepFunc BakedLightmap::bake_substep_function;
Lightmapper::BakeEndFunc BakedLightmap::bake_end_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<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
PoolVector<int> 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<Vector3>::Read vr = vertices.read();
PoolVector<Vector2>::Read u2r = uv2.read();
PoolVector<int>::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<MeshesFound> &meshes, Vector<LightsFound> &lights) {
AABB bounds = AABB(-extents, extents * 2.0);
MeshInstance *mi = Object::cast_to<MeshInstance>(p_at_node);
if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) {
Ref<Mesh> 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) {
Transform mesh_xform = get_global_transform().affine_inverse() * mi->get_global_transform();
AABB aabb = mesh_xform.xform(mesh->get_aabb());
if (bounds.intersects(aabb)) {
MeshesFound mf;
mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF;
mf.generate_lightmap = mi->get_generate_lightmap();
mf.xform = mesh_xform;
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<Material> 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<Spatial>(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();
Ref<Material> all_override;
GeometryInstance *gi = Object::cast_to<GeometryInstance>(p_at_node);
if (gi) {
all_override = mi->get_material_override();
}
for (int i = 0; i < bmeshes.size(); i += 2) {
Ref<Mesh> mesh = bmeshes[i];
if (!mesh.is_valid()) {
continue;
}
Transform mesh_xform = xf * bmeshes[i + 1];
AABB aabb = mesh_xform.xform(mesh->get_aabb());
if (!bounds.intersects(aabb)) {
continue;
}
MeshesFound mf;
mf.xform = mesh_xform;
mf.node_path = get_path_to(s);
mf.subindex = i / 2;
mf.lightmap_scale = 1;
mf.mesh = mesh;
if (gi) {
mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF;
mf.generate_lightmap = mi->get_generate_lightmap();
} else {
mf.cast_shadows = true;
mf.generate_lightmap = true;
}
for (int j = 0; j < mesh->get_surface_count(); j++) {
mf.overrides.push_back(all_override);
}
meshes.push_back(mf);
}
}
}
Light *light = Object::cast_to<Light>(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<Ref<Texture>> &r_albedo_textures, Vector<Ref<Texture>> &r_emission_textures) {
for (int i = 0; i < p_found_mesh.mesh->get_surface_count(); ++i) {
Ref<SpatialMaterial> mat = p_found_mesh.overrides[i];
if (mat.is_null()) {
mat = p_found_mesh.mesh->surface_get_material(i);
}
Ref<Texture> albedo_texture;
Color albedo_add = Color(1, 1, 1, 1);
Color albedo_mul = Color(1, 1, 1, 1);
Ref<Texture> 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();
albedo_add = Color(0, 0, 0, 0);
} 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);
}
}
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;
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);
c.r = MAX(c.r, environment_min_light.r);
c.g = MAX(c.g, environment_min_light.g);
c.b = MAX(c.b, environment_min_light.b);
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) {
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 + "'.");
}
}
uint32_t time_started = OS::get_singleton()->get_ticks_msec();
if (bake_step_function) {
bool cancelled = bake_step_function(0.0, TTR("Finding meshes and lights"), nullptr, true);
if (cancelled) {
bake_end_function(time_started);
return BAKE_ERROR_USER_ABORTED;
}
}
Ref<Lightmapper> lightmapper = Lightmapper::create();
if (lightmapper.is_null()) {
bake_end_function(time_started);
return BAKE_ERROR_NO_LIGHTMAPPER;
}
Vector<LightsFound> lights_found;
Vector<MeshesFound> meshes_found;
_find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), meshes_found, lights_found);
if (meshes_found.size() == 0) {
bake_end_function(time_started);
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) {
bake_end_function(time_started);
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<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
const Vector3 *vr = vertices.ptr();
Vector<Vector2> uv2 = a[Mesh::ARRAY_TEX_UV2];
const Vector2 *uv2r = nullptr;
Vector<Vector2> uv = a[Mesh::ARRAY_TEX_UV];
const Vector2 *uvr = nullptr;
Vector<Vector3> normals = a[Mesh::ARRAY_NORMAL];
const Vector3 *nr = nullptr;
Vector<int> 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<Ref<Texture>> albedo_textures;
Vector<Ref<Texture>> 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<DirectionalLight>(light)) {
DirectionalLight *l = Object::cast_to<DirectionalLight>(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), l->get_param(Light::PARAM_SIZE));
} else if (Object::cast_to<OmniLight>(light)) {
OmniLight *l = Object::cast_to<OmniLight>(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), l->get_param(Light::PARAM_SIZE));
} else if (Object::cast_to<SpotLight>(light)) {
SpotLight *l = Object::cast_to<SpotLight>(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), l->get_param(Light::PARAM_SIZE));
}
}
Ref<Image> 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> world = get_world();
if (world.is_valid()) {
Ref<Environment> 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, environment_custom_energy, Vector2i(128, 64));
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;
environment_image->lock();
for (int i = 0; i < 128; i++) {
for (int j = 0; j < 64; j++) {
environment_image->set_pixel(i, j, c);
}
}
environment_image->unlock();
} break;
}
}
BakeStepUD bsud;
bsud.func = bake_step_function;
bsud.ud = nullptr;
bsud.from_percent = 0.1;
bsud.to_percent = 0.9;
bool gen_atlas = OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2 ? false : generate_atlas;
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bounce_indirect_energy, bias, gen_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function);
if (bake_err != Lightmapper::BAKE_OK) {
bake_end_function(time_started);
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<BakedLightmapData> data;
if (get_light_data().is_valid()) {
data = get_light_data();
set_light_data(Ref<BakedLightmapData>()); //clear
data->clear_data();
} else {
data.instance();
}
if (capture_enabled) {
if (bake_step_function) {
bool cancelled = bake_step_function(0.85, TTR("Generating capture"), nullptr, true);
if (cancelled) {
bake_end_function(time_started);
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<Material>());
}
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) {
bake_end_function(time_started);
return BAKE_ERROR_USER_ABORTED;
}
}
Vector<Ref<Image>> images;
for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) {
images.push_back(lightmapper->get_bake_texture(i));
}
bool use_srgb = use_color && !use_hdr;
if (gen_atlas) {
Ref<Image> 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<TextureLayered> texture;
String base_path = p_data_save_path.get_basename();
if (ResourceLoader::import) {
_save_image(base_path, large_image, use_srgb);
Ref<ConfigFile> 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", use_srgb);
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<TextureLayered> tex;
bool set_path = true;
if (ResourceCache::has(base_path)) {
tex = Ref<Resource>((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> texture;
String base_path = p_data_save_path.get_base_dir().plus_file(images[i]->get_name());
if (ResourceLoader::import) {
_save_image(base_path, images[i], use_srgb);
Ref<ConfigFile> 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", use_srgb);
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<ImageTexture> tex;
bool set_path = true;
if (ResourceCache::has(base_path)) {
tex = Ref<Resource>((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) {
bake_end_function(time_started);
return BAKE_ERROR_USER_ABORTED;
}
}
Error err = ResourceSaver::save(p_data_save_path, data);
data->set_path(p_data_save_path);
if (err != OK) {
bake_end_function(time_started);
return BAKE_ERROR_CANT_CREATE_IMAGE;
}
set_light_data(data);
bake_end_function(time_started);
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("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());
bool atlassed_on_gles2 = false;
for (int i = 0; i < light_data->get_user_count(); i++) {
Ref<Resource> lightmap = light_data->get_user_lightmap(i);
ERR_CONTINUE(!lightmap.is_valid());
ERR_CONTINUE(!Object::cast_to<Texture>(lightmap.ptr()) && !Object::cast_to<TextureLayered>(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()) {
int slice = light_data->get_user_lightmap_slice(i);
atlassed_on_gles2 = atlassed_on_gles2 || (slice != -1 && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2);
VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), slice, light_data->get_user_lightmap_uv_rect(i));
}
} else {
VisualInstance *vi = Object::cast_to<VisualInstance>(node);
ERR_CONTINUE(!vi);
int slice = light_data->get_user_lightmap_slice(i);
atlassed_on_gles2 = atlassed_on_gles2 || (slice != -1 && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2);
VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), slice, light_data->get_user_lightmap_uv_rect(i));
}
}
if (atlassed_on_gles2) {
ERR_PRINT("GLES2 doesn't support layered textures, so lightmap atlassing is not supported. Please re-bake the lightmap or switch to GLES3.");
}
}
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<VisualInstance>(node);
ERR_CONTINUE(!vi);
VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID(), -1, Rect2(0, 0, 1, 1));
}
}
}
Ref<Image> BakedLightmap::_get_irradiance_from_sky(Ref<Sky> p_sky, float p_energy, Vector2i p_size) {
if (p_sky.is_null()) {
return Ref<Image>();
}
Ref<Image> sky_image;
Ref<PanoramaSky> panorama = p_sky;
if (panorama.is_valid()) {
sky_image = panorama->get_panorama()->get_data();
}
Ref<ProceduralSky> procedural = p_sky;
if (procedural.is_valid()) {
sky_image = procedural->get_data();
}
if (sky_image.is_null()) {
return Ref<Image>();
}
sky_image->convert(Image::FORMAT_RGBF);
sky_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_CUBIC);
if (p_energy != 1.0) {
sky_image->lock();
for (int i = 0; i < p_size.y; i++) {
for (int j = 0; j < p_size.x; j++) {
sky_image->set_pixel(j, i, sky_image->get_pixel(j, i) * p_energy);
}
}
sky_image->unlock();
}
return sky_image;
}
Ref<Image> BakedLightmap::_get_irradiance_map(Ref<Environment> 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(), p_env->get_bg_energy(), 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<Image> ret;
ret.instance();
ret->create(p_size.x, p_size.y, false, Image::FORMAT_RGBF);
ret->fill(c);
return ret;
}
default: {
}
}
return Ref<Image>();
}
void BakedLightmap::set_light_data(const Ref<BakedLightmapData> &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<BakedLightmapData> 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_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) {
environment_mode = p_mode;
_change_notify();
}
BakedLightmap::EnvironmentMode BakedLightmap::get_environment_mode() const {
return environment_mode;
}
void BakedLightmap::set_environment_custom_sky(const Ref<Sky> &p_sky) {
environment_custom_sky = p_sky;
}
Ref<Sky> 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_environment_min_light(Color p_min_light) {
environment_min_light = p_min_light;
}
Color BakedLightmap::get_environment_min_light() const {
return environment_min_light;
}
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_bounce_indirect_energy(float p_indirect_energy) {
ERR_FAIL_COND(p_indirect_energy < 0.0);
bounce_indirect_energy = p_indirect_energy;
}
float BakedLightmap::get_bounce_indirect_energy() const {
return bounce_indirect_energy;
}
void BakedLightmap::set_bias(float p_bias) {
ERR_FAIL_COND(p_bias < 0.00001f);
bias = p_bias;
}
float BakedLightmap::get_bias() const {
return bias;
}
AABB BakedLightmap::get_aabb() const {
return AABB(-extents, extents * 2);
}
PoolVector<Face3> BakedLightmap::get_faces(uint32_t p_usage_flags) const {
return PoolVector<Face3>();
}
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_bounce_indirect_energy", "bounce_indirect_energy"), &BakedLightmap::set_bounce_indirect_energy);
ClassDB::bind_method(D_METHOD("get_bounce_indirect_energy"), &BakedLightmap::get_bounce_indirect_energy);
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_environment_min_light", "min_light"), &BakedLightmap::set_environment_min_light);
ClassDB::bind_method(D_METHOD("get_environment_min_light"), &BakedLightmap::get_environment_min_light);
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_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("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::REAL, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,16,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
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, "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_PROPERTY(PropertyInfo(Variant::COLOR, "environment_min_light", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_min_light", "get_environment_min_light");
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_LIGHTMAP_SIZE);
BIND_ENUM_CONSTANT(BAKE_ERROR_INVALID_MESH);
BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED);
BIND_ENUM_CONSTANT(BAKE_ERROR_NO_LIGHTMAPPER);
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;
bounce_indirect_energy = 1.0;
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;
environment_min_light = Color(0.0, 0.0, 0.0);
use_denoiser = true;
use_hdr = true;
use_color = true;
bias = 0.005;
generate_atlas = true;
max_atlas_size = 4096;
}