From 112b4160563811fad1e849e6349f6cbb408fd38a Mon Sep 17 00:00:00 2001 From: JFonS Date: Sat, 19 Dec 2020 18:17:22 +0100 Subject: [PATCH] Implement new CPU lightmapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completely re-write the lightmap generation code: - Follow the general lightmapper code structure from 4.0. - Use proper path tracing to compute the global illumination. - Use atlassing to merge all lightmaps into a single texture (done by @RandomShaper) - Use OpenImageDenoiser to improve the generated lightmaps. - Take into account alpha transparency in material textures. - Allow baking environment lighting. - Add bicubic lightmap filtering. There is some minor compatibility breakage in some properties and methods in BakedLightmap, but lightmaps generated in previous engine versions should work fine out of the box. The scene importer has been changed to generate `.unwrap_cache` files next to the imported scene files. These files *SHOULD* be added to any version control system as they guarantee there won't be differences when re-importing the scene from other OSes or engine versions. This work started as a Google Summer of Code project; Was later funded by IMVU for a good amount of progress; Was then finished and polished by me on my free time. Co-authored-by: Pedro J. Estébanez --- core/math/geometry.cpp | 35 + core/math/geometry.h | 28 + drivers/gles2/rasterizer_scene_gles2.cpp | 5 + drivers/gles2/rasterizer_storage_gles2.cpp | 1 + drivers/gles2/rasterizer_storage_gles2.h | 1 + drivers/gles2/shaders/scene.glsl | 70 +- drivers/gles3/rasterizer_scene_gles3.cpp | 20 +- drivers/gles3/rasterizer_scene_gles3.h | 9 +- drivers/gles3/rasterizer_storage_gles3.cpp | 2 + drivers/gles3/rasterizer_storage_gles3.h | 1 + drivers/gles3/shaders/scene.glsl | 115 +- .../resource_importer_layered_texture.cpp | 9 +- editor/import/resource_importer_scene.cpp | 133 +- .../plugins/baked_lightmap_editor_plugin.cpp | 86 +- editor/plugins/baked_lightmap_editor_plugin.h | 8 +- editor/progress_dialog.cpp | 8 +- editor/progress_dialog.h | 2 +- modules/denoise/lightmap_denoiser.h | 2 +- modules/lightmapper_cpu/SCsub | 8 + modules/lightmapper_cpu/config.py | 6 + modules/lightmapper_cpu/lightmapper_cpu.cpp | 1621 +++++++++++++++++ modules/lightmapper_cpu/lightmapper_cpu.h | 182 ++ modules/lightmapper_cpu/register_types.cpp | 55 + modules/lightmapper_cpu/register_types.h | 37 + modules/xatlas_unwrap/register_types.cpp | 14 +- scene/3d/baked_lightmap.cpp | 1614 +++++++++++----- scene/3d/baked_lightmap.h | 178 +- scene/3d/lightmapper.cpp | 76 + scene/3d/lightmapper.h | 196 ++ scene/3d/visual_instance.cpp | 35 + scene/3d/visual_instance.h | 20 + scene/3d/voxel_light_baker.cpp | 872 +-------- scene/3d/voxel_light_baker.h | 21 +- scene/resources/mesh.cpp | 261 ++- scene/resources/mesh.h | 1 + scene/resources/sky.cpp | 15 +- scene/resources/sky.h | 3 + scene/resources/texture.cpp | 285 +-- scene/resources/texture.h | 18 +- servers/visual/rasterizer.h | 4 + servers/visual/visual_server_raster.h | 2 +- servers/visual/visual_server_scene.cpp | 10 +- servers/visual/visual_server_scene.h | 2 +- servers/visual/visual_server_wrap_mt.h | 2 +- servers/visual_server.cpp | 5 +- servers/visual_server.h | 2 +- thirdparty/README.md | 4 + thirdparty/stb_rect_pack/stb_rect_pack.h | 629 +++++++ 48 files changed, 4981 insertions(+), 1732 deletions(-) create mode 100644 modules/lightmapper_cpu/SCsub create mode 100644 modules/lightmapper_cpu/config.py create mode 100644 modules/lightmapper_cpu/lightmapper_cpu.cpp create mode 100644 modules/lightmapper_cpu/lightmapper_cpu.h create mode 100644 modules/lightmapper_cpu/register_types.cpp create mode 100644 modules/lightmapper_cpu/register_types.h create mode 100644 scene/3d/lightmapper.cpp create mode 100644 scene/3d/lightmapper.h create mode 100644 thirdparty/stb_rect_pack/stb_rect_pack.h diff --git a/core/math/geometry.cpp b/core/math/geometry.cpp index 2425249dfce..83f7f67dd88 100644 --- a/core/math/geometry.cpp +++ b/core/math/geometry.cpp @@ -33,6 +33,8 @@ #include "core/print_string.h" #include "thirdparty/misc/clipper.hpp" #include "thirdparty/misc/triangulator.h" +#define STB_RECT_PACK_IMPLEMENTATION +#include "thirdparty/stb_rect_pack/stb_rect_pack.h" #define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON. @@ -1224,3 +1226,36 @@ Vector Geometry::compute_convex_mesh_points(const Plane *p_planes, int return points; } + +Vector Geometry::partial_pack_rects(const Vector &p_sizes, const Size2i &p_atlas_size) { + + Vector nodes; + nodes.resize(p_atlas_size.width); + zeromem(nodes.ptrw(), sizeof(stbrp_node) * nodes.size()); + + stbrp_context context; + stbrp_init_target(&context, p_atlas_size.width, p_atlas_size.height, nodes.ptrw(), p_atlas_size.width); + + Vector rects; + rects.resize(p_sizes.size()); + + for (int i = 0; i < p_sizes.size(); i++) { + rects.write[i].id = i; + rects.write[i].w = p_sizes[i].width; + rects.write[i].h = p_sizes[i].height; + rects.write[i].x = 0; + rects.write[i].y = 0; + rects.write[i].was_packed = 0; + } + + stbrp_pack_rects(&context, rects.ptrw(), rects.size()); + + Vector ret; + ret.resize(p_sizes.size()); + + for (int i = 0; i < p_sizes.size(); i++) { + ret.write[rects[i].id] = { rects[i].x, rects[i].y, static_cast(rects[i].was_packed) }; + } + + return ret; +} diff --git a/core/math/geometry.h b/core/math/geometry.h index 3ab108e8717..23f4f4fce08 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -502,6 +502,27 @@ public: return (cn.cross(an) > 0) == orientation; } + static Vector3 barycentric_coordinates_2d(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) { + // http://www.blackpawn.com/texts/pointinpoly/ + Vector2 v0 = c - a; + Vector2 v1 = b - a; + Vector2 v2 = s - a; + + // Compute dot products + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + + // Compute barycentric coordinates + double invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01); + double b2 = (dot11 * dot02 - dot01 * dot12) * invDenom; + double b1 = (dot00 * dot12 - dot01 * dot02) * invDenom; + double b0 = 1.0f - b2 - b1; + return Vector3(b0, b1, b2); + } + static Vector2 get_closest_point_to_segment_uncapped_2d(const Vector2 &p_point, const Vector2 *p_segment) { Vector2 p = p_point - p_segment[0]; @@ -1014,6 +1035,13 @@ public: static void make_atlas(const Vector &p_rects, Vector &r_result, Size2i &r_size); + struct PackRectsResult { + int x; + int y; + bool packed; + }; + static Vector partial_pack_rects(const Vector &p_sizes, const Size2i &p_atlas_size); + static Vector compute_convex_mesh_points(const Plane *p_planes, int p_plane_count); private: diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp index ed936fefbc9..d0e4c5a51c6 100644 --- a/drivers/gles2/rasterizer_scene_gles2.cpp +++ b/drivers/gles2/rasterizer_scene_gles2.cpp @@ -2572,6 +2572,9 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements, if (rebind_lightmap && lightmap) { state.scene_shader.set_uniform(SceneShaderGLES2::LIGHTMAP_ENERGY, lightmap_energy); + if (storage->config.use_lightmap_filter_bicubic) { + state.scene_shader.set_uniform(SceneShaderGLES2::LIGHTMAP_TEXTURE_SIZE, Vector2(lightmap->width, lightmap->height)); + } } state.scene_shader.set_uniform(SceneShaderGLES2::WORLD_TRANSFORM, e->instance->transform); @@ -4053,6 +4056,8 @@ void RasterizerSceneGLES2::initialize() { } void RasterizerSceneGLES2::iteration() { + storage->config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + state.scene_shader.set_conditional(SceneShaderGLES2::USE_LIGHTMAP_FILTER_BICUBIC, storage->config.use_lightmap_filter_bicubic); shadow_filter_mode = ShadowFilterMode(int(GLOBAL_GET("rendering/quality/shadows/filter_mode"))); } diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 1c94196055d..4a17f3274d4 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -6299,6 +6299,7 @@ void RasterizerStorageGLES2::initialize() { config.force_vertex_shading = GLOBAL_GET("rendering/quality/shading/force_vertex_shading"); config.use_fast_texture_filter = GLOBAL_GET("rendering/quality/filters/use_nearest_mipmap_filter"); + config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); } void RasterizerStorageGLES2::finalize() { diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index 675f19c622e..7cff4d59353 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -57,6 +57,7 @@ public: bool shrink_textures_x2; bool use_fast_texture_filter; bool use_skeleton_software; + bool use_lightmap_filter_bicubic; int max_vertex_texture_image_units; int max_texture_image_units; diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl index b55dbec3766..d1f59c0a2a8 100644 --- a/drivers/gles2/shaders/scene.glsl +++ b/drivers/gles2/shaders/scene.glsl @@ -889,6 +889,74 @@ void reflection_process(samplerCube reflection_map, #ifdef USE_LIGHTMAP uniform mediump sampler2D lightmap; //texunit:-4 uniform mediump float lightmap_energy; + +#ifdef USE_LIGHTMAP_FILTER_BICUBIC +uniform mediump vec2 lightmap_texture_size; + +// w0, w1, w2, and w3 are the four cubic B-spline basis functions +float w0(float a) { + return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0); +} + +float w1(float a) { + return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0); +} + +float w2(float a) { + return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0); +} + +float w3(float a) { + return (1.0 / 6.0) * (a * a * a); +} + +// g0 and g1 are the two amplitude functions +float g0(float a) { + return w0(a) + w1(a); +} + +float g1(float a) { + return w2(a) + w3(a); +} + +// h0 and h1 are the two offset functions +float h0(float a) { + return -1.0 + w1(a) / (w0(a) + w1(a)); +} + +float h1(float a) { + return 1.0 + w3(a) / (w2(a) + w3(a)); +} + +vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv = uv * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv); + vec2 fuv = fract(uv); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + + (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); +} +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) + +#else //!USE_LIGHTMAP_FILTER_BICUBIC +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) + +#endif //USE_LIGHTMAP_FILTER_BICUBIC #endif #ifdef USE_LIGHTMAP_CAPTURE @@ -1661,7 +1729,7 @@ FRAGMENT_SHADER_CODE #ifdef USE_LIGHTMAP //ambient light will come entirely from lightmap is lightmap is used - ambient_light = texture2D(lightmap, uv2_interp).rgb * lightmap_energy; + ambient_light = LIGHTMAP_TEXTURE_SAMPLE(lightmap, uv2_interp).rgb * lightmap_energy; #endif #ifdef USE_LIGHTMAP_CAPTURE diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 4e36ba94235..7313b559c9a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1949,7 +1949,17 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform if (lightmap && capture) { glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 9); - glBindTexture(GL_TEXTURE_2D, lightmap->tex_id); + if (e->instance->lightmap_slice == -1) { + glBindTexture(GL_TEXTURE_2D, lightmap->tex_id); + } else { + glBindTexture(GL_TEXTURE_2D_ARRAY, lightmap->tex_id); + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_LAYER, e->instance->lightmap_slice); + } + const Rect2 &uvr = e->instance->lightmap_uv_rect; + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_UV_RECT, Color(uvr.get_position().x, uvr.get_position().y, uvr.get_size().x, uvr.get_size().y)); + if (storage->config.use_lightmap_filter_bicubic) { + state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_TEXTURE_SIZE, Vector2(lightmap->width, lightmap->height)); + } state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_ENERGY, capture->energy); } } @@ -2080,6 +2090,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_RADIANCE_MAP, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false); @@ -2088,6 +2099,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, e->instance->gi_probe_instances.size() > 0); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, e->instance->lightmap_slice != -1); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, !e->instance->lightmap_capture_data.empty() && !e->instance->lightmap.is_valid() && e->instance->gi_probe_instances.size() == 0); state.scene_shader.set_conditional(SceneShaderGLES3::SHADELESS, false); @@ -2258,6 +2270,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_ state.scene_shader.set_conditional(SceneShaderGLES3::SHADOW_MODE_PCF_13, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_GI_PROBES, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP, false); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_LAYERED, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_CAPTURE, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_CONTACT_SHADOWS, false); state.scene_shader.set_conditional(SceneShaderGLES3::USE_VERTEX_LIGHTING, false); @@ -2392,6 +2405,9 @@ void RasterizerSceneGLES3::_add_geometry_with_material(RasterizerStorageGLES3::G if (e->instance->lightmap.is_valid()) { e->sort_key |= SORT_KEY_LIGHTMAP_FLAG; + if (e->instance->lightmap_slice != -1) { + e->sort_key |= SORT_KEY_LIGHTMAP_LAYERED_FLAG; + } } if (!e->instance->lightmap_capture_data.empty()) { @@ -5337,6 +5353,8 @@ void RasterizerSceneGLES3::iteration() { subsurface_scatter_quality = SubSurfaceScatterQuality(int(GLOBAL_GET("rendering/quality/subsurface_scattering/quality"))); subsurface_scatter_size = GLOBAL_GET("rendering/quality/subsurface_scattering/scale"); + storage->config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + state.scene_shader.set_conditional(SceneShaderGLES3::USE_LIGHTMAP_FILTER_BICUBIC, storage->config.use_lightmap_filter_bicubic); state.scene_shader.set_conditional(SceneShaderGLES3::VCT_QUALITY_HIGH, GLOBAL_GET("rendering/quality/voxel_cone_tracing/high_quality")); } diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index 772d8df0241..3d379a3800c 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -680,14 +680,15 @@ public: SORT_KEY_OPAQUE_DEPTH_LAYER_SHIFT = 52, SORT_KEY_OPAQUE_DEPTH_LAYER_MASK = 0xF, //64 bits unsupported in MSVC -#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 49) -#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 48) -#define SORT_KEY_LIGHTMAP_CAPTURE_FLAG (uint64_t(1) << 47) +#define SORT_KEY_UNSHADED_FLAG (uint64_t(1) << 50) +#define SORT_KEY_NO_DIRECTIONAL_FLAG (uint64_t(1) << 49) +#define SORT_KEY_LIGHTMAP_CAPTURE_FLAG (uint64_t(1) << 48) +#define SORT_KEY_LIGHTMAP_LAYERED_FLAG (uint64_t(1) << 47) #define SORT_KEY_LIGHTMAP_FLAG (uint64_t(1) << 46) #define SORT_KEY_GI_PROBES_FLAG (uint64_t(1) << 45) #define SORT_KEY_VERTEX_LIT_FLAG (uint64_t(1) << 44) SORT_KEY_SHADING_SHIFT = 44, - SORT_KEY_SHADING_MASK = 63, + SORT_KEY_SHADING_MASK = 127, //44-28 material index SORT_KEY_MATERIAL_INDEX_SHIFT = 28, //28-8 geometry index diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index 0a72c67497d..13e7ce333ed 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -8555,6 +8555,8 @@ void RasterizerStorageGLES3::initialize() { String renderer = (const char *)glGetString(GL_RENDERER); + config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling"); + config.use_depth_prepass = bool(GLOBAL_GET("rendering/quality/depth_prepass/enable")); if (config.use_depth_prepass) { diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index c351f265ec7..1101d25ee0c 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -74,6 +74,7 @@ public: bool shrink_textures_x2; bool use_fast_texture_filter; bool use_anisotropic_filter; + bool use_lightmap_filter_bicubic; bool s3tc_supported; bool latc_supported; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index a45ac2eb8a6..ba430b520d7 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -109,6 +109,10 @@ layout(std140) uniform SceneData { // ubo:0 uniform highp mat4 world_transform; +#ifdef USE_LIGHTMAP +uniform highp vec4 lightmap_uv_rect; +#endif + #ifdef USE_LIGHT_DIRECTIONAL layout(std140) uniform DirectionalLightData { //ubo:3 @@ -346,7 +350,9 @@ void main() { uv_interp = uv_attrib; #endif -#if defined(ENABLE_UV2_INTERP) || defined(USE_LIGHTMAP) +#if defined(USE_LIGHTMAP) + uv2_interp = lightmap_uv_rect.zw * uv2_attrib + lightmap_uv_rect.xy; +#elif defined(ENABLE_UV2_INTERP) uv2_interp = uv2_attrib; #endif @@ -1435,8 +1441,109 @@ void reflection_process(int idx, vec3 vertex, vec3 normal, vec3 binormal, vec3 t } #ifdef USE_LIGHTMAP +#ifdef USE_LIGHTMAP_LAYERED +uniform mediump sampler2DArray lightmap; //texunit:-9 +uniform int lightmap_layer; +#else uniform mediump sampler2D lightmap; //texunit:-9 +#endif + uniform mediump float lightmap_energy; + +#ifdef USE_LIGHTMAP_FILTER_BICUBIC +uniform vec2 lightmap_texture_size; + +// w0, w1, w2, and w3 are the four cubic B-spline basis functions +float w0(float a) { + return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0); +} + +float w1(float a) { + return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0); +} + +float w2(float a) { + return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0); +} + +float w3(float a) { + return (1.0 / 6.0) * (a * a * a); +} + +// g0 and g1 are the two amplitude functions +float g0(float a) { + return w0(a) + w1(a); +} + +float g1(float a) { + return w2(a) + w3(a); +} + +// h0 and h1 are the two offset functions +float h0(float a) { + return -1.0 + w1(a) / (w0(a) + w1(a)); +} + +float h1(float a) { + return 1.0 + w3(a) / (w2(a) + w3(a)); +} + +vec4 texture2D_bicubic(sampler2D tex, vec2 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv = uv * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv); + vec2 fuv = fract(uv); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture2D(tex, p0) + g1x * texture2D(tex, p1))) + + (g1(fuv.y) * (g0x * texture2D(tex, p2) + g1x * texture2D(tex, p3))); +} + +vec4 texture_bicubic(sampler2DArray tex, vec3 uv) { + vec2 texel_size = vec2(1.0) / lightmap_texture_size; + + uv.xy = uv.xy * lightmap_texture_size + vec2(0.5); + + vec2 iuv = floor(uv.xy); + vec2 fuv = fract(uv.xy); + + float g0x = g0(fuv.x); + float g1x = g1(fuv.x); + float h0x = h0(fuv.x); + float h1x = h1(fuv.x); + float h0y = h0(fuv.y); + float h1y = h1(fuv.y); + + vec2 p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - vec2(0.5)) * texel_size; + vec2 p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - vec2(0.5)) * texel_size; + vec2 p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - vec2(0.5)) * texel_size; + + return (g0(fuv.y) * (g0x * texture(tex, vec3(p0, uv.z)) + g1x * texture(tex, vec3(p1, uv.z)))) + + (g1(fuv.y) * (g0x * texture(tex, vec3(p2, uv.z)) + g1x * texture(tex, vec3(p3, uv.z)))); +} + +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D_bicubic(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture_bicubic(m_tex, m_uv) + +#else //!USE_LIGHTMAP_FILTER_BICUBIC +#define LIGHTMAP_TEXTURE_SAMPLE(m_tex, m_uv) texture2D(m_tex, m_uv) +#define LIGHTMAP_TEXTURE_LAYERED_SAMPLE(m_tex, m_uv) texture(m_tex, m_uv) + +#endif //USE_LIGHTMAP_FILTER_BICUBIC #endif #ifdef USE_LIGHTMAP_CAPTURE @@ -1823,7 +1930,11 @@ FRAGMENT_SHADER_CODE #endif #ifdef USE_LIGHTMAP - ambient_light = texture(lightmap, uv2).rgb * lightmap_energy; +#ifdef USE_LIGHTMAP_LAYERED + ambient_light = LIGHTMAP_TEXTURE_LAYERED_SAMPLE(lightmap, vec3(uv2, float(lightmap_layer))).rgb * lightmap_energy; +#else + ambient_light = LIGHTMAP_TEXTURE_SAMPLE(lightmap, uv2).rgb * lightmap_energy; +#endif #endif #ifdef USE_LIGHTMAP_CAPTURE diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index cdbc5b264c6..1b0b4028bac 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -107,16 +107,17 @@ void ResourceImporterLayeredTexture::_save_tex(const Vector > &p_imag f->store_32(p_images[0]->get_height()); f->store_32(p_images.size()); //depth f->store_32(p_texture_flags); + + if ((p_compress_mode == COMPRESS_LOSSLESS) && p_images[0]->get_format() > Image::FORMAT_RGBA8) { + p_compress_mode = COMPRESS_UNCOMPRESSED; //these can't go as lossy + } + if (p_compress_mode != COMPRESS_VIDEO_RAM) { //vram needs to do a first compression to tell what the format is, for the rest its ok f->store_32(p_images[0]->get_format()); f->store_32(p_compress_mode); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed } - if ((p_compress_mode == COMPRESS_LOSSLESS) && p_images[0]->get_format() > Image::FORMAT_RGBA8) { - p_compress_mode = COMPRESS_UNCOMPRESSED; //these can't go as lossy - } - for (int i = 0; i < p_images.size(); i++) { switch (p_compress_mode) { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index d2012508a51..589a916e954 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -931,9 +931,6 @@ static String _make_extname(const String &p_str) { void ResourceImporterScene::_find_meshes(Node *p_node, Map, Transform> &meshes) { - List pi; - p_node->get_property_list(&pi); - MeshInstance *mi = Object::cast_to(p_node); if (mi) { @@ -941,11 +938,11 @@ void ResourceImporterScene::_find_meshes(Node *p_node, Map, Trans Ref mesh = mi->get_mesh(); if (mesh.is_valid() && !meshes.has(mesh)) { - Spatial *s = mi; + Spatial *s = Object::cast_to(mi); Transform transform; while (s) { transform = transform * s->get_transform(); - s = s->get_parent_spatial(); + s = Object::cast_to(s->get_parent()); } meshes[mesh] = transform; @@ -1427,29 +1424,117 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p Map, Transform> meshes; _find_meshes(scene, meshes); - if (light_bake_mode == 2) { + String file_id = src_path.get_file(); + String cache_file_path = base_path.plus_file(file_id + ".unwrap_cache"); - float texel_size = p_options["meshes/lightmap_texel_size"]; - texel_size = MAX(0.001, texel_size); + int *cache_data = nullptr; + unsigned int cache_size = 0; - EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); - int step = 0; - for (Map, Transform>::Element *E = meshes.front(); E; E = E->next()) { + if (FileAccess::exists(cache_file_path)) { + Error err2; + FileAccess *file = FileAccess::open(cache_file_path, FileAccess::READ, &err2); - Ref mesh = E->key(); - String name = mesh->get_name(); - if (name == "") { //should not happen but.. - name = "Mesh " + itos(step); - } - - progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step); - - Error err2 = mesh->lightmap_unwrap(E->get(), texel_size); - if (err2 != OK) { - EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); - } - step++; + if (!err2) { + cache_size = file->get_len(); + cache_data = (int *)memalloc(cache_size); + file->get_buffer((unsigned char *)cache_data, cache_size); } + + if (file) + memdelete(file); + } + + float texel_size = p_options["meshes/lightmap_texel_size"]; + texel_size = MAX(0.001, texel_size); + + Map used_meshes; + + EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); + int step = 0; + for (Map, Transform>::Element *E = meshes.front(); E; E = E->next()) { + + Ref mesh = E->key(); + String name = mesh->get_name(); + if (name == "") { //should not happen but.. + name = "Mesh " + itos(step); + } + + progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step); + + int *ret_cache_data = cache_data; + unsigned int ret_cache_size = cache_size; + bool ret_used_cache = true; // Tell the unwrapper to use the cache + Error err2 = mesh->lightmap_unwrap_cached(ret_cache_data, ret_cache_size, ret_used_cache, E->get(), texel_size); + + if (err2 != OK) { + EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); + } else { + + String hash = String::md5((unsigned char *)ret_cache_data); + used_meshes.insert(hash, ret_cache_size); + + if (!ret_used_cache) { + // Cache was not used, add the generated entry to the current cache + + unsigned int new_cache_size = cache_size + ret_cache_size + (cache_size == 0 ? 4 : 0); + int *new_cache_data = (int *)memalloc(new_cache_size); + + if (cache_size == 0) { + // Cache was empty + new_cache_data[0] = 0; + cache_size = 4; + } else { + memcpy(new_cache_data, cache_data, cache_size); + memfree(cache_data); + } + + memcpy(&new_cache_data[cache_size / sizeof(int)], ret_cache_data, ret_cache_size); + + cache_data = new_cache_data; + cache_size = new_cache_size; + + cache_data[0]++; // Increase entry count + } + } + step++; + } + + Error err2; + FileAccess *file = FileAccess::open(cache_file_path, FileAccess::WRITE, &err2); + + if (err2) { + if (file) + memdelete(file); + } else { + + // Store number of entries + file->store_32(used_meshes.size()); + + // Store cache entries + unsigned int r_idx = 1; + for (int i = 0; i < cache_data[0]; ++i) { + unsigned char *entry_start = (unsigned char *)&cache_data[r_idx]; + String entry_hash = String::md5(entry_start); + if (used_meshes.has(entry_hash)) { + unsigned int entry_size = used_meshes[entry_hash]; + file->store_buffer(entry_start, entry_size); + } + + r_idx += 4; // hash + r_idx += 2; // size hint + + int vertex_count = cache_data[r_idx]; + r_idx += 1; // vertex count + r_idx += vertex_count; // vertex + r_idx += vertex_count * 2; // uvs + + int index_count = cache_data[r_idx]; + r_idx += 1; // index count + r_idx += index_count; // indices + } + + file->close(); + memfree(cache_data); } } diff --git a/editor/plugins/baked_lightmap_editor_plugin.cpp b/editor/plugins/baked_lightmap_editor_plugin.cpp index 059448eb225..8f10236e8e7 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.cpp +++ b/editor/plugins/baked_lightmap_editor_plugin.cpp @@ -30,32 +30,58 @@ #include "baked_lightmap_editor_plugin.h" -void BakedLightmapEditorPlugin::_bake() { - +void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { BakedLightmap::BakeError err; if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { - err = lightmap->bake(lightmap); + err = lightmap->bake(lightmap, p_file); } else { - err = lightmap->bake(lightmap->get_parent()); + err = lightmap->bake(lightmap->get_parent(), p_file); } + bake_func_end(); + switch (err) { - case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: - EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene (for images to be saved in the same dir), or pick a save path from the BakedLightmap properties.")); - break; + case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: { + String scene_path = lightmap->get_filename(); + if (scene_path == String()) { + scene_path = lightmap->get_owner()->get_filename(); + } + if (scene_path == String()) { + EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene and try again.")); + break; + } + scene_path = scene_path.get_basename() + ".lmbake"; + + file_dialog->set_current_path(scene_path); + file_dialog->popup_centered_ratio(); + + } break; case BakedLightmap::BAKE_ERROR_NO_MESHES: EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); break; case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE: EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); break; + case BakedLightmap::BAKE_ERROR_LIGHTMAP_SIZE: + EditorNode::get_singleton()->show_warning(TTR("Failed determining lightmap size. Maximum lightmap size too small?")); + break; + case BakedLightmap::BAKE_ERROR_INVALID_MESH: + EditorNode::get_singleton()->show_warning(TTR("Some mesh is invalid. Make sure the UV2 channel values are conatined within the [0.0,1.0] square region.")); + break; + case BakedLightmap::BAKE_ERROR_NO_LIGHTMAPPER: + EditorNode::get_singleton()->show_warning(TTR("Godot editor was built without ray tracing support, lightmaps can't be baked.")); + break; default: { } } } } +void BakedLightmapEditorPlugin::_bake() { + _bake_select_file(""); +} + void BakedLightmapEditorPlugin::edit(Object *p_object) { BakedLightmap *s = Object::cast_to(p_object); @@ -81,29 +107,40 @@ void BakedLightmapEditorPlugin::make_visible(bool p_visible) { } EditorProgress *BakedLightmapEditorPlugin::tmp_progress = NULL; +EditorProgress *BakedLightmapEditorPlugin::tmp_subprogress = NULL; -void BakedLightmapEditorPlugin::bake_func_begin(int p_steps) { - - ERR_FAIL_COND(tmp_progress != NULL); - - tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), p_steps, true)); +bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh) { + if (!tmp_progress) { + tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), 1000, true)); + ERR_FAIL_COND_V(tmp_progress == nullptr, false); + } + return tmp_progress->step(p_description, p_progress * 1000, p_force_refresh); } -bool BakedLightmapEditorPlugin::bake_func_step(int p_step, const String &p_description) { - - ERR_FAIL_COND_V(tmp_progress == NULL, false); - return tmp_progress->step(p_description, p_step, false); +bool BakedLightmapEditorPlugin::bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh) { + if (!tmp_subprogress) { + tmp_subprogress = memnew(EditorProgress("bake_lightmaps_substep", "", 1000, true)); + ERR_FAIL_COND_V(tmp_subprogress == nullptr, false); + } + return tmp_subprogress->step(p_description, p_progress * 1000, p_force_refresh); } void BakedLightmapEditorPlugin::bake_func_end() { - ERR_FAIL_COND(tmp_progress == NULL); - memdelete(tmp_progress); - tmp_progress = NULL; + if (tmp_progress != nullptr) { + memdelete(tmp_progress); + tmp_progress = nullptr; + } + + if (tmp_subprogress != nullptr) { + memdelete(tmp_subprogress); + tmp_subprogress = nullptr; + } } void BakedLightmapEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &BakedLightmapEditorPlugin::_bake); + ClassDB::bind_method("_bake_select_file", &BakedLightmapEditorPlugin::_bake_select_file); } BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { @@ -114,12 +151,19 @@ BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { bake->set_text(TTR("Bake Lightmaps")); bake->hide(); bake->connect("pressed", this, "_bake"); + + file_dialog = memnew(EditorFileDialog); + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + file_dialog->add_filter("*.lmbake ; LightMap Bake"); + file_dialog->set_title(TTR("Select lightmap bake file:")); + file_dialog->connect("file_selected", this, "_bake_select_file"); + bake->add_child(file_dialog); + add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake); lightmap = NULL; - BakedLightmap::bake_begin_function = bake_func_begin; BakedLightmap::bake_step_function = bake_func_step; - BakedLightmap::bake_end_function = bake_func_end; + BakedLightmap::bake_substep_function = bake_func_substep; } BakedLightmapEditorPlugin::~BakedLightmapEditorPlugin() { diff --git a/editor/plugins/baked_lightmap_editor_plugin.h b/editor/plugins/baked_lightmap_editor_plugin.h index abd39b5ef44..5a4a6189b43 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.h +++ b/editor/plugins/baked_lightmap_editor_plugin.h @@ -45,11 +45,15 @@ class BakedLightmapEditorPlugin : public EditorPlugin { ToolButton *bake; EditorNode *editor; + EditorFileDialog *file_dialog; static EditorProgress *tmp_progress; - static void bake_func_begin(int p_steps); - static bool bake_func_step(int p_step, const String &p_description); + static EditorProgress *tmp_subprogress; + + static bool bake_func_step(float p_progress, const String &p_description, void *, bool p_force_refresh); + static bool bake_func_substep(float p_progress, const String &p_description, void *, bool p_force_refresh); static void bake_func_end(); + void _bake_select_file(const String &p_file); void _bake(); protected: diff --git a/editor/progress_dialog.cpp b/editor/progress_dialog.cpp index 7a09380387d..42aebcb2e04 100644 --- a/editor/progress_dialog.cpp +++ b/editor/progress_dialog.cpp @@ -180,6 +180,7 @@ void ProgressDialog::add_task(const String &p_task, const String &p_label, int p t.progress = memnew(ProgressBar); t.progress->set_max(p_steps); t.progress->set_value(p_steps); + t.last_progress_tick = 0; vb2->add_child(t.progress); t.state = memnew(Label); t.state->set_clip_text(true); @@ -204,20 +205,20 @@ bool ProgressDialog::task_step(const String &p_task, const String &p_state, int ERR_FAIL_COND_V(!tasks.has(p_task), cancelled); + Task &t = tasks[p_task]; if (!p_force_redraw) { uint64_t tus = OS::get_singleton()->get_ticks_usec(); - if (tus - last_progress_tick < 200000) //200ms + if (tus - t.last_progress_tick < 200000) //200ms return cancelled; } - Task &t = tasks[p_task]; if (p_step < 0) t.progress->set_value(t.progress->get_value() + 1); else t.progress->set_value(p_step); t.state->set_text(p_state); - last_progress_tick = OS::get_singleton()->get_ticks_usec(); + t.last_progress_tick = OS::get_singleton()->get_ticks_usec(); if (cancel_hb->is_visible()) { OS::get_singleton()->force_process_input(); } @@ -254,7 +255,6 @@ ProgressDialog::ProgressDialog() { add_child(main); main->set_anchors_and_margins_preset(Control::PRESET_WIDE); set_exclusive(true); - last_progress_tick = 0; singleton = this; cancel_hb = memnew(HBoxContainer); main->add_child(cancel_hb); diff --git a/editor/progress_dialog.h b/editor/progress_dialog.h index bdd4c0ffd1d..0a100a2a7f2 100644 --- a/editor/progress_dialog.h +++ b/editor/progress_dialog.h @@ -77,13 +77,13 @@ class ProgressDialog : public Popup { VBoxContainer *vb; ProgressBar *progress; Label *state; + uint64_t last_progress_tick; }; HBoxContainer *cancel_hb; Button *cancel; Map tasks; VBoxContainer *main; - uint64_t last_progress_tick; static ProgressDialog *singleton; void _popup(); diff --git a/modules/denoise/lightmap_denoiser.h b/modules/denoise/lightmap_denoiser.h index 74a9d8af866..e7f5c23637b 100644 --- a/modules/denoise/lightmap_denoiser.h +++ b/modules/denoise/lightmap_denoiser.h @@ -45,7 +45,7 @@ protected: public: static LightmapDenoiser *create_oidn_denoiser(); - Ref denoise_image(const Ref &p_image) override; + Ref denoise_image(const Ref &p_image); static void make_default_denoiser(); diff --git a/modules/lightmapper_cpu/SCsub b/modules/lightmapper_cpu/SCsub new file mode 100644 index 00000000000..4fbb1b6b1dd --- /dev/null +++ b/modules/lightmapper_cpu/SCsub @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +env_lightmapper_rd = env_modules.Clone() +# Godot source files +env_lightmapper_rd.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/lightmapper_cpu/config.py b/modules/lightmapper_cpu/config.py new file mode 100644 index 00000000000..d01c1726dd3 --- /dev/null +++ b/modules/lightmapper_cpu/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env["tools"] and env["module_raycast_enabled"] + + +def configure(env): + pass diff --git a/modules/lightmapper_cpu/lightmapper_cpu.cpp b/modules/lightmapper_cpu/lightmapper_cpu.cpp new file mode 100644 index 00000000000..375d20976e1 --- /dev/null +++ b/modules/lightmapper_cpu/lightmapper_cpu.cpp @@ -0,0 +1,1621 @@ +/*************************************************************************/ +/* lightmapper_cpu.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "lightmapper_cpu.h" +#include "core/math/geometry.h" +#include "core/os/os.h" +#include "core/os/threaded_array_processor.h" +#include "core/project_settings.h" +#include "modules/raycast/lightmap_raycaster.h" + +Error LightmapperCPU::_layout_atlas(int p_max_size, Vector2i *r_atlas_size, int *r_atlas_slices) { + + Vector2i atlas_size; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + Vector2i size = mesh_instances[i].size; + atlas_size.width = MAX(atlas_size.width, size.width + 2); + atlas_size.height = MAX(atlas_size.height, size.height + 2); + } + } + + int max = nearest_power_of_2_templated(atlas_size.width); + max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); + + if (max > p_max_size) { + return ERR_INVALID_DATA; + } + + Vector2i best_atlas_size; + int best_atlas_slices = 0; + int best_atlas_memory = 0x7FFFFFFF; + float best_atlas_mem_utilization = 0; + Vector best_atlas_offsets; + Vector best_scaled_sizes; + + int first_try_mem_occupied = 0; + int first_try_mem_used = 0; + for (int recovery_percent = 0; recovery_percent <= 100; recovery_percent += 10) { + // These only make sense from the second round of the loop + float recovery_scale = 1; + int target_mem_occupied = 0; + if (recovery_percent != 0) { + target_mem_occupied = first_try_mem_occupied + (first_try_mem_used - first_try_mem_occupied) * recovery_percent * 0.01f; + float new_squared_recovery_scale = static_cast(target_mem_occupied) / first_try_mem_occupied; + if (new_squared_recovery_scale > 1.0f) { + recovery_scale = Math::sqrt(new_squared_recovery_scale); + } + } + + atlas_size = Vector2i(max, max); + while (atlas_size.x <= p_max_size && atlas_size.y <= p_max_size) { + + if (recovery_percent != 0) { + // Find out how much memory is not recoverable (because of lightmaps that can't grow), + // to compute a greater recovery scale for those that can. + int mem_unrecoverable = 0; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + Vector2i scaled_size = Vector2i( + static_cast(recovery_scale * mesh_instances[i].size.x), + static_cast(recovery_scale * mesh_instances[i].size.y)); + if (scaled_size.x + 2 > atlas_size.x || scaled_size.y + 2 > atlas_size.y) { + mem_unrecoverable += scaled_size.x * scaled_size.y - mesh_instances[i].size.x * mesh_instances[i].size.y; + } + } + } + float new_squared_recovery_scale = static_cast(target_mem_occupied - mem_unrecoverable) / (first_try_mem_occupied - mem_unrecoverable); + if (new_squared_recovery_scale > 1.0f) { + recovery_scale = Math::sqrt(new_squared_recovery_scale); + } + } + + Vector scaled_sizes; + scaled_sizes.resize(mesh_instances.size()); + { + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + if (recovery_percent == 0) { + scaled_sizes.write[i] = mesh_instances[i].size; + } else { + Vector2i scaled_size = Vector2i( + static_cast(recovery_scale * mesh_instances[i].size.x), + static_cast(recovery_scale * mesh_instances[i].size.y)); + if (scaled_size.x + 2 <= atlas_size.x && scaled_size.y + 2 <= atlas_size.y) { + scaled_sizes.write[i] = scaled_size; + } else { + scaled_sizes.write[i] = mesh_instances[i].size; + } + } + } else { + // Don't consider meshes with no generated lightmap here; will compensate later + scaled_sizes.write[i] = Vector2i(); + } + } + } + + Vector source_sizes; + source_sizes.resize(scaled_sizes.size()); + Vector source_indices; + source_indices.resize(scaled_sizes.size()); + for (int i = 0; i < source_sizes.size(); i++) { + source_sizes.write[i] = scaled_sizes[i] + Vector2i(2, 2); // Add padding between lightmaps + source_indices.write[i] = i; + } + + Vector curr_atlas_offsets; + curr_atlas_offsets.resize(source_sizes.size()); + + int slices = 0; + + while (source_sizes.size() > 0) { + + Vector offsets = Geometry::partial_pack_rects(source_sizes, atlas_size); + Vector new_indices; + Vector new_sources; + for (int i = 0; i < offsets.size(); i++) { + Geometry::PackRectsResult ofs = offsets[i]; + int sidx = source_indices[i]; + if (ofs.packed) { + curr_atlas_offsets.write[sidx] = { slices, ofs.x + 1, ofs.y + 1 }; + } else { + new_indices.push_back(sidx); + new_sources.push_back(source_sizes[i]); + } + } + + source_sizes = new_sources; + source_indices = new_indices; + slices++; + } + + int mem_used = atlas_size.x * atlas_size.y * slices; + int mem_occupied = 0; + for (int i = 0; i < curr_atlas_offsets.size(); i++) { + mem_occupied += scaled_sizes[i].x * scaled_sizes[i].y; + } + + float mem_utilization = static_cast(mem_occupied) / mem_used; + if (slices * atlas_size.y < 16384) { // Maximum Image size + if (mem_used < best_atlas_memory || (mem_used == best_atlas_memory && mem_utilization > best_atlas_mem_utilization)) { + best_atlas_size = atlas_size; + best_atlas_offsets = curr_atlas_offsets; + best_atlas_slices = slices; + best_atlas_memory = mem_used; + best_atlas_mem_utilization = mem_utilization; + best_scaled_sizes = scaled_sizes; + } + } + + if (recovery_percent == 0) { + first_try_mem_occupied = mem_occupied; + first_try_mem_used = mem_used; + } + + if (atlas_size.width == atlas_size.height) { + atlas_size.width *= 2; + } else { + atlas_size.height *= 2; + } + } + } + + if (best_atlas_size == Vector2i()) { + return ERR_INVALID_DATA; + } + + *r_atlas_size = best_atlas_size; + *r_atlas_slices = best_atlas_slices; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (best_scaled_sizes[i] != Vector2i()) { + mesh_instances[i].size = best_scaled_sizes[i]; + mesh_instances[i].offset = Vector2i(best_atlas_offsets[i].x, best_atlas_offsets[i].y); + mesh_instances[i].slice = best_atlas_offsets[i].slice; + } + } + return OK; +} + +void LightmapperCPU::_thread_func_callback(void *p_thread_data) { + ThreadData *thread_data = reinterpret_cast(p_thread_data); + thread_process_array(thread_data->count, thread_data->instance, &LightmapperCPU::_thread_func_wrapper, thread_data); +} + +void LightmapperCPU::_thread_func_wrapper(uint32_t p_idx, ThreadData *p_thread_data) { + + if (thread_cancelled) { + return; + } + + (p_thread_data->instance->*p_thread_data->thread_func)(p_idx, p_thread_data->userdata); + + thread_progress++; +} + +bool LightmapperCPU::_parallel_run(int p_count, const String &p_description, BakeThreadFunc p_thread_func, void *p_userdata, BakeStepFunc p_substep_func) { + + bool cancelled = false; + if (p_substep_func) { + cancelled = p_substep_func(0.0f, vformat("%s (%d/%d)", p_description, 0, p_count), nullptr, false); + } + + thread_progress = 0; + thread_cancelled = false; + +#ifdef NO_THREAD + for (int i = 0; !cancelled && i < p_count; i++) { + (this->*p_thread_func)(i, p_userdata); + float p = float(i) / p_count; + if (p_substep_func) { + cancelled = p_substep_func(p, vformat("%s (%d/%d)", p_description, i + 1, p_count), nullptr, false); + } + } +#else + + if (p_count == 0) { + return cancelled; + } + + ThreadData td; + td.instance = this; + td.count = p_count; + td.thread_func = p_thread_func; + td.userdata = p_userdata; + Thread *runner_thread = Thread::create(_thread_func_callback, &td); + + int progress = thread_progress; + + while (!cancelled && progress < p_count) { + float p = float(progress) / p_count; + if (p_substep_func) { + cancelled = p_substep_func(p, vformat("%s (%d/%d)", p_description, progress + 1, p_count), nullptr, false); + } + progress = thread_progress; + } + thread_cancelled = cancelled; + Thread::wait_to_finish(runner_thread); +#endif + + thread_cancelled = false; + + return cancelled; +} + +void LightmapperCPU::_generate_buffer(uint32_t p_idx, void *p_unused) { + + const Size2i &size = mesh_instances[p_idx].size; + + int buffer_size = size.x * size.y; + + LocalVector &lightmap = scene_lightmaps[p_idx]; + LocalVector &lightmap_indices = scene_lightmap_indices[p_idx]; + + lightmap_indices.resize(buffer_size); + + for (unsigned int i = 0; i < lightmap_indices.size(); i++) { + lightmap_indices[i] = -1; + } + + MeshData &md = mesh_instances[p_idx].data; + + LocalVector > albedo_images; + LocalVector > emission_images; + + for (int surface_id = 0; surface_id < md.albedo.size(); surface_id++) { + albedo_images.push_back(_init_bake_texture(md.albedo[surface_id], albedo_textures, Image::FORMAT_RGBA8)); + emission_images.push_back(_init_bake_texture(md.emission[surface_id], emission_textures, Image::FORMAT_RGBH)); + } + + int surface_id = 0; + int surface_facecount = 0; + const Vector3 *points_ptr = md.points.ptr(); + const Vector3 *normals_ptr = md.normal.ptr(); + const Vector2 *uvs_ptr = md.uv.empty() ? nullptr : md.uv.ptr(); + const Vector2 *uv2s_ptr = md.uv2.ptr(); + + for (int i = 0; i < md.points.size() / 3; i++) { + + Ref albedo = albedo_images[surface_id]; + Ref emission = emission_images[surface_id]; + + albedo->lock(); + emission->lock(); + _plot_triangle(&(uv2s_ptr[i * 3]), &(points_ptr[i * 3]), &(normals_ptr[i * 3]), uvs_ptr ? &(uvs_ptr[i * 3]) : nullptr, albedo, emission, size, lightmap, lightmap_indices); + albedo->unlock(); + emission->unlock(); + + surface_facecount++; + if (surface_facecount == md.surface_facecounts[surface_id]) { + surface_id++; + surface_facecount = 0; + } + } +} + +Ref LightmapperCPU::_init_bake_texture(const MeshData::TextureDef &p_texture_def, const Map > &p_tex_cache, Image::Format p_default_format) { + Ref ret; + if (p_texture_def.tex_rid.is_valid()) { + ret = p_tex_cache[p_texture_def.tex_rid]->duplicate(); + ret->lock(); + for (int j = 0; j < ret->get_height(); j++) { + for (int i = 0; i < ret->get_width(); i++) { + ret->set_pixel(i, j, ret->get_pixel(i, j) * p_texture_def.mul + p_texture_def.add); + } + } + ret->unlock(); + } else { + ret.instance(); + ret->create(8, 8, false, p_default_format); + ret->fill(p_texture_def.add * p_texture_def.mul); + } + return ret; +} + +Color LightmapperCPU::_bilinear_sample(const Ref &p_img, const Vector2 &p_uv, bool p_clamp_x, bool p_clamp_y) { + + int width = p_img->get_width(); + int height = p_img->get_height(); + + Vector2 uv; + uv.x = p_clamp_x ? p_uv.x : Math::fposmod(p_uv.x, 1.0f); + uv.y = p_clamp_y ? p_uv.y : Math::fposmod(p_uv.y, 1.0f); + + float xf = uv.x * width; + float yf = uv.y * height; + + int xi = (int)xf; + int yi = (int)yf; + + Color texels[4]; + for (int i = 0; i < 4; i++) { + int sample_x = xi + i % 2; + int sample_y = yi + i / 2; + + sample_x = CLAMP(sample_x, 0, width - 1); + sample_y = CLAMP(sample_y, 0, height - 1); + + texels[i] = p_img->get_pixel(sample_x, sample_y); + } + + float tx = xf - xi; + float ty = yf - yi; + + Color c = Color(0, 0, 0, 0); + for (int i = 0; i < 4; i++) { + c[i] = Math::lerp(Math::lerp(texels[0][i], texels[1][i], tx), Math::lerp(texels[2][i], texels[3][i], tx), ty); + } + return c; +} + +Vector3 LightmapperCPU::_fix_sample_position(const Vector3 &p_position, const Vector3 &p_texel_center, const Vector3 &p_normal, const Vector3 &p_tangent, const Vector3 &p_bitangent, const Vector2 &p_texel_size) { + + Basis tangent_basis(p_tangent, p_bitangent, p_normal); + tangent_basis.orthonormalize(); + Vector2 half_size = p_texel_size / 2.0f; + Vector3 corrected = p_position; + + for (int i = -1; i <= 1; i += 1) { + for (int j = -1; j <= 1; j += 1) { + if (i == 0 && j == 0) continue; + Vector3 offset = Vector3(half_size.x * i, half_size.y * j, 0.0); + Vector3 rotated_offset = tangent_basis.xform_inv(offset); + Vector3 target = p_texel_center + rotated_offset; + Vector3 ray_vector = target - corrected; + + Vector3 ray_back_offset = -ray_vector.normalized() * parameters.bias; + Vector3 ray_origin = corrected + ray_back_offset; + ray_vector = target - ray_origin; + float ray_length = ray_vector.length(); + LightmapRaycaster::Ray ray(ray_origin + p_normal * parameters.bias, ray_vector.normalized(), 0.0f, ray_length + parameters.bias); + + bool hit = raycaster->intersect(ray); + if (hit) { + ray.normal.normalize(); + if (ray.normal.dot(ray_vector.normalized()) > 0.0f) { + corrected = ray_origin + ray.dir * ray.tfar + ray.normal * (parameters.bias * 2.0f); + } + } + } + } + + return corrected; +} + +void LightmapperCPU::_plot_triangle(const Vector2 *p_vertices, const Vector3 *p_positions, const Vector3 *p_normals, const Vector2 *p_uvs, const Ref &p_albedo, const Ref &p_emission, Vector2i p_size, LocalVector &r_lightmap, LocalVector &r_lightmap_indices) { + Vector2 pv0 = p_vertices[0]; + Vector2 pv1 = p_vertices[1]; + Vector2 pv2 = p_vertices[2]; + + Vector2 v0 = pv0 * p_size; + Vector2 v1 = pv1 * p_size; + Vector2 v2 = pv2 * p_size; + + Vector3 p0 = p_positions[0]; + Vector3 p1 = p_positions[1]; + Vector3 p2 = p_positions[2]; + + Vector3 n0 = p_normals[0]; + Vector3 n1 = p_normals[1]; + Vector3 n2 = p_normals[2]; + + Vector2 uv0 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[0]; + Vector2 uv1 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[1]; + Vector2 uv2 = p_uvs == nullptr ? Vector2(0.5f, 0.5f) : p_uvs[2]; + +#define edgeFunction(a, b, c) ((c)[0] - (a)[0]) * ((b)[1] - (a)[1]) - ((c)[1] - (a)[1]) * ((b)[0] - (a)[0]) + + if (edgeFunction(v0, v1, v2) < 0.0) { + SWAP(pv1, pv2); + SWAP(v1, v2); + SWAP(p1, p2); + SWAP(n1, n2); + SWAP(uv1, uv2); + } + + Vector3 edge1 = p1 - p0; + Vector3 edge2 = p2 - p0; + + Vector2 uv_edge1 = pv1 - pv0; + Vector2 uv_edge2 = pv2 - pv0; + + float r = 1.0f / (uv_edge1.x * uv_edge2.y - uv_edge1.y * uv_edge2.x); + + Vector3 tangent = (edge1 * uv_edge2.y - edge2 * uv_edge1.y) * r; + Vector3 bitangent = (edge2 * uv_edge1.x - edge1 * uv_edge2.x) * r; + + tangent.normalize(); + bitangent.normalize(); + + // Compute triangle bounding box + Vector2 bbox_min = Vector2(MIN(v0.x, MIN(v1.x, v2.x)), MIN(v0.y, MIN(v1.y, v2.y))); + Vector2 bbox_max = Vector2(MAX(v0.x, MAX(v1.x, v2.x)), MAX(v0.y, MAX(v1.y, v2.y))); + + bbox_min = bbox_min.floor(); + bbox_max = bbox_max.ceil(); + + uint32_t min_x = MAX(bbox_min.x - 2, 0); + uint32_t min_y = MAX(bbox_min.y - 2, 0); + uint32_t max_x = MIN(bbox_max.x, p_size.x - 1); + uint32_t max_y = MIN(bbox_max.y, p_size.y - 1); + + Vector2 texel_size; + Vector2 centroid = (v0 + v1 + v2) / 3.0f; + Vector3 centroid_pos = (p0 + p1 + p2) / 3.0f; + for (int i = 0; i < 2; i++) { + Vector2 p = centroid; + p[i] += 1; + Vector3 bary = Geometry::barycentric_coordinates_2d(p, v0, v1, v2); + Vector3 pos = p0 * bary[0] + p1 * bary[1] + p2 * bary[2]; + texel_size[i] = centroid_pos.distance_to(pos); + } + + Vector pixel_polygon; + pixel_polygon.resize(4); + static const Vector2 corners[4] = { Vector2(0, 0), Vector2(0, 1), Vector2(1, 1), Vector2(1, 0) }; + + Vector triangle_polygon; + triangle_polygon.push_back(v0); + triangle_polygon.push_back(v1); + triangle_polygon.push_back(v2); + + for (uint32_t j = min_y; j <= max_y; ++j) { + for (uint32_t i = min_x; i <= max_x; i++) { + + int ofs = j * p_size.x + i; + int texel_idx = r_lightmap_indices[ofs]; + + if (texel_idx >= 0 && r_lightmap[texel_idx].area_coverage >= 0.5f) { + continue; + } + + Vector3 barycentric_coords; + float area_coverage = 0.0f; + bool intersected = false; + + for (int k = 0; k < 4; k++) { + pixel_polygon.write[k] = Vector2(i, j) + corners[k]; + } + + const float max_dist = 0.05; + bool v0eqv1 = v0.distance_squared_to(v1) < max_dist; + bool v1eqv2 = v1.distance_squared_to(v2) < max_dist; + bool v2eqv0 = v2.distance_squared_to(v0) < max_dist; + if (v0eqv1 && v1eqv2 && v2eqv0) { + intersected = true; + barycentric_coords = Vector3(1, 0, 0); + } else if (v0eqv1 || v1eqv2 || v2eqv0) { + + Vector segment; + segment.resize(2); + if (v0eqv1) { + segment.write[0] = v0; + segment.write[1] = v2; + } else if (v1eqv2) { + segment.write[0] = v1; + segment.write[1] = v0; + } else { + segment.write[0] = v0; + segment.write[1] = v1; + } + + Vector > intersected_segments = Geometry::intersect_polyline_with_polygon_2d(segment, pixel_polygon); + ERR_FAIL_COND_MSG(intersected_segments.size() > 1, "[Lightmapper] Itersecting a segment and a convex polygon should give at most one segment."); + if (!intersected_segments.empty()) { + const Vector &intersected_segment = intersected_segments[0]; + ERR_FAIL_COND_MSG(intersected_segment.size() != 2, "[Lightmapper] Itersecting a segment and a convex polygon should give at most one segment."); + Vector2 sample_pos = (intersected_segment[0] + intersected_segment[1]) / 2.0f; + + float u = (segment[0].distance_to(sample_pos)) / (segment[0].distance_to(segment[1])); + float v = (1.0f - u) / 2.0f; + intersected = true; + if (v0eqv1) { + barycentric_coords = Vector3(v, v, u); + } else if (v1eqv2) { + barycentric_coords = Vector3(u, v, v); + } else { + barycentric_coords = Vector3(v, u, v); + } + } + + } else if (edgeFunction(v0, v1, v2) < 0.005) { + Vector2 direction = v0 - v1; + Vector2 perpendicular = Vector2(direction.y, -direction.x); + + Vector line; + int middle_vertex; + + if (SGN(edgeFunction(v0, v0 + perpendicular, v1)) != SGN(edgeFunction(v0, v0 + perpendicular, v2))) { + line.push_back(v1); + line.push_back(v2); + middle_vertex = 0; + } else if (SGN(edgeFunction(v1, v1 + perpendicular, v0)) != SGN(edgeFunction(v1, v1 + perpendicular, v2))) { + line.push_back(v0); + line.push_back(v2); + middle_vertex = 1; + } else { + line.push_back(v0); + line.push_back(v1); + middle_vertex = 2; + } + + Vector > intersected_lines = Geometry::intersect_polyline_with_polygon_2d(line, pixel_polygon); + + ERR_FAIL_COND_MSG(intersected_lines.size() > 1, "[Lightmapper] Itersecting a line and a convex polygon should give at most one line."); + + if (!intersected_lines.empty()) { + intersected = true; + const Vector &intersected_line = intersected_lines[0]; + Vector2 sample_pos = (intersected_line[0] + intersected_line[1]) / 2.0f; + + float line_length = line[0].distance_to(line[1]); + float norm = line[0].distance_to(sample_pos) / line_length; + + if (middle_vertex == 0) { + barycentric_coords = Vector3(0.0f, 1.0f - norm, norm); + } else if (middle_vertex == 1) { + barycentric_coords = Vector3(1.0f - norm, 0.0f, norm); + } else { + barycentric_coords = Vector3(1.0f - norm, norm, 0.0f); + } + } + } else { + + Vector > intersected_polygons = Geometry::intersect_polygons_2d(pixel_polygon, triangle_polygon); + + ERR_FAIL_COND_MSG(intersected_polygons.size() > 1, "[Lightmapper] Itersecting two convex polygons should give at most one polygon."); + + if (!intersected_polygons.empty()) { + const Vector &intersected_polygon = intersected_polygons[0]; + + // do centroid sampling + Vector2 sample_pos = intersected_polygon[0]; + Vector2 area_center = Vector2(i, j) + Vector2(0.5f, 0.5f); + float intersected_area = (intersected_polygon[0] - area_center).cross(intersected_polygon[intersected_polygon.size() - 1] - area_center); + for (int k = 1; k < intersected_polygon.size(); k++) { + sample_pos += intersected_polygon[k]; + intersected_area += (intersected_polygon[k] - area_center).cross(intersected_polygon[k - 1] - area_center); + } + + if (intersected_area != 0.0f) { + sample_pos /= intersected_polygon.size(); + barycentric_coords = Geometry::barycentric_coordinates_2d(sample_pos, v0, v1, v2); + intersected = true; + area_coverage = ABS(intersected_area) / 2.0f; + } + } + + if (!intersected) { + for (int k = 0; k < 4; ++k) { + for (int l = 0; l < 3; ++l) { + Vector2 intersection_point; + if (Geometry::segment_intersects_segment_2d(pixel_polygon[k], pixel_polygon[(k + 1) % 4], triangle_polygon[l], triangle_polygon[(l + 1) % 3], &intersection_point)) { + intersected = true; + barycentric_coords = Geometry::barycentric_coordinates_2d(intersection_point, v0, v1, v2); + break; + } + } + if (intersected) { + break; + } + } + } + } + + if (texel_idx >= 0 && area_coverage < r_lightmap[texel_idx].area_coverage) { + continue; // A previous triangle gives better pixel coverage + } + + Vector2 pixel = Vector2(i, j); + if (!intersected && v0.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(1, 0, 0); + } + + if (!intersected && v1.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(0, 1, 0); + } + + if (!intersected && v2.floor() == pixel) { + intersected = true; + barycentric_coords = Vector3(0, 0, 1); + } + + if (!intersected) { + continue; + } + + if (Math::is_nan(barycentric_coords.x) || Math::is_nan(barycentric_coords.y) || Math::is_nan(barycentric_coords.z)) { + continue; + } + + if (Math::is_inf(barycentric_coords.x) || Math::is_inf(barycentric_coords.y) || Math::is_inf(barycentric_coords.z)) { + continue; + } + + r_lightmap_indices[ofs] = r_lightmap.size(); + + Vector3 pos = p0 * barycentric_coords[0] + p1 * barycentric_coords[1] + p2 * barycentric_coords[2]; + Vector3 normal = n0 * barycentric_coords[0] + n1 * barycentric_coords[1] + n2 * barycentric_coords[2]; + + Vector2 uv = uv0 * barycentric_coords[0] + uv1 * barycentric_coords[1] + uv2 * barycentric_coords[2]; + Color c = _bilinear_sample(p_albedo, uv); + Color e = _bilinear_sample(p_emission, uv); + + Vector2 texel_center = Vector2(i, j) + Vector2(0.5f, 0.5f); + Vector3 texel_center_bary = Geometry::barycentric_coordinates_2d(texel_center, v0, v1, v2); + + if (!Math::is_nan(texel_center_bary.x) && !Math::is_nan(texel_center_bary.y) && !Math::is_nan(texel_center_bary.z) && !Math::is_inf(texel_center_bary.x) && !Math::is_inf(texel_center_bary.y) && !Math::is_inf(texel_center_bary.z)) { + Vector3 texel_center_pos = p0 * texel_center_bary[0] + p1 * texel_center_bary[1] + p2 * texel_center_bary[2]; + pos = _fix_sample_position(pos, texel_center_pos, normal, tangent, bitangent, texel_size); + } + + LightmapTexel texel; + texel.normal = normal.normalized(); + texel.pos = pos; + texel.albedo = Vector3(c.r, c.g, c.b); + texel.alpha = c.a; + texel.emission = Vector3(e.r, e.g, e.b); + texel.area_coverage = area_coverage; + r_lightmap.push_back(texel); + } + } +} + +void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) { + + LightmapTexel *lightmap = (LightmapTexel *)r_lightmap; + for (unsigned int i = 0; i < lights.size(); ++i) { + + const Light &light = lights[i]; + Vector3 normal = lightmap[p_idx].normal; + Vector3 position = lightmap[p_idx].pos; + Vector3 final_energy; + Color c = light.color; + Vector3 light_energy = Vector3(c.r, c.g, c.b) * light.energy; + + if (light.type == LIGHT_TYPE_OMNI) { + Vector3 light_direction = (position - light.position).normalized(); + if (normal.dot(light_direction) >= 0.0) { + continue; + } + float dist = position.distance_to(light.position); + + if (dist <= light.range) { + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist - parameters.bias); + if (raycaster->intersect(ray)) { + continue; + } + float att = powf(1.0 - dist / light.range, light.attenuation); + final_energy = light_energy * att * MAX(0, normal.dot(-light_direction)); + } + } + + if (light.type == LIGHT_TYPE_SPOT) { + + Vector3 light_direction = (position - light.position).normalized(); + if (normal.dot(light_direction) >= 0.0) { + continue; + } + + float angle = Math::acos(light.direction.dot(light_direction)); + + if (angle > light.spot_angle) { + continue; + } + + float dist = position.distance_to(light.position); + if (dist > light.range) { + continue; + } + + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position, -light_direction, parameters.bias, dist); + if (raycaster->intersect(ray)) { + continue; + } + + float normalized_dist = dist * (1.0f / MAX(0.001f, light.range)); + float norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation); + + float spot_cutoff = Math::cos(light.spot_angle); + float scos = MAX(light_direction.dot(light.direction), spot_cutoff); + float spot_rim = (1.0f - scos) / (1.0f - spot_cutoff); + norm_light_attenuation *= 1.0f - pow(MAX(spot_rim, 0.001f), light.spot_attenuation); + final_energy = light_energy * norm_light_attenuation * MAX(0, normal.dot(-light_direction)); + } + + if (light.type == LIGHT_TYPE_DIRECTIONAL) { + if (normal.dot(light.direction) >= 0.0) { + continue; + } + + LightmapRaycaster::Ray ray = LightmapRaycaster::Ray(position + normal * parameters.bias, -light.direction, parameters.bias); + if (raycaster->intersect(ray)) { + continue; + } + + final_energy = light_energy * MAX(0, normal.dot(-light.direction)); + } + + lightmap[p_idx].direct_light += final_energy * light.indirect_multiplier; + if (light.bake_direct) { + lightmap[p_idx].output_light += final_energy; + } + } +} + +_ALWAYS_INLINE_ float uniform_rand() { + /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ + static thread_local uint32_t state = rand(); + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + return float(state) / UINT32_MAX; +} + +void LightmapperCPU::_compute_indirect_light(uint32_t p_idx, void *r_lightmap) { + + LightmapTexel *lightmap = (LightmapTexel *)r_lightmap; + LightmapTexel &texel = lightmap[p_idx]; + + Vector3 accum; + + const Vector3 const_forward = Vector3(0, 0, 1); + const Vector3 const_up = Vector3(0, 1, 0); + + for (int i = 0; i < parameters.samples; i++) { + + Vector3 color; + Vector3 throughput = Vector3(1.0f, 1.0f, 1.0f); + + Vector3 position = texel.pos; + Vector3 normal = texel.normal; + Vector3 direction; + + for (int depth = 0; depth < parameters.bounces; depth++) { + + Vector3 tangent = const_forward.cross(normal); + if (unlikely(tangent.length_squared() < 0.005f)) { + tangent = const_up.cross(normal); + } + tangent.normalize(); + Vector3 bitangent = tangent.cross(normal); + bitangent.normalize(); + + Basis normal_xform = Basis(tangent, bitangent, normal); + normal_xform.transpose(); + + float u1 = uniform_rand(); + float u2 = uniform_rand(); + + float radius = Math::sqrt(u1); + float theta = Math_TAU * u2; + + Vector3 axis = Vector3(radius * Math::cos(theta), radius * Math::sin(theta), Math::sqrt(MAX(0.0f, 1.0f - u1))); + + direction = normal_xform.xform(axis); + + // We can skip multiplying throughput by cos(theta) because de sampling PDF is also cos(theta) and they cancel each other + //float pdf = normal.dot(direction); + //throughput *= normal.dot(direction)/pdf; + + LightmapRaycaster::Ray ray(position, direction, parameters.bias); + bool hit = raycaster->intersect(ray); + + if (!hit) { + if (parameters.environment_panorama.is_valid()) { + direction = parameters.environment_transform.xform_inv(direction); + Vector2 st = Vector2(Math::atan2(direction.z, direction.x), Math::acos(direction.y)); + + if (Math::is_nan(st.y)) { + st.y = direction.y > 0.0 ? 0.0 : Math_PI; + } + + st.x += Math_PI; + st /= Vector2(Math_TAU, Math_PI); + st.x = Math::fmod(st.x + 0.75, 1.0); + Color c = _bilinear_sample(parameters.environment_panorama, st, false, true); + color += throughput * Vector3(c.r, c.g, c.b) * c.a; + } + break; + } + + unsigned int hit_mesh_id = ray.geomID; + const Vector2i &size = mesh_instances[hit_mesh_id].size; + + int x = ray.u * size.x; + int y = ray.v * size.y; + + const int idx = scene_lightmap_indices[hit_mesh_id][y * size.x + x]; + + if (idx < 0) { + break; + } + + const LightmapTexel &sample = scene_lightmaps[hit_mesh_id][idx]; + + if (sample.normal.dot(ray.dir) > 0.0 && !no_shadow_meshes.has(hit_mesh_id)) { + // We hit a back-face + break; + } + + color += throughput * sample.emission; + throughput *= sample.albedo; + color += throughput * sample.direct_light; + + // Russian Roulette + // https://computergraphics.stackexchange.com/questions/2316/is-russian-roulette-really-the-answer + const float p = throughput[throughput.max_axis()]; + if (uniform_rand() > p) { + break; + } + throughput *= 1.0f / p; + + position = sample.pos; + normal = sample.normal; + } + accum += color; + } + + texel.output_light += accum / parameters.samples; +} + +void LightmapperCPU::_post_process(uint32_t p_idx, void *r_output) { + + const MeshInstance &mesh = mesh_instances[p_idx]; + + if (!mesh.generate_lightmap) { + return; + } + + LocalVector &indices = scene_lightmap_indices[p_idx]; + LocalVector &lightmap = scene_lightmaps[p_idx]; + Vector3 *output = ((LocalVector *)r_output)[p_idx].ptr(); + Vector2i size = mesh.size; + + // Blit texels to buffer + const int margin = 4; + for (int i = 0; i < size.y; i++) { + for (int j = 0; j < size.x; j++) { + int idx = indices[i * size.x + j]; + if (idx >= 0) { + output[i * size.x + j] = lightmap[idx].output_light; + continue; // filled, skip + } + + int closest_idx = -1; + float closest_dist = 1e20; + + for (int y = i - margin; y <= i + margin; y++) { + for (int x = j - margin; x <= j + margin; x++) { + + if (x == j && y == i) + continue; + if (x < 0 || x >= size.x) + continue; + if (y < 0 || y >= size.y) + continue; + int cell_idx = indices[y * size.x + x]; + if (cell_idx < 0) { + continue; //also ensures that blitted stuff is not reused + } + + float dist = Vector2(i - y, j - x).length_squared(); + if (dist < closest_dist) { + closest_dist = dist; + closest_idx = cell_idx; + } + } + } + + if (closest_idx != -1) { + output[i * size.x + j] = lightmap[closest_idx].output_light; + } + } + } + + lightmap.clear(); + + LocalVector seams; + _compute_seams(mesh, seams); + + _fix_seams(seams, output, size); + _dilate_lightmap(output, indices, size, margin); + + if (parameters.use_denoiser) { + Ref denoiser = LightmapDenoiser::create(); + + if (denoiser.is_valid()) { + int data_size = size.x * size.y * sizeof(Vector3); + Ref current_image; + current_image.instance(); + { + PoolByteArray data; + data.resize(data_size); + PoolByteArray::Write w = data.write(); + copymem(w.ptr(), output, data_size); + current_image->create(size.x, size.y, false, Image::FORMAT_RGBF, data); + } + + Ref denoised_image = denoiser->denoise_image(current_image); + + PoolByteArray denoised_data = denoised_image->get_data(); + denoised_image.unref(); + PoolByteArray::Read r = denoised_data.read(); + copymem(output, r.ptr(), data_size); + } + } + + _dilate_lightmap(output, indices, size, margin); + _fix_seams(seams, output, size); + _dilate_lightmap(output, indices, size, margin); + + indices.clear(); +} + +void LightmapperCPU::_compute_seams(const MeshInstance &p_mesh, LocalVector &r_seams) { + float max_uv_distance = 1.0f / MAX(p_mesh.size.x, p_mesh.size.y); + max_uv_distance *= max_uv_distance; // We use distance_to_squared(), so wee need to square the max distance as well + float max_pos_distance = 0.0005f; + float max_normal_distance = 0.05f; + + const Vector &points = p_mesh.data.points; + const Vector &uv2s = p_mesh.data.uv2; + const Vector &normals = p_mesh.data.normal; + + LocalVector edges; + edges.resize(points.size()); // One edge per vertex + + for (int i = 0; i < points.size(); i += 3) { + Vector3 triangle_vtxs[3] = { points[i + 0], points[i + 1], points[i + 2] }; + Vector2 triangle_uvs[3] = { uv2s[i + 0], uv2s[i + 1], uv2s[i + 2] }; + Vector3 triangle_normals[3] = { normals[i + 0], normals[i + 1], normals[i + 2] }; + + for (int k = 0; k < 3; k++) { + int idx[2]; + idx[0] = k; + idx[1] = (k + 1) % 3; + + if (triangle_vtxs[idx[1]] < triangle_vtxs[idx[0]]) { + SWAP(idx[0], idx[1]); + } + + SeamEdge e; + for (int l = 0; l < 2; ++l) { + e.pos[l] = triangle_vtxs[idx[l]]; + e.uv[l] = triangle_uvs[idx[l]]; + e.normal[l] = triangle_normals[idx[l]]; + } + edges[i + k] = e; + } + } + + edges.sort(); + + for (unsigned int j = 0; j < edges.size(); j++) { + const SeamEdge &edge0 = edges[j]; + for (unsigned int k = j + 1; k < edges.size() && edges[k].pos[0].x < (edge0.pos[0].x + max_pos_distance * 1.1f); k++) { + const SeamEdge &edge1 = edges[k]; + + if (edge0.uv[0].distance_squared_to(edge1.uv[0]) < max_uv_distance && edge0.uv[1].distance_squared_to(edge1.uv[1]) < max_uv_distance) { + continue; + } + + if (edge0.pos[0].distance_squared_to(edge1.pos[0]) > max_pos_distance || edge0.pos[1].distance_squared_to(edge1.pos[1]) > max_pos_distance) { + continue; + } + + if (edge0.normal[0].distance_squared_to(edge1.normal[0]) > max_normal_distance || edge0.normal[1].distance_squared_to(edge1.normal[1]) > max_normal_distance) { + continue; + } + + UVSeam s; + s.edge0[0] = edge0.uv[0]; + s.edge0[1] = edge0.uv[1]; + s.edge1[0] = edge1.uv[0]; + s.edge1[1] = edge1.uv[1]; + r_seams.push_back(s); + } + } +} + +void LightmapperCPU::_fix_seams(const LocalVector &p_seams, Vector3 *r_lightmap, Vector2i p_size) { + LocalVector extra_buffer; + extra_buffer.resize(p_size.x * p_size.y); + + copymem(extra_buffer.ptr(), r_lightmap, p_size.x * p_size.y * sizeof(Vector3)); + + Vector3 *read_ptr = extra_buffer.ptr(); + Vector3 *write_ptr = r_lightmap; + + for (int i = 0; i < 5; i++) { + for (unsigned int j = 0; j < p_seams.size(); j++) { + _fix_seam(p_seams[j].edge0[0], p_seams[j].edge0[1], p_seams[j].edge1[0], p_seams[j].edge1[1], read_ptr, write_ptr, p_size); + _fix_seam(p_seams[j].edge1[0], p_seams[j].edge1[1], p_seams[j].edge0[0], p_seams[j].edge0[1], read_ptr, write_ptr, p_size); + } + copymem(read_ptr, write_ptr, p_size.x * p_size.y * sizeof(Vector3)); + } +} + +void LightmapperCPU::_fix_seam(const Vector2 &p_pos0, const Vector2 &p_pos1, const Vector2 &p_uv0, const Vector2 &p_uv1, const Vector3 *p_read_buffer, Vector3 *r_write_buffer, const Vector2i &p_size) { + + Vector2 line[2]; + line[0] = p_pos0 * p_size; + line[1] = p_pos1 * p_size; + + const Vector2i start_pixel = line[0].floor(); + const Vector2i end_pixel = line[1].floor(); + + Vector2 seam_dir = (line[1] - line[0]).normalized(); + Vector2 t_delta = Vector2(1.0f / Math::abs(seam_dir.x), 1.0f / Math::abs(seam_dir.y)); + Vector2i step = Vector2(seam_dir.x > 0 ? 1 : (seam_dir.x < 0 ? -1 : 0), seam_dir.y > 0 ? 1 : (seam_dir.y < 0 ? -1 : 0)); + + Vector2 t_next = Vector2(Math::fmod(line[0].x, 1.0f), Math::fmod(line[0].y, 1.0f)); + + if (step.x == 1) { + t_next.x = 1.0f - t_next.x; + } + + if (step.y == 1) { + t_next.y = 1.0f - t_next.y; + } + + t_next.x /= Math::abs(seam_dir.x); + t_next.y /= Math::abs(seam_dir.y); + + if (Math::is_nan(t_next.x)) { + t_next.x = 1e20f; + } + + if (Math::is_nan(t_next.y)) { + t_next.y = 1e20f; + } + + Vector2i pixel = start_pixel; + Vector2 start_p = start_pixel; + float line_length = line[0].distance_to(line[1]); + + if (line_length == 0.0f) { + return; + } + + while (start_p.distance_to(pixel) < line_length + 1.0f) { + + Vector2 current_point = Vector2(pixel) + Vector2(0.5f, 0.5f); + current_point = Geometry::get_closest_point_to_segment_2d(current_point, line); + float t = line[0].distance_to(current_point) / line_length; + + Vector2 current_uv = p_uv0 * (1.0 - t) + p_uv1 * t; + Vector2i sampled_point = (current_uv * p_size).floor(); + + Vector3 current_color = r_write_buffer[pixel.y * p_size.x + pixel.x]; + Vector3 sampled_color = p_read_buffer[sampled_point.y * p_size.x + sampled_point.x]; + + r_write_buffer[pixel.y * p_size.x + pixel.x] = current_color * 0.6f + sampled_color * 0.4f; + + if (pixel == end_pixel) { + break; + } + + if (t_next.x < t_next.y) { + pixel.x += step.x; + t_next.x += t_delta.x; + } else { + pixel.y += step.y; + t_next.y += t_delta.y; + } + } +} + +void LightmapperCPU::_dilate_lightmap(Vector3 *r_lightmap, const LocalVector p_indices, Vector2i p_size, int margin) { + for (int i = 0; i < p_size.y; i++) { + for (int j = 0; j < p_size.x; j++) { + int idx = p_indices[i * p_size.x + j]; + if (idx >= 0) { + continue; //filled, skip + } + + Vector2i closest; + float closest_dist = 1e20; + + for (int y = i - margin; y <= i + margin; y++) { + for (int x = j - margin; x <= j + margin; x++) { + + if (x == j && y == i) + continue; + if (x < 0 || x >= p_size.x) + continue; + if (y < 0 || y >= p_size.y) + continue; + int cell_idx = p_indices[y * p_size.x + x]; + if (cell_idx < 0) { + continue; //also ensures that blitted stuff is not reused + } + + float dist = Vector2(i - y, j - x).length_squared(); + if (dist < closest_dist) { + closest_dist = dist; + closest = Vector2(x, y); + } + } + } + + if (closest_dist < 1e20) { + r_lightmap[i * p_size.x + j] = r_lightmap[closest.y * p_size.x + closest.x]; + } + } + } +} + +void LightmapperCPU::_blit_lightmap(const Vector &p_src, const Vector2i &p_size, Ref &p_dst, int p_x, int p_y, bool p_with_padding) { + int padding = p_with_padding ? 1 : 0; + ERR_FAIL_COND(p_x < padding || p_y < padding); + ERR_FAIL_COND(p_x + p_size.x > p_dst->get_width() - padding); + ERR_FAIL_COND(p_y + p_size.y > p_dst->get_height() - padding); + + p_dst->lock(); + for (int y = 0; y < p_size.y; y++) { + const Vector3 *__restrict src = p_src.ptr() + y * p_size.x; + for (int x = 0; x < p_size.x; x++) { + p_dst->set_pixel(p_x + x, p_y + y, Color(src->x, src->y, src->z)); + src++; + } + } + + if (p_with_padding) { + for (int y = -1; y < p_size.y + 1; y++) { + int yy = CLAMP(y, 0, p_size.y - 1); + int idx_left = yy * p_size.x; + int idx_right = idx_left + p_size.x - 1; + p_dst->set_pixel(p_x - 1, p_y + y, Color(p_src[idx_left].x, p_src[idx_left].y, p_src[idx_left].z)); + p_dst->set_pixel(p_x + p_size.x, p_y + y, Color(p_src[idx_right].x, p_src[idx_right].y, p_src[idx_right].z)); + } + + for (int x = -1; x < p_size.x + 1; x++) { + int xx = CLAMP(x, 0, p_size.x - 1); + int idx_top = xx; + int idx_bot = idx_top + (p_size.y - 1) * p_size.x; + p_dst->set_pixel(p_x + x, p_y - 1, Color(p_src[idx_top].x, p_src[idx_top].y, p_src[idx_top].z)); + p_dst->set_pixel(p_x + x, p_y + p_size.y, Color(p_src[idx_bot].x, p_src[idx_bot].y, p_src[idx_bot].z)); + } + } + p_dst->unlock(); +} + +LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, BakeStepFunc p_substep_function) { + + if (p_step_function) { + bool cancelled = p_step_function(0.0, TTR("Begin Bake"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + raycaster = LightmapRaycaster::create(); + + ERR_FAIL_COND_V(raycaster.is_null(), BAKE_ERROR_NO_RAYCASTER); + + // Collect parameters + parameters.use_denoiser = p_use_denoiser; + parameters.bias = p_bias; + parameters.bounces = p_bounces; + parameters.environment_transform = p_environment_transform; + parameters.environment_panorama = p_environment_panorama; + + switch (p_quality) { + case BAKE_QUALITY_LOW: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/low_quality_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/medium_quality_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/high_quality_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + parameters.samples = GLOBAL_GET("rendering/cpu_lightmapper/quality/ultra_quality_ray_count"); + } break; + } + + bake_textures.clear(); + + if (p_step_function) { + bool cancelled = p_step_function(0.1, TTR("Preparing data structures"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + raycaster->add_mesh(mesh_instances[i].data.points, mesh_instances[i].data.normal, mesh_instances[i].data.uv2, i); + } + raycaster->commit(); + + scene_lightmaps.resize(mesh_instances.size()); + scene_lightmap_indices.resize(mesh_instances.size()); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].cast_shadows) { + no_shadow_meshes.insert(i); + } + } + + raycaster->set_mesh_filter(no_shadow_meshes); + + Vector2i atlas_size = Vector2i(-1, -1); + int atlas_slices = -1; + if (p_generate_atlas) { + Error err = _layout_atlas(p_max_texture_size, &atlas_size, &atlas_slices); + if (err != OK) { + return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.2, TTR("Generate buffers"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(mesh_instances.size(), "Rasterizing meshes", &LightmapperCPU::_generate_buffer, nullptr, p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + const Size2i &size = mesh_instances[i].size; + bool has_alpha = false; + PoolVector alpha_data; + alpha_data.resize(size.x * size.y); + { + PoolVector::Write w = alpha_data.write(); + for (unsigned int j = 0; j < scene_lightmap_indices[i].size(); ++j) { + int idx = scene_lightmap_indices[i][j]; + uint8_t alpha = 0; + if (idx >= 0) { + alpha = CLAMP(scene_lightmaps[i][idx].alpha * 255, 0, 255); + if (alpha < 255) { + has_alpha = true; + } + } + w[j] = alpha; + } + } + + if (has_alpha) { + Ref alpha_texture; + alpha_texture.instance(); + alpha_texture->create(size.x, size.y, false, Image::FORMAT_L8, alpha_data); + raycaster->set_mesh_alpha_texture(alpha_texture, i); + } + } + + albedo_textures.clear(); + emission_textures.clear(); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (p_step_function) { + float p = float(i) / mesh_instances.size(); + bool cancelled = p_step_function(0.2 + p * 0.2, vformat("%s (%d/%d)", TTR("Direct lighting"), i, mesh_instances.size()), p_bake_userdata, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(scene_lightmaps[i].size(), "Computing direct light", &LightmapperCPU::_compute_direct_light, scene_lightmaps[i].ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + } + raycaster->clear_mesh_filter(); + + int n_lit_meshes = 0; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + n_lit_meshes++; + } + } + + if (parameters.environment_panorama.is_valid()) { + parameters.environment_panorama->lock(); + } + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + if (p_step_function) { + float p = float(i) / n_lit_meshes; + bool cancelled = p_step_function(0.4 + p * 0.4, vformat("%s (%d/%d)", TTR("Indirect lighting"), i, mesh_instances.size()), p_bake_userdata, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (!scene_lightmaps[i].empty()) { + if (_parallel_run(scene_lightmaps[i].size(), "Computing indirect light", &LightmapperCPU::_compute_indirect_light, scene_lightmaps[i].ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + } + } + if (parameters.environment_panorama.is_valid()) { + parameters.environment_panorama->unlock(); + } + + raycaster.unref(); // Not needed anymore, free some memory. + + LocalVector > lightmaps_data; + lightmaps_data.resize(mesh_instances.size()); + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (mesh_instances[i].generate_lightmap) { + const Vector2i size = mesh_instances[i].size; + lightmaps_data[i].resize(size.x * size.y); + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.8, TTR("Post processing"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + if (_parallel_run(mesh_instances.size(), "Denoise & fix seams", &LightmapperCPU::_post_process, lightmaps_data.ptr(), p_substep_function)) { + return BAKE_ERROR_USER_ABORTED; + } + + if (p_generate_atlas) { + bake_textures.resize(atlas_slices); + + for (int i = 0; i < atlas_slices; i++) { + Ref image; + image.instance(); + image->create(atlas_size.x, atlas_size.y, false, Image::FORMAT_RGBH); + bake_textures[i] = image; + } + } else { + bake_textures.resize(mesh_instances.size()); + + Set used_mesh_names; + + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + String mesh_name = mesh_instances[i].node_name; + if (mesh_name == "" || mesh_name.find(":") != -1 || mesh_name.find("/") != -1) { + mesh_name = "LightMap"; + } + + if (used_mesh_names.has(mesh_name)) { + int idx = 2; + String base = mesh_name; + while (true) { + mesh_name = base + itos(idx); + if (!used_mesh_names.has(mesh_name)) + break; + idx++; + } + } + used_mesh_names.insert(mesh_name); + + Ref image; + image.instance(); + image->create(mesh_instances[i].size.x, mesh_instances[i].size.y, false, Image::FORMAT_RGBH); + image->set_name(mesh_name); + bake_textures[i] = image; + } + } + + if (p_step_function) { + bool cancelled = p_step_function(0.9, TTR("Plotting lightmaps"), p_bake_userdata, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + { + int j = 0; + for (unsigned int i = 0; i < mesh_instances.size(); i++) { + if (!mesh_instances[i].generate_lightmap) { + continue; + } + + if (p_generate_atlas) { + _blit_lightmap(lightmaps_data[i], mesh_instances[i].size, bake_textures[mesh_instances[i].slice], mesh_instances[i].offset.x, mesh_instances[i].offset.y, true); + } else { + _blit_lightmap(lightmaps_data[i], mesh_instances[i].size, bake_textures[j], 0, 0, false); + } + j++; + } + } + + return BAKE_OK; +} + +int LightmapperCPU::get_bake_texture_count() const { + return bake_textures.size(); +} + +Ref LightmapperCPU::get_bake_texture(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)bake_textures.size(), Ref()); + return bake_textures[p_index]; +} + +int LightmapperCPU::get_bake_mesh_count() const { + return mesh_instances.size(); +} + +Variant LightmapperCPU::get_bake_mesh_userdata(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)mesh_instances.size(), Variant()); + return mesh_instances[p_index].data.userdata; +} + +Rect2 LightmapperCPU::get_bake_mesh_uv_scale(int p_index) const { + ERR_FAIL_COND_V(bake_textures.size() == 0, Rect2()); + Rect2 uv_ofs; + Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height()); + uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size; + uv_ofs.size = Vector2(mesh_instances[p_index].size) / atlas_size; + return uv_ofs; +} + +int LightmapperCPU::get_bake_mesh_texture_slice(int p_index) const { + ERR_FAIL_INDEX_V(p_index, (int)mesh_instances.size(), Variant()); + return mesh_instances[p_index].slice; +} + +void LightmapperCPU::add_albedo_texture(Ref p_texture) { + if (p_texture.is_null()) { + return; + } + + RID texture_rid = p_texture->get_rid(); + if (!texture_rid.is_valid() || albedo_textures.has(texture_rid)) { + return; + } + + Ref texture_data = p_texture->get_data(); + + if (texture_data.is_null()) { + return; + } + + if (texture_data->is_compressed()) { + texture_data->decompress(); + } + + texture_data->convert(Image::FORMAT_RGBA8); + + albedo_textures.insert(texture_rid, texture_data); +} + +void LightmapperCPU::add_emission_texture(Ref p_texture) { + if (p_texture.is_null()) { + return; + } + + RID texture_rid = p_texture->get_rid(); + if (!texture_rid.is_valid() || emission_textures.has(texture_rid)) { + return; + } + + Ref texture_data = p_texture->get_data(); + + if (texture_data.is_null()) { + return; + } + + if (texture_data->is_compressed()) { + texture_data->decompress(); + } + + texture_data->convert(Image::FORMAT_RGBH); + + emission_textures.insert(texture_rid, texture_data); +} + +void LightmapperCPU::add_mesh(const MeshData &p_mesh, Vector2i p_size) { + ERR_FAIL_COND(p_mesh.points.size() == 0); + ERR_FAIL_COND(p_mesh.points.size() != p_mesh.uv2.size()); + ERR_FAIL_COND(p_mesh.points.size() != p_mesh.normal.size()); + ERR_FAIL_COND(!p_mesh.uv.empty() && p_mesh.points.size() != p_mesh.uv.size()); + ERR_FAIL_COND(p_mesh.surface_facecounts.size() != p_mesh.albedo.size()); + ERR_FAIL_COND(p_mesh.surface_facecounts.size() != p_mesh.emission.size()); + + MeshInstance mi; + mi.data = p_mesh; + mi.size = p_size; + mi.generate_lightmap = true; + mi.cast_shadows = true; + mi.node_name = ""; + + Dictionary userdata = p_mesh.userdata; + if (userdata.has("cast_shadows")) { + mi.cast_shadows = userdata["cast_shadows"]; + } + if (userdata.has("generate_lightmap")) { + mi.generate_lightmap = userdata["generate_lightmap"]; + } + if (userdata.has("node_name")) { + mi.node_name = userdata["node_name"]; + } + + mesh_instances.push_back(mi); +} + +void LightmapperCPU::add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier) { + Light l; + l.type = LIGHT_TYPE_DIRECTIONAL; + l.direction = p_direction; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +void LightmapperCPU::add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation) { + Light l; + l.type = LIGHT_TYPE_OMNI; + l.position = p_position; + l.range = p_range; + l.attenuation = p_attenuation; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +void LightmapperCPU::add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation) { + Light l; + l.type = LIGHT_TYPE_SPOT; + l.position = p_position; + l.direction = p_direction; + l.range = p_range; + l.attenuation = p_attenuation; + l.spot_angle = Math::deg2rad(p_spot_angle); + l.spot_attenuation = p_spot_attenuation; + l.color = p_color; + l.energy = p_energy; + l.indirect_multiplier = p_indirect_multiplier; + l.bake_direct = p_bake_direct; + lights.push_back(l); +} + +LightmapperCPU::LightmapperCPU() { + thread_progress = 0; + thread_cancelled = false; +} diff --git a/modules/lightmapper_cpu/lightmapper_cpu.h b/modules/lightmapper_cpu/lightmapper_cpu.h new file mode 100644 index 00000000000..e809240099b --- /dev/null +++ b/modules/lightmapper_cpu/lightmapper_cpu.h @@ -0,0 +1,182 @@ +/*************************************************************************/ +/* lightmapper_cpu.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_CPU_H +#define LIGHTMAPPER_CPU_H + +#include "core/local_vector.h" +#include "scene/3d/lightmapper.h" +#include "scene/resources/mesh.h" +#include "scene/resources/surface_tool.h" +#include + +class LightmapperCPU : public Lightmapper { + GDCLASS(LightmapperCPU, Lightmapper) + + struct MeshInstance { + MeshData data; + int slice = 0; + Vector2i offset; + Vector2i size; + bool cast_shadows; + bool generate_lightmap; + String node_name; + }; + + struct Light { + Vector3 position; + uint32_t type = LIGHT_TYPE_DIRECTIONAL; + Vector3 direction; + float energy; + float indirect_multiplier; + Color color; + float range; + float attenuation; + float spot_angle; + float spot_attenuation; + bool bake_direct; + }; + + struct LightmapTexel { + Vector3 albedo; + float alpha; + Vector3 emission; + Vector3 pos; + Vector3 normal; + + Vector3 direct_light; + Vector3 output_light; + + float area_coverage; + }; + + struct BakeParams { + float bias; + int bounces; + int samples; + bool use_denoiser = true; + Ref environment_panorama; + Basis environment_transform; + }; + + struct UVSeam { + Vector2 edge0[2]; + Vector2 edge1[2]; + }; + + struct SeamEdge { + Vector3 pos[2]; + Vector3 normal[2]; + Vector2 uv[2]; + + _FORCE_INLINE_ bool operator<(const SeamEdge &p_edge) const { + return pos[0].x < p_edge.pos[0].x; + } + }; + + struct AtlasOffset { + int slice; + int x; + int y; + }; + + struct ThreadData; + + typedef void (LightmapperCPU::*BakeThreadFunc)(uint32_t, void *); + + struct ThreadData { + LightmapperCPU *instance; + uint32_t count; + BakeThreadFunc thread_func; + void *userdata; + }; + + BakeParams parameters; + + LocalVector > bake_textures; + Map > albedo_textures; + Map > emission_textures; + + LocalVector mesh_instances; + LocalVector lights; + + LocalVector > scene_lightmaps; + LocalVector > scene_lightmap_indices; + Set no_shadow_meshes; + + std::atomic thread_progress; + std::atomic thread_cancelled; + + Ref raycaster; + + Error _layout_atlas(int p_max_size, Vector2i *r_atlas_size, int *r_atlas_slices); + + static void _thread_func_callback(void *p_thread_data); + void _thread_func_wrapper(uint32_t p_idx, ThreadData *p_thread_data); + bool _parallel_run(int p_count, const String &p_description, BakeThreadFunc p_thread_func, void *p_userdata, BakeStepFunc p_substep_func = nullptr); + + void _generate_buffer(uint32_t p_idx, void *p_unused); + Ref _init_bake_texture(const MeshData::TextureDef &p_texture_def, const Map > &p_tex_cache, Image::Format p_default_format); + Color _bilinear_sample(const Ref &p_img, const Vector2 &p_uv, bool p_clamp_x = false, bool p_clamp_y = false); + Vector3 _fix_sample_position(const Vector3 &p_position, const Vector3 &p_texel_center, const Vector3 &p_normal, const Vector3 &p_tangent, const Vector3 &p_bitangent, const Vector2 &p_texel_size); + void _plot_triangle(const Vector2 *p_vertices, const Vector3 *p_positions, const Vector3 *p_normals, const Vector2 *p_uvs, const Ref &p_albedo_texture, const Ref &p_emission_texture, Vector2i p_size, LocalVector &r_texels, LocalVector &r_lightmap_indices); + + void _compute_direct_light(uint32_t p_idx, void *r_lightmap); + + void _compute_indirect_light(uint32_t p_idx, void *r_lightmap); + + void _post_process(uint32_t p_idx, void *r_output); + void _compute_seams(const MeshInstance &p_mesh, LocalVector &r_seams); + void _fix_seams(const LocalVector &p_seams, Vector3 *r_lightmap, Vector2i p_size); + void _fix_seam(const Vector2 &p_pos0, const Vector2 &p_pos1, const Vector2 &p_uv0, const Vector2 &p_uv1, const Vector3 *p_read_buffer, Vector3 *r_write_buffer, const Vector2i &p_size); + void _dilate_lightmap(Vector3 *r_lightmap, const LocalVector p_indices, Vector2i p_size, int margin); + + void _blit_lightmap(const Vector &p_src, const Vector2i &p_size, Ref &p_dst, int p_x, int p_y, bool p_with_padding); + +public: + virtual void add_albedo_texture(Ref p_texture); + virtual void add_emission_texture(Ref p_texture); + virtual void add_mesh(const MeshData &p_mesh, Vector2i p_size); + virtual void add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier); + virtual void add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation); + virtual void add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation); + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, BakeStepFunc p_substep_function = nullptr); + + int get_bake_texture_count() const; + Ref get_bake_texture(int p_index) const; + int get_bake_mesh_count() const; + Variant get_bake_mesh_userdata(int p_index) const; + Rect2 get_bake_mesh_uv_scale(int p_index) const; + int get_bake_mesh_texture_slice(int p_index) const; + + LightmapperCPU(); +}; + +#endif // LIGHTMAPPER_H diff --git a/modules/lightmapper_cpu/register_types.cpp b/modules/lightmapper_cpu/register_types.cpp new file mode 100644 index 00000000000..ccf443669f0 --- /dev/null +++ b/modules/lightmapper_cpu/register_types.cpp @@ -0,0 +1,55 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "register_types.h" + +#include "core/project_settings.h" +#include "lightmapper_cpu.h" +#include "scene/3d/lightmapper.h" + +#ifndef _3D_DISABLED +static Lightmapper *create_lightmapper_cpu() { + return memnew(LightmapperCPU); +} +#endif + +void register_lightmapper_cpu_types() { + GLOBAL_DEF("rendering/cpu_lightmapper/quality/low_quality_ray_count", 64); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/medium_quality_ray_count", 256); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/high_quality_ray_count", 512); + GLOBAL_DEF("rendering/cpu_lightmapper/quality/ultra_quality_ray_count", 1024); +#ifndef _3D_DISABLED + ClassDB::register_class(); + Lightmapper::create_cpu = create_lightmapper_cpu; +#endif +} + +void unregister_lightmapper_cpu_types() { +} diff --git a/modules/lightmapper_cpu/register_types.h b/modules/lightmapper_cpu/register_types.h new file mode 100644 index 00000000000..11594184c6b --- /dev/null +++ b/modules/lightmapper_cpu/register_types.h @@ -0,0 +1,37 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_CPU_REGISTER_TYPES_H +#define LIGHTMAPPER_CPU_REGISTER_TYPES_H + +void register_lightmapper_cpu_types(); +void unregister_lightmapper_cpu_types(); + +#endif // LIGHTMAPPER_CPU_REGISTER_TYPES_H diff --git a/modules/xatlas_unwrap/register_types.cpp b/modules/xatlas_unwrap/register_types.cpp index 95339277e2a..72026a2a411 100644 --- a/modules/xatlas_unwrap/register_types.cpp +++ b/modules/xatlas_unwrap/register_types.cpp @@ -41,7 +41,7 @@ extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const flo bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, const int *p_face_materials, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y) { - //set up input mesh + // set up input mesh xatlas::MeshDecl input_mesh; input_mesh.indexData = p_indices; input_mesh.indexCount = p_index_count; @@ -56,18 +56,19 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver input_mesh.vertexUvStride = 0; xatlas::ChartOptions chart_options; - xatlas::PackOptions pack_options; + chart_options.fixWinding = true; - pack_options.maxChartSize = 4096; + xatlas::PackOptions pack_options; + pack_options.padding = 1; + pack_options.maxChartSize = 4094; // Lightmap atlassing needs 2 for padding between meshes, so 4096-2 pack_options.blockAlign = true; pack_options.texelsPerUnit = 1.0 / p_texel_size; xatlas::Atlas *atlas = xatlas::Create(); - printf("Adding mesh..\n"); + xatlas::AddMeshError err = xatlas::AddMesh(atlas, input_mesh, 1); ERR_FAIL_COND_V_MSG(err != xatlas::AddMeshError::Success, false, xatlas::StringForEnum(err)); - printf("Generate..\n"); xatlas::Generate(atlas, chart_options, pack_options); *r_size_hint_x = atlas->width; @@ -96,7 +97,6 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver max_y = MAX(max_y, output.vertexArray[i].uv[1]); } - printf("Final texture size: %f,%f - max %f,%f\n", w, h, max_x, max_y); *r_vertex_count = output.vertexCount; for (uint32_t i = 0; i < output.indexCount; i++) { @@ -106,7 +106,7 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver *r_index_count = output.indexCount; xatlas::Destroy(atlas); - printf("Done\n"); + return true; } diff --git a/scene/3d/baked_lightmap.cpp b/scene/3d/baked_lightmap.cpp index 5fbec7b3b25..642f1cb0465 100644 --- a/scene/3d/baked_lightmap.cpp +++ b/scene/3d/baked_lightmap.cpp @@ -31,6 +31,7 @@ #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" @@ -86,12 +87,21 @@ float BakedLightmapData::get_energy() const { return energy; } -void BakedLightmapData::add_user(const NodePath &p_path, const Ref &p_lightmap, int p_instance) { +void BakedLightmapData::add_user(const NodePath &p_path, const Ref &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance) { ERR_FAIL_COND_MSG(p_lightmap.is_null(), "It's not a reference to a valid Texture object."); + ERR_FAIL_COND(p_lightmap_slice == -1 && !Object::cast_to(p_lightmap.ptr())); + ERR_FAIL_COND(p_lightmap_slice != -1 && !Object::cast_to(p_lightmap.ptr())); + User user; user.path = p_path; - user.lightmap = p_lightmap; + 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); } @@ -105,10 +115,26 @@ NodePath BakedLightmapData::get_user_path(int p_user) const { ERR_FAIL_INDEX_V(p_user, users.size(), NodePath()); return users[p_user].path; } -Ref BakedLightmapData::get_user_lightmap(int p_user) const { +Ref BakedLightmapData::get_user_lightmap(int p_user) const { - ERR_FAIL_INDEX_V(p_user, users.size(), Ref()); - return users[p_user].lightmap; + ERR_FAIL_INDEX_V(p_user, users.size(), Ref()); + if (users[p_user].lightmap_slice == -1) { + return users[p_user].lightmap.single; + } else { + return users[p_user].lightmap.layered; + } +} + +int BakedLightmapData::get_user_lightmap_slice(int p_user) const { + + ERR_FAIL_INDEX_V(p_user, users.size(), -1); + return users[p_user].lightmap_slice; +} + +Rect2 BakedLightmapData::get_user_lightmap_uv_rect(int p_user) const { + + ERR_FAIL_INDEX_V(p_user, users.size(), Rect2(0, 0, 1, 1)); + return users[p_user].lightmap_uv_rect; } int BakedLightmapData::get_user_instance(int p_user) const { @@ -123,10 +149,39 @@ void BakedLightmapData::clear_users() { void BakedLightmapData::_set_user_data(const Array &p_data) { - ERR_FAIL_COND((p_data.size() % 3) != 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_PRINTS("Geometry at path " + String(p_data[0]) + " is using old lightmapper data. Please re-bake."); +#endif + Array adapted_data; + adapted_data.resize((p_data.size() / 3) * 5); + for (int i = 0; i < p_data.size() / 3; i++) { + adapted_data[i * 5 + 0] = p_data[i * 3 + 0]; + adapted_data[i * 5 + 1] = p_data[i * 3 + 1]; + adapted_data[i * 5 + 2] = -1; + adapted_data[i * 5 + 3] = Rect2(0, 0, 1, 1); + adapted_data[i * 5 + 4] = p_data[i * 3 + 2]; + } + _set_user_data(adapted_data); + return; + } + } - for (int i = 0; i < p_data.size(); i += 3) { - add_user(p_data[i], p_data[i + 1], p_data[i + 2]); + 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]); } } @@ -135,7 +190,9 @@ 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); + ret.push_back(users[i].lightmap_slice == -1 ? Ref(users[i].lightmap.single) : Ref(users[i].lightmap.layered)); + ret.push_back(users[i].lightmap_slice); + ret.push_back(users[i].lightmap_uv_rect); ret.push_back(users[i].instance_index); } return ret; @@ -164,7 +221,7 @@ void BakedLightmapData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmapData::set_energy); ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmapData::get_energy); - ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap", "instance"), &BakedLightmapData::add_user); + 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); @@ -192,20 +249,782 @@ BakedLightmapData::~BakedLightmapData() { /////////////////////////// -BakedLightmap::BakeBeginFunc BakedLightmap::bake_begin_function = NULL; -BakedLightmap::BakeStepFunc BakedLightmap::bake_step_function = NULL; -BakedLightmap::BakeEndFunc BakedLightmap::bake_end_function = NULL; +Lightmapper::BakeStepFunc BakedLightmap::bake_step_function; +Lightmapper::BakeStepFunc BakedLightmap::bake_substep_function; -void BakedLightmap::set_bake_cell_size(float p_cell_size) { - bake_cell_size = p_cell_size; +Size2i BakedLightmap::_compute_lightmap_size(const MeshesFound &p_mesh) { + double area = 0; + double uv_area = 0; + for (int i = 0; i < p_mesh.mesh->get_surface_count(); i++) { + Array arrays = p_mesh.mesh->surface_get_arrays(i); + PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; + PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; + PoolVector indices = arrays[Mesh::ARRAY_INDEX]; + + ERR_FAIL_COND_V(vertices.size() == 0, Vector2()); + ERR_FAIL_COND_V(uv2.size() == 0, Vector2()); + + int vc = vertices.size(); + PoolVector::Read vr = vertices.read(); + PoolVector::Read u2r = uv2.read(); + PoolVector::Read ir; + int ic = 0; + + if (indices.size()) { + ic = indices.size(); + ir = indices.read(); + } + + int faces = ic ? ic / 3 : vc / 3; + for (int j = 0; j < faces; j++) { + Vector3 vertex[3]; + Vector2 uv[3]; + + for (int k = 0; k < 3; k++) { + int idx = ic ? ir[j * 3 + k] : j * 3 + k; + vertex[k] = p_mesh.xform.xform(vr[idx]); + uv[k] = u2r[idx]; + } + + Vector3 p1 = vertex[0]; + Vector3 p2 = vertex[1]; + Vector3 p3 = vertex[2]; + double a = p1.distance_to(p2); + double b = p2.distance_to(p3); + double c = p3.distance_to(p1); + double halfPerimeter = (a + b + c) / 2.0; + area += sqrt(halfPerimeter * (halfPerimeter - a) * (halfPerimeter - b) * (halfPerimeter - c)); + + Vector2 uv_p1 = uv[0]; + Vector2 uv_p2 = uv[1]; + Vector2 uv_p3 = uv[2]; + double uv_a = uv_p1.distance_to(uv_p2); + double uv_b = uv_p2.distance_to(uv_p3); + double uv_c = uv_p3.distance_to(uv_p1); + double uv_halfPerimeter = (uv_a + uv_b + uv_c) / 2.0; + uv_area += sqrt( + uv_halfPerimeter * (uv_halfPerimeter - uv_a) * (uv_halfPerimeter - uv_b) * (uv_halfPerimeter - uv_c)); + } + } + + if (uv_area < 0.0001f) { + uv_area = 1.0; + } + + int pixels = Math::round(ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); + int size = CLAMP(pixels, 2, 4096); + return Vector2i(size, size); } -float BakedLightmap::get_bake_cell_size() const { - return bake_cell_size; +void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, Vector &meshes, Vector &lights) { + MeshInstance *mi = Object::cast_to(p_at_node); + if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) { + Ref mesh = mi->get_mesh(); + if (mesh.is_valid()) { + bool all_have_uv2_and_normal = true; + bool surfaces_found = false; + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_TEX_UV2)) { + all_have_uv2_and_normal = false; + break; + } + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_NORMAL)) { + all_have_uv2_and_normal = false; + break; + } + surfaces_found = true; + } + + if (surfaces_found && all_have_uv2_and_normal) { + MeshesFound mf; + mf.cast_shadows = mi->get_cast_shadows_setting() != GeometryInstance::SHADOW_CASTING_SETTING_OFF; + mf.generate_lightmap = mi->get_generate_lightmap(); + mf.xform = get_global_transform().affine_inverse() * mi->get_global_transform(); + mf.node_path = get_path_to(mi); + mf.subindex = -1; + mf.mesh = mesh; + + static const int lightmap_scale[4] = { 1, 2, 4, 8 }; //GeometryInstance3D::LIGHTMAP_SCALE_MAX = { 1, 2, 4, 8 }; + mf.lightmap_scale = lightmap_scale[mi->get_lightmap_scale()]; + + Ref all_override = mi->get_material_override(); + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (all_override.is_valid()) { + mf.overrides.push_back(all_override); + } else { + mf.overrides.push_back(mi->get_surface_material(i)); + } + } + + meshes.push_back(mf); + } + } + } + + Spatial *s = Object::cast_to(p_at_node); + + if (!mi && s) { + Array bmeshes = p_at_node->call("get_bake_meshes"); + if (bmeshes.size() && (bmeshes.size() & 1) == 0) { + Transform xf = get_global_transform().affine_inverse() * s->get_global_transform(); + for (int i = 0; i < bmeshes.size(); i += 2) { + Ref mesh = bmeshes[i]; + if (!mesh.is_valid()) { + continue; + } + + MeshesFound mf; + + Transform mesh_xf = bmeshes[i + 1]; + mf.xform = xf * mesh_xf; + mf.node_path = get_path_to(s); + mf.subindex = i / 2; + mf.lightmap_scale = 1; + mf.mesh = mesh; + + meshes.push_back(mf); + } + } + } + + Light *light = Object::cast_to(p_at_node); + + if (light && light->get_bake_mode() != Light::BAKE_DISABLED) { + LightsFound lf; + lf.xform = get_global_transform().affine_inverse() * light->get_global_transform(); + lf.light = light; + lights.push_back(lf); + } + + for (int i = 0; i < p_at_node->get_child_count(); i++) { + Node *child = p_at_node->get_child(i); + if (!child->get_owner()) { + continue; //maybe a helper + } + + _find_meshes_and_lights(child, meshes, lights); + } +} + +void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightmapper::MeshData &r_mesh_data, Vector > &r_albedo_textures, Vector > &r_emission_textures) { + + for (int i = 0; i < p_found_mesh.mesh->get_surface_count(); ++i) { + Ref mat = p_found_mesh.overrides[i]; + + if (mat.is_null()) { + mat = p_found_mesh.mesh->surface_get_material(i); + } + + Ref albedo_texture; + Color albedo_add = Color(0, 0, 0, 0); + Color albedo_mul = Color(1, 1, 1, 1); + + Ref emission_texture; + Color emission_add = Color(0, 0, 0, 0); + Color emission_mul = Color(1, 1, 1, 1); + + if (mat.is_valid()) { + + albedo_texture = mat->get_texture(SpatialMaterial::TEXTURE_ALBEDO); + + if (albedo_texture.is_valid()) { + albedo_mul = mat->get_albedo(); + } else { + albedo_add = mat->get_albedo(); + } + + emission_texture = mat->get_texture(SpatialMaterial::TEXTURE_EMISSION); + Color emission_color = mat->get_emission(); + float emission_energy = mat->get_emission_energy(); + + if (mat->get_emission_operator() == SpatialMaterial::EMISSION_OP_ADD) { + emission_mul = Color(1, 1, 1) * emission_energy; + emission_add = emission_color * emission_energy; + } else { + emission_mul = emission_color * emission_energy; + emission_add = Color(0, 0, 0); + } + } + + Lightmapper::MeshData::TextureDef albedo; + albedo.mul = albedo_mul; + albedo.add = albedo_add; + + if (albedo_texture.is_valid()) { + albedo.tex_rid = albedo_texture->get_rid(); + r_albedo_textures.push_back(albedo_texture); + } + + r_mesh_data.albedo.push_back(albedo); + + Lightmapper::MeshData::TextureDef emission; + emission.mul = emission_mul; + emission.add = emission_add; + + if (emission_texture.is_valid()) { + emission.tex_rid = emission_texture->get_rid(); + r_emission_textures.push_back(emission_texture); + } + r_mesh_data.emission.push_back(emission); + } +} + +bool BakedLightmap::_lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh) { + BakeStepUD *bsud = (BakeStepUD *)ud; + bool ret = false; + if (bsud->func) { + ret = bsud->func(bsud->from_percent + p_completion * (bsud->to_percent - bsud->from_percent), p_text, bsud->ud, p_refresh); + } + return ret; +} + +BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_save_path) { + + bool no_save_path = false; + if (p_data_save_path == "" && (get_light_data().is_null() || !get_light_data()->get_path().is_resource_file())) { + no_save_path = true; + } + + if (p_data_save_path == "") { + if (get_light_data().is_null()) { + no_save_path = true; + } else { + p_data_save_path = get_light_data()->get_path(); + if (!p_data_save_path.is_resource_file()) { + no_save_path = true; + } + } + } + + if (no_save_path) { + + if (image_path == "") { + return BAKE_ERROR_NO_SAVE_PATH; + } else { + p_data_save_path = image_path; + } + + WARN_PRINT("Using the deprecated property \"image_path\" as a save path, consider providing a better save path via the \"data_save_path\" parameter"); + p_data_save_path = image_path.plus_file("BakedLightmap.lmbake"); + } + + { + //check for valid save path + DirAccessRef d = DirAccess::open(p_data_save_path.get_base_dir()); + if (!d) { + ERR_FAIL_V_MSG(BAKE_ERROR_NO_SAVE_PATH, "Invalid save path '" + p_data_save_path + "'."); + } + } + + if (bake_step_function) { + bool cancelled = bake_step_function(0.0, TTR("Finding meshes and lights"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + Ref lightmapper = Lightmapper::create(); + ERR_FAIL_COND_V(lightmapper.is_null(), BAKE_ERROR_NO_LIGHTMAPPER); + + Vector lights_found; + Vector meshes_found; + + _find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), meshes_found, lights_found); + + if (meshes_found.size() == 0) { + return BAKE_ERROR_NO_MESHES; + } + + for (int m_i = 0; m_i < meshes_found.size(); m_i++) { + if (bake_step_function) { + float p = (float)(m_i) / meshes_found.size(); + bool cancelled = bake_step_function(p * 0.05, vformat(TTR("Preparing geometry (%d/%d)"), m_i + 1, meshes_found.size()), nullptr, false); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + MeshesFound &mf = meshes_found.write[m_i]; + + Size2i lightmap_size = mf.mesh->get_lightmap_size_hint(); + + if (lightmap_size == Vector2i(0, 0)) { + lightmap_size = _compute_lightmap_size(mf); + } + lightmap_size *= mf.lightmap_scale; + + Lightmapper::MeshData md; + + { + Dictionary d; + d["path"] = mf.node_path; + if (mf.subindex >= 0) { + d["subindex"] = mf.subindex; + } + d["cast_shadows"] = mf.cast_shadows; + d["generate_lightmap"] = mf.generate_lightmap; + d["node_name"] = mf.node_path.get_name(mf.node_path.get_name_count() - 1); + md.userdata = d; + } + + Basis normal_xform = mf.xform.basis.inverse().transposed(); + + for (int i = 0; i < mf.mesh->get_surface_count(); i++) { + if (mf.mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + Array a = mf.mesh->surface_get_arrays(i); + + Vector vertices = a[Mesh::ARRAY_VERTEX]; + const Vector3 *vr = vertices.ptr(); + Vector uv2 = a[Mesh::ARRAY_TEX_UV2]; + const Vector2 *uv2r = nullptr; + Vector uv = a[Mesh::ARRAY_TEX_UV]; + const Vector2 *uvr = nullptr; + Vector normals = a[Mesh::ARRAY_NORMAL]; + const Vector3 *nr = nullptr; + Vector index = a[Mesh::ARRAY_INDEX]; + + ERR_CONTINUE(uv2.size() == 0); + ERR_CONTINUE(normals.size() == 0); + + if (!uv.empty()) { + uvr = uv.ptr(); + } + + uv2r = uv2.ptr(); + nr = normals.ptr(); + + int facecount; + const int *ir = nullptr; + + if (index.size()) { + facecount = index.size() / 3; + ir = index.ptr(); + } else { + facecount = vertices.size() / 3; + } + + md.surface_facecounts.push_back(facecount); + + for (int j = 0; j < facecount; j++) { + uint32_t vidx[3]; + + if (ir) { + for (int k = 0; k < 3; k++) { + vidx[k] = ir[j * 3 + k]; + } + } else { + for (int k = 0; k < 3; k++) { + vidx[k] = j * 3 + k; + } + } + + for (int k = 0; k < 3; k++) { + Vector3 v = mf.xform.xform(vr[vidx[k]]); + md.points.push_back(v); + + md.uv2.push_back(uv2r[vidx[k]]); + md.normal.push_back(normal_xform.xform(nr[vidx[k]]).normalized()); + + if (uvr != nullptr) { + md.uv.push_back(uvr[vidx[k]]); + } + } + } + } + + Vector > albedo_textures; + Vector > emission_textures; + + _get_material_images(mf, md, albedo_textures, emission_textures); + + for (int j = 0; j < albedo_textures.size(); j++) { + lightmapper->add_albedo_texture(albedo_textures[j]); + } + + for (int j = 0; j < emission_textures.size(); j++) { + lightmapper->add_emission_texture(emission_textures[j]); + } + + lightmapper->add_mesh(md, lightmap_size); + } + + for (int i = 0; i < lights_found.size(); i++) { + Light *light = lights_found[i].light; + Transform xf = lights_found[i].xform; + + if (Object::cast_to(light)) { + DirectionalLight *l = Object::cast_to(light); + lightmapper->add_directional_light(light->get_bake_mode() == Light::BAKE_ALL, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY)); + } else if (Object::cast_to(light)) { + OmniLight *l = Object::cast_to(light); + lightmapper->add_omni_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION)); + } else if (Object::cast_to(light)) { + SpotLight *l = Object::cast_to(light); + lightmapper->add_spot_light(light->get_bake_mode() == Light::BAKE_ALL, xf.origin, -xf.basis.get_axis(Vector3::AXIS_Z).normalized(), l->get_color(), l->get_param(Light::PARAM_ENERGY), l->get_param(Light::PARAM_INDIRECT_ENERGY), l->get_param(Light::PARAM_RANGE), l->get_param(Light::PARAM_ATTENUATION), l->get_param(Light::PARAM_SPOT_ANGLE), l->get_param(Light::PARAM_SPOT_ATTENUATION)); + } + } + + Ref environment_image; + Basis environment_xform; + + if (environment_mode != ENVIRONMENT_MODE_DISABLED) { + if (bake_step_function) { + bake_step_function(0.1, TTR("Preparing environment"), nullptr, true); + } + + switch (environment_mode) { + case ENVIRONMENT_MODE_DISABLED: { + //nothing + } break; + case ENVIRONMENT_MODE_SCENE: { + Ref world = get_world(); + if (world.is_valid()) { + Ref env = world->get_environment(); + if (env.is_null()) { + env = world->get_fallback_environment(); + } + + if (env.is_valid()) { + environment_image = _get_irradiance_map(env, Vector2i(128, 64)); + environment_xform = get_global_transform().affine_inverse().basis * env->get_sky_orientation(); + } + } + } break; + case ENVIRONMENT_MODE_CUSTOM_SKY: { + if (environment_custom_sky.is_valid()) { + environment_image = _get_irradiance_from_sky(environment_custom_sky, Vector2i(128, 64)); + print_line(vformat("env -> %s", environment_custom_sky_rotation_degrees)); + environment_xform.set_euler(environment_custom_sky_rotation_degrees * Math_PI / 180.0); + } + + } break; + case ENVIRONMENT_MODE_CUSTOM_COLOR: { + environment_image.instance(); + environment_image->create(128, 64, false, Image::FORMAT_RGBF); + Color c = environment_custom_color; + c.r *= environment_custom_energy; + c.g *= environment_custom_energy; + c.b *= environment_custom_energy; + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 64; j++) { + environment_image->set_pixel(i, j, c); + } + } + + } break; + } + } + + BakeStepUD bsud; + bsud.func = bake_step_function; + bsud.ud = nullptr; + bsud.from_percent = 0.1; + bsud.to_percent = 0.9; + + Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, generate_atlas, max_atlas_size, environment_image, environment_xform, _lightmap_bake_step_function, &bsud, bake_substep_function); + + if (bake_err != Lightmapper::BAKE_OK) { + switch (bake_err) { + case Lightmapper::BAKE_ERROR_USER_ABORTED: { + return BAKE_ERROR_USER_ABORTED; + } + case Lightmapper::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { + return BAKE_ERROR_LIGHTMAP_SIZE; + } + case Lightmapper::BAKE_ERROR_NO_MESHES: { + return BAKE_ERROR_NO_MESHES; + } + default: { + } + } + return BAKE_ERROR_NO_LIGHTMAPPER; + } + + Ref data; + if (get_light_data().is_valid()) { + data = get_light_data(); + set_light_data(Ref()); //clear + data->clear_users(); + } else { + data.instance(); + } + + if (capture_enabled) { + + if (bake_step_function) { + bool cancelled = bake_step_function(0.85, TTR("Generating capture"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + VoxelLightBaker voxel_baker; + + int bake_subdiv; + int capture_subdiv; + AABB bake_bounds; + { + bake_bounds = AABB(-extents, extents * 2.0); + int subdiv = nearest_power_of_2_templated(int(bake_bounds.get_longest_axis_size() / capture_cell_size)); + bake_bounds.size[bake_bounds.get_longest_axis_index()] = subdiv * capture_cell_size; + bake_subdiv = nearest_shift(subdiv) + 1; + + capture_subdiv = bake_subdiv; + float css = capture_cell_size; + while (css < capture_cell_size && capture_subdiv > 2) { + capture_subdiv--; + css *= 2.0; + } + } + + voxel_baker.begin_bake(capture_subdiv + 1, bake_bounds); + + for (int mesh_id = 0; mesh_id < meshes_found.size(); mesh_id++) { + MeshesFound &mf = meshes_found.write[mesh_id]; + voxel_baker.plot_mesh(mf.xform, mf.mesh, mf.overrides, Ref()); + } + + VoxelLightBaker::BakeQuality capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_HIGH; + if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_LOW) { + capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_LOW; + } else if (capture_quality == BakedLightmap::BakeQuality::BAKE_QUALITY_MEDIUM) { + capt_quality = VoxelLightBaker::BakeQuality::BAKE_QUALITY_MEDIUM; + } + + voxel_baker.begin_bake_light(capt_quality, capture_propagation); + + for (int i = 0; i < lights_found.size(); i++) { + LightsFound &lf = lights_found.write[i]; + switch (lf.light->get_light_type()) { + case VS::LIGHT_DIRECTIONAL: { + voxel_baker.plot_light_directional(-lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_bake_mode() == Light::BAKE_ALL); + } break; + case VS::LIGHT_OMNI: { + voxel_baker.plot_light_omni(lf.xform.origin, lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); + } break; + case VS::LIGHT_SPOT: { + voxel_baker.plot_light_spot(lf.xform.origin, lf.xform.basis.get_axis(2), lf.light->get_color(), lf.light->get_param(Light::PARAM_ENERGY), lf.light->get_param(Light::PARAM_INDIRECT_ENERGY), lf.light->get_param(Light::PARAM_RANGE), lf.light->get_param(Light::PARAM_ATTENUATION), lf.light->get_param(Light::PARAM_SPOT_ANGLE), lf.light->get_param(Light::PARAM_SPOT_ATTENUATION), lf.light->get_bake_mode() == Light::BAKE_ALL); + + } break; + } + } + + voxel_baker.end_bake(); + + AABB bounds = AABB(-extents, extents * 2); + data->set_cell_subdiv(capture_subdiv); + data->set_bounds(bounds); + data->set_octree(voxel_baker.create_capture_octree(capture_subdiv)); + + { + float bake_bound_size = bake_bounds.get_longest_axis_size(); + Transform to_bounds; + to_bounds.basis.scale(Vector3(bake_bound_size, bake_bound_size, bake_bound_size)); + to_bounds.origin = bounds.position; + + Transform to_grid; + to_grid.basis.scale(Vector3(1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1), 1 << (capture_subdiv - 1))); + + Transform to_cell_space = to_grid * to_bounds.affine_inverse(); + data->set_cell_space_transform(to_cell_space); + } + } + + if (bake_step_function) { + bool cancelled = bake_step_function(0.9, TTR("Saving lightmaps"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + Vector > images; + for (int i = 0; i < lightmapper->get_bake_texture_count(); i++) { + images.push_back(lightmapper->get_bake_texture(i)); + } + + if (generate_atlas) { + + Ref large_image; + large_image.instance(); + large_image->create(images[0]->get_width(), images[0]->get_height() * images.size(), false, images[0]->get_format()); + for (int i = 0; i < images.size(); i++) { + large_image->blit_rect(images[i], Rect2(0, 0, images[0]->get_width(), images[0]->get_height()), Point2(0, images[0]->get_height() * i)); + } + + Ref texture; + String base_path = p_data_save_path.get_basename(); + + if (ResourceLoader::import) { + + base_path += ".exr"; + String relative_path = base_path; + if (relative_path.begins_with("res://")) { + relative_path = relative_path.substr(6, relative_path.length()); + } + large_image->save_exr(relative_path, false); + + Ref config; + config.instance(); + if (FileAccess::exists(base_path + ".import")) { + config->load(base_path + ".import"); + } else { + // Set only if settings don't exist, to keep user choice + config->set_value("params", "compress/mode", 0); + } + config->set_value("remap", "importer", "texture_array"); + config->set_value("remap", "type", "TextureArray"); + config->set_value("params", "detect_3d", false); + config->set_value("params", "flags/repeat", false); + config->set_value("params", "flags/filter", true); + config->set_value("params", "flags/mipmaps", false); + config->set_value("params", "flags/srgb", false); + config->set_value("params", "slices/horizontal", 1); + config->set_value("params", "slices/vertical", images.size()); + config->save(base_path + ".import"); + + ResourceLoader::import(base_path); + texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? + } else { + + base_path += ".texarr"; + Ref tex; + bool set_path = true; + if (ResourceCache::has(base_path)) { + tex = Ref((Resource *)ResourceCache::get(base_path)); + set_path = false; + } + + if (!tex.is_valid()) { + tex.instance(); + } + + tex->create(images[0]->get_width(), images[0]->get_height(), images.size(), images[0]->get_format(), Texture::FLAGS_DEFAULT); + for (int i = 0; i < images.size(); i++) { + tex->set_layer_data(images[i], i); + } + + ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); + if (set_path) { + tex->set_path(base_path); + } + texture = tex; + } + + for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { + if (meshes_found[i].generate_lightmap) { + Dictionary d = lightmapper->get_bake_mesh_userdata(i); + NodePath np = d["path"]; + int32_t subindex = -1; + if (d.has("subindex")) { + subindex = d["subindex"]; + } + + Rect2 uv_rect = lightmapper->get_bake_mesh_uv_scale(i); + int slice_index = lightmapper->get_bake_mesh_texture_slice(i); + data->add_user(np, texture, slice_index, uv_rect, subindex); + } + } + } else { + for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { + + if (!meshes_found[i].generate_lightmap) { + continue; + } + + Ref texture; + String base_path = p_data_save_path.get_base_dir().plus_file(images[i]->get_name()); + + if (ResourceLoader::import) { + + base_path += ".exr"; + String relative_path = base_path; + if (relative_path.begins_with("res://")) { + relative_path = relative_path.substr(6, relative_path.length()); + } + images[i]->save_exr(relative_path, false); + + Ref config; + config.instance(); + if (FileAccess::exists(base_path + ".import")) { + config->load(base_path + ".import"); + } else { + // Set only if settings don't exist, to keep user choice + config->set_value("params", "compress/mode", 0); + } + config->set_value("remap", "importer", "texture"); + config->set_value("remap", "type", "StreamTexture"); + config->set_value("params", "detect_3d", false); + config->set_value("params", "flags/repeat", false); + config->set_value("params", "flags/filter", true); + config->set_value("params", "flags/mipmaps", false); + config->set_value("params", "flags/srgb", false); + + config->save(base_path + ".import"); + + ResourceLoader::import(base_path); + texture = ResourceLoader::load(base_path); //if already loaded, it will be updated on refocus? + } else { + + base_path += ".tex"; + Ref tex; + bool set_path = true; + if (ResourceCache::has(base_path)) { + tex = Ref((Resource *)ResourceCache::get(base_path)); + set_path = false; + } + + if (!tex.is_valid()) { + tex.instance(); + } + + tex->create_from_image(images[i], Texture::FLAGS_DEFAULT); + + ResourceSaver::save(base_path, tex, ResourceSaver::FLAG_CHANGE_PATH); + if (set_path) { + tex->set_path(base_path); + } + texture = tex; + } + + Dictionary d = lightmapper->get_bake_mesh_userdata(i); + NodePath np = d["path"]; + int32_t subindex = -1; + if (d.has("subindex")) { + subindex = d["subindex"]; + } + + Rect2 uv_rect = Rect2(0, 0, 1, 1); + int slice_index = -1; + data->add_user(np, texture, slice_index, uv_rect, subindex); + } + } + + if (bake_step_function) { + bool cancelled = bake_step_function(1.0, TTR("Done"), nullptr, true); + if (cancelled) { + return BAKE_ERROR_USER_ABORTED; + } + } + + Error err = ResourceSaver::save(p_data_save_path, data); + data->set_path(p_data_save_path); + + if (err != OK) { + return BAKE_ERROR_CANT_CREATE_IMAGE; + } + + set_light_data(data); + + return BAKE_ERROR_OK; } void BakedLightmap::set_capture_cell_size(float p_cell_size) { - capture_cell_size = p_cell_size; + capture_cell_size = MAX(0.1, p_cell_size); } float BakedLightmap::get_capture_cell_size() const { @@ -222,428 +1041,21 @@ Vector3 BakedLightmap::get_extents() const { return extents; } -void BakedLightmap::set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit) { - bake_default_texels_per_unit = p_bake_texels_per_unit; - update_gizmo(); +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_bake_default_texels_per_unit() const { - return bake_default_texels_per_unit; +float BakedLightmap::get_default_texels_per_unit() const { + return default_texels_per_unit; } -void BakedLightmap::_find_meshes_and_lights(Node *p_at_node, List &plot_meshes, List &plot_lights) { - - MeshInstance *mi = Object::cast_to(p_at_node); - if (mi && mi->get_flag(GeometryInstance::FLAG_USE_BAKED_LIGHT) && mi->is_visible_in_tree()) { - Ref mesh = mi->get_mesh(); - if (mesh.is_valid()) { - - bool all_have_uv2 = true; - for (int i = 0; i < mesh->get_surface_count(); i++) { - if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_TEX_UV2)) { - all_have_uv2 = false; - break; - } - } - - if (all_have_uv2) { - //READY TO BAKE! size hint could be computed if not found, actually.. - - AABB aabb = mesh->get_aabb(); - - Transform xf = get_global_transform().affine_inverse() * mi->get_global_transform(); - - if (AABB(-extents, extents * 2).intersects(xf.xform(aabb))) { - PlotMesh pm; - pm.local_xform = xf; - pm.mesh = mesh; - pm.path = get_path_to(mi); - pm.instance_idx = -1; - for (int i = 0; i < mesh->get_surface_count(); i++) { - pm.instance_materials.push_back(mi->get_surface_material(i)); - } - pm.override_material = mi->get_material_override(); - plot_meshes.push_back(pm); - } - } - } - } - - Spatial *s = Object::cast_to(p_at_node); - - if (!mi && s) { - Array meshes = p_at_node->call("get_bake_meshes"); - if (meshes.size() && (meshes.size() & 1) == 0) { - Transform xf = get_global_transform().affine_inverse() * s->get_global_transform(); - for (int i = 0; i < meshes.size(); i += 2) { - PlotMesh pm; - Transform mesh_xf = meshes[i + 1]; - pm.local_xform = xf * mesh_xf; - pm.mesh = meshes[i]; - pm.instance_idx = i / 2; - if (!pm.mesh.is_valid()) - continue; - pm.path = get_path_to(s); - plot_meshes.push_back(pm); - } - } - } - - Light *light = Object::cast_to(p_at_node); - - if (light && light->get_bake_mode() != Light::BAKE_DISABLED) { - PlotLight pl; - Transform xf = get_global_transform().affine_inverse() * light->get_global_transform(); - - pl.local_xform = xf; - pl.light = light; - plot_lights.push_back(pl); - } - 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, plot_meshes, plot_lights); - } +void BakedLightmap::set_capture_enabled(bool p_enable) { + capture_enabled = p_enable; + _change_notify(); } -void BakedLightmap::set_hdr(bool p_enable) { - hdr = p_enable; -} - -bool BakedLightmap::is_hdr() const { - return hdr; -} - -bool BakedLightmap::_bake_time(void *ud, float p_secs, float p_progress) { - - uint64_t time = OS::get_singleton()->get_ticks_usec(); - BakeTimeData *btd = (BakeTimeData *)ud; - - if (time - btd->last_step > 1000000) { - - int mins_left = p_secs / 60; - int secs_left = Math::fmod(p_secs, 60.0f); - int percent = p_progress * 100; - bool abort = bake_step_function(btd->pass + percent, btd->text + " " + vformat(RTR("%d%%"), percent) + " " + vformat(RTR("(Time Left: %d:%02d s)"), mins_left, secs_left)); - btd->last_step = time; - if (abort) - return true; - } - - return false; -} - -BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, bool p_create_visual_debug) { - - String save_path; - - if (image_path.begins_with("res://")) { - save_path = image_path; - } else { - if (get_filename() != "") { - save_path = get_filename().get_base_dir(); - } else if (get_owner() && get_owner()->get_filename() != "") { - save_path = get_owner()->get_filename().get_base_dir(); - } - - if (save_path == "") { - return BAKE_ERROR_NO_SAVE_PATH; - } - if (image_path != "") { - save_path.plus_file(image_path); - } - } - { - //check for valid save path - DirAccessRef d = DirAccess::open(save_path); - if (!d) { - ERR_PRINTS("Invalid Save Path '" + save_path + "'."); - return BAKE_ERROR_NO_SAVE_PATH; - } - } - - Ref new_light_data; - new_light_data.instance(); - - VoxelLightBaker 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() / bake_cell_size)); - bake_bounds.size[bake_bounds.get_longest_axis_index()] = subdiv * bake_cell_size; - bake_subdiv = nearest_shift(subdiv) + 1; - - capture_subdiv = bake_subdiv; - float css = bake_cell_size; - while (css < capture_cell_size && capture_subdiv > 2) { - capture_subdiv--; - css *= 2.0; - } - } - - baker.begin_bake(bake_subdiv, bake_bounds); - - List mesh_list; - List light_list; - - _find_meshes_and_lights(p_from_node ? p_from_node : get_parent(), mesh_list, light_list); - - if (bake_begin_function) { - bake_begin_function(mesh_list.size() + light_list.size() + 1 + mesh_list.size() * 100); - } - - int step = 0; - - int pmc = 0; - - for (List::Element *E = mesh_list.front(); E; E = E->next()) { - - if (bake_step_function) { - bake_step_function(step++, RTR("Plotting Meshes: ") + " (" + itos(pmc + 1) + "/" + itos(mesh_list.size()) + ")"); - } - - pmc++; - baker.plot_mesh(E->get().local_xform, E->get().mesh, E->get().instance_materials, E->get().override_material); - } - - pmc = 0; - baker.begin_bake_light(VoxelLightBaker::BakeQuality(bake_quality), VoxelLightBaker::BakeMode(bake_mode), propagation, energy); - - for (List::Element *E = light_list.front(); E; E = E->next()) { - - if (bake_step_function) { - bake_step_function(step++, RTR("Plotting Lights:") + " (" + itos(pmc + 1) + "/" + itos(light_list.size()) + ")"); - } - - pmc++; - PlotLight pl = E->get(); - switch (pl.light->get_light_type()) { - case VS::LIGHT_DIRECTIONAL: { - baker.plot_light_directional(-pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_bake_mode() == Light::BAKE_ALL); - } break; - case VS::LIGHT_OMNI: { - baker.plot_light_omni(pl.local_xform.origin, pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL); - } break; - case VS::LIGHT_SPOT: { - baker.plot_light_spot(pl.local_xform.origin, pl.local_xform.basis.get_axis(2), pl.light->get_color(), pl.light->get_param(Light::PARAM_ENERGY), pl.light->get_param(Light::PARAM_INDIRECT_ENERGY), pl.light->get_param(Light::PARAM_RANGE), pl.light->get_param(Light::PARAM_ATTENUATION), pl.light->get_param(Light::PARAM_SPOT_ANGLE), pl.light->get_param(Light::PARAM_SPOT_ATTENUATION), pl.light->get_bake_mode() == Light::BAKE_ALL); - - } break; - } - } - /*if (bake_step_function) { - bake_step_function(pmc++, RTR("Finishing Plot")); - }*/ - - baker.end_bake(); - - Set used_mesh_names; - - pmc = 0; - for (List::Element *E = mesh_list.front(); E; E = E->next()) { - - String mesh_name = E->get().mesh->get_name(); - if (mesh_name == "" || mesh_name.find(":") != -1 || mesh_name.find("/") != -1) { - mesh_name = "LightMap"; - } - - if (used_mesh_names.has(mesh_name)) { - int idx = 2; - String base = mesh_name; - while (true) { - mesh_name = base + itos(idx); - if (!used_mesh_names.has(mesh_name)) - break; - idx++; - } - } - used_mesh_names.insert(mesh_name); - - pmc++; - VoxelLightBaker::LightMapData lm; - - Error err; - if (bake_step_function) { - BakeTimeData btd; - btd.text = RTR("Lighting Meshes: ") + mesh_name + " (" + itos(pmc) + "/" + itos(mesh_list.size()) + ")"; - btd.pass = step; - btd.last_step = 0; - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm, _bake_time, &btd); - if (err != OK) { - bake_end_function(); - if (err == ERR_SKIP) - return BAKE_ERROR_USER_ABORTED; - return BAKE_ERROR_CANT_CREATE_IMAGE; - } - step += 100; - } else { - - err = baker.make_lightmap(E->get().local_xform, E->get().mesh, bake_default_texels_per_unit, lm); - } - - if (err == OK) { - - Ref image; - image.instance(); - - uint32_t tex_flags = Texture::FLAGS_DEFAULT; - if (hdr) { - - //just save a regular image - PoolVector data; - int s = lm.light.size(); - data.resize(lm.light.size() * 2); - { - - PoolVector::Write w = data.write(); - PoolVector::Read r = lm.light.read(); - uint16_t *hfw = (uint16_t *)w.ptr(); - for (int i = 0; i < s; i++) { - hfw[i] = Math::make_half_float(r[i]); - } - } - - image->create(lm.width, lm.height, false, Image::FORMAT_RGBH, data); - - } else { - - //just save a regular image - PoolVector data; - int s = lm.light.size(); - data.resize(lm.light.size()); - { - - PoolVector::Write w = data.write(); - PoolVector::Read r = lm.light.read(); - for (int i = 0; i < s; i += 3) { - Color c(r[i + 0], r[i + 1], r[i + 2]); - c = c.to_srgb(); - w[i + 0] = CLAMP(c.r * 255, 0, 255); - w[i + 1] = CLAMP(c.g * 255, 0, 255); - w[i + 2] = CLAMP(c.b * 255, 0, 255); - } - } - - image->create(lm.width, lm.height, false, Image::FORMAT_RGB8, data); - - //This texture is saved to SRGB for two reasons: - // 1) first is so it looks better when doing the LINEAR->SRGB conversion (more accurate) - // 2) So it can be used in the GLES2 backend, which does not support linkear workflow - tex_flags |= Texture::FLAG_CONVERT_TO_LINEAR; - } - - String image_path = save_path.plus_file(mesh_name); - Ref texture; - - if (ResourceLoader::import) { - - bool srgb = false; - if (false && hdr) { - //save hdr - } else { - image_path += ".png"; - print_line("image path saving png: " + image_path); - image->save_png(image_path); - srgb = true; - } - - if (!FileAccess::exists(image_path + ".import")) { - Ref config; - config.instance(); - config->set_value("remap", "importer", "texture"); - config->set_value("remap", "type", "StreamTexture"); - config->set_value("params", "compress/mode", 2); - 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", srgb); - - config->save(image_path + ".import"); - } - - ResourceLoader::import(image_path); - texture = ResourceLoader::load(image_path); //if already loaded, it will be updated on refocus? - } else { - - image_path += ".text"; - Ref tex; - bool set_path = true; - if (ResourceCache::has(image_path)) { - tex = Ref((Resource *)ResourceCache::get(image_path)); - set_path = false; - } - - if (!tex.is_valid()) { - tex.instance(); - } - - tex->create_from_image(image, tex_flags); - - err = ResourceSaver::save(image_path, tex, ResourceSaver::FLAG_CHANGE_PATH); - if (set_path) { - tex->set_path(image_path); - } - texture = tex; - } - if (err != OK) { - if (bake_end_function) { - bake_end_function(); - } - ERR_FAIL_COND_V(err != OK, BAKE_ERROR_CANT_CREATE_IMAGE); - } - - new_light_data->add_user(E->get().path, texture, E->get().instance_idx); - } - } - - AABB bounds = AABB(-extents, extents * 2); - new_light_data->set_cell_subdiv(capture_subdiv); - new_light_data->set_bounds(bounds); - new_light_data->set_octree(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(); - new_light_data->set_cell_space_transform(to_cell_space); - } - - if (bake_end_function) { - bake_end_function(); - } - - //create the data for visual server - - if (p_create_visual_debug) { - MultiMeshInstance *mmi = memnew(MultiMeshInstance); - mmi->set_multimesh(baker.create_debug_multimesh(VoxelLightBaker::DEBUG_LIGHT)); - add_child(mmi); -#ifdef TOOLS_ENABLED - if (get_tree()->get_edited_scene_root() == this) { - mmi->set_owner(this); - } else { - mmi->set_owner(get_owner()); - } -#else - mmi->set_owner(get_owner()); -#endif - } - - set_light_data(new_light_data); - - return BAKE_ERROR_OK; +bool BakedLightmap::get_capture_enabled() const { + return capture_enabled; } void BakedLightmap::_notification(int p_what) { @@ -668,20 +1080,21 @@ void BakedLightmap::_assign_lightmaps() { ERR_FAIL_COND(!light_data.is_valid()); for (int i = 0; i < light_data->get_user_count(); i++) { - Ref lightmap = light_data->get_user_lightmap(i); + Ref lightmap = light_data->get_user_lightmap(i); ERR_CONTINUE(!lightmap.is_valid()); + ERR_CONTINUE(!Object::cast_to(lightmap.ptr()) && !Object::cast_to(lightmap.ptr())); Node *node = get_node(light_data->get_user_path(i)); int instance_idx = light_data->get_user_instance(i); if (instance_idx >= 0) { RID instance = node->call("get_bake_mesh_instance", instance_idx); if (instance.is_valid()) { - VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid()); + VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); - VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid()); + VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), lightmap->get_rid(), light_data->get_user_lightmap_slice(i), light_data->get_user_lightmap_uv_rect(i)); } } } @@ -694,16 +1107,65 @@ void BakedLightmap::_clear_lightmaps() { 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()); + VS::get_singleton()->instance_set_use_lightmap(instance, get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); } } else { VisualInstance *vi = Object::cast_to(node); ERR_CONTINUE(!vi); - VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID()); + VS::get_singleton()->instance_set_use_lightmap(vi->get_instance(), get_instance(), RID(), -1, Rect2(0, 0, 1, 1)); } } } +Ref BakedLightmap::_get_irradiance_from_sky(Ref p_sky, Vector2i p_size) { + if (p_sky.is_null()) { + return Ref(); + } + + Ref sky_image; + Ref panorama = p_sky; + if (panorama.is_valid()) { + sky_image = panorama->get_panorama()->get_data(); + } + Ref procedural = p_sky; + if (procedural.is_valid()) { + sky_image = procedural->get_panorama(); + } + + if (sky_image.is_null()) { + return Ref(); + } + + sky_image->convert(Image::FORMAT_RGBF); + sky_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_CUBIC); + return sky_image; +} + +Ref BakedLightmap::_get_irradiance_map(Ref p_env, Vector2i p_size) { + Environment::BGMode bg_mode = p_env->get_background(); + switch (bg_mode) { + case Environment::BG_SKY: { + return _get_irradiance_from_sky(p_env->get_sky(), Vector2i(128, 64)); + } + case Environment::BG_CLEAR_COLOR: + case Environment::BG_COLOR: { + Color c = bg_mode == Environment::BG_CLEAR_COLOR ? Color(GLOBAL_GET("rendering/environment/default_clear_color")) : p_env->get_bg_color(); + c.r *= p_env->get_bg_energy(); + c.g *= p_env->get_bg_energy(); + c.b *= p_env->get_bg_energy(); + + Ref ret; + ret.instance(); + ret->create(p_size.x, p_size.y, false, Image::FORMAT_RGBF); + ret->fill(c); + return ret; + } + default: { + } + } + return Ref(); +} + void BakedLightmap::set_light_data(const Ref &p_data) { if (light_data.is_valid()) { @@ -713,6 +1175,7 @@ void BakedLightmap::set_light_data(const Ref &p_data) { set_base(RID()); } light_data = p_data; + _change_notify(); if (light_data.is_valid()) { set_base(light_data->get_rid()); @@ -727,44 +1190,50 @@ Ref BakedLightmap::get_light_data() const { return light_data; } -void BakedLightmap::_debug_bake() { - bake(get_parent(), true); +void BakedLightmap::set_capture_propagation(float p_propagation) { + capture_propagation = p_propagation; } -void BakedLightmap::set_propagation(float p_propagation) { - propagation = p_propagation; +float BakedLightmap::get_capture_propagation() const { + + return capture_propagation; } -float BakedLightmap::get_propagation() const { - - return propagation; +void BakedLightmap::set_capture_quality(BakeQuality p_quality) { + capture_quality = p_quality; } -void BakedLightmap::set_energy(float p_energy) { - energy = p_energy; +BakedLightmap::BakeQuality BakedLightmap::get_capture_quality() const { + return capture_quality; } -float BakedLightmap::get_energy() const { +void BakedLightmap::set_generate_atlas(bool p_enabled) { + generate_atlas = p_enabled; +} - return energy; +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; } -void BakedLightmap::set_bake_mode(BakeMode p_mode) { - bake_mode = p_mode; -} - -BakedLightmap::BakeMode BakedLightmap::get_bake_mode() const { - return bake_mode; -} - +#ifndef DISABLE_DEPRECATED void BakedLightmap::set_image_path(const String &p_path) { image_path = p_path; } @@ -772,6 +1241,74 @@ void BakedLightmap::set_image_path(const String &p_path) { String BakedLightmap::get_image_path() const { return image_path; } +#endif + +void BakedLightmap::set_use_denoiser(bool p_enable) { + + use_denoiser = p_enable; +} + +bool BakedLightmap::is_using_denoiser() const { + + return use_denoiser; +} + +void BakedLightmap::set_environment_mode(EnvironmentMode p_mode) { + environment_mode = p_mode; + _change_notify(); +} + +BakedLightmap::EnvironmentMode BakedLightmap::get_environment_mode() const { + return environment_mode; +} + +void BakedLightmap::set_environment_custom_sky(const Ref &p_sky) { + environment_custom_sky = p_sky; +} + +Ref BakedLightmap::get_environment_custom_sky() const { + return environment_custom_sky; +} + +void BakedLightmap::set_environment_custom_sky_rotation_degrees(const Vector3 &p_rotation) { + environment_custom_sky_rotation_degrees = p_rotation; +} + +Vector3 BakedLightmap::get_environment_custom_sky_rotation_degrees() const { + return environment_custom_sky_rotation_degrees; +} + +void BakedLightmap::set_environment_custom_color(const Color &p_color) { + environment_custom_color = p_color; +} +Color BakedLightmap::get_environment_custom_color() const { + return environment_custom_color; +} + +void BakedLightmap::set_environment_custom_energy(float p_energy) { + environment_custom_energy = p_energy; +} +float BakedLightmap::get_environment_custom_energy() const { + return environment_custom_energy; +} + +void BakedLightmap::set_bounces(int p_bounces) { + ERR_FAIL_COND(p_bounces < 0 || p_bounces > 16); + bounces = p_bounces; +} + +int BakedLightmap::get_bounces() const { + return bounces; +} + +void BakedLightmap::set_bias(float p_bias) { + ERR_FAIL_COND(p_bias < 0.00001); + bias = p_bias; +} + +float BakedLightmap::get_bias() const { + return bias; +} AABB BakedLightmap::get_aabb() const { return AABB(-extents, extents * 2); @@ -780,85 +1317,160 @@ PoolVector BakedLightmap::get_faces(uint32_t p_usage_flags) const { return PoolVector(); } +void BakedLightmap::_validate_property(PropertyInfo &property) const { + + if (property.name.begins_with("environment_custom_sky") && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { + property.usage = 0; + } + + if (property.name == "environment_custom_color" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR) { + property.usage = 0; + } + + if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) { + property.usage = 0; + } + + if (property.name.begins_with("atlas") && OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) { + property.usage = PROPERTY_USAGE_NOEDITOR; + } + + if (property.name.begins_with("capture") && property.name != "capture_enabled" && !capture_enabled) { + property.usage = 0; + } +} + void BakedLightmap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_light_data", "data"), &BakedLightmap::set_light_data); ClassDB::bind_method(D_METHOD("get_light_data"), &BakedLightmap::get_light_data); - ClassDB::bind_method(D_METHOD("set_bake_cell_size", "bake_cell_size"), &BakedLightmap::set_bake_cell_size); - ClassDB::bind_method(D_METHOD("get_bake_cell_size"), &BakedLightmap::get_bake_cell_size); - - 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); - - ClassDB::bind_method(D_METHOD("set_bake_quality", "bake_quality"), &BakedLightmap::set_bake_quality); + 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_bake_mode", "bake_mode"), &BakedLightmap::set_bake_mode); - ClassDB::bind_method(D_METHOD("get_bake_mode"), &BakedLightmap::get_bake_mode); + ClassDB::bind_method(D_METHOD("set_bounces", "bounces"), &BakedLightmap::set_bounces); + ClassDB::bind_method(D_METHOD("get_bounces"), &BakedLightmap::get_bounces); + + ClassDB::bind_method(D_METHOD("set_bias", "bias"), &BakedLightmap::set_bias); + ClassDB::bind_method(D_METHOD("get_bias"), &BakedLightmap::get_bias); + + ClassDB::bind_method(D_METHOD("set_environment_mode", "mode"), &BakedLightmap::set_environment_mode); + ClassDB::bind_method(D_METHOD("get_environment_mode"), &BakedLightmap::get_environment_mode); + + ClassDB::bind_method(D_METHOD("set_environment_custom_sky", "sky"), &BakedLightmap::set_environment_custom_sky); + ClassDB::bind_method(D_METHOD("get_environment_custom_sky"), &BakedLightmap::get_environment_custom_sky); + + ClassDB::bind_method(D_METHOD("set_environment_custom_sky_rotation_degrees", "rotation"), &BakedLightmap::set_environment_custom_sky_rotation_degrees); + ClassDB::bind_method(D_METHOD("get_environment_custom_sky_rotation_degrees"), &BakedLightmap::get_environment_custom_sky_rotation_degrees); + + ClassDB::bind_method(D_METHOD("set_environment_custom_color", "color"), &BakedLightmap::set_environment_custom_color); + ClassDB::bind_method(D_METHOD("get_environment_custom_color"), &BakedLightmap::get_environment_custom_color); + + ClassDB::bind_method(D_METHOD("set_environment_custom_energy", "energy"), &BakedLightmap::set_environment_custom_energy); + ClassDB::bind_method(D_METHOD("get_environment_custom_energy"), &BakedLightmap::get_environment_custom_energy); + + ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser); + ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser); + + ClassDB::bind_method(D_METHOD("set_generate_atlas", "enabled"), &BakedLightmap::set_generate_atlas); + ClassDB::bind_method(D_METHOD("is_generate_atlas_enabled"), &BakedLightmap::is_generate_atlas_enabled); + + ClassDB::bind_method(D_METHOD("set_max_atlas_size", "max_atlas_size"), &BakedLightmap::set_max_atlas_size); + ClassDB::bind_method(D_METHOD("get_max_atlas_size"), &BakedLightmap::get_max_atlas_size); + + ClassDB::bind_method(D_METHOD("set_capture_quality", "capture_quality"), &BakedLightmap::set_capture_quality); + ClassDB::bind_method(D_METHOD("get_capture_quality"), &BakedLightmap::get_capture_quality); ClassDB::bind_method(D_METHOD("set_extents", "extents"), &BakedLightmap::set_extents); ClassDB::bind_method(D_METHOD("get_extents"), &BakedLightmap::get_extents); - ClassDB::bind_method(D_METHOD("set_bake_default_texels_per_unit", "texels"), &BakedLightmap::set_bake_default_texels_per_unit); - ClassDB::bind_method(D_METHOD("get_bake_default_texels_per_unit"), &BakedLightmap::get_bake_default_texels_per_unit); + 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_propagation", "propagation"), &BakedLightmap::set_propagation); - ClassDB::bind_method(D_METHOD("get_propagation"), &BakedLightmap::get_propagation); + 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_energy", "energy"), &BakedLightmap::set_energy); - ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmap::get_energy); - - ClassDB::bind_method(D_METHOD("set_hdr", "hdr"), &BakedLightmap::set_hdr); - ClassDB::bind_method(D_METHOD("is_hdr"), &BakedLightmap::is_hdr); + 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("")); - ClassDB::bind_method(D_METHOD("bake", "from_node", "create_visual_debug"), &BakedLightmap::bake, DEFVAL(Variant()), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("debug_bake"), &BakedLightmap::_debug_bake); - ClassDB::set_method_flags(get_class_static(), _scs_create("debug_bake"), METHOD_FLAGS_DEFAULT | METHOD_FLAG_EDITOR); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "extents"), "set_extents", "get_extents"); + + ADD_GROUP("Tweaks", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "quality", PROPERTY_HINT_ENUM, "Low,Medium,High,Ultra"), "set_bake_quality", "get_bake_quality"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,16,1"), "set_bounces", "get_bounces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_texels_per_unit", PROPERTY_HINT_RANGE, "0.0,64.0,0.01,or_greater"), "set_default_texels_per_unit", "get_default_texels_per_unit"); + + ADD_GROUP("Atlas", "atlas_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "atlas_generate"), "set_generate_atlas", "is_generate_atlas_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "atlas_max_size"), "set_max_atlas_size", "get_max_atlas_size"); + + ADD_GROUP("Environment", "environment_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "environment_mode", PROPERTY_HINT_ENUM, "Disabled,Scene,Custom Sky,Custom Color"), "set_environment_mode", "get_environment_mode"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment_custom_sky", PROPERTY_HINT_RESOURCE_TYPE, "Sky"), "set_environment_custom_sky", "get_environment_custom_sky"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "environment_custom_sky_rotation_degrees", PROPERTY_HINT_NONE), "set_environment_custom_sky_rotation_degrees", "get_environment_custom_sky_rotation_degrees"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "environment_custom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_custom_color", "get_environment_custom_color"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "environment_custom_energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_environment_custom_energy", "get_environment_custom_energy"); - ADD_GROUP("Bake", "bake_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_bake_cell_size", "get_bake_cell_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), "set_bake_quality", "get_bake_quality"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bake_mode", PROPERTY_HINT_ENUM, "ConeTrace,RayTrace"), "set_bake_mode", "get_bake_mode"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_propagation", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_propagation", "get_propagation"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_energy", PROPERTY_HINT_RANGE, "0,32,0.01"), "set_energy", "get_energy"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_hdr"), "set_hdr", "is_hdr"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "bake_extents"), "set_extents", "get_extents"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "bake_default_texels_per_unit"), "set_bake_default_texels_per_unit", "get_bake_default_texels_per_unit"); ADD_GROUP("Capture", "capture_"); - ADD_PROPERTY(PropertyInfo(Variant::REAL, "capture_cell_size", PROPERTY_HINT_RANGE, "0.01,64,0.01"), "set_capture_cell_size", "get_capture_cell_size"); + 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", ""); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "image_path", PROPERTY_HINT_DIR), "set_image_path", "get_image_path"); +#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_MODE_CONE_TRACE); - BIND_ENUM_CONSTANT(BAKE_MODE_RAY_TRACE); + BIND_ENUM_CONSTANT(BAKE_QUALITY_ULTRA); BIND_ENUM_CONSTANT(BAKE_ERROR_OK); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_SAVE_PATH); BIND_ENUM_CONSTANT(BAKE_ERROR_NO_MESHES); BIND_ENUM_CONSTANT(BAKE_ERROR_CANT_CREATE_IMAGE); BIND_ENUM_CONSTANT(BAKE_ERROR_USER_ABORTED); + + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_DISABLED); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_SCENE); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_SKY); + BIND_ENUM_CONSTANT(ENVIRONMENT_MODE_CUSTOM_COLOR); } BakedLightmap::BakedLightmap() { extents = Vector3(10, 10, 10); - bake_default_texels_per_unit = 20; - bake_cell_size = 0.25; + + default_texels_per_unit = 16.0f; + bake_quality = BAKE_QUALITY_MEDIUM; + capture_quality = BAKE_QUALITY_MEDIUM; + capture_propagation = 1; + capture_enabled = true; + bounces = 3; + image_path = ""; + set_disable_scale(true); capture_cell_size = 0.5; - bake_quality = BAKE_QUALITY_MEDIUM; - bake_mode = BAKE_MODE_CONE_TRACE; - energy = 1; - propagation = 1; - hdr = false; - image_path = "."; - set_disable_scale(true); + environment_mode = ENVIRONMENT_MODE_DISABLED; + environment_custom_color = Color(0.2, 0.7, 1.0); + environment_custom_energy = 1.0; + + use_denoiser = true; + bias = 0.005; + + generate_atlas = true; + max_atlas_size = 4096; } diff --git a/scene/3d/baked_lightmap.h b/scene/3d/baked_lightmap.h index 28f6196dee7..33f9a62a57c 100644 --- a/scene/3d/baked_lightmap.h +++ b/scene/3d/baked_lightmap.h @@ -31,12 +31,15 @@ #ifndef BAKED_INDIRECT_LIGHT_H #define BAKED_INDIRECT_LIGHT_H +#include "core/local_vector.h" #include "multimesh_instance.h" #include "scene/3d/light.h" +#include "scene/3d/lightmapper.h" #include "scene/3d/visual_instance.h" class BakedLightmapData : public Resource { GDCLASS(BakedLightmapData, Resource); + RES_BASE_EXTENSION("lmbake") RID baked_light; AABB bounds; @@ -47,7 +50,12 @@ class BakedLightmapData : public Resource { struct User { NodePath path; - Ref lightmap; + struct { + Ref single; + Ref layered; + } lightmap; + int lightmap_slice; + Rect2 lightmap_uv_rect; int instance_index; }; @@ -75,10 +83,12 @@ public: void set_energy(float p_energy); float get_energy() const; - void add_user(const NodePath &p_path, const Ref &p_lightmap, int p_instance = -1); + void add_user(const NodePath &p_path, const Ref &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance); int get_user_count() const; NodePath get_user_path(int p_user) const; - Ref get_user_lightmap(int p_user) const; + Ref get_user_lightmap(int p_user) const; + int get_user_lightmap_slice(int p_user) const; + Rect2 get_user_lightmap_uv_rect(int p_user) const; int get_user_instance(int p_user) const; void clear_users(); @@ -94,12 +104,8 @@ public: enum BakeQuality { BAKE_QUALITY_LOW, BAKE_QUALITY_MEDIUM, - BAKE_QUALITY_HIGH - }; - - enum BakeMode { - BAKE_MODE_CONE_TRACE, - BAKE_MODE_RAY_TRACE, + BAKE_QUALITY_HIGH, + BAKE_QUALITY_ULTRA }; enum BakeError { @@ -107,108 +113,154 @@ public: BAKE_ERROR_NO_SAVE_PATH, BAKE_ERROR_NO_MESHES, BAKE_ERROR_CANT_CREATE_IMAGE, - BAKE_ERROR_USER_ABORTED + BAKE_ERROR_LIGHTMAP_SIZE, + BAKE_ERROR_INVALID_MESH, + BAKE_ERROR_USER_ABORTED, + BAKE_ERROR_NO_LIGHTMAPPER }; - typedef void (*BakeBeginFunc)(int); - typedef bool (*BakeStepFunc)(int, const String &); - typedef void (*BakeEndFunc)(); + enum EnvironmentMode { + ENVIRONMENT_MODE_DISABLED, + ENVIRONMENT_MODE_SCENE, + ENVIRONMENT_MODE_CUSTOM_SKY, + ENVIRONMENT_MODE_CUSTOM_COLOR + }; + + struct BakeStepUD { + Lightmapper::BakeStepFunc func; + void *ud; + float from_percent; + float to_percent; + }; + + struct LightsFound { + Transform xform; + Light *light; + }; + + struct MeshesFound { + Transform xform; + NodePath node_path; + int32_t subindex; + Ref mesh; + int32_t lightmap_scale; + Vector > overrides; + bool cast_shadows; + bool generate_lightmap; + }; private: - float bake_cell_size; - float capture_cell_size; Vector3 extents; - float bake_default_texels_per_unit; - float propagation; - float energy; + float default_texels_per_unit; + float bias; BakeQuality bake_quality; - BakeMode bake_mode; - bool hdr; - String image_path; + bool generate_atlas; + int max_atlas_size; + bool capture_enabled; + int bounces; + bool use_denoiser; + + EnvironmentMode environment_mode; + Ref environment_custom_sky; + Vector3 environment_custom_sky_rotation_degrees; + Color environment_custom_color; + float environment_custom_energy; + + BakeQuality capture_quality; + float capture_propagation; + float capture_cell_size; + + String image_path; // (Deprecated property) Ref light_data; - struct PlotMesh { - Ref override_material; - Vector > instance_materials; - Ref mesh; - Transform local_xform; - NodePath path; - int instance_idx; - }; - - struct PlotLight { - Light *light; - Transform local_xform; - }; - - void _find_meshes_and_lights(Node *p_at_node, List &plot_meshes, List &plot_lights); - - void _debug_bake(); - void _assign_lightmaps(); void _clear_lightmaps(); - static bool _bake_time(void *ud, float p_secs, float p_progress); + void _get_material_images(const MeshesFound &p_found_mesh, Lightmapper::MeshData &r_mesh_data, Vector > &r_albedo_textures, Vector > &r_emission_textures); + Ref _get_irradiance_from_sky(Ref p_sky, Vector2i p_size); + Ref _get_irradiance_map(Ref p_env, Vector2i p_size); + void _find_meshes_and_lights(Node *p_at_node, Vector &meshes, Vector &lights); + Vector2i _compute_lightmap_size(const MeshesFound &p_mesh); - struct BakeTimeData { - String text; - int pass; - uint64_t last_step; - }; + static bool _lightmap_bake_step_function(float p_completion, const String &p_text, void *ud, bool p_refresh); protected: static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; void _notification(int p_what); public: - static BakeBeginFunc bake_begin_function; - static BakeStepFunc bake_step_function; - static BakeEndFunc bake_end_function; + static Lightmapper::BakeStepFunc bake_step_function; + static Lightmapper::BakeStepFunc bake_substep_function; void set_light_data(const Ref &p_data); Ref get_light_data() const; - void set_bake_cell_size(float p_cell_size); - float get_bake_cell_size() const; - void set_capture_cell_size(float p_cell_size); float get_capture_cell_size() const; void set_extents(const Vector3 &p_extents); Vector3 get_extents() const; - void set_bake_default_texels_per_unit(const float &p_bake_texels_per_unit); - float get_bake_default_texels_per_unit() const; + void set_default_texels_per_unit(const float &p_extents); + float get_default_texels_per_unit() const; - void set_propagation(float p_propagation); - float get_propagation() const; + void set_capture_propagation(float p_propagation); + float get_capture_propagation() const; - void set_energy(float p_energy); - float get_energy() const; + void set_capture_quality(BakeQuality p_quality); + BakeQuality get_capture_quality() const; void set_bake_quality(BakeQuality p_quality); BakeQuality get_bake_quality() const; - void set_bake_mode(BakeMode p_mode); - BakeMode get_bake_mode() const; + void set_generate_atlas(bool p_enabled); + bool is_generate_atlas_enabled() const; - void set_hdr(bool p_enable); - bool is_hdr() const; + void set_max_atlas_size(int p_size); + int get_max_atlas_size() const; + + void set_capture_enabled(bool p_enable); + bool get_capture_enabled() const; void set_image_path(const String &p_path); String get_image_path() const; + void set_environment_mode(EnvironmentMode p_mode); + EnvironmentMode get_environment_mode() const; + + void set_environment_custom_sky(const Ref &p_sky); + Ref get_environment_custom_sky() const; + + void set_environment_custom_sky_rotation_degrees(const Vector3 &p_rotation); + Vector3 get_environment_custom_sky_rotation_degrees() const; + + void set_environment_custom_color(const Color &p_color); + Color get_environment_custom_color() const; + + void set_environment_custom_energy(float p_energy); + float get_environment_custom_energy() const; + + void set_use_denoiser(bool p_enable); + bool is_using_denoiser() const; + + void set_bounces(int p_bounces); + int get_bounces() const; + + void set_bias(float p_bias); + float get_bias() const; + AABB get_aabb() const; PoolVector get_faces(uint32_t p_usage_flags) const; - BakeError bake(Node *p_from_node, bool p_create_visual_debug = false); + BakeError bake(Node *p_from_node, String p_data_save_path = ""); BakedLightmap(); }; VARIANT_ENUM_CAST(BakedLightmap::BakeQuality); -VARIANT_ENUM_CAST(BakedLightmap::BakeMode); VARIANT_ENUM_CAST(BakedLightmap::BakeError); +VARIANT_ENUM_CAST(BakedLightmap::EnvironmentMode); #endif // BAKED_INDIRECT_LIGHT_H diff --git a/scene/3d/lightmapper.cpp b/scene/3d/lightmapper.cpp new file mode 100644 index 00000000000..839186299b9 --- /dev/null +++ b/scene/3d/lightmapper.cpp @@ -0,0 +1,76 @@ +/*************************************************************************/ +/* lightmapper.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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 "lightmapper.h" + +LightmapDenoiser *(*LightmapDenoiser::create_function)() = nullptr; + +Ref LightmapDenoiser::create() { + if (create_function) { + return Ref(create_function()); + } + return Ref(); +} + +LightmapRaycaster *(*LightmapRaycaster::create_function)() = nullptr; + +Ref LightmapRaycaster::create() { + if (create_function) { + return Ref(create_function()); + } + return Ref(); +} + +Lightmapper::CreateFunc Lightmapper::create_custom = nullptr; +Lightmapper::CreateFunc Lightmapper::create_gpu = nullptr; +Lightmapper::CreateFunc Lightmapper::create_cpu = nullptr; + +Ref Lightmapper::create() { + Lightmapper *lm = nullptr; + if (create_custom) { + lm = create_custom(); + } + + if (!lm && create_gpu) { + lm = create_gpu(); + } + + if (!lm && create_cpu) { + lm = create_cpu(); + } + if (!lm) { + return Ref(); + } else { + return Ref(lm); + } +} + +Lightmapper::Lightmapper() { +} diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h new file mode 100644 index 00000000000..ccb08e4630d --- /dev/null +++ b/scene/3d/lightmapper.h @@ -0,0 +1,196 @@ +/*************************************************************************/ +/* lightmapper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 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. */ +/*************************************************************************/ + +#ifndef LIGHTMAPPER_H +#define LIGHTMAPPER_H + +#include "scene/resources/mesh.h" + +#if !defined(__aligned) + +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)) && !defined(__CYGWIN__) +#define __aligned(...) __declspec(align(__VA_ARGS__)) +#else +#define __aligned(...) __attribute__((aligned(__VA_ARGS__))) +#endif + +#endif + +class LightmapDenoiser : public Reference { + GDCLASS(LightmapDenoiser, Reference) +protected: + static LightmapDenoiser *(*create_function)(); + +public: + virtual Ref denoise_image(const Ref &p_image) = 0; + static Ref create(); +}; + +class LightmapRaycaster : public Reference { + GDCLASS(LightmapRaycaster, Reference) +protected: + static LightmapRaycaster *(*create_function)(); + +public: + // compatible with embree3 rays + struct __aligned(16) Ray { + const static unsigned int INVALID_GEOMETRY_ID = ((unsigned int)-1); // from rtcore_common.h + + /*! Default construction does nothing. */ + _FORCE_INLINE_ Ray() : + geomID(INVALID_GEOMETRY_ID) {} + + /*! Constructs a ray from origin, direction, and ray segment. Near + * has to be smaller than far. */ + _FORCE_INLINE_ Ray(const Vector3 &org, + const Vector3 &dir, + float tnear = 0.0f, + float tfar = INFINITY) : + org(org), + tnear(tnear), + dir(dir), + time(0.0f), + tfar(tfar), + mask(-1), + u(0.0), + v(0.0), + primID(INVALID_GEOMETRY_ID), + geomID(INVALID_GEOMETRY_ID), + instID(INVALID_GEOMETRY_ID) {} + + /*! Tests if we hit something. */ + _FORCE_INLINE_ explicit operator bool() const { return geomID != INVALID_GEOMETRY_ID; } + + public: + Vector3 org; //!< Ray origin + tnear + float tnear; //!< Start of ray segment + Vector3 dir; //!< Ray direction + tfar + float time; //!< Time of this ray for motion blur. + float tfar; //!< End of ray segment + unsigned int mask; //!< used to mask out objects during traversal + unsigned int id; //!< ray ID + unsigned int flags; //!< ray flags + + Vector3 normal; //!< Not normalized geometry normal + float u; //!< Barycentric u coordinate of hit + float v; //!< Barycentric v coordinate of hit + unsigned int primID; //!< primitive ID + unsigned int geomID; //!< geometry ID + unsigned int instID; //!< instance ID + }; + + virtual bool intersect(Ray &p_ray) = 0; + + virtual void intersect(Vector &r_rays) = 0; + + virtual void add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id) = 0; + virtual void set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id) = 0; + virtual void commit() = 0; + + virtual void set_mesh_filter(const Set &p_mesh_ids) = 0; + virtual void clear_mesh_filter() = 0; + + static Ref create(); +}; + +class Lightmapper : public Reference { + GDCLASS(Lightmapper, Reference) +public: + enum LightType { + LIGHT_TYPE_DIRECTIONAL, + LIGHT_TYPE_OMNI, + LIGHT_TYPE_SPOT + }; + + enum BakeError { + BAKE_ERROR_LIGHTMAP_TOO_SMALL, + BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, + BAKE_ERROR_NO_MESHES, + BAKE_ERROR_USER_ABORTED, + BAKE_ERROR_NO_RAYCASTER, + BAKE_OK + }; + + enum BakeQuality { + BAKE_QUALITY_LOW, + BAKE_QUALITY_MEDIUM, + BAKE_QUALITY_HIGH, + BAKE_QUALITY_ULTRA, + }; + + typedef Lightmapper *(*CreateFunc)(); + + static CreateFunc create_custom; + static CreateFunc create_gpu; + static CreateFunc create_cpu; + +protected: +public: + typedef bool (*BakeStepFunc)(float, const String &, void *, bool); //progress, step description, userdata, force refresh + + struct MeshData { + struct TextureDef { + RID tex_rid; + Color mul; + Color add; + }; + + //triangle data + Vector points; + Vector uv; + Vector uv2; + Vector normal; + Vector albedo; + Vector emission; + Vector surface_facecounts; + Variant userdata; + }; + + virtual void add_albedo_texture(Ref p_texture) = 0; + virtual void add_emission_texture(Ref p_texture) = 0; + virtual void add_mesh(const MeshData &p_mesh, Vector2i p_size) = 0; + virtual void add_directional_light(bool p_bake_direct, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier) = 0; + virtual void add_omni_light(bool p_bake_direct, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation) = 0; + virtual void add_spot_light(bool p_bake_direct, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_multiplier, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation) = 0; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, bool p_generate_atlas, int p_max_texture_size, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, BakeStepFunc p_substep_function = nullptr) = 0; + + virtual int get_bake_texture_count() const = 0; + virtual Ref get_bake_texture(int p_index) const = 0; + virtual int get_bake_mesh_count() const = 0; + virtual Variant get_bake_mesh_userdata(int p_index) const = 0; + virtual Rect2 get_bake_mesh_uv_scale(int p_index) const = 0; + virtual int get_bake_mesh_texture_slice(int p_index) const = 0; + + static Ref create(); + + Lightmapper(); +}; + +#endif // LIGHTMAPPER_H diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index a66b18e0986..7bea31af214 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -170,6 +170,23 @@ Ref GeometryInstance::get_material_override() const { return material_override; } +void GeometryInstance::set_generate_lightmap(bool p_enabled) { + generate_lightmap = p_enabled; +} + +bool GeometryInstance::get_generate_lightmap() { + return generate_lightmap; +} + +void GeometryInstance::set_lightmap_scale(LightmapScale p_scale) { + ERR_FAIL_INDEX(p_scale, LIGHTMAP_SCALE_MAX); + lightmap_scale = p_scale; +} + +GeometryInstance::LightmapScale GeometryInstance::get_lightmap_scale() const { + return lightmap_scale; +} + void GeometryInstance::set_lod_min_distance(float p_dist) { lod_min_distance = p_dist; @@ -274,6 +291,12 @@ void GeometryInstance::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cast_shadows_setting", "shadow_casting_setting"), &GeometryInstance::set_cast_shadows_setting); ClassDB::bind_method(D_METHOD("get_cast_shadows_setting"), &GeometryInstance::get_cast_shadows_setting); + ClassDB::bind_method(D_METHOD("set_generate_lightmap", "enabled"), &GeometryInstance::set_generate_lightmap); + ClassDB::bind_method(D_METHOD("get_generate_lightmap"), &GeometryInstance::get_generate_lightmap); + + ClassDB::bind_method(D_METHOD("set_lightmap_scale", "scale"), &GeometryInstance::set_lightmap_scale); + ClassDB::bind_method(D_METHOD("get_lightmap_scale"), &GeometryInstance::get_lightmap_scale); + ClassDB::bind_method(D_METHOD("set_lod_max_hysteresis", "mode"), &GeometryInstance::set_lod_max_hysteresis); ClassDB::bind_method(D_METHOD("get_lod_max_hysteresis"), &GeometryInstance::get_lod_max_hysteresis); @@ -297,7 +320,11 @@ void GeometryInstance::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material_override", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial,SpatialMaterial"), "set_material_override", "get_material_override"); ADD_PROPERTY(PropertyInfo(Variant::INT, "cast_shadow", PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"), "set_cast_shadows_setting", "get_cast_shadows_setting"); ADD_PROPERTY(PropertyInfo(Variant::REAL, "extra_cull_margin", PROPERTY_HINT_RANGE, "0,16384,0.01"), "set_extra_cull_margin", "get_extra_cull_margin"); + + ADD_GROUP("Baked Light", ""); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "use_in_baked_light"), "set_flag", "get_flag", FLAG_USE_BAKED_LIGHT); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_lightmap"), "set_generate_lightmap", "get_generate_lightmap"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "lightmap_scale", PROPERTY_HINT_ENUM, "1x,2x,4x,8x"), "set_lightmap_scale", "get_lightmap_scale"); ADD_GROUP("LOD", "lod_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "lod_min_distance", PROPERTY_HINT_RANGE, "0,32768,0.01"), "set_lod_min_distance", "get_lod_min_distance"); @@ -307,6 +334,12 @@ void GeometryInstance::_bind_methods() { //ADD_SIGNAL( MethodInfo("visibility_changed")); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_1X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_2X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_4X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_8X); + BIND_ENUM_CONSTANT(LIGHTMAP_SCALE_MAX); + BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_OFF); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_ON); BIND_ENUM_CONSTANT(SHADOW_CASTING_SETTING_DOUBLE_SIDED); @@ -329,5 +362,7 @@ GeometryInstance::GeometryInstance() { shadow_casting_setting = SHADOW_CASTING_SETTING_ON; extra_cull_margin = 0; + generate_lightmap = true; + lightmap_scale = LightmapScale::LIGHTMAP_SCALE_1X; //VS::get_singleton()->instance_geometry_set_baked_light_texture_index(get_instance(),0); } diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index ce67b4dd6a3..6c12ce5b2a2 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -91,6 +91,14 @@ public: FLAG_MAX = VS::INSTANCE_FLAG_MAX, }; + enum LightmapScale { + LIGHTMAP_SCALE_1X, + LIGHTMAP_SCALE_2X, + LIGHTMAP_SCALE_4X, + LIGHTMAP_SCALE_8X, + LIGHTMAP_SCALE_MAX, + }; + enum ShadowCastingSetting { SHADOW_CASTING_SETTING_OFF = VS::SHADOW_CASTING_SETTING_OFF, SHADOW_CASTING_SETTING_ON = VS::SHADOW_CASTING_SETTING_ON, @@ -100,6 +108,8 @@ public: private: bool flags[FLAG_MAX]; + bool generate_lightmap; + LightmapScale lightmap_scale; ShadowCastingSetting shadow_casting_setting; Ref material_override; float lod_min_distance; @@ -120,6 +130,15 @@ public: void set_cast_shadows_setting(ShadowCastingSetting p_shadow_casting_setting); ShadowCastingSetting get_cast_shadows_setting() const; + void set_bake_cast_shadows(bool p_enabled); + bool get_bake_cast_shadows(); + + void set_generate_lightmap(bool p_enabled); + bool get_generate_lightmap(); + + void set_lightmap_scale(LightmapScale p_scale); + LightmapScale get_lightmap_scale() const; + void set_lod_min_distance(float p_dist); float get_lod_min_distance() const; @@ -144,6 +163,7 @@ public: }; VARIANT_ENUM_CAST(GeometryInstance::Flags); +VARIANT_ENUM_CAST(GeometryInstance::LightmapScale); VARIANT_ENUM_CAST(GeometryInstance::ShadowCastingSetting); #endif diff --git a/scene/3d/voxel_light_baker.cpp b/scene/3d/voxel_light_baker.cpp index 48d722ff2ad..0af8cbf20c1 100644 --- a/scene/3d/voxel_light_baker.cpp +++ b/scene/3d/voxel_light_baker.cpp @@ -718,12 +718,10 @@ void VoxelLightBaker::_init_light_plot(int p_idx, int p_level, int p_x, int p_y, } } -void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, BakeMode p_bake_mode, float p_propagation, float p_energy) { +void VoxelLightBaker::begin_bake_light(BakeQuality p_quality, float p_propagation) { _check_init_light(); propagation = p_propagation; bake_quality = p_quality; - bake_mode = p_bake_mode; - energy = p_energy; } void VoxelLightBaker::_check_init_light() { @@ -733,7 +731,6 @@ void VoxelLightBaker::_check_init_light() { leaf_voxel_count = 0; _fixup_plot(0, 0); //pre fixup, so normal, albedo, emission, etc. work for lighting. bake_light.resize(bake_cells.size()); - print_line("bake light size: " + itos(bake_light.size())); //zeromem(bake_light.ptrw(), bake_light.size() * sizeof(Light)); first_leaf = -1; _init_light_plot(0, 0, 0, 0, 0, CHILD_EMPTY); @@ -1289,872 +1286,6 @@ void VoxelLightBaker::_fixup_plot(int p_idx, int p_level) { } } -//make sure any cell (save for the root) has an empty cell previous to it, so it can be interpolated into - -void VoxelLightBaker::_plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height) { - - int x[3]; - int y[3]; - - for (int j = 0; j < 3; j++) { - - x[j] = vertices[j].x * width; - y[j] = vertices[j].y * height; - //x[j] = CLAMP(x[j], 0, bt.width - 1); - //y[j] = CLAMP(y[j], 0, bt.height - 1); - } - - // sort the points vertically - if (y[1] > y[2]) { - SWAP(x[1], x[2]); - SWAP(y[1], y[2]); - SWAP(positions[1], positions[2]); - SWAP(normals[1], normals[2]); - } - if (y[0] > y[1]) { - SWAP(x[0], x[1]); - SWAP(y[0], y[1]); - SWAP(positions[0], positions[1]); - SWAP(normals[0], normals[1]); - } - if (y[1] > y[2]) { - SWAP(x[1], x[2]); - SWAP(y[1], y[2]); - SWAP(positions[1], positions[2]); - SWAP(normals[1], normals[2]); - } - - double dx_far = double(x[2] - x[0]) / (y[2] - y[0] + 1); - double dx_upper = double(x[1] - x[0]) / (y[1] - y[0] + 1); - double dx_low = double(x[2] - x[1]) / (y[2] - y[1] + 1); - double xf = x[0]; - double xt = x[0] + dx_upper; // if y[0] == y[1], special case - for (int yi = y[0]; yi <= (y[2] > height - 1 ? height - 1 : y[2]); yi++) { - if (yi >= 0) { - for (int xi = (xf > 0 ? int(xf) : 0); xi <= (xt < width ? xt : width - 1); xi++) { - //pixels[int(x + y * width)] = color; - - Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]); - Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]); - //vertices[2] - vertices[0]; - Vector2 v2 = Vector2(xi - x[0], yi - y[0]); - float d00 = v0.dot(v0); - float d01 = v0.dot(v1); - float d11 = v1.dot(v1); - float d20 = v2.dot(v0); - float d21 = v2.dot(v1); - float denom = (d00 * d11 - d01 * d01); - Vector3 pos; - Vector3 normal; - if (denom == 0) { - pos = positions[0]; - normal = normals[0]; - } else { - float v = (d11 * d20 - d01 * d21) / denom; - float w = (d00 * d21 - d01 * d20) / denom; - float u = 1.0f - v - w; - pos = positions[0] * u + positions[1] * v + positions[2] * w; - normal = normals[0] * u + normals[1] * v + normals[2] * w; - } - - int ofs = yi * width + xi; - pixels[ofs].normal = normal; - pixels[ofs].pos = pos; - } - - for (int xi = (xf < width ? int(xf) : width - 1); xi >= (xt > 0 ? xt : 0); xi--) { - //pixels[int(x + y * width)] = color; - Vector2 v0 = Vector2(x[1] - x[0], y[1] - y[0]); - Vector2 v1 = Vector2(x[2] - x[0], y[2] - y[0]); - //vertices[2] - vertices[0]; - Vector2 v2 = Vector2(xi - x[0], yi - y[0]); - float d00 = v0.dot(v0); - float d01 = v0.dot(v1); - float d11 = v1.dot(v1); - float d20 = v2.dot(v0); - float d21 = v2.dot(v1); - float denom = (d00 * d11 - d01 * d01); - Vector3 pos; - Vector3 normal; - if (denom == 0) { - pos = positions[0]; - normal = normals[0]; - } else { - float v = (d11 * d20 - d01 * d21) / denom; - float w = (d00 * d21 - d01 * d20) / denom; - float u = 1.0f - v - w; - pos = positions[0] * u + positions[1] * v + positions[2] * w; - normal = normals[0] * u + normals[1] * v + normals[2] * w; - } - - int ofs = yi * width + xi; - pixels[ofs].normal = normal; - pixels[ofs].pos = pos; - } - } - xf += dx_far; - if (yi < y[1]) - xt += dx_upper; - else - xt += dx_low; - } -} - -void VoxelLightBaker::_sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha) { - - int size = 1 << (cell_subdiv - 1); - - int clamp_v = size - 1; - //first of all, clamp - Vector3 pos; - pos.x = CLAMP(p_posf.x, 0, clamp_v); - pos.y = CLAMP(p_posf.y, 0, clamp_v); - pos.z = CLAMP(p_posf.z, 0, clamp_v); - - float level = (cell_subdiv - 1) - p_level; - - int target_level; - float level_filter; - if (level <= 0.0) { - level_filter = 0; - target_level = 0; - } else { - target_level = Math::ceil(level); - level_filter = target_level - level; - } - - const Cell *cells = bake_cells.ptr(); - const Light *light = bake_light.ptr(); - - Vector3 color[2][8]; - float alpha[2][8]; - zeromem(alpha, sizeof(float) * 2 * 8); - - //find cell at given level first - - for (int c = 0; c < 2; c++) { - - int current_level = MAX(0, target_level - c); - int level_cell_size = (1 << (cell_subdiv - 1)) >> current_level; - - for (int n = 0; n < 8; n++) { - - int x = int(pos.x); - int y = int(pos.y); - int z = int(pos.z); - - if (n & 1) - x += level_cell_size; - if (n & 2) - y += level_cell_size; - if (n & 4) - z += level_cell_size; - - int ofs_x = 0; - int ofs_y = 0; - int ofs_z = 0; - - x = CLAMP(x, 0, clamp_v); - y = CLAMP(y, 0, clamp_v); - z = CLAMP(z, 0, clamp_v); - - int half = size / 2; - uint32_t cell = 0; - for (int i = 0; i < current_level; i++) { - - const Cell *bc = &cells[cell]; - - int child = 0; - if (x >= ofs_x + half) { - child |= 1; - ofs_x += half; - } - if (y >= ofs_y + half) { - child |= 2; - ofs_y += half; - } - if (z >= ofs_z + half) { - child |= 4; - ofs_z += half; - } - - cell = bc->children[child]; - if (cell == CHILD_EMPTY) - break; - - half >>= 1; - } - - if (cell == CHILD_EMPTY) { - alpha[c][n] = 0; - } else { - alpha[c][n] = cells[cell].alpha; - - for (int i = 0; i < 6; i++) { - //anisotropic read light - float amount = p_direction.dot(aniso_normal[i]); - if (amount < 0) - amount = 0; - color[c][n].x += light[cell].accum[i][0] * amount; - color[c][n].y += light[cell].accum[i][1] * amount; - color[c][n].z += light[cell].accum[i][2] * amount; - } - - color[c][n].x += cells[cell].emission[0]; - color[c][n].y += cells[cell].emission[1]; - color[c][n].z += cells[cell].emission[2]; - } - } - } - - float target_level_size = size >> target_level; - Vector3 pos_fract[2]; - - pos_fract[0].x = Math::fmod(pos.x, target_level_size) / target_level_size; - pos_fract[0].y = Math::fmod(pos.y, target_level_size) / target_level_size; - pos_fract[0].z = Math::fmod(pos.z, target_level_size) / target_level_size; - - target_level_size = size >> MAX(0, target_level - 1); - - pos_fract[1].x = Math::fmod(pos.x, target_level_size) / target_level_size; - pos_fract[1].y = Math::fmod(pos.y, target_level_size) / target_level_size; - pos_fract[1].z = Math::fmod(pos.z, target_level_size) / target_level_size; - - float alpha_interp[2]; - Vector3 color_interp[2]; - - for (int i = 0; i < 2; i++) { - - Vector3 color_x00 = color[i][0].linear_interpolate(color[i][1], pos_fract[i].x); - Vector3 color_xy0 = color[i][2].linear_interpolate(color[i][3], pos_fract[i].x); - Vector3 blend_z0 = color_x00.linear_interpolate(color_xy0, pos_fract[i].y); - - Vector3 color_x0z = color[i][4].linear_interpolate(color[i][5], pos_fract[i].x); - Vector3 color_xyz = color[i][6].linear_interpolate(color[i][7], pos_fract[i].x); - Vector3 blend_z1 = color_x0z.linear_interpolate(color_xyz, pos_fract[i].y); - - color_interp[i] = blend_z0.linear_interpolate(blend_z1, pos_fract[i].z); - - float alpha_x00 = Math::lerp(alpha[i][0], alpha[i][1], pos_fract[i].x); - float alpha_xy0 = Math::lerp(alpha[i][2], alpha[i][3], pos_fract[i].x); - float alpha_z0 = Math::lerp(alpha_x00, alpha_xy0, pos_fract[i].y); - - float alpha_x0z = Math::lerp(alpha[i][4], alpha[i][5], pos_fract[i].x); - float alpha_xyz = Math::lerp(alpha[i][6], alpha[i][7], pos_fract[i].x); - float alpha_z1 = Math::lerp(alpha_x0z, alpha_xyz, pos_fract[i].y); - - alpha_interp[i] = Math::lerp(alpha_z0, alpha_z1, pos_fract[i].z); - } - - r_color = color_interp[0].linear_interpolate(color_interp[1], level_filter); - r_alpha = Math::lerp(alpha_interp[0], alpha_interp[1], level_filter); -} - -Vector3 VoxelLightBaker::_voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture) { - - float bias = 2.5; - float max_distance = (Vector3(1, 1, 1) * (1 << (cell_subdiv - 1))).length(); - - float dist = bias; - float alpha = 0.0; - Vector3 color; - - Vector3 scolor; - float salpha; - - while (dist < max_distance && alpha < 0.95) { - float diameter = MAX(1.0, 2.0 * p_aperture * dist); - _sample_baked_octree_filtered_and_anisotropic(p_pos + dist * p_normal, p_normal, log2(diameter), scolor, salpha); - float a = (1.0 - alpha); - color += scolor * a; - alpha += a * salpha; - dist += diameter * 0.5; - } - - /*if (blend_ambient) { - color.rgb = mix(ambient,color.rgb,min(1.0,alpha/0.95)); - }*/ - - return color; -} - -Vector3 VoxelLightBaker::_compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) { - - //find arbitrary tangent and bitangent, then build a matrix - Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0); - Vector3 tangent = v0.cross(p_normal).normalized(); - Vector3 bitangent = tangent.cross(p_normal).normalized(); - Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed(); - - const Vector3 *cone_dirs = NULL; - const float *cone_weights = NULL; - int cone_dir_count = 0; - float cone_aperture = 0; - - switch (bake_quality) { - case BAKE_QUALITY_LOW: { - //default quality - static const Vector3 dirs[4] = { - Vector3(Math_SQRT12, 0, Math_SQRT12), - Vector3(0, Math_SQRT12, Math_SQRT12), - Vector3(-Math_SQRT12, 0, Math_SQRT12), - Vector3(0, -Math_SQRT12, Math_SQRT12) - }; - - static const float weights[4] = { 0.25, 0.25, 0.25, 0.25 }; - - cone_dirs = dirs; - cone_dir_count = 4; - cone_aperture = 1.0; // tan(angle) 90 degrees - cone_weights = weights; - } break; - case BAKE_QUALITY_MEDIUM: { - //default quality - static const Vector3 dirs[6] = { - Vector3(0, 0, 1), - Vector3(0.866025, 0, 0.5), - Vector3(0.267617, 0.823639, 0.5), - Vector3(-0.700629, 0.509037, 0.5), - Vector3(-0.700629, -0.509037, 0.5), - Vector3(0.267617, -0.823639, 0.5) - }; - static const float weights[6] = { 0.25f, 0.15f, 0.15f, 0.15f, 0.15f, 0.15f }; - // - cone_dirs = dirs; - cone_dir_count = 6; - cone_aperture = 0.577; // tan(angle) 60 degrees - cone_weights = weights; - } break; - case BAKE_QUALITY_HIGH: { - - //high qualily - static const Vector3 dirs[10] = { - Vector3(0.8781648411741658, 0.0, 0.478358141694643), - Vector3(0.5369754325592234, 0.6794204427701518, 0.5000452447267606), - Vector3(-0.19849436573466497, 0.8429904390140635, 0.49996710542041645), - Vector3(-0.7856196499811189, 0.3639120321329737, 0.5003696617825604), - Vector3(-0.7856196499811189, -0.3639120321329737, 0.5003696617825604), - Vector3(-0.19849436573466497, -0.8429904390140635, 0.49996710542041645), - Vector3(0.5369754325592234, -0.6794204427701518, 0.5000452447267606), - Vector3(-0.4451656858129485, 0.0, 0.8954482185892644), - Vector3(0.19124006749743122, 0.39355745585016605, 0.8991883926788214), - Vector3(0.19124006749743122, -0.39355745585016605, 0.8991883926788214), - }; - static const float weights[10] = { 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.08571f, 0.133333f, 0.133333f, 0.13333f }; - cone_dirs = dirs; - cone_dir_count = 10; - cone_aperture = 0.404; // tan(angle) 45 degrees - cone_weights = weights; - } break; - } - - Vector3 accum; - - for (int i = 0; i < cone_dir_count; i++) { - Vector3 dir = normal_xform.xform(cone_dirs[i]).normalized(); //normal may not completely correct when transformed to cell - accum += _voxel_cone_trace(p_pos, dir, cone_aperture) * cone_weights[i]; - } - - return accum; -} - -_ALWAYS_INLINE_ uint32_t xorshift32(uint32_t *state) { - /* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */ - uint32_t x = *state; - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - *state = x; - return x; -} - -Vector3 VoxelLightBaker::_compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal) { - - int samples_per_quality[3] = { 48, 128, 512 }; - - int samples = samples_per_quality[bake_quality]; - - //create a basis in Z - Vector3 v0 = Math::abs(p_normal.z) < 0.999 ? Vector3(0, 0, 1) : Vector3(0, 1, 0); - Vector3 tangent = v0.cross(p_normal).normalized(); - Vector3 bitangent = tangent.cross(p_normal).normalized(); - Basis normal_xform = Basis(tangent, bitangent, p_normal).transposed(); - - float bias = 1.5; - int max_level = cell_subdiv - 1; - int size = 1 << max_level; - - Vector3 accum; - float spread = Math::deg2rad(80.0); - - const Light *light = bake_light.ptr(); - const Cell *cells = bake_cells.ptr(); - - uint32_t local_rng_state = rand(); //needs to be fixed again - - for (int i = 0; i < samples; i++) { - - float random_angle1 = (((xorshift32(&local_rng_state) % 65535) / 65535.0) * 2.0 - 1.0) * spread; - Vector3 axis(0, sin(random_angle1), cos(random_angle1)); - float random_angle2 = ((xorshift32(&local_rng_state) % 65535) / 65535.0) * Math_PI * 2.0; - Basis rot(Vector3(0, 0, 1), random_angle2); - axis = rot.xform(axis); - - Vector3 direction = normal_xform.xform(axis).normalized(); - - Vector3 advance = direction * _get_normal_advance(direction); - - Vector3 pos = p_pos /*+ Vector3(0.5, 0.5, 0.5)*/ + advance * bias; - - uint32_t cell = CHILD_EMPTY; - - while (cell == CHILD_EMPTY) { - - int x = int(pos.x); - int y = int(pos.y); - int z = int(pos.z); - - int ofs_x = 0; - int ofs_y = 0; - int ofs_z = 0; - int half = size / 2; - - if (x < 0 || x >= size) - break; - if (y < 0 || y >= size) - break; - if (z < 0 || z >= size) - break; - - //int level_limit = max_level; - - cell = 0; //start from root - for (int j = 0; j < max_level; j++) { - - const Cell *bc = &cells[cell]; - - int child = 0; - if (x >= ofs_x + half) { - child |= 1; - ofs_x += half; - } - if (y >= ofs_y + half) { - child |= 2; - ofs_y += half; - } - if (z >= ofs_z + half) { - child |= 4; - ofs_z += half; - } - - cell = bc->children[child]; - if (unlikely(cell == CHILD_EMPTY)) - break; - - half >>= 1; - } - - pos += advance; - } - - if (unlikely(cell != CHILD_EMPTY)) { - for (int j = 0; j < 6; j++) { - //anisotropic read light - float amount = direction.dot(aniso_normal[j]); - if (amount <= 0) - continue; - accum.x += light[cell].accum[j][0] * amount; - accum.y += light[cell].accum[j][1] * amount; - accum.z += light[cell].accum[j][2] * amount; - } - accum.x += cells[cell].emission[0]; - accum.y += cells[cell].emission[1]; - accum.z += cells[cell].emission[2]; - } - } - - // Make sure we don't reset this thread's RNG state - - return accum / samples; -} - -void VoxelLightBaker::_lightmap_bake_point(uint32_t p_x, LightMap *p_line) { - - LightMap *pixel = &p_line[p_x]; - if (pixel->pos == Vector3()) - return; - switch (bake_mode) { - case BAKE_MODE_CONE_TRACE: { - pixel->light = _compute_pixel_light_at_pos(pixel->pos, pixel->normal) * energy; - } break; - case BAKE_MODE_RAY_TRACE: { - pixel->light = _compute_ray_trace_at_pos(pixel->pos, pixel->normal) * energy; - } break; - } -} - -Error VoxelLightBaker::make_lightmap(const Transform &p_xform, Ref &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float), void *p_bake_time_ud) { - - //transfer light information to a lightmap - Ref mesh = p_mesh; - - //step 1 - create lightmap - int width; - int height; - Vector lightmap; - Transform xform = to_cell_space * p_xform; - if (mesh->get_lightmap_size_hint() == Size2()) { - double area = 0; - double uv_area = 0; - for (int i = 0; i < mesh->get_surface_count(); i++) { - Array arrays = mesh->surface_get_arrays(i); - PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; - PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; - PoolVector indices = arrays[Mesh::ARRAY_INDEX]; - - ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER); - - int vc = vertices.size(); - PoolVector::Read vr = vertices.read(); - PoolVector::Read u2r = uv2.read(); - PoolVector::Read ir; - int ic = 0; - - if (indices.size()) { - ic = indices.size(); - ir = indices.read(); - } - - int faces = ic ? ic / 3 : vc / 3; - for (int j = 0; j < faces; j++) { - Vector3 vertex[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - int idx = ic ? ir[j * 3 + k] : j * 3 + k; - vertex[k] = 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 = (ceil((1.0 / sqrt(uv_area)) * sqrt(area * default_texels_per_unit))); - width = height = CLAMP(pixels, 2, 4096); - } else { - width = mesh->get_lightmap_size_hint().x; - height = mesh->get_lightmap_size_hint().y; - } - - lightmap.resize(width * height); - - //step 2 plot faces to lightmap - for (int i = 0; i < mesh->get_surface_count(); i++) { - Array arrays = mesh->surface_get_arrays(i); - PoolVector vertices = arrays[Mesh::ARRAY_VERTEX]; - PoolVector normals = arrays[Mesh::ARRAY_NORMAL]; - PoolVector uv2 = arrays[Mesh::ARRAY_TEX_UV2]; - PoolVector indices = arrays[Mesh::ARRAY_INDEX]; - - ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(normals.size() == 0, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER); - - int vc = vertices.size(); - PoolVector::Read vr = vertices.read(); - PoolVector::Read nr = normals.read(); - PoolVector::Read u2r = uv2.read(); - PoolVector::Read ir; - int ic = 0; - - if (indices.size()) { - ic = indices.size(); - ir = indices.read(); - } - - int faces = ic ? ic / 3 : vc / 3; - for (int j = 0; j < faces; j++) { - Vector3 vertex[3]; - Vector3 normal[3]; - Vector2 uv[3]; - - for (int k = 0; k < 3; k++) { - int idx = ic ? ir[j * 3 + k] : j * 3 + k; - vertex[k] = xform.xform(vr[idx]); - normal[k] = xform.basis.xform(nr[idx]).normalized(); - uv[k] = u2r[idx]; - } - - _plot_triangle(uv, vertex, normal, lightmap.ptrw(), width, height); - } - } - - //step 3 perform voxel cone trace on lightmap pixels - { - LightMap *lightmap_ptr = lightmap.ptrw(); - uint64_t begin_time = OS::get_singleton()->get_ticks_usec(); - volatile int lines = 0; - - // make sure our OS-level rng is seeded - - for (int i = 0; i < height; i++) { - - thread_process_array(width, this, &VoxelLightBaker::_lightmap_bake_point, &lightmap_ptr[i * width]); - - lines = MAX(lines, i); //for multithread - if (p_bake_time_func) { - uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - begin_time; - float elapsed_sec = double(elapsed) / 1000000.0; - float remaining = lines < 1 ? 0 : (elapsed_sec / lines) * (height - lines - 1); - if (p_bake_time_func(p_bake_time_ud, remaining, lines / float(height))) { - return ERR_SKIP; - } - } - } - - if (bake_mode == BAKE_MODE_RAY_TRACE) { - //blur - //gauss kernel, 7 step sigma 2 - static const float gauss_kernel[4] = { 0.214607f, 0.189879f, 0.131514f, 0.071303f }; - //horizontal pass - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal == Vector3()) { - continue; //empty - } - float gauss_sum = gauss_kernel[0]; - Vector3 accum = lightmap_ptr[i * width + j].light * gauss_kernel[0]; - for (int k = 1; k < 4; k++) { - int new_x = j + k; - if (new_x >= width || lightmap_ptr[i * width + new_x].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k]; - } - for (int k = 1; k < 4; k++) { - int new_x = j - k; - if (new_x < 0 || lightmap_ptr[i * width + new_x].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[i * width + new_x].light * gauss_kernel[k]; - } - - lightmap_ptr[i * width + j].pos = accum /= gauss_sum; - } - } - //vertical pass - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal == Vector3()) - continue; //empty, don't write over it anyway - float gauss_sum = gauss_kernel[0]; - Vector3 accum = lightmap_ptr[i * width + j].pos * gauss_kernel[0]; - for (int k = 1; k < 4; k++) { - int new_y = i + k; - if (new_y >= height || lightmap_ptr[new_y * width + j].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k]; - } - for (int k = 1; k < 4; k++) { - int new_y = i - k; - if (new_y < 0 || lightmap_ptr[new_y * width + j].normal == Vector3()) - break; - gauss_sum += gauss_kernel[k]; - accum += lightmap_ptr[new_y * width + j].pos * gauss_kernel[k]; - } - - lightmap_ptr[i * width + j].light = accum /= gauss_sum; - } - } - } - - //add directional light (do this after blur) - { - const Cell *cells = bake_cells.ptr(); - const Light *light = bake_light.ptr(); -#ifdef _OPENMP -#pragma omp parallel -#endif - for (int i = 0; i < height; i++) { -#ifdef _OPENMP -#pragma omp parallel for schedule(dynamic, 1) -#endif - for (int j = 0; j < width; j++) { - - //if (i == 125 && j == 280) { - - LightMap *pixel = &lightmap_ptr[i * width + j]; - if (pixel->pos == Vector3()) - continue; //unused, skipe - - int x = int(pixel->pos.x) - 1; - int y = int(pixel->pos.y) - 1; - int z = int(pixel->pos.z) - 1; - Color accum; - int size = 1 << (cell_subdiv - 1); - - int found = 0; - - for (int k = 0; k < 8; k++) { - - int ofs_x = x; - int ofs_y = y; - int ofs_z = z; - - if (k & 1) - ofs_x++; - if (k & 2) - ofs_y++; - if (k & 4) - ofs_z++; - - if (x < 0 || x >= size) - continue; - if (y < 0 || y >= size) - continue; - if (z < 0 || z >= size) - continue; - - uint32_t cell = _find_cell_at_pos(cells, ofs_x, ofs_y, ofs_z); - - if (cell == CHILD_EMPTY) - continue; - for (int l = 0; l < 6; l++) { - float s = pixel->normal.dot(aniso_normal[l]); - if (s < 0) - s = 0; - accum.r += light[cell].direct_accum[l][0] * s; - accum.g += light[cell].direct_accum[l][1] * s; - accum.b += light[cell].direct_accum[l][2] * s; - } - found++; - } - if (found) { - accum /= found; - pixel->light.x += accum.r; - pixel->light.y += accum.g; - pixel->light.z += accum.b; - } - } - } - } - - { - //fill gaps with neighbour vertices to avoid filter fades to black on edges - - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - if (lightmap_ptr[i * width + j].normal != Vector3()) { - continue; //filled, skip - } - - //this can't be made separatable.. - - int closest_i = -1, closest_j = 1; - float closest_dist = 1e20; - - const int margin = 3; - for (int y = i - margin; y <= i + margin; y++) { - for (int x = j - margin; x <= j + margin; x++) { - - if (x == j && y == i) - continue; - if (x < 0 || x >= width) - continue; - if (y < 0 || y >= height) - continue; - if (lightmap_ptr[y * width + x].normal == Vector3()) - continue; //also ensures that blitted stuff is not reused - - float dist = Vector2(i - y, j - x).length(); - if (dist > closest_dist) - continue; - - closest_dist = dist; - closest_i = y; - closest_j = x; - } - } - - if (closest_i != -1) { - lightmap_ptr[i * width + j].light = lightmap_ptr[closest_i * width + closest_j].light; - } - } - } - } - - { - //fill the lightmap data - r_lightmap.width = width; - r_lightmap.height = height; - r_lightmap.light.resize(lightmap.size() * 3); - PoolVector::Write w = r_lightmap.light.write(); - for (int i = 0; i < lightmap.size(); i++) { - w[i * 3 + 0] = lightmap[i].light.x; - w[i * 3 + 1] = lightmap[i].light.y; - w[i * 3 + 2] = lightmap[i].light.z; - } - } - -#if 0 // Enable for debugging. - { - PoolVector img; - int ls = lightmap.size(); - img.resize(ls * 3); - { - PoolVector::Write w = img.write(); - for (int i = 0; i < ls; i++) { - w[i * 3 + 0] = CLAMP(lightmap_ptr[i].light.x * 255, 0, 255); - w[i * 3 + 1] = CLAMP(lightmap_ptr[i].light.y * 255, 0, 255); - w[i * 3 + 2] = CLAMP(lightmap_ptr[i].light.z * 255, 0, 255); - //w[i * 3 + 0] = CLAMP(lightmap_ptr[i].normal.x * 255, 0, 255); - //w[i * 3 + 1] = CLAMP(lightmap_ptr[i].normal.y * 255, 0, 255); - //w[i * 3 + 2] = CLAMP(lightmap_ptr[i].normal.z * 255, 0, 255); - //w[i * 3 + 0] = CLAMP(lightmap_ptr[i].pos.x / (1 << (cell_subdiv - 1)) * 255, 0, 255); - //w[i * 3 + 1] = CLAMP(lightmap_ptr[i].pos.y / (1 << (cell_subdiv - 1)) * 255, 0, 255); - //w[i * 3 + 2] = CLAMP(lightmap_ptr[i].pos.z / (1 << (cell_subdiv - 1)) * 255, 0, 255); - } - } - - Ref image; - image.instance(); - image->create(width, height, false, Image::FORMAT_RGB8, img); - - String name = p_mesh->get_name(); - if (name == "") { - name = "Mesh" + itos(p_mesh->get_instance_id()); - } - image->save_png(name + ".png"); - } -#endif - } - - return OK; -} - void VoxelLightBaker::begin_bake(int p_subdiv, const AABB &p_bounds) { original_bounds = p_bounds; @@ -2482,5 +1613,4 @@ VoxelLightBaker::VoxelLightBaker() { color_scan_cell_width = 4; bake_texture_size = 128; propagation = 0.85; - energy = 1.0; } diff --git a/scene/3d/voxel_light_baker.h b/scene/3d/voxel_light_baker.h index 135fae2ac81..9f7258caeb3 100644 --- a/scene/3d/voxel_light_baker.h +++ b/scene/3d/voxel_light_baker.h @@ -128,10 +128,8 @@ private: int bake_texture_size; float cell_size; float propagation; - float energy; BakeQuality bake_quality; - BakeMode bake_mode; int max_original_cells; @@ -147,25 +145,10 @@ private: uint32_t _find_cell_at_pos(const Cell *cells, int x, int y, int z); - struct LightMap { - Vector3 light; - Vector3 pos; - Vector3 normal; - }; - - void _plot_triangle(Vector2 *vertices, Vector3 *positions, Vector3 *normals, LightMap *pixels, int width, int height); - - _FORCE_INLINE_ void _sample_baked_octree_filtered_and_anisotropic(const Vector3 &p_posf, const Vector3 &p_direction, float p_level, Vector3 &r_color, float &r_alpha); - _FORCE_INLINE_ Vector3 _voxel_cone_trace(const Vector3 &p_pos, const Vector3 &p_normal, float p_aperture); - _FORCE_INLINE_ Vector3 _compute_pixel_light_at_pos(const Vector3 &p_pos, const Vector3 &p_normal); - _FORCE_INLINE_ Vector3 _compute_ray_trace_at_pos(const Vector3 &p_pos, const Vector3 &p_normal); - - void _lightmap_bake_point(uint32_t p_x, LightMap *p_line); - public: void begin_bake(int p_subdiv, const AABB &p_bounds); void plot_mesh(const Transform &p_xform, Ref &p_mesh, const Vector > &p_materials, const Ref &p_override_material); - void begin_bake_light(BakeQuality p_quality = BAKE_QUALITY_MEDIUM, BakeMode p_bake_mode = BAKE_MODE_CONE_TRACE, float p_propagation = 0.85, float p_energy = 1); + void begin_bake_light(BakeQuality p_quality = BAKE_QUALITY_MEDIUM, float p_propagation = 0.85); void plot_light_directional(const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, bool p_direct); void plot_light_omni(const Vector3 &p_pos, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, bool p_direct); void plot_light_spot(const Vector3 &p_pos, const Vector3 &p_axis, const Color &p_color, float p_energy, float p_indirect_energy, float p_radius, float p_attenutation, float p_spot_angle, float p_spot_attenuation, bool p_direct); @@ -177,8 +160,6 @@ public: PoolVector light; }; - Error make_lightmap(const Transform &p_xform, Ref &p_mesh, float default_texels_per_unit, LightMapData &r_lightmap, bool (*p_bake_time_func)(void *, float, float) = NULL, void *p_bake_time_ud = NULL); - PoolVector create_gi_probe_data(); Ref create_debug_multimesh(DebugMode p_mode = DEBUG_ALBEDO); PoolVector create_capture_octree(int p_subdiv); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index fb0595e58d2..57df051a9f6 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -30,11 +30,14 @@ #include "mesh.h" +#include "core/local_vector.h" #include "core/pair.h" #include "scene/resources/concave_polygon_shape.h" #include "scene/resources/convex_polygon_shape.h" #include "surface_tool.h" +#include "core/crypto/crypto_core.h" + #include Mesh::ConvexDecompositionFunc Mesh::convex_composition_function = NULL; @@ -1108,18 +1111,35 @@ struct ArrayMeshLightmapSurface { }; Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texel_size) { + int *cache_data = nullptr; + unsigned int cache_size = 0; + bool use_cache = false; // Don't use cache + return lightmap_unwrap_cached(cache_data, cache_size, use_cache, p_base_transform, p_texel_size); +} + +Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size) { ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); - Vector vertices; - Vector normals; - Vector indices; - Vector face_materials; - Vector uv; - Vector > uv_index; + LocalVector vertices; + LocalVector normals; + LocalVector indices; + LocalVector face_materials; + LocalVector uv; + LocalVector > uv_indices; + + Vector lightmap_surfaces; + + // Keep only the scale + Basis basis = p_base_transform.get_basis(); + Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + + Transform transform; + transform.scale(scale); + + Basis normal_basis = transform.basis.inverse().transposed(); - Vector surfaces; for (int i = 0; i < get_surface_count(); i++) { ArrayMeshLightmapSurface s; s.primitive = surface_get_primitive_type(i); @@ -1143,30 +1163,36 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe vertices.resize((vertex_ofs + vc) * 3); normals.resize((vertex_ofs + vc) * 3); - uv_index.resize(vertex_ofs + vc); + uv_indices.resize(vertex_ofs + vc); for (int j = 0; j < vc; j++) { - Vector3 v = p_base_transform.xform(r[j]); - Vector3 n = p_base_transform.basis.xform(rn[j]).normalized(); + Vector3 v = transform.xform(r[j]); + Vector3 n = normal_basis.xform(rn[j]).normalized(); - vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; - vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; - vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; - normals.write[(j + vertex_ofs) * 3 + 0] = n.x; - normals.write[(j + vertex_ofs) * 3 + 1] = n.y; - normals.write[(j + vertex_ofs) * 3 + 2] = n.z; - uv_index.write[j + vertex_ofs] = Pair(i, j); + vertices[(j + vertex_ofs) * 3 + 0] = v.x; + vertices[(j + vertex_ofs) * 3 + 1] = v.y; + vertices[(j + vertex_ofs) * 3 + 2] = v.z; + normals[(j + vertex_ofs) * 3 + 0] = n.x; + normals[(j + vertex_ofs) * 3 + 1] = n.y; + normals[(j + vertex_ofs) * 3 + 2] = n.z; + uv_indices[j + vertex_ofs] = Pair(i, j); } PoolVector rindices = arrays[Mesh::ARRAY_INDEX]; int ic = rindices.size(); + float eps = 1.19209290e-7F; // Taken from xatlas.h if (ic == 0) { for (int j = 0; j < vc / 3; j++) { - if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) + Vector3 p0 = transform.xform(r[j * 3 + 0]); + Vector3 p1 = transform.xform(r[j * 3 + 1]); + Vector3 p2 = transform.xform(r[j * 3 + 2]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; + } indices.push_back(vertex_ofs + j * 3 + 0); indices.push_back(vertex_ofs + j * 3 + 1); @@ -1178,8 +1204,14 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe PoolVector::Read ri = rindices.read(); for (int j = 0; j < ic / 3; j++) { - if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) + Vector3 p0 = transform.xform(r[ri[j * 3 + 0]]); + Vector3 p1 = transform.xform(r[ri[j * 3 + 1]]); + Vector3 p2 = transform.xform(r[ri[j * 3 + 2]]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { continue; + } + indices.push_back(vertex_ofs + ri[j * 3 + 0]); indices.push_back(vertex_ofs + ri[j * 3 + 1]); indices.push_back(vertex_ofs + ri[j * 3 + 2]); @@ -1187,7 +1219,49 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe } } - surfaces.push_back(s); + lightmap_surfaces.push_back(s); + } + + CryptoCore::MD5Context ctx; + ctx.start(); + + ctx.update((unsigned char *)&p_texel_size, sizeof(float)); + ctx.update((unsigned char *)indices.ptr(), sizeof(int) * indices.size()); + ctx.update((unsigned char *)face_materials.ptr(), sizeof(int) * face_materials.size()); + ctx.update((unsigned char *)vertices.ptr(), sizeof(float) * vertices.size()); + ctx.update((unsigned char *)normals.ptr(), sizeof(float) * normals.size()); + + unsigned char hash[16]; + ctx.finish(hash); + + bool cached = false; + unsigned int cache_idx = 0; + + if (r_used_cache && r_cache_data) { + //Check if hash is in cache data + + int *cache_data = r_cache_data; + int n_entries = cache_data[0]; + unsigned int r_idx = 1; + for (int i = 0; i < n_entries; ++i) { + if (memcmp(&cache_data[r_idx], hash, 16) == 0) { + cached = true; + cache_idx = r_idx; + break; + } + + r_idx += 4; // hash + r_idx += 2; // size hint + + int vertex_count = cache_data[r_idx]; + r_idx += 1; // vertex count + r_idx += vertex_count; // vertex + r_idx += vertex_count * 2; // uvs + + int index_count = cache_data[r_idx]; + r_idx += 1; // index count + r_idx += index_count; // indices + } } //unwrap @@ -1200,10 +1274,86 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe int size_x; int size_y; - bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), face_materials.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + if (r_used_cache && cached) { + int *cache_data = r_cache_data; - if (!ok) { - return ERR_CANT_CREATE; + // Return cache data pointer to the caller + r_cache_data = &cache_data[cache_idx]; + + cache_idx += 4; + + // Load size + size_x = ((int *)cache_data)[cache_idx]; + size_y = ((int *)cache_data)[cache_idx + 1]; + cache_idx += 2; + + // Load vertices + gen_vertex_count = cache_data[cache_idx]; + cache_idx++; + gen_vertices = &cache_data[cache_idx]; + cache_idx += gen_vertex_count; + + // Load UVs + gen_uvs = (float *)&cache_data[cache_idx]; + cache_idx += gen_vertex_count * 2; + + // Load indices + gen_index_count = cache_data[cache_idx]; + cache_idx++; + gen_indices = &cache_data[cache_idx]; + + // Return cache data size to the caller + r_cache_size = sizeof(int) * (4 + 2 + 1 + gen_vertex_count + (gen_vertex_count * 2) + 1 + gen_index_count); // hash + size hint + vertex_count + vertices + uvs + index_count + indices + r_used_cache = true; + } + + if (!cached) { + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), face_materials.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + + if (!ok) { + return ERR_CANT_CREATE; + } + + if (r_used_cache) { + unsigned int new_cache_size = 4 + 2 + 1 + gen_vertex_count + (gen_vertex_count * 2) + 1 + gen_index_count; // hash + size hint + vertex_count + vertices + uvs + index_count + indices + new_cache_size *= sizeof(int); + int *new_cache_data = (int *)memalloc(new_cache_size); + unsigned int new_cache_idx = 0; + + // hash + memcpy(&new_cache_data[new_cache_idx], hash, 16); + new_cache_idx += 4; + + // size hint + new_cache_data[new_cache_idx] = size_x; + new_cache_data[new_cache_idx + 1] = size_y; + new_cache_idx += 2; + + // vertex count + new_cache_data[new_cache_idx] = gen_vertex_count; + new_cache_idx++; + + // vertices + memcpy(&new_cache_data[new_cache_idx], gen_vertices, sizeof(int) * gen_vertex_count); + new_cache_idx += gen_vertex_count; + + // uvs + memcpy(&new_cache_data[new_cache_idx], gen_uvs, sizeof(float) * gen_vertex_count * 2); + new_cache_idx += gen_vertex_count * 2; + + // index count + new_cache_data[new_cache_idx] = gen_index_count; + new_cache_idx++; + + // indices + memcpy(&new_cache_data[new_cache_idx], gen_indices, sizeof(int) * gen_index_count); + new_cache_idx += gen_index_count; + + // Return cache data to the caller + r_cache_data = new_cache_data; + r_cache_size = new_cache_size; + r_used_cache = false; + } } //remove surfaces @@ -1212,13 +1362,13 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe } //create surfacetools for each surface.. - Vector > surfaces_tools; + LocalVector > surfaces_tools; - for (int i = 0; i < surfaces.size(); i++) { + for (int i = 0; i < lightmap_surfaces.size(); i++) { Ref st; st.instance(); st->begin(Mesh::PRIMITIVE_TRIANGLES); - st->set_material(surfaces[i].material); + st->set_material(lightmap_surfaces[i].material); surfaces_tools.push_back(st); //stay there } @@ -1226,61 +1376,62 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe //go through all indices for (int i = 0; i < gen_index_count; i += 3) { - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_index.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_index.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_index.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_COND_V(uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 1]]].first || uv_index[gen_vertices[gen_indices[i + 0]]].first != uv_index[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); + ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); - int surface = uv_index[gen_vertices[gen_indices[i + 0]]].first; + int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; for (int j = 0; j < 3; j++) { - SurfaceTool::Vertex v = surfaces[surface].vertices[uv_index[gen_vertices[gen_indices[i + j]]].second]; + SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; - if (surfaces[surface].format & ARRAY_FORMAT_COLOR) { - surfaces_tools.write[surface]->add_color(v.color); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_COLOR) { + surfaces_tools[surface]->add_color(v.color); } - if (surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { - surfaces_tools.write[surface]->add_uv(v.uv); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TEX_UV) { + surfaces_tools[surface]->add_uv(v.uv); } - if (surfaces[surface].format & ARRAY_FORMAT_NORMAL) { - surfaces_tools.write[surface]->add_normal(v.normal); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_NORMAL) { + surfaces_tools[surface]->add_normal(v.normal); } - if (surfaces[surface].format & ARRAY_FORMAT_TANGENT) { + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TANGENT) { Plane t; t.normal = v.tangent; t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools.write[surface]->add_tangent(t); + surfaces_tools[surface]->add_tangent(t); } - if (surfaces[surface].format & ARRAY_FORMAT_BONES) { - surfaces_tools.write[surface]->add_bones(v.bones); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_BONES) { + surfaces_tools[surface]->add_bones(v.bones); } - if (surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { - surfaces_tools.write[surface]->add_weights(v.weights); + if (lightmap_surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) { + surfaces_tools[surface]->add_weights(v.weights); } Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools.write[surface]->add_uv2(uv2); + surfaces_tools[surface]->add_uv2(uv2); - surfaces_tools.write[surface]->add_vertex(v.vertex); + surfaces_tools[surface]->add_vertex(v.vertex); } } - //free stuff - ::free(gen_vertices); - ::free(gen_indices); - ::free(gen_uvs); - //generate surfaces - - for (int i = 0; i < surfaces_tools.size(); i++) { - surfaces_tools.write[i]->index(); - surfaces_tools.write[i]->commit(Ref((ArrayMesh *)this), surfaces[i].format); + for (unsigned int i = 0; i < surfaces_tools.size(); i++) { + surfaces_tools[i]->index(); + surfaces_tools[i]->commit(Ref((ArrayMesh *)this), lightmap_surfaces[i].format); } set_lightmap_size_hint(Size2(size_x, size_y)); + if (!cached) { + //free stuff + ::free(gen_vertices); + ::free(gen_indices); + ::free(gen_uvs); + } + return OK; } diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index f4f86ff4598..3c4cbb570e5 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -231,6 +231,7 @@ public: void regen_normalmaps(); Error lightmap_unwrap(const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); + Error lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform = Transform(), float p_texel_size = 0.05); virtual void reload_from_file(); diff --git a/scene/resources/sky.cpp b/scene/resources/sky.cpp index a724b435d6c..5bd96bac0e6 100644 --- a/scene/resources/sky.cpp +++ b/scene/resources/sky.cpp @@ -390,6 +390,10 @@ ProceduralSky::TextureSize ProceduralSky::get_texture_size() const { return texture_size; } +Ref ProceduralSky::get_panorama() const { + return panorama; +} + RID ProceduralSky::get_rid() const { return sky; } @@ -414,9 +418,9 @@ void ProceduralSky::_update_sky() { } } else { - Ref image = _generate_sky(); - VS::get_singleton()->texture_allocate(texture, image->get_width(), image->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); - VS::get_singleton()->texture_set_data(texture, image); + panorama = _generate_sky(); + VS::get_singleton()->texture_allocate(texture, panorama->get_width(), panorama->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); + VS::get_singleton()->texture_set_data(texture, panorama); _radiance_changed(); } } @@ -432,8 +436,9 @@ void ProceduralSky::_queue_update() { void ProceduralSky::_thread_done(const Ref &p_image) { - VS::get_singleton()->texture_allocate(texture, p_image->get_width(), p_image->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); - VS::get_singleton()->texture_set_data(texture, p_image); + panorama = p_image; + VS::get_singleton()->texture_allocate(texture, panorama->get_width(), panorama->get_height(), 0, Image::FORMAT_RGBE9995, VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER | VS::TEXTURE_FLAG_REPEAT); + VS::get_singleton()->texture_set_data(texture, panorama); _radiance_changed(); Thread::wait_to_finish(sky_thread); memdelete(sky_thread); diff --git a/scene/resources/sky.h b/scene/resources/sky.h index 5d763cbdbb9..503b23976f1 100644 --- a/scene/resources/sky.h +++ b/scene/resources/sky.h @@ -122,6 +122,7 @@ private: RID sky; RID texture; + Ref panorama; bool update_queued; bool regen_queued; @@ -189,6 +190,8 @@ public: void set_texture_size(TextureSize p_size); TextureSize get_texture_size() const; + Ref get_panorama() const; + virtual RID get_rid() const; ProceduralSky(bool p_desaturate = false); diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 0e94864b8f7..03e5a676d93 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -2250,6 +2250,143 @@ Image::Format TextureLayered::get_format() const { return format; } +Error TextureLayered::load(const String &p_path) { + + Error error; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &error); + ERR_FAIL_COND_V(error, error); + + uint8_t header[5] = { 0, 0, 0, 0, 0 }; + f->get_buffer(header, 4); + + if (header[0] == 'G' && header[1] == 'D' && header[2] == '3' && header[3] == 'T') { + if (!Object::cast_to(this)) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_INVALID_DATA); + } + } else if (header[0] == 'G' && header[1] == 'D' && header[2] == 'A' && header[3] == 'T') { + if (!Object::cast_to(this)) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_INVALID_DATA); + } + } else { + + f->close(); + memdelete(f); + ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Unrecognized layered texture file format: " + String((const char *)header)); + } + + int tw = f->get_32(); + int th = f->get_32(); + int td = f->get_32(); + int flags = f->get_32(); //texture flags! + Image::Format format = Image::Format(f->get_32()); + uint32_t compression = f->get_32(); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed + + create(tw, th, td, format, flags); + + for (int layer = 0; layer < td; layer++) { + + Ref image; + image.instance(); + + if (compression == COMPRESS_LOSSLESS) { + //look for a PNG file inside + + int mipmaps = f->get_32(); + Vector > mipmap_images; + + for (int i = 0; i < mipmaps; i++) { + uint32_t size = f->get_32(); + + PoolVector pv; + pv.resize(size); + { + PoolVector::Write w = pv.write(); + f->get_buffer(w.ptr(), size); + } + + Ref img = Image::lossless_unpacker(pv); + + if (img.is_null() || img->empty() || format != img->get_format()) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + + mipmap_images.push_back(img); + } + + if (mipmap_images.size() == 1) { + + image = mipmap_images[0]; + + } else { + int total_size = Image::get_image_data_size(tw, th, format, true); + PoolVector img_data; + img_data.resize(total_size); + + { + PoolVector::Write w = img_data.write(); + + int ofs = 0; + for (int i = 0; i < mipmap_images.size(); i++) { + + PoolVector id = mipmap_images[i]->get_data(); + int len = id.size(); + PoolVector::Read r = id.read(); + copymem(&w[ofs], r.ptr(), len); + ofs += len; + } + } + + image->create(tw, th, true, format, img_data); + if (image->empty()) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } + + } else { + + //look for regular format + bool mipmaps = (flags & Texture::FLAG_MIPMAPS); + int total_size = Image::get_image_data_size(tw, th, format, mipmaps); + + PoolVector img_data; + img_data.resize(total_size); + + { + PoolVector::Write w = img_data.write(); + int bytes = f->get_buffer(w.ptr(), total_size); + if (bytes != total_size) { + f->close(); + memdelete(f); + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + } + + image->create(tw, th, mipmaps, format, img_data); + } + + set_layer_data(image, layer); + } + + memdelete(f); + + path_to_file = p_path; + _change_notify(); + return OK; +} + +String TextureLayered::get_load_path() const { + + return path_to_file; +} + uint32_t TextureLayered::get_width() const { return width; } @@ -2262,6 +2399,20 @@ uint32_t TextureLayered::get_depth() const { return depth; } +void TextureLayered::reload_from_file() { + + String path = get_path(); + if (!path.is_resource_file()) + return; + + path = ResourceLoader::path_remap(path); //remap for translation + path = ResourceLoader::import_remap(path); //remap for import + if (!path.is_resource_file()) + return; + + load(path); +} + void TextureLayered::_set_data(const Dictionary &p_data) { ERR_FAIL_COND(!p_data.has("width")); ERR_FAIL_COND(!p_data.has("height")); @@ -2410,139 +2561,11 @@ RES ResourceFormatLoaderTextureLayered::load(const String &p_path, const String ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture extension."); } - FileAccess *f = FileAccess::open(p_path, FileAccess::READ); - ERR_FAIL_COND_V_MSG(!f, RES(), "Cannot open file '" + p_path + "'."); - - uint8_t header[5] = { 0, 0, 0, 0, 0 }; - f->get_buffer(header, 4); - - if (header[0] == 'G' && header[1] == 'D' && header[2] == '3' && header[3] == 'T') { - if (tex3d.is_null()) { - f->close(); - memdelete(f); - ERR_FAIL_COND_V(tex3d.is_null(), RES()) - } - } else if (header[0] == 'G' && header[1] == 'D' && header[2] == 'A' && header[3] == 'T') { - if (texarr.is_null()) { - f->close(); - memdelete(f); - ERR_FAIL_COND_V(texarr.is_null(), RES()) - } - } else { - - f->close(); - memdelete(f); - ERR_FAIL_V_MSG(RES(), "Unrecognized layered texture file format '" + String((const char *)header) + "'."); - } - - int tw = f->get_32(); - int th = f->get_32(); - int td = f->get_32(); - int flags = f->get_32(); //texture flags! - Image::Format format = Image::Format(f->get_32()); - uint32_t compression = f->get_32(); // 0 - lossless (PNG), 1 - vram, 2 - uncompressed - - lt->create(tw, th, td, format, flags); - - for (int layer = 0; layer < td; layer++) { - - Ref image; - image.instance(); - - if (compression == COMPRESSION_LOSSLESS) { - //look for a PNG file inside - - int mipmaps = f->get_32(); - Vector > mipmap_images; - - for (int i = 0; i < mipmaps; i++) { - uint32_t size = f->get_32(); - - PoolVector pv; - pv.resize(size); - { - PoolVector::Write w = pv.write(); - f->get_buffer(w.ptr(), size); - } - - Ref img = Image::lossless_unpacker(pv); - - if (img.is_null() || img->empty() || format != img->get_format()) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - - mipmap_images.push_back(img); - } - - if (mipmap_images.size() == 1) { - - image = mipmap_images[0]; - - } else { - int total_size = Image::get_image_data_size(tw, th, format, true); - PoolVector img_data; - img_data.resize(total_size); - - { - PoolVector::Write w = img_data.write(); - - int ofs = 0; - for (int i = 0; i < mipmap_images.size(); i++) { - - PoolVector id = mipmap_images[i]->get_data(); - int len = id.size(); - PoolVector::Read r = id.read(); - copymem(&w[ofs], r.ptr(), len); - ofs += len; - } - } - - image->create(tw, th, true, format, img_data); - if (image->empty()) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - } - - } else { - - //look for regular format - bool mipmaps = (flags & Texture::FLAG_MIPMAPS); - int total_size = Image::get_image_data_size(tw, th, format, mipmaps); - - PoolVector img_data; - img_data.resize(total_size); - - { - PoolVector::Write w = img_data.write(); - int bytes = f->get_buffer(w.ptr(), total_size); - if (bytes != total_size) { - if (r_error) { - *r_error = ERR_FILE_CORRUPT; - } - f->close(); - memdelete(f); - ERR_FAIL_V(RES()); - } - } - - image->create(tw, th, mipmaps, format, img_data); - } - - lt->set_layer_data(image, layer); - } - + Error err = lt->load(p_path); if (r_error) *r_error = OK; + if (err != OK) + return RES(); return lt; } diff --git a/scene/resources/texture.h b/scene/resources/texture.h index add9572dffd..9ee35ee13eb 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -477,7 +477,14 @@ public: FLAGS_DEFAULT = FLAG_FILTER, }; + enum CompressMode { + COMPRESS_LOSSLESS, + COMPRESS_VIDEO_RAM, + COMPRESS_UNCOMPRESSED + }; + private: + String path_to_file; bool is_3d; RID texture; Image::Format format; @@ -487,6 +494,8 @@ private: int height; int depth; + virtual void reload_from_file(); + void _set_data(const Dictionary &p_data); Dictionary _get_data() const; @@ -498,6 +507,9 @@ public: uint32_t get_flags() const; Image::Format get_format() const; + Error load(const String &p_path); + String get_load_path() const; + uint32_t get_width() const; uint32_t get_height() const; uint32_t get_depth() const; @@ -536,12 +548,6 @@ public: class ResourceFormatLoaderTextureLayered : public ResourceFormatLoader { public: - enum Compression { - COMPRESSION_LOSSLESS, - COMPRESSION_VRAM, - COMPRESSION_UNCOMPRESSED - }; - virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); virtual void get_recognized_extensions(List *p_extensions) const; virtual bool handles_type(const String &p_type) const; diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index bf0bd1b4a4c..894b782f5b3 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -120,6 +120,8 @@ public: InstanceBase *lightmap_capture; RID lightmap; Vector lightmap_capture_data; //in a array (12 values) to avoid wasting space if unused. Alpha is unused, but needed to send to shader + int lightmap_slice; + Rect2 lightmap_uv_rect; virtual void base_removed() = 0; virtual void base_changed(bool p_aabb, bool p_materials) = 0; @@ -135,6 +137,8 @@ public: baked_light = false; redraw_if_visible = false; lightmap_capture = NULL; + lightmap_slice = -1; + lightmap_uv_rect = Rect2(0, 0, 1, 1); } }; diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index daae705e142..cf6d6f7d729 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -548,7 +548,7 @@ public: BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_surface_material, RID, int, RID) BIND2(instance_set_visible, RID, bool) - BIND3(instance_set_use_lightmap, RID, RID, RID) + BIND5(instance_set_use_lightmap, RID, RID, RID, int, const Rect2 &) BIND2(instance_set_custom_aabb, RID, AABB) diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index d62ee8791e9..7c4b201400a 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -484,7 +484,7 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) { InstanceLightmapCaptureData *lightmap_capture = static_cast(instance->base_data); //erase dependencies, since no longer a lightmap while (lightmap_capture->users.front()) { - instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID()); + instance_set_use_lightmap(lightmap_capture->users.front()->get()->self, RID(), RID(), -1, Rect2(0, 0, 1, 1)); } } break; case VS::INSTANCE_GI_PROBE: { @@ -805,7 +805,7 @@ inline bool is_geometry_instance(VisualServer::InstanceType p_type) { return p_type == VS::INSTANCE_MESH || p_type == VS::INSTANCE_MULTIMESH || p_type == VS::INSTANCE_PARTICLES || p_type == VS::INSTANCE_IMMEDIATE; } -void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) { +void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); @@ -814,6 +814,8 @@ void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap InstanceLightmapCaptureData *lightmap_capture = static_cast(((Instance *)instance->lightmap_capture)->base_data); lightmap_capture->users.erase(instance); instance->lightmap = RID(); + instance->lightmap_slice = -1; + instance->lightmap_uv_rect = Rect2(0, 0, 1, 1); instance->lightmap_capture = NULL; } @@ -826,6 +828,8 @@ void VisualServerScene::instance_set_use_lightmap(RID p_instance, RID p_lightmap InstanceLightmapCaptureData *lightmap_capture = static_cast(((Instance *)instance->lightmap_capture)->base_data); lightmap_capture->users.insert(instance); instance->lightmap = p_lightmap; + instance->lightmap_slice = p_lightmap_slice; + instance->lightmap_uv_rect = p_lightmap_uv_rect; } } @@ -3618,7 +3622,7 @@ bool VisualServerScene::free(RID p_rid) { Instance *instance = instance_owner.get(p_rid); - instance_set_use_lightmap(p_rid, RID(), RID()); + instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1)); instance_set_scenario(p_rid, RID()); instance_set_base(p_rid, RID()); instance_geometry_set_material_override(p_rid, RID()); diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h index c228e2731a3..a33e9a4e97d 100644 --- a/servers/visual/visual_server_scene.h +++ b/servers/visual/visual_server_scene.h @@ -517,7 +517,7 @@ public: virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material); virtual void instance_set_visible(RID p_instance, bool p_visible); - virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap); + virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect); virtual void instance_set_custom_aabb(RID p_instance, AABB p_aabb); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 70052f2bd0a..0f24d7908f7 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -470,7 +470,7 @@ public: FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_surface_material, RID, int, RID) FUNC2(instance_set_visible, RID, bool) - FUNC3(instance_set_use_lightmap, RID, RID, RID) + FUNC5(instance_set_use_lightmap, RID, RID, RID, int, const Rect2 &) FUNC2(instance_set_custom_aabb, RID, AABB) diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index c14a2d9abc7..d5fcb3f230f 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1938,7 +1938,7 @@ void VisualServer::_bind_methods() { ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &VisualServer::instance_set_blend_shape_weight); ClassDB::bind_method(D_METHOD("instance_set_surface_material", "instance", "surface", "material"), &VisualServer::instance_set_surface_material); ClassDB::bind_method(D_METHOD("instance_set_visible", "instance", "visible"), &VisualServer::instance_set_visible); - ClassDB::bind_method(D_METHOD("instance_set_use_lightmap", "instance", "lightmap_instance", "lightmap"), &VisualServer::instance_set_use_lightmap); + ClassDB::bind_method(D_METHOD("instance_set_use_lightmap", "instance", "lightmap_instance", "lightmap", "lightmap_slice", "lightmap_uv_rect"), &VisualServer::instance_set_use_lightmap, DEFVAL(-1), DEFVAL(Rect2(0, 0, 1, 1))); ClassDB::bind_method(D_METHOD("instance_set_custom_aabb", "instance", "aabb"), &VisualServer::instance_set_custom_aabb); ClassDB::bind_method(D_METHOD("instance_attach_skeleton", "instance", "skeleton"), &VisualServer::instance_attach_skeleton); ClassDB::bind_method(D_METHOD("instance_set_exterior", "instance", "enabled"), &VisualServer::instance_set_exterior); @@ -2446,6 +2446,9 @@ VisualServer::VisualServer() { GLOBAL_DEF(sz_balance_render_tree, 0.0f); ProjectSettings::get_singleton()->set_custom_property_info(sz_balance_render_tree, PropertyInfo(Variant::REAL, sz_balance_render_tree, PROPERTY_HINT_RANGE, "0.0,1.0,0.01")); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling", true); + GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false); + GLOBAL_DEF("rendering/quality/2d/use_software_skinning", true); GLOBAL_DEF("rendering/quality/2d/ninepatch_mode", 0); ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/2d/ninepatch_mode", PropertyInfo(Variant::INT, "rendering/quality/2d/ninepatch_mode", PROPERTY_HINT_ENUM, "Default,Scaling")); diff --git a/servers/visual_server.h b/servers/visual_server.h index 48e73e04380..dee3b8749ef 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -843,7 +843,7 @@ public: virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0; virtual void instance_set_visible(RID p_instance, bool p_visible) = 0; - virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap) = 0; + virtual void instance_set_use_lightmap(RID p_instance, RID p_lightmap_instance, RID p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect) = 0; virtual void instance_set_custom_aabb(RID p_instance, AABB aabb) = 0; diff --git a/thirdparty/README.md b/thirdparty/README.md index 81d87717ba9..0a406db1c5f 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -350,6 +350,10 @@ Collection of single-file libraries used in Godot components. * Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012) * License: BSD-3-Clause * Modifications: use `const char*` instead of `char*` for input string +- `stb_rect_pack.h` + * Upstream: https://github.com/nothings/stb + * Version: 1.00 + * License: Public Domain (Unlicense) or MIT - `stb_vorbis.c` * Upstream: https://github.com/nothings/stb * Version: 1.20 (314d0a6f9af5af27e585336eecea333e95c5a2d8, 2020) diff --git a/thirdparty/stb_rect_pack/stb_rect_pack.h b/thirdparty/stb_rect_pack/stb_rect_pack.h new file mode 100644 index 00000000000..3336fe7395b --- /dev/null +++ b/thirdparty/stb_rect_pack/stb_rect_pack.h @@ -0,0 +1,629 @@ +// stb_rect_pack.h - v1.00 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ +