Skip to content

Commit

Permalink
Merge pull request #65493 from V-Sekai/lod_scaling
Browse files Browse the repository at this point in the history
Fixes LOD scaling issues on skinned meshes.
  • Loading branch information
akien-mga authored Sep 14, 2022
2 parents 240fb86 + 13f5c62 commit d9e974c
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 4 deletions.
2 changes: 2 additions & 0 deletions doc/classes/ImporterMesh.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@
<return type="void" />
<param index="0" name="normal_merge_angle" type="float" />
<param index="1" name="normal_split_angle" type="float" />
<param index="2" name="bone_transform_array" type="Array" />
<description>
Generates all lods for this ImporterMesh.
[param normal_merge_angle] and [param normal_split_angle] are in degrees and used in the same way as the importer settings in [code]lods[/code]. As a good default, use 25 and 60 respectively.
The number of generated lods can be accessed using [method get_surface_lod_count], and each LOD is available in [method get_surface_lod_size] and [method get_surface_lod_indices].
[param bone_transform_array] is an [Array] which can be either empty or contain [Transform3D]s which, for each of the mesh's bone IDs, will apply mesh skinning when generating the LOD mesh variations. This is usually used to account for discrepancies in scale between the mesh itself and its skinning data.
</description>
</method>
<method name="get_blend_shape_count" qualifiers="const">
Expand Down
36 changes: 35 additions & 1 deletion editor/import/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,39 @@ void ResourceImporterScene::_replace_owner(Node *p_node, Node *p_scene, Node *p_
}
}

Array ResourceImporterScene::_get_skinned_pose_transforms(ImporterMeshInstance3D *p_src_mesh_node) {
Array skin_pose_transform_array;

const Ref<Skin> skin = p_src_mesh_node->get_skin();
if (skin.is_valid()) {
NodePath skeleton_path = p_src_mesh_node->get_skeleton_path();
const Node *node = p_src_mesh_node->get_node_or_null(skeleton_path);
const Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node);
if (skeleton) {
int bind_count = skin->get_bind_count();

for (int i = 0; i < bind_count; i++) {
Transform3D bind_pose = skin->get_bind_pose(i);
String bind_name = skin->get_bind_name(i);

int bone_idx = bind_name.is_empty() ? skin->get_bind_bone(i) : skeleton->find_bone(bind_name);
ERR_FAIL_COND_V(bone_idx >= skeleton->get_bone_count(), Array());

Transform3D bp_global_rest;
if (bone_idx >= 0) {
bp_global_rest = skeleton->get_bone_global_pose(bone_idx);
} else {
bp_global_rest = skeleton->get_bone_global_pose(i);
}

skin_pose_transform_array.push_back(bp_global_rest * bind_pose);
}
}
}

return skin_pose_transform_array;
}

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) {
ImporterMeshInstance3D *src_mesh_node = Object::cast_to<ImporterMeshInstance3D>(p_node);
if (src_mesh_node) {
Expand Down Expand Up @@ -2007,7 +2040,8 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
}

if (generate_lods) {
src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle);
Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node);
src_mesh_node->get_mesh()->generate_lods(merge_angle, split_angle, skin_pose_transform_array);
}

if (create_shadow_meshes) {
Expand Down
2 changes: 2 additions & 0 deletions editor/import/resource_importer_scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "core/error/error_macros.h"
#include "core/io/resource_importer.h"
#include "core/variant/dictionary.h"
#include "scene/3d/importer_mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/mesh.h"
Expand Down Expand Up @@ -208,6 +209,7 @@ class ResourceImporterScene : public ResourceImporter {
SHAPE_TYPE_CAPSULE,
};

Array _get_skinned_pose_transforms(ImporterMeshInstance3D *p_src_mesh_node);
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<Vector<uint8_t>> &r_lightmap_caches);
void _add_shapes(Node *p_node, const Vector<Ref<Shape3D>> &p_shapes);
Expand Down
41 changes: 39 additions & 2 deletions scene/resources/importer_mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,20 @@ void ImporterMesh::set_surface_material(int p_surface, const Ref<Material> &p_ma
mesh.unref();
}

void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle) {
#define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \
Vector3 transformed_vert = Vector3(); \
for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \
int bone_idx = bone_array[vert_idx * bone_count + weight_idx]; \
float w = weight_array[vert_idx * bone_count + weight_idx]; \
if (w < FLT_EPSILON) { \
continue; \
} \
ERR_FAIL_INDEX(bone_idx, static_cast<int>(transform_array.size())); \
transformed_vert += transform_array[bone_idx].xform(read_array[vert_idx]) * w; \
} \
write_array[vert_idx] = transformed_vert;

void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) {
if (!SurfaceTool::simplify_scale_func) {
return;
}
Expand All @@ -265,6 +278,12 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
return;
}

LocalVector<Transform3D> bone_transform_vector;
for (int i = 0; i < p_bone_transform_array.size(); i++) {
ERR_FAIL_COND(p_bone_transform_array[i].get_type() != Variant::TRANSFORM3D);
bone_transform_vector.push_back(p_bone_transform_array[i]);
}

for (int i = 0; i < surfaces.size(); i++) {
if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) {
continue;
Expand All @@ -276,6 +295,8 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
Vector<Vector3> normals = surfaces[i].arrays[RS::ARRAY_NORMAL];
Vector<Vector2> uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV];
Vector<Vector2> uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2];
Vector<int> bones = surfaces[i].arrays[RS::ARRAY_BONES];
Vector<float> weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS];

unsigned int index_count = indices.size();
unsigned int vertex_count = vertices.size();
Expand All @@ -301,6 +322,22 @@ void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_spli
}
}

if (bones.size() > 0 && weights.size() && bone_transform_vector.size() > 0) {
Vector3 *vertices_ptrw = vertices.ptrw();

// Apply bone transforms to regular surface.
unsigned int bone_weight_length = surfaces[i].flags & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4;

const int *bo = bones.ptr();
const float *we = weights.ptr();

for (unsigned int j = 0; j < vertex_count; j++) {
VERTEX_SKIN_FUNC(bone_weight_length, j, vertices_ptr, vertices_ptrw, bone_transform_vector, bo, we)
}

vertices_ptr = vertices.ptr();
}

float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle));
float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f)));
float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle));
Expand Down Expand Up @@ -1246,7 +1283,7 @@ void ImporterMesh::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name);
ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material);

ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle"), &ImporterMesh::generate_lods);
ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods);
ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref<ArrayMesh>()));
ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear);

Expand Down
2 changes: 1 addition & 1 deletion scene/resources/importer_mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class ImporterMesh : public Resource {

void set_surface_material(int p_surface, const Ref<Material> &p_material);

void generate_lods(float p_normal_merge_angle, float p_normal_split_angle);
void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array);

void create_shadow_mesh();
Ref<ImporterMesh> get_shadow_mesh() const;
Expand Down

0 comments on commit d9e974c

Please sign in to comment.