Assorted fixes to UV unwrapping and GPU lightmapper
Various fixes to UV2 unwrapping and the GPU lightmapper. Listed here for context in case of git blame/bisect: * Fix UV2 unwrapping on import, also cleaned up the unwrap cache code. * Fix saving of RGBA images in EXR format. * Fixes to the GPU lightmapper: - Added padding between atlas elements, avoids bleeding. - Remove old SDF generation code. - Fix baked attenuation for Omni/Spot lights. - Fix baking of material properties onto UV2 (wireframe was wrongly used before). - Disable statically baked lights for objects that have a lightmap texture to avoid applying the same light twice. - Fix lightmap pairing in RendererSceneCull. - Fix UV2 array generated from `RenderingServer::mesh_surface_get_arrays()`. - Port autoexposure fix for OIDN from 3.x. - Save debug textures as EXR when using floating point format.
This commit is contained in:
parent
eb57dcdb90
commit
6995b0429c
|
@ -1136,7 +1136,7 @@ Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(Edito
|
|||
return importer->import_animation(p_path, p_flags, p_bake_fps);
|
||||
}
|
||||
|
||||
void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache) {
|
||||
void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches) {
|
||||
EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node);
|
||||
if (src_mesh_node) {
|
||||
//is mesh
|
||||
|
@ -1216,7 +1216,28 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
|
|||
n = n->get_parent_spatial();
|
||||
}
|
||||
|
||||
//use xf as transform for mesh, and bake it
|
||||
Vector<uint8_t> lightmap_cache;
|
||||
src_mesh_node->get_mesh()->lightmap_unwrap_cached(xf, p_lightmap_texel_size, p_src_lightmap_cache, lightmap_cache);
|
||||
|
||||
if (!lightmap_cache.is_empty()) {
|
||||
if (r_lightmap_caches.is_empty()) {
|
||||
r_lightmap_caches.push_back(lightmap_cache);
|
||||
} else {
|
||||
String new_md5 = String::md5(lightmap_cache.ptr()); // MD5 is stored at the beginning of the cache data
|
||||
|
||||
for (int i = 0; i < r_lightmap_caches.size(); i++) {
|
||||
String md5 = String::md5(r_lightmap_caches[i].ptr());
|
||||
if (new_md5 < md5) {
|
||||
r_lightmap_caches.insert(i, lightmap_cache);
|
||||
break;
|
||||
}
|
||||
|
||||
if (new_md5 == md5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (save_to_file != String()) {
|
||||
|
@ -1265,7 +1286,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
|
|||
}
|
||||
|
||||
for (int i = 0; i < p_node->get_child_count(); i++) {
|
||||
_generate_meshes(p_node->get_child(i), p_mesh_data, p_generate_lods, p_create_shadow_meshes, p_light_bake_mode, p_lightmap_texel_size, p_src_lightmap_cache, r_dst_lightmap_cache);
|
||||
_generate_meshes(p_node->get_child(i), p_mesh_data, p_generate_lods, p_create_shadow_meshes, p_light_bake_mode, p_lightmap_texel_size, p_src_lightmap_cache, r_lightmap_caches);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1433,7 +1454,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
|
|||
float lightmap_texel_size = MAX(0.001, texel_size);
|
||||
|
||||
Vector<uint8_t> src_lightmap_cache;
|
||||
Vector<uint8_t> dst_lightmap_cache;
|
||||
Vector<Vector<uint8_t>> mesh_lightmap_caches;
|
||||
|
||||
{
|
||||
src_lightmap_cache = FileAccess::get_file_as_array(p_source_file + ".unwrap_cache", &err);
|
||||
|
@ -1446,125 +1467,21 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
|
|||
if (subresources.has("meshes")) {
|
||||
mesh_data = subresources["meshes"];
|
||||
}
|
||||
_generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, dst_lightmap_cache);
|
||||
_generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, mesh_lightmap_caches);
|
||||
|
||||
if (dst_lightmap_cache.size()) {
|
||||
if (mesh_lightmap_caches.size()) {
|
||||
FileAccessRef f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);
|
||||
if (f) {
|
||||
f->store_buffer(dst_lightmap_cache.ptr(), dst_lightmap_cache.size());
|
||||
f->store_32(mesh_lightmap_caches.size());
|
||||
for (int i = 0; i < mesh_lightmap_caches.size(); i++) {
|
||||
String md5 = String::md5(mesh_lightmap_caches[i].ptr());
|
||||
f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());
|
||||
}
|
||||
f->close();
|
||||
}
|
||||
}
|
||||
err = OK;
|
||||
|
||||
#if 0
|
||||
if (light_bake_mode == 2 /* || generate LOD */) {
|
||||
Map<Ref<ArrayMesh>, Transform> meshes;
|
||||
_find_meshes(scene, meshes);
|
||||
|
||||
String file_id = src_path.get_file();
|
||||
String cache_file_path = base_path.plus_file(file_id + ".unwrap_cache");
|
||||
|
||||
Vector<unsigned char> cache_data;
|
||||
|
||||
if (FileAccess::exists(cache_file_path)) {
|
||||
Error err2;
|
||||
FileAccess *file = FileAccess::open(cache_file_path, FileAccess::READ, &err2);
|
||||
|
||||
if (err2) {
|
||||
if (file) {
|
||||
memdelete(file);
|
||||
}
|
||||
} else {
|
||||
int cache_size = file->get_len();
|
||||
cache_data.resize(cache_size);
|
||||
file->get_buffer(cache_data.ptrw(), cache_size);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, unsigned int> used_unwraps;
|
||||
|
||||
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()) {
|
||||
Ref<ArrayMesh> mesh = E->key();
|
||||
String name = mesh->get_name();
|
||||
if (name == "") { //should not happen but..
|
||||
name = "Mesh " + itos(step);
|
||||
}
|
||||
|
||||
progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step);
|
||||
|
||||
int *ret_cache_data = (int *)cache_data.ptrw();
|
||||
unsigned int ret_cache_size = cache_data.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_unwraps.insert(hash, ret_cache_size);
|
||||
|
||||
if (!ret_used_cache) {
|
||||
// Cache was not used, add the generated entry to the current cache
|
||||
if (cache_data.is_empty()) {
|
||||
cache_data.resize(4 + ret_cache_size);
|
||||
int *data = (int *)cache_data.ptrw();
|
||||
data[0] = 1;
|
||||
memcpy(&data[1], ret_cache_data, ret_cache_size);
|
||||
} else {
|
||||
int current_size = cache_data.size();
|
||||
cache_data.resize(cache_data.size() + ret_cache_size);
|
||||
unsigned char *ptrw = cache_data.ptrw();
|
||||
memcpy(&ptrw[current_size], ret_cache_data, ret_cache_size);
|
||||
int *data = (int *)ptrw;
|
||||
data[0] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
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_unwraps.size());
|
||||
|
||||
// Store cache entries
|
||||
const int *cache = (int *)cache_data.ptr();
|
||||
unsigned int r_idx = 1;
|
||||
for (int i = 0; i < cache[0]; ++i) {
|
||||
unsigned char *entry_start = (unsigned char *)&cache[r_idx];
|
||||
String entry_hash = String::md5(entry_start);
|
||||
if (used_unwraps.has(entry_hash)) {
|
||||
unsigned int entry_size = used_unwraps[entry_hash];
|
||||
file->store_buffer(entry_start, entry_size);
|
||||
}
|
||||
|
||||
r_idx += 4; // hash
|
||||
r_idx += 2; // size hint
|
||||
|
||||
int vertex_count = cache[r_idx];
|
||||
r_idx += 1; // vertex count
|
||||
r_idx += vertex_count; // vertex
|
||||
r_idx += vertex_count * 2; // uvs
|
||||
|
||||
int index_count = cache[r_idx];
|
||||
r_idx += 1; // index count
|
||||
r_idx += index_count; // indices
|
||||
}
|
||||
|
||||
file->close();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
progress.step(TTR("Running Custom Script..."), 2);
|
||||
|
||||
String post_import_script_path = p_options["import_script/path"];
|
||||
|
|
|
@ -119,7 +119,7 @@ class ResourceImporterScene : public ResourceImporter {
|
|||
};
|
||||
|
||||
void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner);
|
||||
void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache);
|
||||
void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);
|
||||
void _add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes);
|
||||
|
||||
public:
|
||||
|
|
|
@ -583,7 +583,7 @@ Ref<NavigationMesh> EditorSceneImporterMesh::create_navigation_mesh() {
|
|||
return nm;
|
||||
}
|
||||
|
||||
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, 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, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache);
|
||||
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, 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);
|
||||
|
||||
struct EditorSceneImporterMeshLightmapSurface {
|
||||
Ref<Material> material;
|
||||
|
@ -593,22 +593,24 @@ struct EditorSceneImporterMeshLightmapSurface {
|
|||
String name;
|
||||
};
|
||||
|
||||
Error EditorSceneImporterMesh::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) {
|
||||
Error EditorSceneImporterMesh::lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache) {
|
||||
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<float> uv;
|
||||
Vector<Pair<int, int>> uv_indices;
|
||||
LocalVector<float> vertices;
|
||||
LocalVector<float> normals;
|
||||
LocalVector<int> indices;
|
||||
LocalVector<float> uv;
|
||||
LocalVector<Pair<int, int>> uv_indices;
|
||||
|
||||
Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces;
|
||||
|
||||
// Keep only the scale
|
||||
Transform transform = p_base_transform;
|
||||
transform.origin = Vector3();
|
||||
transform.looking_at(Vector3(1, 0, 0), Vector3(0, 1, 0));
|
||||
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();
|
||||
|
||||
|
@ -623,15 +625,10 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
|
||||
SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
|
||||
|
||||
Vector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX];
|
||||
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
|
||||
int vc = rvertices.size();
|
||||
const Vector3 *r = rvertices.ptr();
|
||||
|
||||
Vector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL];
|
||||
|
||||
ERR_FAIL_COND_V_MSG(rnormals.size() == 0, ERR_UNAVAILABLE, "Normals are required for lightmap unwrap.");
|
||||
|
||||
const Vector3 *rn = rnormals.ptr();
|
||||
PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL];
|
||||
|
||||
int vertex_ofs = vertices.size() / 3;
|
||||
|
||||
|
@ -640,24 +637,29 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
uv_indices.resize(vertex_ofs + vc);
|
||||
|
||||
for (int j = 0; j < vc; j++) {
|
||||
Vector3 v = transform.xform(r[j]);
|
||||
Vector3 n = normal_basis.xform(rn[j]).normalized();
|
||||
Vector3 v = transform.xform(rvertices[j]);
|
||||
Vector3 n = normal_basis.xform(rnormals[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_indices.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);
|
||||
}
|
||||
|
||||
Vector<int> rindices = arrays[Mesh::ARRAY_INDEX];
|
||||
PackedInt32Array 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(rvertices[j * 3 + 0]);
|
||||
Vector3 p1 = transform.xform(rvertices[j * 3 + 1]);
|
||||
Vector3 p2 = transform.xform(rvertices[j * 3 + 2]);
|
||||
|
||||
if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -667,15 +669,18 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
}
|
||||
|
||||
} else {
|
||||
const int *ri = rindices.ptr();
|
||||
|
||||
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(rvertices[rindices[j * 3 + 0]]);
|
||||
Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]);
|
||||
Vector3 p2 = transform.xform(rvertices[rindices[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]);
|
||||
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 0]);
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 1]);
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -684,6 +689,9 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
|
||||
//unwrap
|
||||
|
||||
bool use_cache = true; // Used to request cache generation and to know if cache was used
|
||||
uint8_t *gen_cache;
|
||||
int gen_cache_size;
|
||||
float *gen_uvs;
|
||||
int *gen_vertices;
|
||||
int *gen_indices;
|
||||
|
@ -692,7 +700,7 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
int size_x;
|
||||
int size_y;
|
||||
|
||||
bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y, r_cache_data, r_cache_size, r_used_cache);
|
||||
bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y);
|
||||
|
||||
if (!ok) {
|
||||
return ERR_CANT_CREATE;
|
||||
|
@ -702,7 +710,7 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
clear();
|
||||
|
||||
//create surfacetools for each surface..
|
||||
Vector<Ref<SurfaceTool>> surfaces_tools;
|
||||
LocalVector<Ref<SurfaceTool>> surfaces_tools;
|
||||
|
||||
for (int i = 0; i < lightmap_surfaces.size(); i++) {
|
||||
Ref<SurfaceTool> st;
|
||||
|
@ -714,11 +722,12 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
}
|
||||
|
||||
print_verbose("Mesh: Gen indices: " + itos(gen_index_count));
|
||||
|
||||
//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_indices.size(), ERR_BUG);
|
||||
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_indices.size(), ERR_BUG);
|
||||
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_indices.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_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);
|
||||
|
||||
|
@ -728,49 +737,54 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign
|
|||
SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second];
|
||||
|
||||
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) {
|
||||
surfaces_tools.write[surface]->set_color(v.color);
|
||||
surfaces_tools[surface]->set_color(v.color);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) {
|
||||
surfaces_tools.write[surface]->set_uv(v.uv);
|
||||
surfaces_tools[surface]->set_uv(v.uv);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) {
|
||||
surfaces_tools.write[surface]->set_normal(v.normal);
|
||||
surfaces_tools[surface]->set_normal(v.normal);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & Mesh::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]->set_tangent(t);
|
||||
surfaces_tools[surface]->set_tangent(t);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) {
|
||||
surfaces_tools.write[surface]->set_bones(v.bones);
|
||||
surfaces_tools[surface]->set_bones(v.bones);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) {
|
||||
surfaces_tools.write[surface]->set_weights(v.weights);
|
||||
surfaces_tools[surface]->set_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]->set_uv2(uv2);
|
||||
surfaces_tools[surface]->set_uv2(uv2);
|
||||
|
||||
surfaces_tools.write[surface]->add_vertex(v.vertex);
|
||||
surfaces_tools[surface]->add_vertex(v.vertex);
|
||||
}
|
||||
}
|
||||
|
||||
//generate surfaces
|
||||
|
||||
for (int i = 0; i < surfaces_tools.size(); i++) {
|
||||
surfaces_tools.write[i]->index();
|
||||
Array arrays = surfaces_tools.write[i]->commit_to_arrays();
|
||||
add_surface(surfaces_tools.write[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools.write[i]->get_material(), surfaces_tools.write[i]->get_meta("name"));
|
||||
for (unsigned int i = 0; i < surfaces_tools.size(); i++) {
|
||||
surfaces_tools[i]->index();
|
||||
Array arrays = surfaces_tools[i]->commit_to_arrays();
|
||||
add_surface(surfaces_tools[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name"));
|
||||
}
|
||||
|
||||
set_lightmap_size_hint(Size2(size_x, size_y));
|
||||
|
||||
if (!r_used_cache) {
|
||||
//free stuff
|
||||
::free(gen_vertices);
|
||||
::free(gen_indices);
|
||||
::free(gen_uvs);
|
||||
if (gen_cache_size > 0) {
|
||||
r_dst_cache.resize(gen_cache_size);
|
||||
memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size);
|
||||
memfree(gen_cache);
|
||||
}
|
||||
|
||||
if (!use_cache) {
|
||||
// Cache was not used, free the buffers
|
||||
memfree(gen_vertices);
|
||||
memfree(gen_indices);
|
||||
memfree(gen_uvs);
|
||||
}
|
||||
|
||||
return OK;
|
||||
|
|
|
@ -105,7 +105,7 @@ public:
|
|||
Vector<Ref<Shape3D>> convex_decompose() const;
|
||||
Ref<Shape3D> create_trimesh_shape() const;
|
||||
Ref<NavigationMesh> create_navigation_mesh();
|
||||
Error 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);
|
||||
Error lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache);
|
||||
|
||||
void set_lightmap_size_hint(const Size2i &p_size);
|
||||
Size2i get_lightmap_size_hint() const;
|
||||
|
|
|
@ -162,8 +162,8 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
|
|||
MeshInstance &mi = mesh_instances.write[m_i];
|
||||
Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height());
|
||||
sizes.push_back(s);
|
||||
atlas_size.width = MAX(atlas_size.width, s.width);
|
||||
atlas_size.height = MAX(atlas_size.height, s.height);
|
||||
atlas_size.width = MAX(atlas_size.width, s.width + 2);
|
||||
atlas_size.height = MAX(atlas_size.height, s.height + 2);
|
||||
}
|
||||
|
||||
int max = nearest_power_of_2_templated(atlas_size.width);
|
||||
|
@ -186,10 +186,12 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
|
|||
|
||||
//determine best texture array atlas size by bruteforce fitting
|
||||
while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) {
|
||||
Vector<Vector2i> source_sizes = sizes;
|
||||
Vector<Vector2i> source_sizes;
|
||||
Vector<int> source_indices;
|
||||
source_indices.resize(source_sizes.size());
|
||||
source_sizes.resize(sizes.size());
|
||||
source_indices.resize(sizes.size());
|
||||
for (int i = 0; i < source_indices.size(); i++) {
|
||||
source_sizes.write[i] = sizes[i] + Vector2i(2, 2); // Add padding between lightmaps
|
||||
source_indices.write[i] = i;
|
||||
}
|
||||
Vector<Vector3i> atlas_offsets;
|
||||
|
@ -207,7 +209,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
|
|||
if (ofs.z > 0) {
|
||||
//valid
|
||||
ofs.z = slices;
|
||||
atlas_offsets.write[sidx] = ofs;
|
||||
atlas_offsets.write[sidx] = ofs + Vector3i(1, 1, 0); // Center lightmap in the reserved oversized region
|
||||
} else {
|
||||
new_indices.push_back(sidx);
|
||||
new_sources.push_back(source_sizes[i]);
|
||||
|
@ -272,7 +274,7 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_
|
|||
return BAKE_OK;
|
||||
}
|
||||
|
||||
void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
|
||||
void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata) {
|
||||
HashMap<Vertex, uint32_t, VertexHash> vertex_map;
|
||||
|
||||
//fill triangles array and vertex array
|
||||
|
@ -482,14 +484,6 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
|
|||
img->save_png("res://grid_layer_" + itos(1000 + i).substr(1, 3) + ".png");
|
||||
}
|
||||
#endif
|
||||
if (p_step_function) {
|
||||
p_step_function(0.45, TTR("Generating Signed Distance Field"), p_bake_userdata, true);
|
||||
}
|
||||
|
||||
//generate SDF for raytracing
|
||||
Vector<uint32_t> euclidean_pos = Geometry3D::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), false);
|
||||
Vector<uint32_t> euclidean_neg = Geometry3D::generate_edf(solid, Vector3i(grid_size, grid_size, grid_size), true);
|
||||
Vector<int8_t> sdf8 = Geometry3D::generate_sdf8(euclidean_pos, euclidean_neg);
|
||||
|
||||
/*****************************/
|
||||
/*** CREATE GPU STRUCTURES ***/
|
||||
|
@ -551,10 +545,6 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
|
|||
tf.format = RD::DATA_FORMAT_R32G32_UINT;
|
||||
texdata.write[0] = grid_indices.to_byte_array();
|
||||
grid_texture = rd->texture_create(tf, RD::TextureView(), texdata);
|
||||
//sdf
|
||||
tf.format = RD::DATA_FORMAT_R8_SNORM;
|
||||
texdata.write[0] = sdf8.to_byte_array();
|
||||
grid_texture_sdf = rd->texture_create(tf, RD::TextureView(), texdata);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -755,8 +745,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
light_environment_tex = rd->texture_create(tfp, RD::TextureView(), tdata);
|
||||
|
||||
#ifdef DEBUG_TEXTURES
|
||||
panorama_tex->convert(Image::FORMAT_RGB8);
|
||||
panorama_tex->save_png("res://0_panorama.png");
|
||||
panorama_tex->save_exr("res://0_panorama.exr", false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -770,7 +759,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
RID lights_buffer;
|
||||
RID triangle_cell_indices_buffer;
|
||||
RID grid_texture;
|
||||
RID grid_texture_sdf;
|
||||
RID seams_buffer;
|
||||
RID probe_positions_buffer;
|
||||
|
||||
|
@ -783,11 +771,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
rd->free(lights_buffer); \
|
||||
rd->free(triangle_cell_indices_buffer); \
|
||||
rd->free(grid_texture); \
|
||||
rd->free(grid_texture_sdf); \
|
||||
rd->free(seams_buffer); \
|
||||
rd->free(probe_positions_buffer);
|
||||
|
||||
_create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, grid_texture_sdf, seams_buffer, p_step_function, p_bake_userdata);
|
||||
_create_acceleration_structures(rd, atlas_size, atlas_slices, bounds, grid_size, probe_positions, p_generate_probes, slice_triangle_count, slice_seam_count, vertex_buffer, triangle_buffer, box_buffer, lights_buffer, triangle_cell_indices_buffer, probe_positions_buffer, grid_texture, seams_buffer, p_step_function, p_bake_userdata);
|
||||
|
||||
if (p_step_function) {
|
||||
p_step_function(0.47, TTR("Preparing shaders"), p_bake_userdata, true);
|
||||
|
@ -883,27 +870,20 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
RD::Uniform u;
|
||||
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
|
||||
u.binding = 9;
|
||||
u.ids.push_back(grid_texture_sdf);
|
||||
base_uniforms.push_back(u);
|
||||
}
|
||||
{
|
||||
RD::Uniform u;
|
||||
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
|
||||
u.binding = 10;
|
||||
u.ids.push_back(albedo_array_tex);
|
||||
base_uniforms.push_back(u);
|
||||
}
|
||||
{
|
||||
RD::Uniform u;
|
||||
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
|
||||
u.binding = 11;
|
||||
u.binding = 10;
|
||||
u.ids.push_back(emission_array_tex);
|
||||
base_uniforms.push_back(u);
|
||||
}
|
||||
{
|
||||
RD::Uniform u;
|
||||
u.uniform_type = RD::UNIFORM_TYPE_SAMPLER;
|
||||
u.binding = 12;
|
||||
u.binding = 11;
|
||||
u.ids.push_back(sampler);
|
||||
base_uniforms.push_back(u);
|
||||
}
|
||||
|
@ -937,13 +917,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
Ref<Image> img;
|
||||
img.instance();
|
||||
img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAF, s);
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
img->save_png("res://1_position_" + itos(i) + ".png");
|
||||
img->save_exr("res://1_position_" + itos(i) + ".exr", false);
|
||||
|
||||
s = rd->texture_get_data(normal_tex, i);
|
||||
img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
img->save_png("res://1_normal_" + itos(i) + ".png");
|
||||
img->save_exr("res://1_normal_" + itos(i) + ".exr", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -966,27 +944,27 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
}
|
||||
ERR_FAIL_COND_V(err != OK, BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
|
||||
|
||||
//unoccluder
|
||||
// Unoccluder
|
||||
RID compute_shader_unocclude = rd->shader_create_from_bytecode(compute_shader->get_bytecode("unocclude"));
|
||||
ERR_FAIL_COND_V(compute_shader_unocclude.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
|
||||
RID compute_shader_unocclude_pipeline = rd->compute_pipeline_create(compute_shader_unocclude);
|
||||
|
||||
//direct light
|
||||
// Direct light
|
||||
RID compute_shader_primary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("primary"));
|
||||
ERR_FAIL_COND_V(compute_shader_primary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); // internal check, should not happen
|
||||
RID compute_shader_primary_pipeline = rd->compute_pipeline_create(compute_shader_primary);
|
||||
|
||||
//indirect light
|
||||
// Indirect light
|
||||
RID compute_shader_secondary = rd->shader_create_from_bytecode(compute_shader->get_bytecode("secondary"));
|
||||
ERR_FAIL_COND_V(compute_shader_secondary.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
|
||||
RID compute_shader_secondary_pipeline = rd->compute_pipeline_create(compute_shader_secondary);
|
||||
|
||||
//dilate
|
||||
// Dilate
|
||||
RID compute_shader_dilate = rd->shader_create_from_bytecode(compute_shader->get_bytecode("dilate"));
|
||||
ERR_FAIL_COND_V(compute_shader_dilate.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
|
||||
RID compute_shader_dilate_pipeline = rd->compute_pipeline_create(compute_shader_dilate);
|
||||
|
||||
//dilate
|
||||
// Light probes
|
||||
RID compute_shader_light_probes = rd->shader_create_from_bytecode(compute_shader->get_bytecode("light_probes"));
|
||||
ERR_FAIL_COND_V(compute_shader_light_probes.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen
|
||||
RID compute_shader_light_probes_pipeline = rd->compute_pipeline_create(compute_shader_light_probes);
|
||||
|
@ -1153,8 +1131,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
Ref<Image> img;
|
||||
img.instance();
|
||||
img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
img->save_png("res://2_light_primary_" + itos(i) + ".png");
|
||||
img->save_exr("res://2_light_primary_" + itos(i) + ".exr", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1212,7 +1189,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
RD::Uniform u;
|
||||
u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
|
||||
u.binding = 6;
|
||||
u.ids.push_back(light_environment_tex); //reuse unocclude tex
|
||||
u.ids.push_back(light_environment_tex);
|
||||
uniforms.push_back(u);
|
||||
}
|
||||
}
|
||||
|
@ -1298,9 +1275,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (b == 0) {
|
||||
// This disables the environment for subsequent bounces
|
||||
push_constant.environment_xform[3] = -99.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the correct environment transform
|
||||
push_constant.environment_xform[3] = 0.0f;
|
||||
}
|
||||
|
||||
/* LIGHPROBES */
|
||||
|
||||
RID light_probe_buffer;
|
||||
|
@ -1449,8 +1434,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
Ref<Image> img;
|
||||
img.instance();
|
||||
img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
img->save_png("res://4_light_secondary_" + itos(i) + ".png");
|
||||
img->save_exr("res://4_light_secondary_" + itos(i) + ".exr", false);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1582,6 +1566,11 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
clear_colors.push_back(Color(0, 0, 0, 1));
|
||||
for (int i = 0; i < atlas_slices; i++) {
|
||||
int subslices = (p_bake_sh ? 4 : 1);
|
||||
|
||||
if (slice_seam_count[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int k = 0; k < subslices; k++) {
|
||||
RasterSeamsPushConstant seams_push_constant;
|
||||
seams_push_constant.slice = uint32_t(i * subslices + k);
|
||||
|
@ -1654,8 +1643,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
Ref<Image> img;
|
||||
img.instance();
|
||||
img->create(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
|
||||
img->convert(Image::FORMAT_RGBA8);
|
||||
img->save_png("res://5_blendseams" + itos(i) + ".png");
|
||||
img->save_exr("res://5_blendseams" + itos(i) + ".exr", false);
|
||||
}
|
||||
#endif
|
||||
if (p_step_function) {
|
||||
|
@ -1682,7 +1670,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
|
|||
Ref<Image> img2;
|
||||
img2.instance();
|
||||
img2->create(probe_values.size(), 1, false, Image::FORMAT_RGBAF, probe_data);
|
||||
img2->save_png("res://6_lightprobes.png");
|
||||
img2->save_exr("res://6_lightprobes.exr", false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -231,7 +231,7 @@ class LightmapperRD : public Lightmapper {
|
|||
Vector<Color> probe_values;
|
||||
|
||||
BakeError _blit_meshes_into_atlas(int p_max_texture_size, Vector<Ref<Image>> &albedo_images, Vector<Ref<Image>> &emission_images, AABB &bounds, Size2i &atlas_size, int &atlas_slices, BakeStepFunc p_step_function, void *p_bake_userdata);
|
||||
void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &grid_texture_sdf, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata);
|
||||
void _create_acceleration_structures(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, AABB &bounds, int grid_size, Vector<Probe> &probe_positions, GenerateProbes p_generate_probes, Vector<int> &slice_triangle_count, Vector<int> &slice_seam_count, RID &vertex_buffer, RID &triangle_buffer, RID &box_buffer, RID &lights_buffer, RID &triangle_cell_indices_buffer, RID &probe_positions_buffer, RID &grid_texture, RID &seams_buffer, BakeStepFunc p_step_function, void *p_bake_userdata);
|
||||
void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform);
|
||||
|
||||
public:
|
||||
|
|
|
@ -84,9 +84,8 @@ layout(set = 0, binding = 7, std430) restrict readonly buffer Probes {
|
|||
probe_positions;
|
||||
|
||||
layout(set = 0, binding = 8) uniform utexture3D grid;
|
||||
layout(set = 0, binding = 9) uniform texture3D grid_sdf;
|
||||
|
||||
layout(set = 0, binding = 10) uniform texture2DArray albedo_tex;
|
||||
layout(set = 0, binding = 11) uniform texture2DArray emission_tex;
|
||||
layout(set = 0, binding = 9) uniform texture2DArray albedo_tex;
|
||||
layout(set = 0, binding = 10) uniform texture2DArray emission_tex;
|
||||
|
||||
layout(set = 0, binding = 12) uniform sampler linear_sampler;
|
||||
layout(set = 0, binding = 11) uniform sampler linear_sampler;
|
||||
|
|
|
@ -96,15 +96,22 @@ params;
|
|||
bool ray_hits_triangle(vec3 from, vec3 dir, float max_dist, vec3 p0, vec3 p1, vec3 p2, out float r_distance, out vec3 r_barycentric) {
|
||||
const vec3 e0 = p1 - p0;
|
||||
const vec3 e1 = p0 - p2;
|
||||
vec3 triangleNormal = cross(e1, e0);
|
||||
vec3 triangle_normal = cross(e1, e0);
|
||||
|
||||
const vec3 e2 = (1.0 / dot(triangleNormal, dir)) * (p0 - from);
|
||||
float n_dot_dir = dot(triangle_normal, dir);
|
||||
|
||||
if (abs(n_dot_dir) < 0.01) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const vec3 e2 = (p0 - from) / n_dot_dir;
|
||||
const vec3 i = cross(dir, e2);
|
||||
|
||||
r_barycentric.y = dot(i, e1);
|
||||
r_barycentric.z = dot(i, e0);
|
||||
r_barycentric.x = 1.0 - (r_barycentric.z + r_barycentric.y);
|
||||
r_distance = dot(triangleNormal, e2);
|
||||
r_distance = dot(triangle_normal, e2);
|
||||
|
||||
return (r_distance > params.bias) && (r_distance < max_dist) && all(greaterThanEqual(r_barycentric, vec3(0.0)));
|
||||
}
|
||||
|
||||
|
@ -307,8 +314,6 @@ void main() {
|
|||
continue;
|
||||
}
|
||||
|
||||
d /= lights.data[i].range;
|
||||
|
||||
attenuation = get_omni_attenuation(d, 1.0 / lights.data[i].range, lights.data[i].attenuation);
|
||||
|
||||
if (lights.data[i].type == LIGHT_TYPE_SPOT) {
|
||||
|
@ -410,7 +415,7 @@ void main() {
|
|||
uint tidx;
|
||||
vec3 barycentric;
|
||||
|
||||
vec3 light;
|
||||
vec3 light = vec3(0.0);
|
||||
if (trace_ray(position + ray_dir * params.bias, position + ray_dir * length(params.world_size), tidx, barycentric)) {
|
||||
//hit a triangle
|
||||
vec2 uv0 = vertices.data[triangles.data[tidx].indices.x].uv;
|
||||
|
@ -419,8 +424,8 @@ void main() {
|
|||
vec3 uvw = vec3(barycentric.x * uv0 + barycentric.y * uv1 + barycentric.z * uv2, float(triangles.data[tidx].slice));
|
||||
|
||||
light = textureLod(sampler2DArray(source_light, linear_sampler), uvw, 0.0).rgb;
|
||||
} else {
|
||||
//did not hit a triangle, reach out for the sky
|
||||
} else if (params.env_transform[0][3] == 0.0) { // Use env_transform[0][3] to indicate when we are computing the first bounce
|
||||
// Did not hit a triangle, reach out for the sky
|
||||
vec3 sky_dir = normalize(mat3(params.env_transform) * ray_dir);
|
||||
|
||||
vec2 st = vec2(
|
||||
|
|
|
@ -169,7 +169,7 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale)
|
|||
{ 0 }, // R
|
||||
{ 1, 0 }, // GR
|
||||
{ 2, 1, 0 }, // BGR
|
||||
{ 2, 1, 0, 3 } // BGRA
|
||||
{ 3, 2, 1, 0 } // ABGR
|
||||
};
|
||||
|
||||
int channel_count = get_channel_count(format);
|
||||
|
|
|
@ -29,26 +29,19 @@
|
|||
/*************************************************************************/
|
||||
|
||||
#include "register_types.h"
|
||||
|
||||
#include "core/error/error_macros.h"
|
||||
|
||||
#include "core/crypto/crypto_core.h"
|
||||
|
||||
#include "thirdparty/xatlas/xatlas.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, 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);
|
||||
|
||||
extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, 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, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache);
|
||||
|
||||
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, int p_index_count, float **r_uvs, int **r_vertices, int *r_vertex_count, int **r_indices, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache) {
|
||||
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, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, 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) {
|
||||
CryptoCore::MD5Context ctx;
|
||||
ctx.start();
|
||||
|
||||
ctx.update((unsigned char *)&p_texel_size, sizeof(float));
|
||||
ctx.update((unsigned char *)p_indices, sizeof(int) * p_index_count);
|
||||
ctx.update((unsigned char *)p_vertices, sizeof(float) * p_vertex_count);
|
||||
ctx.update((unsigned char *)p_normals, sizeof(float) * p_vertex_count);
|
||||
ctx.update((unsigned char *)p_vertices, sizeof(float) * p_vertex_count * 3);
|
||||
ctx.update((unsigned char *)p_normals, sizeof(float) * p_vertex_count * 3);
|
||||
|
||||
unsigned char hash[16];
|
||||
ctx.finish(hash);
|
||||
|
@ -56,38 +49,37 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
bool cached = false;
|
||||
unsigned int cache_idx = 0;
|
||||
|
||||
if (r_used_cache && r_cache_size) {
|
||||
//Check if hash is in cache data
|
||||
*r_mesh_cache = nullptr;
|
||||
*r_mesh_cache_size = 0;
|
||||
|
||||
int *cache_data = r_cache_data;
|
||||
if (p_cache_data) {
|
||||
//Check if hash is in cache data
|
||||
int *cache_data = (int *)p_cache_data;
|
||||
int n_entries = cache_data[0];
|
||||
unsigned int r_idx = 1;
|
||||
unsigned int read_idx = 1;
|
||||
for (int i = 0; i < n_entries; ++i) {
|
||||
if (memcmp(&cache_data[r_idx], hash, 16) == 0) {
|
||||
if (memcmp(&cache_data[read_idx], hash, 16) == 0) {
|
||||
cached = true;
|
||||
cache_idx = r_idx;
|
||||
cache_idx = read_idx;
|
||||
break;
|
||||
}
|
||||
|
||||
r_idx += 4; // hash
|
||||
r_idx += 2; // size hint
|
||||
read_idx += 4; // hash
|
||||
read_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 vertex_count = cache_data[read_idx];
|
||||
read_idx += 1; // vertex count
|
||||
read_idx += vertex_count; // vertex
|
||||
read_idx += vertex_count * 2; // uvs
|
||||
|
||||
int index_count = cache_data[r_idx];
|
||||
r_idx += 1; // index count
|
||||
r_idx += index_count; // indices
|
||||
int index_count = cache_data[read_idx];
|
||||
read_idx += 1; // index count
|
||||
read_idx += index_count; // indices
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
if (cached) {
|
||||
int *cache_data = (int *)p_cache_data;
|
||||
|
||||
cache_idx += 4;
|
||||
|
||||
|
@ -99,24 +91,18 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
// Load vertices
|
||||
*r_vertex_count = cache_data[cache_idx];
|
||||
cache_idx++;
|
||||
*r_vertices = &cache_data[cache_idx];
|
||||
*r_vertex = &cache_data[cache_idx];
|
||||
cache_idx += *r_vertex_count;
|
||||
|
||||
// Load UVs
|
||||
*r_uvs = (float *)&cache_data[cache_idx];
|
||||
*r_uv = (float *)&cache_data[cache_idx];
|
||||
cache_idx += *r_vertex_count * 2;
|
||||
|
||||
// Load indices
|
||||
*r_index_count = cache_data[cache_idx];
|
||||
cache_idx++;
|
||||
*r_indices = &cache_data[cache_idx];
|
||||
|
||||
// Return cache data size to the caller
|
||||
r_cache_size = sizeof(int) * (4 + 2 + 1 + *r_vertex_count + (*r_vertex_count * 2) + 1 + *r_index_count); // hash + size hint + vertex_count + vertices + uvs + index_count + indices
|
||||
r_used_cache = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
*r_index = &cache_data[cache_idx];
|
||||
} else {
|
||||
// set up input mesh
|
||||
xatlas::MeshDecl input_mesh;
|
||||
input_mesh.indexData = p_indices;
|
||||
|
@ -128,23 +114,23 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
input_mesh.vertexPositionStride = sizeof(float) * 3;
|
||||
input_mesh.vertexNormalData = p_normals;
|
||||
input_mesh.vertexNormalStride = sizeof(uint32_t) * 3;
|
||||
input_mesh.vertexUvData = nullptr;
|
||||
input_mesh.vertexUvData = NULL;
|
||||
input_mesh.vertexUvStride = 0;
|
||||
|
||||
xatlas::ChartOptions chart_options;
|
||||
xatlas::PackOptions pack_options;
|
||||
chart_options.fixWinding = true;
|
||||
|
||||
pack_options.maxChartSize = 4096;
|
||||
pack_options.blockAlign = true;
|
||||
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;
|
||||
|
@ -160,35 +146,37 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
|
||||
const xatlas::Mesh &output = atlas->meshes[0];
|
||||
|
||||
*r_vertices = (int *)malloc(sizeof(int) * output.vertexCount);
|
||||
ERR_FAIL_NULL_V_MSG(*r_vertices, false, "Out of memory.");
|
||||
*r_uvs = (float *)malloc(sizeof(float) * output.vertexCount * 2);
|
||||
ERR_FAIL_NULL_V_MSG(*r_uvs, false, "Out of memory.");
|
||||
*r_indices = (int *)malloc(sizeof(int) * output.indexCount);
|
||||
ERR_FAIL_NULL_V_MSG(*r_indices, false, "Out of memory.");
|
||||
*r_vertex = (int *)memalloc(sizeof(int) * output.vertexCount);
|
||||
ERR_FAIL_NULL_V_MSG(*r_vertex, false, "Out of memory.");
|
||||
*r_uv = (float *)memalloc(sizeof(float) * output.vertexCount * 2);
|
||||
ERR_FAIL_NULL_V_MSG(*r_uv, false, "Out of memory.");
|
||||
*r_index = (int *)memalloc(sizeof(int) * output.indexCount);
|
||||
ERR_FAIL_NULL_V_MSG(*r_index, false, "Out of memory.");
|
||||
|
||||
float max_x = 0.0;
|
||||
float max_y = 0.0;
|
||||
float max_x = 0;
|
||||
float max_y = 0;
|
||||
for (uint32_t i = 0; i < output.vertexCount; i++) {
|
||||
(*r_vertices)[i] = output.vertexArray[i].xref;
|
||||
(*r_uvs)[i * 2 + 0] = output.vertexArray[i].uv[0] / w;
|
||||
(*r_uvs)[i * 2 + 1] = output.vertexArray[i].uv[1] / h;
|
||||
(*r_vertex)[i] = output.vertexArray[i].xref;
|
||||
(*r_uv)[i * 2 + 0] = output.vertexArray[i].uv[0] / w;
|
||||
(*r_uv)[i * 2 + 1] = output.vertexArray[i].uv[1] / h;
|
||||
max_x = MAX(max_x, output.vertexArray[i].uv[0]);
|
||||
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++) {
|
||||
(*r_indices)[i] = output.indexArray[i];
|
||||
(*r_index)[i] = output.indexArray[i];
|
||||
}
|
||||
|
||||
*r_index_count = output.indexCount;
|
||||
|
||||
xatlas::Destroy(atlas);
|
||||
}
|
||||
|
||||
if (*r_use_cache) {
|
||||
// Build cache data for current mesh
|
||||
|
||||
if (r_used_cache) {
|
||||
unsigned int new_cache_size = 4 + 2 + 1 + *r_vertex_count + (*r_vertex_count * 2) + 1 + *r_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);
|
||||
|
@ -208,11 +196,11 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
new_cache_idx++;
|
||||
|
||||
// vertices
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_vertices, sizeof(int) * *r_vertex_count);
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_vertex, sizeof(int) * (*r_vertex_count));
|
||||
new_cache_idx += *r_vertex_count;
|
||||
|
||||
// uvs
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_uvs, sizeof(float) * *r_vertex_count * 2);
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_uv, sizeof(float) * (*r_vertex_count) * 2);
|
||||
new_cache_idx += *r_vertex_count * 2;
|
||||
|
||||
// index count
|
||||
|
@ -220,15 +208,15 @@ bool xatlas_mesh_lightmap_unwrap_callback(float p_texel_size, const float *p_ver
|
|||
new_cache_idx++;
|
||||
|
||||
// indices
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_indices, sizeof(int) * *r_index_count);
|
||||
new_cache_idx += *r_index_count;
|
||||
memcpy(&new_cache_data[new_cache_idx], *r_index, sizeof(int) * (*r_index_count));
|
||||
|
||||
// Return cache data to the caller
|
||||
r_cache_data = new_cache_data;
|
||||
r_cache_size = new_cache_size;
|
||||
r_used_cache = false;
|
||||
*r_mesh_cache = (uint8_t *)new_cache_data;
|
||||
*r_mesh_cache_size = new_cache_size;
|
||||
}
|
||||
|
||||
*r_use_cache = cached; // Return whether cache was used.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1401,7 +1401,7 @@ void ArrayMesh::regen_normal_maps() {
|
|||
}
|
||||
|
||||
//dirty hack
|
||||
bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, 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, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache);
|
||||
bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, 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) = NULL;
|
||||
|
||||
struct ArrayMeshLightmapSurface {
|
||||
Ref<Material> material;
|
||||
|
@ -1411,28 +1411,28 @@ 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);
|
||||
Vector<uint8_t> null_cache;
|
||||
return lightmap_unwrap_cached(p_base_transform, p_texel_size, null_cache, null_cache, false);
|
||||
}
|
||||
|
||||
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) {
|
||||
Error ArrayMesh::lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache, bool p_generate_cache) {
|
||||
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<float> uv;
|
||||
Vector<Pair<int, int>> uv_indices;
|
||||
LocalVector<float> vertices;
|
||||
LocalVector<float> normals;
|
||||
LocalVector<int> indices;
|
||||
LocalVector<float> uv;
|
||||
LocalVector<Pair<int, int>> uv_indices;
|
||||
|
||||
Vector<ArrayMeshLightmapSurface> lightmap_surfaces;
|
||||
|
||||
// Keep only the scale
|
||||
Transform transform = p_base_transform;
|
||||
transform.origin = Vector3();
|
||||
transform.looking_at(Vector3(1, 0, 0), Vector3(0, 1, 0));
|
||||
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();
|
||||
|
||||
|
@ -1446,14 +1446,12 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
|
||||
Array arrays = surface_get_arrays(i);
|
||||
s.material = surface_get_material(i);
|
||||
SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices);
|
||||
SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format);
|
||||
|
||||
Vector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX];
|
||||
PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];
|
||||
int vc = rvertices.size();
|
||||
const Vector3 *r = rvertices.ptr();
|
||||
|
||||
Vector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL];
|
||||
const Vector3 *rn = rnormals.ptr();
|
||||
PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL];
|
||||
|
||||
int vertex_ofs = vertices.size() / 3;
|
||||
|
||||
|
@ -1462,24 +1460,29 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
uv_indices.resize(vertex_ofs + vc);
|
||||
|
||||
for (int j = 0; j < vc; j++) {
|
||||
Vector3 v = transform.xform(r[j]);
|
||||
Vector3 n = normal_basis.xform(rn[j]).normalized();
|
||||
Vector3 v = transform.xform(rvertices[j]);
|
||||
Vector3 n = normal_basis.xform(rnormals[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_indices.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);
|
||||
}
|
||||
|
||||
Vector<int> rindices = arrays[Mesh::ARRAY_INDEX];
|
||||
PackedInt32Array 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(rvertices[j * 3 + 0]);
|
||||
Vector3 p1 = transform.xform(rvertices[j * 3 + 1]);
|
||||
Vector3 p2 = transform.xform(rvertices[j * 3 + 2]);
|
||||
|
||||
if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1489,15 +1492,18 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
}
|
||||
|
||||
} else {
|
||||
const int *ri = rindices.ptr();
|
||||
|
||||
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(rvertices[rindices[j * 3 + 0]]);
|
||||
Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]);
|
||||
Vector3 p2 = transform.xform(rvertices[rindices[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]);
|
||||
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 0]);
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 1]);
|
||||
indices.push_back(vertex_ofs + rindices[j * 3 + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1506,6 +1512,9 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
|
||||
//unwrap
|
||||
|
||||
bool use_cache = p_generate_cache; // Used to request cache generation and to know if cache was used
|
||||
uint8_t *gen_cache;
|
||||
int gen_cache_size;
|
||||
float *gen_uvs;
|
||||
int *gen_vertices;
|
||||
int *gen_indices;
|
||||
|
@ -1514,17 +1523,16 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
int size_x;
|
||||
int size_y;
|
||||
|
||||
bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y, r_cache_data, r_cache_size, r_used_cache);
|
||||
bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y);
|
||||
|
||||
if (!ok) {
|
||||
return ERR_CANT_CREATE;
|
||||
}
|
||||
|
||||
//remove surfaces
|
||||
clear_surfaces();
|
||||
|
||||
//create surfacetools for each surface..
|
||||
Vector<Ref<SurfaceTool>> surfaces_tools;
|
||||
LocalVector<Ref<SurfaceTool>> surfaces_tools;
|
||||
|
||||
for (int i = 0; i < lightmap_surfaces.size(); i++) {
|
||||
Ref<SurfaceTool> st;
|
||||
|
@ -1535,11 +1543,12 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
}
|
||||
|
||||
print_verbose("Mesh: Gen indices: " + itos(gen_index_count));
|
||||
|
||||
//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_indices.size(), ERR_BUG);
|
||||
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_indices.size(), ERR_BUG);
|
||||
ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_indices.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_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);
|
||||
|
||||
|
@ -1549,48 +1558,53 @@ Error ArrayMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cach
|
|||
SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second];
|
||||
|
||||
if (lightmap_surfaces[surface].format & ARRAY_FORMAT_COLOR) {
|
||||
surfaces_tools.write[surface]->set_color(v.color);
|
||||
surfaces_tools[surface]->set_color(v.color);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & ARRAY_FORMAT_TEX_UV) {
|
||||
surfaces_tools.write[surface]->set_uv(v.uv);
|
||||
surfaces_tools[surface]->set_uv(v.uv);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & ARRAY_FORMAT_NORMAL) {
|
||||
surfaces_tools.write[surface]->set_normal(v.normal);
|
||||
surfaces_tools[surface]->set_normal(v.normal);
|
||||
}
|
||||
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]->set_tangent(t);
|
||||
surfaces_tools[surface]->set_tangent(t);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & ARRAY_FORMAT_BONES) {
|
||||
surfaces_tools.write[surface]->set_bones(v.bones);
|
||||
surfaces_tools[surface]->set_bones(v.bones);
|
||||
}
|
||||
if (lightmap_surfaces[surface].format & ARRAY_FORMAT_WEIGHTS) {
|
||||
surfaces_tools.write[surface]->set_weights(v.weights);
|
||||
surfaces_tools[surface]->set_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]->set_uv2(uv2);
|
||||
surfaces_tools[surface]->set_uv2(uv2);
|
||||
|
||||
surfaces_tools.write[surface]->add_vertex(v.vertex);
|
||||
surfaces_tools[surface]->add_vertex(v.vertex);
|
||||
}
|
||||
}
|
||||
|
||||
//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), lightmap_surfaces[i].format);
|
||||
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 (!r_used_cache) {
|
||||
//free stuff
|
||||
::free(gen_vertices);
|
||||
::free(gen_indices);
|
||||
::free(gen_uvs);
|
||||
if (gen_cache_size > 0) {
|
||||
r_dst_cache.resize(gen_cache_size);
|
||||
memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size);
|
||||
memfree(gen_cache);
|
||||
}
|
||||
|
||||
if (!use_cache) {
|
||||
// Cache was not used, free the buffers
|
||||
memfree(gen_vertices);
|
||||
memfree(gen_indices);
|
||||
memfree(gen_uvs);
|
||||
}
|
||||
|
||||
return OK;
|
||||
|
|
|
@ -263,7 +263,7 @@ public:
|
|||
void regen_normal_maps();
|
||||
|
||||
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);
|
||||
Error lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache, bool p_generate_cache = true);
|
||||
|
||||
virtual void reload_from_file() override;
|
||||
|
||||
|
|
|
@ -1698,6 +1698,7 @@ void RenderForwardClustered::_render_uv2(const PagedArray<GeometryInstance *> &p
|
|||
_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_framebuffer), &render_list_params, 0, render_list_params.element_count); //first wireframe, for pseudo conservative
|
||||
}
|
||||
render_list_params.uv_offset = Vector2();
|
||||
render_list_params.force_wireframe = false;
|
||||
_render_list(draw_list, RD::get_singleton()->framebuffer_get_format(p_framebuffer), &render_list_params, 0, render_list_params.element_count); //second regular triangles
|
||||
|
||||
RD::get_singleton()->draw_list_end();
|
||||
|
|
|
@ -2554,6 +2554,7 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
|
|||
|
||||
light_data.soft_shadow_scale = storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_BLUR);
|
||||
light_data.softshadow_angle = angular_diameter;
|
||||
light_data.bake_mode = storage->light_get_bake_mode(base);
|
||||
|
||||
if (angular_diameter <= 0.0) {
|
||||
light_data.soft_shadow_scale *= directional_shadow_quality_radius_get(); // Only use quality radius for PCF
|
||||
|
@ -2621,6 +2622,7 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const
|
|||
light_data.color[1] = linear_col.g * energy;
|
||||
light_data.color[2] = linear_col.b * energy;
|
||||
light_data.specular_amount = storage->light_get_param(base, RS::LIGHT_PARAM_SPECULAR) * 2.0;
|
||||
light_data.bake_mode = storage->light_get_bake_mode(base);
|
||||
|
||||
float radius = MAX(0.001, storage->light_get_param(base, RS::LIGHT_PARAM_RANGE));
|
||||
light_data.inv_radius = 1.0 / radius;
|
||||
|
|
|
@ -497,7 +497,7 @@ private:
|
|||
float soft_shadow_scale;
|
||||
uint32_t mask;
|
||||
float shadow_volumetric_fog_fade;
|
||||
uint32_t pad;
|
||||
uint32_t bake_mode;
|
||||
float projector_rect[4];
|
||||
};
|
||||
|
||||
|
@ -514,7 +514,8 @@ private:
|
|||
uint32_t shadow_enabled;
|
||||
float fade_from;
|
||||
float fade_to;
|
||||
uint32_t pad[3];
|
||||
uint32_t pad[2];
|
||||
uint32_t bake_mode;
|
||||
float shadow_volumetric_fog_fade;
|
||||
float shadow_bias[4];
|
||||
float shadow_normal_bias[4];
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#define LIGHT_BAKE_DISABLED 0
|
||||
#define LIGHT_BAKE_DYNAMIC 1
|
||||
#define LIGHT_BAKE_STATIC 2
|
||||
|
||||
struct LightData { //this structure needs to be as packed as possible
|
||||
vec3 position;
|
||||
|
@ -23,7 +26,7 @@ struct LightData { //this structure needs to be as packed as possible
|
|||
float soft_shadow_scale; // scales the shadow kernel for blurrier shadows
|
||||
uint mask;
|
||||
float shadow_volumetric_fog_fade;
|
||||
uint pad;
|
||||
uint bake_mode;
|
||||
vec4 projector_rect; //projector rect in srgb decal atlas
|
||||
};
|
||||
|
||||
|
@ -60,7 +63,8 @@ struct DirectionalLightData {
|
|||
bool shadow_enabled;
|
||||
float fade_from;
|
||||
float fade_to;
|
||||
uvec3 pad;
|
||||
uvec2 pad;
|
||||
uint bake_mode;
|
||||
float shadow_volumetric_fog_fade;
|
||||
vec4 shadow_bias;
|
||||
vec4 shadow_normal_bias;
|
||||
|
|
|
@ -1227,6 +1227,10 @@ void main() {
|
|||
continue; //not masked
|
||||
}
|
||||
|
||||
if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
|
||||
continue; // Statically baked light and object uses lightmap, skip
|
||||
}
|
||||
|
||||
float shadow = 1.0;
|
||||
|
||||
#ifdef USE_SOFT_SHADOWS
|
||||
|
@ -1676,6 +1680,10 @@ void main() {
|
|||
continue; //not masked
|
||||
}
|
||||
|
||||
if (omni_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
|
||||
continue; // Statically baked light and object uses lightmap, skip
|
||||
}
|
||||
|
||||
float shadow = light_process_omni_shadow(light_index, vertex, view);
|
||||
|
||||
shadow = blur_shadow(shadow);
|
||||
|
@ -1749,6 +1757,10 @@ void main() {
|
|||
continue; //not masked
|
||||
}
|
||||
|
||||
if (spot_lights.data[light_index].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
|
||||
continue; // Statically baked light and object uses lightmap, skip
|
||||
}
|
||||
|
||||
float shadow = light_process_spot_shadow(light_index, vertex, view);
|
||||
|
||||
shadow = blur_shadow(shadow);
|
||||
|
|
|
@ -2394,7 +2394,7 @@ void RendererSceneCull::_frustum_cull(CullData &cull_data, FrustumCullResult &cu
|
|||
cull_result.gi_probes.push_back(RID::from_uint64(idata.instance_data_rid));
|
||||
|
||||
} else if (base_type == RS::INSTANCE_LIGHTMAP) {
|
||||
cull_result.gi_probes.push_back(RID::from_uint64(idata.instance_data_rid));
|
||||
cull_result.lightmaps.push_back(RID::from_uint64(idata.instance_data_rid));
|
||||
} else if (((1 << base_type) & RS::INSTANCE_GEOMETRY_MASK) && !(idata.flags & InstanceData::FLAG_CAST_SHADOWS_ONLY)) {
|
||||
bool keep = true;
|
||||
|
||||
|
|
|
@ -472,8 +472,8 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint
|
|||
const Vector2 *src = array.ptr();
|
||||
|
||||
for (int i = 0; i < p_vertex_array_len; i++) {
|
||||
uint16_t uv[2] = { Math::make_half_float(src[i].x), Math::make_half_float(src[i].y) };
|
||||
memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], uv, 2 * 2);
|
||||
float uv[2] = { src[i].x, src[i].y };
|
||||
memcpy(&aw[p_offsets[ai] + i * p_attrib_stride], uv, 2 * 4);
|
||||
}
|
||||
} break;
|
||||
case RS::ARRAY_CUSTOM0:
|
||||
|
|
|
@ -24,10 +24,6 @@ namespace oidn {
|
|||
float AutoexposureNode::autoexposure(const Image& color)
|
||||
{
|
||||
assert(color.format == Format::Float3);
|
||||
// -- GODOT start --
|
||||
// We don't want to mess with TTB and we don't use autoexposure, so we disable this code
|
||||
#if 0
|
||||
// -- GODOT end --
|
||||
|
||||
constexpr float key = 0.18f;
|
||||
constexpr float eps = 1e-8f;
|
||||
|
@ -42,16 +38,24 @@ namespace oidn {
|
|||
// Compute the average log luminance of the downsampled image
|
||||
using Sum = std::pair<float, int>;
|
||||
|
||||
Sum sum =
|
||||
tbb::parallel_reduce(
|
||||
tbb::blocked_range2d<int>(0, HK, 0, WK),
|
||||
Sum(0.f, 0),
|
||||
[&](const tbb::blocked_range2d<int>& r, Sum sum) -> Sum
|
||||
// -- GODOT start --
|
||||
// Sum sum =
|
||||
// tbb::parallel_reduce(
|
||||
// tbb::blocked_range2d<int>(0, HK, 0, WK),
|
||||
// Sum(0.f, 0),
|
||||
// [&](const tbb::blocked_range2d<int>& r, Sum sum) -> Sum
|
||||
// {
|
||||
// // Iterate over blocks
|
||||
// for (int i = r.rows().begin(); i != r.rows().end(); ++i)
|
||||
// {
|
||||
// for (int j = r.cols().begin(); j != r.cols().end(); ++j)
|
||||
// {
|
||||
|
||||
Sum sum = Sum(0.0f, 0);
|
||||
|
||||
for (int i = 0; i != HK; ++i)
|
||||
{
|
||||
// Iterate over blocks
|
||||
for (int i = r.rows().begin(); i != r.rows().end(); ++i)
|
||||
{
|
||||
for (int j = r.cols().begin(); j != r.cols().end(); ++j)
|
||||
for (int j = 0; j != WK; ++j)
|
||||
{
|
||||
// Compute the average luminance in the current block
|
||||
const int beginH = int(ptrdiff_t(i) * H / HK);
|
||||
|
@ -86,17 +90,14 @@ namespace oidn {
|
|||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
},
|
||||
[](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); },
|
||||
tbb::static_partitioner()
|
||||
);
|
||||
// return sum;
|
||||
// },
|
||||
// [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); },
|
||||
// tbb::static_partitioner()
|
||||
// );
|
||||
// -- GODOT end --
|
||||
|
||||
return (sum.second > 0) ? (key / exp2(sum.first / float(sum.second))) : 1.f;
|
||||
// -- GODOT start --
|
||||
#endif
|
||||
return 1.0;
|
||||
// -- GODOT end --
|
||||
}
|
||||
|
||||
} // namespace oidn
|
||||
|
|
|
@ -280,28 +280,58 @@ index 8c2de09..ed8328c 100644
|
|||
namespace oidn {
|
||||
|
||||
diff --git a/core/transfer_function.cpp b/core/transfer_function.cpp
|
||||
index 601f814..487f0a9 100644
|
||||
index 601f814..ce5deca 100644
|
||||
--- a/core/transfer_function.cpp
|
||||
+++ b/core/transfer_function.cpp
|
||||
@@ -24,6 +24,10 @@ namespace oidn {
|
||||
float AutoexposureNode::autoexposure(const Image& color)
|
||||
@@ -38,16 +38,24 @@ namespace oidn {
|
||||
// Compute the average log luminance of the downsampled image
|
||||
using Sum = std::pair<float, int>;
|
||||
|
||||
- Sum sum =
|
||||
- tbb::parallel_reduce(
|
||||
- tbb::blocked_range2d<int>(0, HK, 0, WK),
|
||||
- Sum(0.f, 0),
|
||||
- [&](const tbb::blocked_range2d<int>& r, Sum sum) -> Sum
|
||||
+ // -- GODOT start --
|
||||
+ // Sum sum =
|
||||
+ // tbb::parallel_reduce(
|
||||
+ // tbb::blocked_range2d<int>(0, HK, 0, WK),
|
||||
+ // Sum(0.f, 0),
|
||||
+ // [&](const tbb::blocked_range2d<int>& r, Sum sum) -> Sum
|
||||
+ // {
|
||||
+ // // Iterate over blocks
|
||||
+ // for (int i = r.rows().begin(); i != r.rows().end(); ++i)
|
||||
+ // {
|
||||
+ // for (int j = r.cols().begin(); j != r.cols().end(); ++j)
|
||||
+ // {
|
||||
+
|
||||
+ Sum sum = Sum(0.0f, 0);
|
||||
+
|
||||
+ for (int i = 0; i != HK; ++i)
|
||||
{
|
||||
assert(color.format == Format::Float3);
|
||||
+// -- GODOT start --
|
||||
+// We don't want to mess with TTB and we don't use autoexposure, so we disable this code
|
||||
+#if 0
|
||||
+// -- GODOT end --
|
||||
|
||||
constexpr float key = 0.18f;
|
||||
constexpr float eps = 1e-8f;
|
||||
@@ -89,6 +93,10 @@ namespace oidn {
|
||||
);
|
||||
|
||||
return (sum.second > 0) ? (key / exp2(sum.first / float(sum.second))) : 1.f;
|
||||
+// -- GODOT start --
|
||||
+#endif
|
||||
+ return 1.0;
|
||||
+// -- GODOT end --
|
||||
- // Iterate over blocks
|
||||
- for (int i = r.rows().begin(); i != r.rows().end(); ++i)
|
||||
- {
|
||||
- for (int j = r.cols().begin(); j != r.cols().end(); ++j)
|
||||
+ for (int j = 0; j != WK; ++j)
|
||||
{
|
||||
// Compute the average luminance in the current block
|
||||
const int beginH = int(ptrdiff_t(i) * H / HK);
|
||||
@@ -82,11 +90,12 @@ namespace oidn {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace oidn
|
||||
- return sum;
|
||||
- },
|
||||
- [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); },
|
||||
- tbb::static_partitioner()
|
||||
- );
|
||||
+ // return sum;
|
||||
+ // },
|
||||
+ // [](Sum a, Sum b) -> Sum { return Sum(a.first+b.first, a.second+b.second); },
|
||||
+ // tbb::static_partitioner()
|
||||
+ // );
|
||||
+ // -- GODOT end --
|
||||
|
||||
return (sum.second > 0) ? (key / exp2(sum.first / float(sum.second))) : 1.f;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue