feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -33,13 +33,16 @@
|
|||
#include "core/config/project_settings.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/math/delaunay_3d.h"
|
||||
#include "lightmap_probe.h"
|
||||
#include "core/object/object.h"
|
||||
#include "scene/3d/lightmap_probe.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/resources/camera_attributes.h"
|
||||
#include "scene/resources/environment.h"
|
||||
#include "scene/resources/image_texture.h"
|
||||
#include "scene/resources/sky.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h" // For lightmapper_rd.
|
||||
|
||||
void LightmapGIData::add_user(const NodePath &p_path, const Rect2 &p_uv_scale, int p_slice_index, int32_t p_sub_instance) {
|
||||
User user;
|
||||
user.path = p_path;
|
||||
|
|
@ -98,15 +101,15 @@ Array LightmapGIData::_get_user_data() const {
|
|||
}
|
||||
|
||||
void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_data) {
|
||||
light_textures = p_data;
|
||||
storage_light_textures = p_data;
|
||||
if (p_data.is_empty()) {
|
||||
light_texture = Ref<TextureLayered>();
|
||||
combined_light_texture = Ref<TextureLayered>();
|
||||
_reset_lightmap_textures();
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_data.size() == 1) {
|
||||
light_texture = p_data[0];
|
||||
combined_light_texture = p_data[0];
|
||||
} else {
|
||||
Vector<Ref<Image>> images;
|
||||
for (int i = 0; i < p_data.size(); i++) {
|
||||
|
|
@ -121,13 +124,59 @@ void LightmapGIData::set_lightmap_textures(const TypedArray<TextureLayered> &p_d
|
|||
combined_texture.instantiate();
|
||||
|
||||
combined_texture->create_from_images(images);
|
||||
light_texture = combined_texture;
|
||||
combined_light_texture = combined_texture;
|
||||
}
|
||||
_reset_lightmap_textures();
|
||||
}
|
||||
|
||||
TypedArray<TextureLayered> LightmapGIData::get_lightmap_textures() const {
|
||||
return light_textures;
|
||||
return storage_light_textures;
|
||||
}
|
||||
|
||||
void LightmapGIData::set_shadowmask_textures(const TypedArray<TextureLayered> &p_data) {
|
||||
storage_shadowmask_textures = p_data;
|
||||
|
||||
if (p_data.is_empty()) {
|
||||
combined_shadowmask_texture = Ref<TextureLayered>();
|
||||
_reset_shadowmask_textures();
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_data.size() == 1) {
|
||||
combined_shadowmask_texture = p_data[0];
|
||||
|
||||
} else {
|
||||
Vector<Ref<Image>> images;
|
||||
for (int i = 0; i < p_data.size(); i++) {
|
||||
Ref<TextureLayered> texture = p_data[i];
|
||||
ERR_FAIL_COND_MSG(texture.is_null(), vformat("Invalid TextureLayered at index %d.", i));
|
||||
for (int j = 0; j < texture->get_layers(); j++) {
|
||||
images.push_back(texture->get_layer_data(j));
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Texture2DArray> combined_texture;
|
||||
combined_texture.instantiate();
|
||||
|
||||
combined_texture->create_from_images(images);
|
||||
combined_shadowmask_texture = combined_texture;
|
||||
}
|
||||
|
||||
_reset_shadowmask_textures();
|
||||
}
|
||||
|
||||
TypedArray<TextureLayered> LightmapGIData::get_shadowmask_textures() const {
|
||||
return storage_shadowmask_textures;
|
||||
}
|
||||
|
||||
void LightmapGIData::clear_shadowmask_textures() {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, RID());
|
||||
storage_shadowmask_textures.clear();
|
||||
combined_shadowmask_texture.unref();
|
||||
}
|
||||
|
||||
bool LightmapGIData::has_shadowmask_textures() {
|
||||
return !storage_shadowmask_textures.is_empty() && combined_shadowmask_texture.is_valid();
|
||||
}
|
||||
|
||||
RID LightmapGIData::get_rid() const {
|
||||
|
|
@ -139,7 +188,11 @@ void LightmapGIData::clear() {
|
|||
}
|
||||
|
||||
void LightmapGIData::_reset_lightmap_textures() {
|
||||
RS::get_singleton()->lightmap_set_textures(lightmap, light_texture.is_valid() ? light_texture->get_rid() : RID(), uses_spherical_harmonics);
|
||||
RS::get_singleton()->lightmap_set_textures(lightmap, combined_light_texture.is_valid() ? combined_light_texture->get_rid() : RID(), uses_spherical_harmonics);
|
||||
}
|
||||
|
||||
void LightmapGIData::_reset_shadowmask_textures() {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, combined_shadowmask_texture.is_valid() ? combined_shadowmask_texture->get_rid() : RID());
|
||||
}
|
||||
|
||||
void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) {
|
||||
|
|
@ -151,6 +204,22 @@ bool LightmapGIData::is_using_spherical_harmonics() const {
|
|||
return uses_spherical_harmonics;
|
||||
}
|
||||
|
||||
void LightmapGIData::_set_uses_packed_directional(bool p_enable) {
|
||||
_uses_packed_directional = p_enable;
|
||||
}
|
||||
|
||||
bool LightmapGIData::_is_using_packed_directional() const {
|
||||
return _uses_packed_directional;
|
||||
}
|
||||
|
||||
void LightmapGIData::update_shadowmask_mode(ShadowmaskMode p_mode) {
|
||||
RS::get_singleton()->lightmap_set_shadowmask_mode(lightmap, (RS::ShadowmaskMode)p_mode);
|
||||
}
|
||||
|
||||
LightmapGIData::ShadowmaskMode LightmapGIData::get_shadowmask_mode() const {
|
||||
return (ShadowmaskMode)RS::get_singleton()->lightmap_get_shadowmask_mode(lightmap);
|
||||
}
|
||||
|
||||
void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
|
||||
if (p_points.size()) {
|
||||
int pc = p_points.size();
|
||||
|
|
@ -230,10 +299,10 @@ void LightmapGIData::set_light_texture(const Ref<TextureLayered> &p_light_textur
|
|||
}
|
||||
|
||||
Ref<TextureLayered> LightmapGIData::get_light_texture() const {
|
||||
if (light_textures.is_empty()) {
|
||||
if (storage_light_textures.is_empty()) {
|
||||
return Ref<TextureLayered>();
|
||||
}
|
||||
return light_textures.get(0);
|
||||
return storage_light_textures.get(0);
|
||||
}
|
||||
|
||||
void LightmapGIData::_set_light_textures_data(const Array &p_data) {
|
||||
|
|
@ -241,7 +310,7 @@ void LightmapGIData::_set_light_textures_data(const Array &p_data) {
|
|||
}
|
||||
|
||||
Array LightmapGIData::_get_light_textures_data() const {
|
||||
return Array(light_textures);
|
||||
return Array(storage_light_textures);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -252,9 +321,15 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_lightmap_textures", "light_textures"), &LightmapGIData::set_lightmap_textures);
|
||||
ClassDB::bind_method(D_METHOD("get_lightmap_textures"), &LightmapGIData::get_lightmap_textures);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shadowmask_textures", "shadowmask_textures"), &LightmapGIData::set_shadowmask_textures);
|
||||
ClassDB::bind_method(D_METHOD("get_shadowmask_textures"), &LightmapGIData::get_shadowmask_textures);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
|
||||
ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional);
|
||||
ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user);
|
||||
ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count);
|
||||
ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path);
|
||||
|
|
@ -263,10 +338,12 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("_set_probe_data", "data"), &LightmapGIData::_set_probe_data);
|
||||
ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_NO_EDITOR), "set_lightmap_textures", "get_lightmap_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_lightmap_textures", "get_lightmap_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "shadowmask_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_shadowmask_textures", "get_shadowmask_textures");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional");
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture);
|
||||
|
|
@ -275,9 +352,13 @@ void LightmapGIData::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("_set_light_textures_data", "data"), &LightmapGIData::_set_light_textures_data);
|
||||
ClassDB::bind_method(D_METHOD("_get_light_textures_data"), &LightmapGIData::_get_light_textures_data);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_EDITOR), "set_light_texture", "get_light_texture");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_NONE), "set_light_texture", "get_light_texture");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
|
||||
#endif
|
||||
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_NONE);
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_REPLACE);
|
||||
BIND_ENUM_CONSTANT(SHADOWMASK_MODE_OVERLAY);
|
||||
}
|
||||
|
||||
LightmapGIData::LightmapGIData() {
|
||||
|
|
@ -321,9 +402,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
mf.node_path = get_path_to(mi);
|
||||
mf.subindex = -1;
|
||||
mf.mesh = mesh;
|
||||
|
||||
static const int lightmap_scale[GeometryInstance3D::LIGHTMAP_SCALE_MAX] = { 1, 2, 4, 8 };
|
||||
mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()];
|
||||
mf.lightmap_scale = mi->get_lightmap_texel_scale();
|
||||
|
||||
Ref<Material> all_override = mi->get_material_override();
|
||||
for (int i = 0; i < mesh->get_surface_count(); i++) {
|
||||
|
|
@ -347,7 +426,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
Transform3D xf = get_global_transform().affine_inverse() * s->get_global_transform();
|
||||
for (int i = 0; i < bmeshes.size(); i += 2) {
|
||||
Ref<Mesh> mesh = bmeshes[i];
|
||||
if (!mesh.is_valid()) {
|
||||
if (mesh.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +436,7 @@ void LightmapGI::_find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &m
|
|||
mf.xform = xf * mesh_xf;
|
||||
mf.node_path = get_path_to(s);
|
||||
mf.subindex = i / 2;
|
||||
mf.lightmap_scale = 1;
|
||||
mf.lightmap_scale = 1.0;
|
||||
mf.mesh = mesh;
|
||||
|
||||
meshes.push_back(mf);
|
||||
|
|
@ -709,7 +788,7 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
|
|||
const Vector3 *pp = probe_positions.ptr();
|
||||
bool exists = false;
|
||||
for (int j = 0; j < ppcount; j++) {
|
||||
if (pp[j].is_equal_approx(real_pos)) {
|
||||
if (pp[j].distance_to(real_pos) < (p_cell_size * 0.5f)) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -728,6 +807,83 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
|
|||
}
|
||||
}
|
||||
|
||||
LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_is_shadowmask) const {
|
||||
Vector<Ref<Image>> images;
|
||||
images.resize(p_is_shadowmask ? p_lightmapper->get_shadowmask_texture_count() : p_lightmapper->get_bake_texture_count());
|
||||
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
images.set(i, p_is_shadowmask ? p_lightmapper->get_shadowmask_texture(i) : p_lightmapper->get_bake_texture(i));
|
||||
}
|
||||
|
||||
const int slice_count = images.size();
|
||||
const int slice_width = images[0]->get_width();
|
||||
const int slice_height = images[0]->get_height();
|
||||
|
||||
const int slices_per_texture = Image::MAX_HEIGHT / slice_height;
|
||||
const int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
|
||||
const int last_count = slice_count % slices_per_texture;
|
||||
|
||||
r_textures.resize(texture_count);
|
||||
|
||||
for (int i = 0; i < texture_count; i++) {
|
||||
const int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
|
||||
|
||||
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
|
||||
|
||||
for (int j = 0; j < texture_slice_count; j++) {
|
||||
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
|
||||
}
|
||||
|
||||
const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
|
||||
const String config_path = atlas_path + ".import";
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
// Load an import configuration if present.
|
||||
if (FileAccess::exists(config_path)) {
|
||||
config->load(config_path);
|
||||
}
|
||||
|
||||
config->set_value("remap", "importer", "2d_array_texture");
|
||||
config->set_value("remap", "type", "CompressedTexture2DArray");
|
||||
if (!config->has_section_key("params", "compress/mode")) {
|
||||
// Do not override an existing compression mode.
|
||||
config->set_value("params", "compress/mode", 2);
|
||||
}
|
||||
config->set_value("params", "compress/channel_pack", 1);
|
||||
config->set_value("params", "mipmaps/generate", false);
|
||||
config->set_value("params", "slices/horizontal", 1);
|
||||
config->set_value("params", "slices/vertical", texture_slice_count);
|
||||
|
||||
config->save(config_path);
|
||||
|
||||
if (supersampling_enabled) {
|
||||
texture_image->resize(texture_image->get_width() / supersampling_factor, texture_image->get_height() / supersampling_factor, Image::INTERPOLATE_TRILINEAR);
|
||||
}
|
||||
|
||||
// Save the file.
|
||||
Error save_err;
|
||||
if (p_is_shadowmask) {
|
||||
save_err = texture_image->save_png(atlas_path);
|
||||
} else {
|
||||
save_err = texture_image->save_exr(atlas_path, false);
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
|
||||
// Reimport the file.
|
||||
ResourceLoader::import(atlas_path);
|
||||
Ref<TextureLayered> t = ResourceLoader::load(atlas_path); // If already loaded, it will be updated on refocus?
|
||||
ERR_FAIL_COND_V(t.is_null(), LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
|
||||
// Store the atlas in the array.
|
||||
r_textures[i] = t;
|
||||
}
|
||||
|
||||
return LightmapGI::BAKE_ERROR_OK;
|
||||
}
|
||||
|
||||
LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_path, Lightmapper::BakeStepFunc p_bake_step, void *p_bake_userdata) {
|
||||
if (p_image_data_path.is_empty()) {
|
||||
if (get_light_data().is_null()) {
|
||||
|
|
@ -781,7 +937,8 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
// For now set to basic size to avoid crash.
|
||||
mesh_lightmap_size = Size2i(64, 64);
|
||||
}
|
||||
Size2i lightmap_size = Size2i(Size2(mesh_lightmap_size) * mf.lightmap_scale * texel_scale);
|
||||
// Double lightmap texel density if downsampling is enabled, as the final texture size will be halved before saving lightmaps.
|
||||
Size2i lightmap_size = Size2i(Size2(mesh_lightmap_size) * mf.lightmap_scale * texel_scale) * (supersampling_enabled ? supersampling_factor : 1.0);
|
||||
ERR_FAIL_COND_V(lightmap_size.x == 0 || lightmap_size.y == 0, BAKE_ERROR_LIGHTMAP_TOO_SMALL);
|
||||
|
||||
TypedArray<RID> overrides;
|
||||
|
|
@ -832,7 +989,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
w_albedo[i + 0] = uint8_t(CLAMP(float(r_aa[i + 0]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 1] = uint8_t(CLAMP(float(r_aa[i + 1]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 2] = uint8_t(CLAMP(float(r_aa[i + 2]) * (1.0 - float(r_orm[i + 2] / 255.0)), 0, 255));
|
||||
w_albedo[i + 3] = 255;
|
||||
w_albedo[i + 3] = r_aa[i + 3];
|
||||
}
|
||||
|
||||
md.albedo_on_uv2.instantiate();
|
||||
|
|
@ -853,6 +1010,11 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
continue;
|
||||
}
|
||||
Array a = mf.mesh->surface_get_arrays(i);
|
||||
Ref<Material> mat = mf.mesh->surface_get_material(i);
|
||||
RID mat_rid;
|
||||
if (mat.is_valid()) {
|
||||
mat_rid = mat->get_rid();
|
||||
}
|
||||
|
||||
Vector<Vector3> vertices = a[Mesh::ARRAY_VERTEX];
|
||||
const Vector3 *vr = vertices.ptr();
|
||||
|
|
@ -902,6 +1064,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
|
||||
md.uv2.push_back(uvr[vidx[k]]);
|
||||
md.normal.push_back(normal_xform.xform(nr[vidx[k]]).normalized());
|
||||
md.material.push_back(mat_rid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1026,20 +1189,20 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
if (Object::cast_to<DirectionalLight3D>(light)) {
|
||||
DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light);
|
||||
if (l->get_sky_mode() != DirectionalLight3D::SKY_MODE_SKY_ONLY) {
|
||||
lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_directional_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
}
|
||||
} else if (Object::cast_to<OmniLight3D>(light)) {
|
||||
OmniLight3D *l = Object::cast_to<OmniLight3D>(light);
|
||||
if (use_physical_light_units) {
|
||||
energy *= (1.0 / (Math_PI * 4.0));
|
||||
}
|
||||
lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_omni_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
} else if (Object::cast_to<SpotLight3D>(light)) {
|
||||
SpotLight3D *l = Object::cast_to<SpotLight3D>(light);
|
||||
if (use_physical_light_units) {
|
||||
energy *= (1.0 / Math_PI);
|
||||
}
|
||||
lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
lightmapper->add_spot_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < probes_found.size(); i++) {
|
||||
|
|
@ -1072,6 +1235,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
|
||||
if (env.is_valid()) {
|
||||
environment_image = RS::get_singleton()->environment_bake_panorama(env->get_rid(), true, Size2i(128, 64));
|
||||
environment_transform = Basis::from_euler(env->get_sky_rotation()).inverse();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
|
@ -1102,7 +1266,9 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
}
|
||||
|
||||
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
|
||||
Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, denoiser_strength, denoiser_range, bounces,
|
||||
bounce_indirect_energy, bias, max_texture_size, directional, shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE, use_texture_for_bounces,
|
||||
Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization, (supersampling_enabled ? supersampling_factor : 1));
|
||||
|
||||
if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
|
||||
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
|
||||
|
|
@ -1110,83 +1276,50 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
return BAKE_ERROR_MESHES_INVALID;
|
||||
} else if (bake_err == Lightmapper::BAKE_ERROR_ATLAS_TOO_SMALL) {
|
||||
return BAKE_ERROR_ATLAS_TOO_SMALL;
|
||||
} else if (bake_err == Lightmapper::BAKE_ERROR_USER_ABORTED) {
|
||||
return BAKE_ERROR_USER_ABORTED;
|
||||
}
|
||||
|
||||
// POSTBAKE: Save Textures.
|
||||
TypedArray<TextureLayered> lightmap_textures;
|
||||
TypedArray<TextureLayered> shadowmask_textures;
|
||||
|
||||
TypedArray<TextureLayered> textures;
|
||||
{
|
||||
Vector<Ref<Image>> images;
|
||||
images.resize(lightmapper->get_bake_texture_count());
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
images.set(i, lightmapper->get_bake_texture(i));
|
||||
}
|
||||
const String texture_filename = p_image_data_path.get_basename();
|
||||
const int shadowmask_texture_count = lightmapper->get_shadowmask_texture_count();
|
||||
const bool save_shadowmask = shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && shadowmask_texture_count > 0;
|
||||
|
||||
int slice_count = images.size();
|
||||
int slice_width = images[0]->get_width();
|
||||
int slice_height = images[0]->get_height();
|
||||
// Save the lightmap atlases.
|
||||
BakeError save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename, lightmap_textures, false);
|
||||
ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
|
||||
|
||||
int slices_per_texture = Image::MAX_HEIGHT / slice_height;
|
||||
int texture_count = Math::ceil(slice_count / (float)slices_per_texture);
|
||||
|
||||
textures.resize(texture_count);
|
||||
|
||||
String base_path = p_image_data_path.get_basename();
|
||||
|
||||
int last_count = slice_count % slices_per_texture;
|
||||
for (int i = 0; i < texture_count; i++) {
|
||||
int texture_slice_count = (i == texture_count - 1 && last_count != 0) ? last_count : slices_per_texture;
|
||||
|
||||
Ref<Image> texture_image = Image::create_empty(slice_width, slice_height * texture_slice_count, false, images[0]->get_format());
|
||||
|
||||
for (int j = 0; j < texture_slice_count; j++) {
|
||||
texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
|
||||
}
|
||||
|
||||
String texture_path = texture_count > 1 ? base_path + "_" + itos(i) + ".exr" : base_path + ".exr";
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
if (FileAccess::exists(texture_path + ".import")) {
|
||||
config->load(texture_path + ".import");
|
||||
}
|
||||
|
||||
config->set_value("remap", "importer", "2d_array_texture");
|
||||
config->set_value("remap", "type", "CompressedTexture2DArray");
|
||||
if (!config->has_section_key("params", "compress/mode")) {
|
||||
// User may want another compression, so leave it be, but default to VRAM uncompressed.
|
||||
config->set_value("params", "compress/mode", 3);
|
||||
}
|
||||
config->set_value("params", "compress/channel_pack", 1);
|
||||
config->set_value("params", "mipmaps/generate", false);
|
||||
config->set_value("params", "slices/horizontal", 1);
|
||||
config->set_value("params", "slices/vertical", texture_slice_count);
|
||||
|
||||
config->save(texture_path + ".import");
|
||||
|
||||
Error err = texture_image->save_exr(texture_path, false);
|
||||
ERR_FAIL_COND_V(err, BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
ResourceLoader::import(texture_path);
|
||||
Ref<TextureLayered> t = ResourceLoader::load(texture_path); // If already loaded, it will be updated on refocus?
|
||||
ERR_FAIL_COND_V(t.is_null(), BAKE_ERROR_CANT_CREATE_IMAGE);
|
||||
textures[i] = t;
|
||||
}
|
||||
if (save_shadowmask) {
|
||||
// Save the shadowmask atlases.
|
||||
save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename + "_shadow", shadowmask_textures, true);
|
||||
ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
|
||||
}
|
||||
|
||||
/* POSTBAKE: Save Light Data */
|
||||
|
||||
// POSTBAKE: Save Light Data.
|
||||
Ref<LightmapGIData> gi_data;
|
||||
|
||||
if (get_light_data().is_valid()) {
|
||||
gi_data = get_light_data();
|
||||
set_light_data(Ref<LightmapGIData>()); //clear
|
||||
set_light_data(Ref<LightmapGIData>()); // Clear.
|
||||
gi_data->clear();
|
||||
|
||||
} else {
|
||||
gi_data.instantiate();
|
||||
}
|
||||
|
||||
gi_data->set_lightmap_textures(textures);
|
||||
gi_data->set_lightmap_textures(lightmap_textures);
|
||||
|
||||
if (save_shadowmask) {
|
||||
gi_data->set_shadowmask_textures(shadowmask_textures);
|
||||
} else {
|
||||
gi_data->clear_shadowmask_textures();
|
||||
}
|
||||
|
||||
gi_data->set_uses_spherical_harmonics(directional);
|
||||
gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
|
||||
|
||||
for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) {
|
||||
Dictionary d = lightmapper->get_bake_mesh_userdata(i);
|
||||
|
|
@ -1202,7 +1335,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
|
||||
{
|
||||
// create tetrahedrons
|
||||
// Create tetrahedrons.
|
||||
Vector<Vector3> points;
|
||||
Vector<Color> sh;
|
||||
points.resize(lightmapper->get_bake_probe_count());
|
||||
|
|
@ -1216,11 +1349,11 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
}
|
||||
|
||||
//Obtain solved simplices
|
||||
|
||||
// Obtain solved simplices.
|
||||
if (p_bake_step) {
|
||||
p_bake_step(0.8, RTR("Generating Probe Volumes"), p_bake_userdata, true);
|
||||
}
|
||||
|
||||
Vector<Delaunay3D::OutputSimplex> solved_simplices = Delaunay3D::tetrahedralize(points);
|
||||
|
||||
LocalVector<BSPSimplex> bsp_simplices;
|
||||
|
|
@ -1343,6 +1476,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
|
|||
}
|
||||
|
||||
set_light_data(gi_data);
|
||||
update_configuration_warnings();
|
||||
|
||||
return BAKE_ERROR_OK;
|
||||
}
|
||||
|
|
@ -1351,11 +1485,23 @@ void LightmapGI::_notification(int p_what) {
|
|||
switch (p_what) {
|
||||
case NOTIFICATION_POST_ENTER_TREE: {
|
||||
if (light_data.is_valid()) {
|
||||
ERR_FAIL_COND_MSG(
|
||||
light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(),
|
||||
vformat(
|
||||
"%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.",
|
||||
get_light_data()->get_path(), get_name()));
|
||||
|
||||
if (last_owner && last_owner != get_owner()) {
|
||||
light_data->clear_users();
|
||||
}
|
||||
|
||||
_assign_lightmaps();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
last_owner = get_owner();
|
||||
|
||||
if (light_data.is_valid()) {
|
||||
_clear_lightmaps();
|
||||
}
|
||||
|
|
@ -1364,7 +1510,7 @@ void LightmapGI::_notification(int p_what) {
|
|||
}
|
||||
|
||||
void LightmapGI::_assign_lightmaps() {
|
||||
ERR_FAIL_COND(!light_data.is_valid());
|
||||
ERR_FAIL_COND(light_data.is_null());
|
||||
|
||||
for (int i = 0; i < light_data->get_user_count(); i++) {
|
||||
Node *node = get_node(light_data->get_user_path(i));
|
||||
|
|
@ -1383,7 +1529,7 @@ void LightmapGI::_assign_lightmaps() {
|
|||
}
|
||||
|
||||
void LightmapGI::_clear_lightmaps() {
|
||||
ERR_FAIL_COND(!light_data.is_valid());
|
||||
ERR_FAIL_COND(light_data.is_null());
|
||||
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_sub_instance(i);
|
||||
|
|
@ -1414,6 +1560,7 @@ void LightmapGI::set_light_data(const Ref<LightmapGIData> &p_data) {
|
|||
if (is_inside_tree()) {
|
||||
_assign_lightmaps();
|
||||
}
|
||||
light_data->update_shadowmask_mode(shadowmask_mode);
|
||||
}
|
||||
|
||||
update_gizmos();
|
||||
|
|
@ -1468,6 +1615,19 @@ bool LightmapGI::is_directional() const {
|
|||
return directional;
|
||||
}
|
||||
|
||||
void LightmapGI::set_shadowmask_mode(LightmapGIData::ShadowmaskMode p_mode) {
|
||||
shadowmask_mode = p_mode;
|
||||
if (light_data.is_valid()) {
|
||||
light_data->update_shadowmask_mode(p_mode);
|
||||
}
|
||||
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
LightmapGIData::ShadowmaskMode LightmapGI::get_shadowmask_mode() const {
|
||||
return shadowmask_mode;
|
||||
}
|
||||
|
||||
void LightmapGI::set_use_texture_for_bounces(bool p_enable) {
|
||||
use_texture_for_bounces = p_enable;
|
||||
}
|
||||
|
|
@ -1563,6 +1723,26 @@ int LightmapGI::get_max_texture_size() const {
|
|||
return max_texture_size;
|
||||
}
|
||||
|
||||
void LightmapGI::set_supersampling_enabled(bool p_enable) {
|
||||
supersampling_enabled = p_enable;
|
||||
|
||||
notify_property_list_changed();
|
||||
}
|
||||
|
||||
bool LightmapGI::is_supersampling_enabled() const {
|
||||
return supersampling_enabled;
|
||||
}
|
||||
|
||||
void LightmapGI::set_supersampling_factor(float p_factor) {
|
||||
ERR_FAIL_COND(p_factor < 1);
|
||||
|
||||
supersampling_factor = p_factor;
|
||||
}
|
||||
|
||||
float LightmapGI::get_supersampling_factor() const {
|
||||
return supersampling_factor;
|
||||
}
|
||||
|
||||
void LightmapGI::set_generate_probes(GenerateProbes p_generate_probes) {
|
||||
gen_probes = p_generate_probes;
|
||||
}
|
||||
|
|
@ -1580,31 +1760,45 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const {
|
|||
}
|
||||
|
||||
PackedStringArray LightmapGI::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node::get_configuration_warnings();
|
||||
PackedStringArray warnings = VisualInstance3D::get_configuration_warnings();
|
||||
|
||||
if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") {
|
||||
warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail."));
|
||||
#ifdef MODULE_LIGHTMAPPER_RD_ENABLED
|
||||
if (!DisplayServer::get_singleton()->can_create_rendering_device()) {
|
||||
warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
|
||||
return warnings;
|
||||
}
|
||||
|
||||
if (shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && light_data.is_valid() && !light_data->has_shadowmask_textures()) {
|
||||
warnings.push_back(RTR("The lightmap has no baked shadowmask textures. Please rebake with the Shadowmask Mode set to anything other than None."));
|
||||
}
|
||||
|
||||
#elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
|
||||
warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
|
||||
#else
|
||||
warnings.push_back(RTR("Lightmaps cannot be baked, as the `lightmapper_rd` module was disabled at compile-time. Rendering existing baked lightmaps will still work."));
|
||||
#endif
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void LightmapGI::_validate_property(PropertyInfo &p_property) const {
|
||||
if (p_property.name == "supersampling_factor" && !supersampling_enabled) {
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_sky" && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_color" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "denoiser_strength" && !use_denoiser) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
if (p_property.name == "denoiser_range" && !use_denoiser) {
|
||||
p_property.usage = PROPERTY_USAGE_NONE;
|
||||
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1645,6 +1839,12 @@ void LightmapGI::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_max_texture_size", "max_texture_size"), &LightmapGI::set_max_texture_size);
|
||||
ClassDB::bind_method(D_METHOD("get_max_texture_size"), &LightmapGI::get_max_texture_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_supersampling_enabled", "enable"), &LightmapGI::set_supersampling_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_supersampling_enabled"), &LightmapGI::is_supersampling_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_supersampling_factor", "factor"), &LightmapGI::set_supersampling_factor);
|
||||
ClassDB::bind_method(D_METHOD("get_supersampling_factor"), &LightmapGI::get_supersampling_factor);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &LightmapGI::set_use_denoiser);
|
||||
ClassDB::bind_method(D_METHOD("is_using_denoiser"), &LightmapGI::is_using_denoiser);
|
||||
|
||||
|
|
@ -1660,6 +1860,9 @@ void LightmapGI::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_directional", "directional"), &LightmapGI::set_directional);
|
||||
ClassDB::bind_method(D_METHOD("is_directional"), &LightmapGI::is_directional);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_shadowmask_mode", "mode"), &LightmapGI::set_shadowmask_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_shadowmask_mode"), &LightmapGI::get_shadowmask_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_texture_for_bounces", "use_texture_for_bounces"), &LightmapGI::set_use_texture_for_bounces);
|
||||
ClassDB::bind_method(D_METHOD("is_using_texture_for_bounces"), &LightmapGI::is_using_texture_for_bounces);
|
||||
|
||||
|
|
@ -1670,9 +1873,12 @@ void LightmapGI::_bind_methods() {
|
|||
|
||||
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::BOOL, "supersampling"), "set_supersampling_enabled", "is_supersampling_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "supersampling_factor", PROPERTY_HINT_RANGE, "1,8,1"), "set_supersampling_factor", "get_supersampling_factor");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,6,1,or_greater"), "set_bounces", "get_bounces");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadowmask_mode", PROPERTY_HINT_ENUM, "None,Replace,Overlay"), "set_shadowmask_mode", "get_shadowmask_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_for_bounces"), "set_use_texture_for_bounces", "is_using_texture_for_bounces");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue