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. +------------------------------------------------------------------------------ +*/ +