690f9716fa
Instead of fitting all atlas slices into a single image, which meant there was a hard limit on the size, BakedLightmap will now save as many images as needed to fit all the slices generated by the lightmapper.
1627 lines
56 KiB
C++
1627 lines
56 KiB
C++
/*************************************************************************/
|
|
/* baked_lightmap.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2022 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 = RID_PRIME(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 = RID_PRIME(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 = gi->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 = gi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF;
|
|
mf.generate_lightmap = gi->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) {
|
|
if (!p_from_node && !get_parent()) {
|
|
return BAKE_ERROR_NO_ROOT;
|
|
}
|
|
|
|
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) {
|
|
int slice_count = images.size();
|
|
int slice_width = images[0]->get_width();
|
|
int slice_height = images[0]->get_height();
|
|
|
|
int slices_per_texture = Image::MAX_HEIGHT / slice_height;
|
|
int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
|
|
|
|
Vector<Ref<TextureLayered>> textures;
|
|
textures.resize(texture_count);
|
|
String base_path = p_data_save_path.get_basename();
|
|
|
|
int last_count = slice_count % slices_per_texture;
|
|
for (int i = 0; i < texture_count; i++) {
|
|
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) : base_path;
|
|
int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
|
|
|
|
Ref<Image> large_image;
|
|
large_image.instance();
|
|
large_image->create(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
|
|
|
|
for (int j = 0; j < texture_slice_count; j++) {
|
|
large_image->blit_rect(images[i * slices_per_texture + j], Rect2(0, 0, slice_width, slice_height), Point2(0, slice_height * j));
|
|
}
|
|
_save_image(texture_path, large_image, use_srgb);
|
|
|
|
Ref<ConfigFile> config;
|
|
config.instance();
|
|
if (FileAccess::exists(texture_path + ".import")) {
|
|
config->load(texture_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", texture_slice_count);
|
|
|
|
config->save(texture_path + ".import");
|
|
|
|
ResourceLoader::import(texture_path);
|
|
textures.write[i] = ResourceLoader::load(texture_path); //if already loaded, it will be updated on refocus?
|
|
}
|
|
|
|
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
|
|
if (!meshes_found[i].generate_lightmap) {
|
|
continue;
|
|
}
|
|
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, textures[slice_index / slices_per_texture], slice_index % slices_per_texture, 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(BAKE_ERROR_NO_ROOT);
|
|
|
|
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;
|
|
}
|