Implement new CPU lightmapper

Completely re-write the lightmap generation code:
- Follow the general lightmapper code structure from 4.0.
- Use proper path tracing to compute the global illumination.
- Use atlassing to merge all lightmaps into a single texture (done by @RandomShaper)
- Use OpenImageDenoiser to improve the generated lightmaps.
- Take into account alpha transparency in material textures.
- Allow baking environment lighting.
- Add bicubic lightmap filtering.

There is some minor compatibility breakage in some properties and methods
in BakedLightmap, but lightmaps generated in previous engine versions
should work fine out of the box.

The scene importer has been changed to generate `.unwrap_cache` files
next to the imported scene files. These files *SHOULD* be added to any
version control system as they guarantee there won't be differences when
re-importing the scene from other OSes or engine versions.

This work started as a Google Summer of Code project; Was later funded by IMVU for a good amount of progress;
Was then finished and polished by me on my free time.

Co-authored-by: Pedro J. Estébanez <pedrojrulez@gmail.com>
This commit is contained in:
JFonS 2020-12-19 18:17:22 +01:00
parent a80e4a6158
commit 112b416056
48 changed files with 4981 additions and 1732 deletions

View File

@ -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<Vector3> Geometry::compute_convex_mesh_points(const Plane *p_planes, int
return points;
}
Vector<Geometry::PackRectsResult> Geometry::partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size) {
Vector<stbrp_node> 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<stbrp_rect> 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<PackRectsResult> 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<bool>(rects[i].was_packed) };
}
return ret;
}

View File

@ -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<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size);
struct PackRectsResult {
int x;
int y;
bool packed;
};
static Vector<PackRectsResult> partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size);
static Vector<Vector3> compute_convex_mesh_points(const Plane *p_planes, int p_plane_count);
private:

View File

@ -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")));
}

View File

@ -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() {

View File

@ -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;

View File

@ -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

View File

@ -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);
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"));
}

View File

@ -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

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -107,16 +107,17 @@ void ResourceImporterLayeredTexture::_save_tex(const Vector<Ref<Image> > &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) {

View File

@ -931,9 +931,6 @@ static String _make_extname(const String &p_str) {
void ResourceImporterScene::_find_meshes(Node *p_node, Map<Ref<ArrayMesh>, Transform> &meshes) {
List<PropertyInfo> pi;
p_node->get_property_list(&pi);
MeshInstance *mi = Object::cast_to<MeshInstance>(p_node);
if (mi) {
@ -941,11 +938,11 @@ void ResourceImporterScene::_find_meshes(Node *p_node, Map<Ref<ArrayMesh>, Trans
Ref<ArrayMesh> mesh = mi->get_mesh();
if (mesh.is_valid() && !meshes.has(mesh)) {
Spatial *s = mi;
Spatial *s = Object::cast_to<Spatial>(mi);
Transform transform;
while (s) {
transform = transform * s->get_transform();
s = s->get_parent_spatial();
s = Object::cast_to<Spatial>(s->get_parent());
}
meshes[mesh] = transform;
@ -1427,11 +1424,31 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
Map<Ref<ArrayMesh>, 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");
int *cache_data = nullptr;
unsigned int cache_size = 0;
if (FileAccess::exists(cache_file_path)) {
Error err2;
FileAccess *file = FileAccess::open(cache_file_path, FileAccess::READ, &err2);
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<String, unsigned int> used_meshes;
EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size());
int step = 0;
for (Map<Ref<ArrayMesh>, Transform>::Element *E = meshes.front(); E; E = E->next()) {
@ -1444,12 +1461,80 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step);
Error err2 = mesh->lightmap_unwrap(E->get(), texel_size);
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);
}
}

View File

@ -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."));
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<BakedLightmap>(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);
if (tmp_progress != nullptr) {
memdelete(tmp_progress);
tmp_progress = NULL;
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() {

View File

@ -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:

View File

@ -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);

View File

@ -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<String, Task> tasks;
VBoxContainer *main;
uint64_t last_progress_tick;
static ProgressDialog *singleton;
void _popup();

View File

@ -45,7 +45,7 @@ protected:
public:
static LightmapDenoiser *create_oidn_denoiser();
Ref<Image> denoise_image(const Ref<Image> &p_image) override;
Ref<Image> denoise_image(const Ref<Image> &p_image);
static void make_default_denoiser();

View File

@ -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")

View File

@ -0,0 +1,6 @@
def can_build(env, platform):
return env["tools"] and env["module_raycast_enabled"]
def configure(env):
pass

File diff suppressed because it is too large Load Diff

View File

@ -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 <atomic>
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<Image> 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<Ref<Image> > bake_textures;
Map<RID, Ref<Image> > albedo_textures;
Map<RID, Ref<Image> > emission_textures;
LocalVector<MeshInstance> mesh_instances;
LocalVector<Light> lights;
LocalVector<LocalVector<LightmapTexel> > scene_lightmaps;
LocalVector<LocalVector<int> > scene_lightmap_indices;
Set<int> no_shadow_meshes;
std::atomic<uint32_t> thread_progress;
std::atomic<bool> thread_cancelled;
Ref<LightmapRaycaster> 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<Image> _init_bake_texture(const MeshData::TextureDef &p_texture_def, const Map<RID, Ref<Image> > &p_tex_cache, Image::Format p_default_format);
Color _bilinear_sample(const Ref<Image> &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<Image> &p_albedo_texture, const Ref<Image> &p_emission_texture, Vector2i p_size, LocalVector<LightmapTexel> &r_texels, LocalVector<int> &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<UVSeam> &r_seams);
void _fix_seams(const LocalVector<UVSeam> &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<int> p_indices, Vector2i p_size, int margin);
void _blit_lightmap(const Vector<Vector3> &p_src, const Vector2i &p_size, Ref<Image> &p_dst, int p_x, int p_y, bool p_with_padding);
public:
virtual void add_albedo_texture(Ref<Texture> p_texture);
virtual void add_emission_texture(Ref<Texture> 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<Image> &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<Image> 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

View File

@ -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<LightmapperCPU>();
Lightmapper::create_cpu = create_lightmapper_cpu;
#endif
}
void unregister_lightmapper_cpu_types() {
}

View File

@ -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

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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<Texture> lightmap;
struct {
Ref<Texture> single;
Ref<TextureLayered> 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<Texture> &p_lightmap, int p_instance = -1);
void add_user(const NodePath &p_path, const Ref<Resource> &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<Texture> get_user_lightmap(int p_user) const;
Ref<Resource> 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> mesh;
int32_t lightmap_scale;
Vector<Ref<Material> > 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<Sky> 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<BakedLightmapData> light_data;
struct PlotMesh {
Ref<Material> override_material;
Vector<Ref<Material> > instance_materials;
Ref<Mesh> 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<PlotMesh> &plot_meshes, List<PlotLight> &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<Ref<Texture> > &r_albedo_textures, Vector<Ref<Texture> > &r_emission_textures);
Ref<Image> _get_irradiance_from_sky(Ref<Sky> p_sky, Vector2i p_size);
Ref<Image> _get_irradiance_map(Ref<Environment> p_env, Vector2i p_size);
void _find_meshes_and_lights(Node *p_at_node, Vector<MeshesFound> &meshes, Vector<LightsFound> &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<BakedLightmapData> &p_data);
Ref<BakedLightmapData> 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<Sky> &p_sky);
Ref<Sky> 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<Face3> 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

76
scene/3d/lightmapper.cpp Normal file
View File

@ -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> LightmapDenoiser::create() {
if (create_function) {
return Ref<LightmapDenoiser>(create_function());
}
return Ref<LightmapDenoiser>();
}
LightmapRaycaster *(*LightmapRaycaster::create_function)() = nullptr;
Ref<LightmapRaycaster> LightmapRaycaster::create() {
if (create_function) {
return Ref<LightmapRaycaster>(create_function());
}
return Ref<LightmapRaycaster>();
}
Lightmapper::CreateFunc Lightmapper::create_custom = nullptr;
Lightmapper::CreateFunc Lightmapper::create_gpu = nullptr;
Lightmapper::CreateFunc Lightmapper::create_cpu = nullptr;
Ref<Lightmapper> 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<Lightmapper>();
} else {
return Ref<Lightmapper>(lm);
}
}
Lightmapper::Lightmapper() {
}

196
scene/3d/lightmapper.h Normal file
View File

@ -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<Image> denoise_image(const Ref<Image> &p_image) = 0;
static Ref<LightmapDenoiser> 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<Ray> &r_rays) = 0;
virtual void add_mesh(const Vector<Vector3> &p_vertices, const Vector<Vector3> &p_normals, const Vector<Vector2> &p_uv2s, unsigned int p_id) = 0;
virtual void set_mesh_alpha_texture(Ref<Image> p_alpha_texture, unsigned int p_id) = 0;
virtual void commit() = 0;
virtual void set_mesh_filter(const Set<int> &p_mesh_ids) = 0;
virtual void clear_mesh_filter() = 0;
static Ref<LightmapRaycaster> 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<Vector3> points;
Vector<Vector2> uv;
Vector<Vector2> uv2;
Vector<Vector3> normal;
Vector<TextureDef> albedo;
Vector<TextureDef> emission;
Vector<int> surface_facecounts;
Variant userdata;
};
virtual void add_albedo_texture(Ref<Texture> p_texture) = 0;
virtual void add_emission_texture(Ref<Texture> 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<Image> &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<Image> 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<Lightmapper> create();
Lightmapper();
};
#endif // LIGHTMAPPER_H

View File

@ -170,6 +170,23 @@ Ref<Material> 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);
}

View File

@ -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> 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

View File

@ -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<Mesh> &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> mesh = p_mesh;
//step 1 - create lightmap
int width;
int height;
Vector<LightMap> 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<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
PoolVector<int> indices = arrays[Mesh::ARRAY_INDEX];
ERR_FAIL_COND_V(vertices.size() == 0, ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(uv2.size() == 0, ERR_INVALID_PARAMETER);
int vc = vertices.size();
PoolVector<Vector3>::Read vr = vertices.read();
PoolVector<Vector2>::Read u2r = uv2.read();
PoolVector<int>::Read ir;
int ic = 0;
if (indices.size()) {
ic = indices.size();
ir = indices.read();
}
int faces = ic ? ic / 3 : vc / 3;
for (int j = 0; j < faces; j++) {
Vector3 vertex[3];
Vector2 uv[3];
for (int k = 0; k < 3; k++) {
int idx = ic ? ir[j * 3 + k] : j * 3 + k;
vertex[k] = 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<Vector3> vertices = arrays[Mesh::ARRAY_VERTEX];
PoolVector<Vector3> normals = arrays[Mesh::ARRAY_NORMAL];
PoolVector<Vector2> uv2 = arrays[Mesh::ARRAY_TEX_UV2];
PoolVector<int> 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<Vector3>::Read vr = vertices.read();
PoolVector<Vector3>::Read nr = normals.read();
PoolVector<Vector2>::Read u2r = uv2.read();
PoolVector<int>::Read ir;
int ic = 0;
if (indices.size()) {
ic = indices.size();
ir = indices.read();
}
int faces = ic ? ic / 3 : vc / 3;
for (int j = 0; j < faces; j++) {
Vector3 vertex[3];
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<float>::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<uint8_t> img;
int ls = lightmap.size();
img.resize(ls * 3);
{
PoolVector<uint8_t>::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;
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;
}

View File

@ -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<Mesh> &p_mesh, const Vector<Ref<Material> > &p_materials, const Ref<Material> &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<float> light;
};
Error make_lightmap(const Transform &p_xform, Ref<Mesh> &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<int> create_gi_probe_data();
Ref<MultiMesh> create_debug_multimesh(DebugMode p_mode = DEBUG_ALBEDO);
PoolVector<uint8_t> create_capture_octree(int p_subdiv);

View File

@ -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 <stdlib.h>
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<float> vertices;
Vector<float> normals;
Vector<int> indices;
Vector<int> face_materials;
Vector<float> uv;
Vector<Pair<int, int> > uv_index;
LocalVector<float> vertices;
LocalVector<float> normals;
LocalVector<int> indices;
LocalVector<int> face_materials;
LocalVector<float> uv;
LocalVector<Pair<int, int> > uv_indices;
Vector<ArrayMeshLightmapSurface> 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<ArrayMeshLightmapSurface> 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<int, int>(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<int, int>(i, j);
}
PoolVector<int> 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<int>::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,25 +1274,101 @@ Error ArrayMesh::lightmap_unwrap(const Transform &p_base_transform, float p_texe
int size_x;
int size_y;
if (r_used_cache && cached) {
int *cache_data = r_cache_data;
// 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
while (get_surface_count()) {
surface_remove(0);
}
//create surfacetools for each surface..
Vector<Ref<SurfaceTool> > surfaces_tools;
LocalVector<Ref<SurfaceTool> > surfaces_tools;
for (int i = 0; i < surfaces.size(); i++) {
for (int i = 0; i < lightmap_surfaces.size(); i++) {
Ref<SurfaceTool> 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);
}
}
//generate surfaces
for (unsigned int i = 0; i < surfaces_tools.size(); i++) {
surfaces_tools[i]->index();
surfaces_tools[i]->commit(Ref<ArrayMesh>((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);
//generate surfaces
for (int i = 0; i < surfaces_tools.size(); i++) {
surfaces_tools.write[i]->index();
surfaces_tools.write[i]->commit(Ref<ArrayMesh>((ArrayMesh *)this), surfaces[i].format);
}
set_lightmap_size_hint(Size2(size_x, size_y));
return OK;
}

View File

@ -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();

View File

@ -390,6 +390,10 @@ ProceduralSky::TextureSize ProceduralSky::get_texture_size() const {
return texture_size;
}
Ref<Image> ProceduralSky::get_panorama() const {
return panorama;
}
RID ProceduralSky::get_rid() const {
return sky;
}
@ -414,9 +418,9 @@ void ProceduralSky::_update_sky() {
}
} else {
Ref<Image> 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<Image> &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);

View File

@ -122,6 +122,7 @@ private:
RID sky;
RID texture;
Ref<Image> 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<Image> get_panorama() const;
virtual RID get_rid() const;
ProceduralSky(bool p_desaturate = false);

View File

@ -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<Texture3D>(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<TextureArray>(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;
image.instance();
if (compression == COMPRESS_LOSSLESS) {
//look for a PNG file inside
int mipmaps = f->get_32();
Vector<Ref<Image> > mipmap_images;
for (int i = 0; i < mipmaps; i++) {
uint32_t size = f->get_32();
PoolVector<uint8_t> pv;
pv.resize(size);
{
PoolVector<uint8_t>::Write w = pv.write();
f->get_buffer(w.ptr(), size);
}
Ref<Image> 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<uint8_t> img_data;
img_data.resize(total_size);
{
PoolVector<uint8_t>::Write w = img_data.write();
int ofs = 0;
for (int i = 0; i < mipmap_images.size(); i++) {
PoolVector<uint8_t> id = mipmap_images[i]->get_data();
int len = id.size();
PoolVector<uint8_t>::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<uint8_t> img_data;
img_data.resize(total_size);
{
PoolVector<uint8_t>::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;
image.instance();
if (compression == COMPRESSION_LOSSLESS) {
//look for a PNG file inside
int mipmaps = f->get_32();
Vector<Ref<Image> > mipmap_images;
for (int i = 0; i < mipmaps; i++) {
uint32_t size = f->get_32();
PoolVector<uint8_t> pv;
pv.resize(size);
{
PoolVector<uint8_t>::Write w = pv.write();
f->get_buffer(w.ptr(), size);
}
Ref<Image> 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<uint8_t> img_data;
img_data.resize(total_size);
{
PoolVector<uint8_t>::Write w = img_data.write();
int ofs = 0;
for (int i = 0; i < mipmap_images.size(); i++) {
PoolVector<uint8_t> id = mipmap_images[i]->get_data();
int len = id.size();
PoolVector<uint8_t>::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<uint8_t> img_data;
img_data.resize(total_size);
{
PoolVector<uint8_t>::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;
}

View File

@ -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<String> *p_extensions) const;
virtual bool handles_type(const String &p_type) const;

View File

@ -120,6 +120,8 @@ public:
InstanceBase *lightmap_capture;
RID lightmap;
Vector<Color> 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);
}
};

View File

@ -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)

View File

@ -484,7 +484,7 @@ void VisualServerScene::instance_set_base(RID p_instance, RID p_base) {
InstanceLightmapCaptureData *lightmap_capture = static_cast<InstanceLightmapCaptureData *>(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<InstanceLightmapCaptureData *>(((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<InstanceLightmapCaptureData *>(((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());

View File

@ -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);

View File

@ -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)

View File

@ -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"));

View File

@ -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;

View File

@ -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)

629
thirdparty/stb_rect_pack/stb_rect_pack.h vendored Normal file
View File

@ -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 <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#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.
------------------------------------------------------------------------------
*/