From 7307a3c57d84e481de137816033198ba1895f293 Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Fri, 13 Sep 2019 21:59:14 +0200 Subject: glTF exporter: Fix skinning space / Inverse Bind Matrix --- io_scene_gltf2/__init__.py | 2 +- .../blender/exp/gltf2_blender_extract.py | 105 +++++++++++++++------ .../blender/exp/gltf2_blender_gather_mesh.py | 5 +- .../blender/exp/gltf2_blender_gather_nodes.py | 22 ++++- .../blender/exp/gltf2_blender_gather_primitives.py | 6 +- .../blender/exp/gltf2_blender_gather_skins.py | 10 +- 6 files changed, 105 insertions(+), 45 deletions(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index c001b529..800aeab2 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (0, 9, 64), + "version": (0, 9, 65), 'blender': (2, 81, 6), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py index a4705e91..3b33872d 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py @@ -16,7 +16,7 @@ # Imports # -from mathutils import Vector, Quaternion +from mathutils import Vector, Quaternion, Matrix from mathutils.geometry import tessellate_polygon from operator import attrgetter @@ -24,6 +24,7 @@ from . import gltf2_blender_export_keys from ...io.com.gltf2_io_debug import print_console from ...io.com.gltf2_io_color_management import color_srgb_to_scene_linear from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins +import bpy # # Globals @@ -63,24 +64,61 @@ class ShapeKey: # Functions # -def convert_swizzle_location(loc, export_settings): +def convert_swizzle_normal_and_tangent(loc, armature, blender_object, export_settings): + """Convert a normal data from Blender coordinate system to glTF coordinate system.""" + if not armature: + # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((loc[0], loc[2], -loc[1])) + else: + return Vector((loc[0], loc[1], loc[2])) + else: + # Mesh is skined, we have to apply armature transforms on data + apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + new_loc = apply_matrix.to_quaternion() @ loc + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((new_loc[0], new_loc[2], -new_loc[1])) + else: + return Vector((new_loc[0], new_loc[1], new_loc[2])) + +def convert_swizzle_location(loc, armature, blender_object, export_settings): """Convert a location from Blender coordinate system to glTF coordinate system.""" - if export_settings[gltf2_blender_export_keys.YUP]: - return Vector((loc[0], loc[2], -loc[1])) + if not armature: + # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((loc[0], loc[2], -loc[1])) + else: + return Vector((loc[0], loc[1], loc[2])) else: - return Vector((loc[0], loc[1], loc[2])) + # Mesh is skined, we have to apply armature transforms on data + apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + new_loc = (armature.matrix_world @ apply_matrix @ Matrix.Translation(Vector((loc[0], loc[1], loc[2])))).to_translation() + + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((new_loc[0], new_loc[2], -new_loc[1])) + else: + return Vector((new_loc[0], new_loc[1], new_loc[2])) -def convert_swizzle_tangent(tan, export_settings): +def convert_swizzle_tangent(tan, armature, blender_object, export_settings): """Convert a tangent from Blender coordinate system to glTF coordinate system.""" if tan[0] == 0.0 and tan[1] == 0.0 and tan[2] == 0.0: print_console('WARNING', 'Tangent has zero length.') - if export_settings[gltf2_blender_export_keys.YUP]: - return Vector((tan[0], tan[2], -tan[1], 1.0)) + if not armature: + # Classic case. Mesh is not skined, no need to apply armature transfoms on vertices / normals / tangents + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((tan[0], tan[2], -tan[1], 1.0)) + else: + return Vector((tan[0], tan[1], tan[2], 1.0)) else: - return Vector((tan[0], tan[1], tan[2], 1.0)) - + # Mesh is skined, we have to apply armature transforms on data + apply_matrix = armature.matrix_world.inverted() @ blender_object.matrix_world + new_tan = apply_matrix.to_quaternion() @ tan + if export_settings[gltf2_blender_export_keys.YUP]: + return Vector((new_tan[0], new_tan[2], -new_tan[1], 1.0)) + else: + return Vector((new_tan[0], new_tan[1], new_tan[2], 1.0)) def convert_swizzle_rotation(rot, export_settings): """ @@ -383,7 +421,7 @@ def extract_primitive_pack(a, indices, use_tangents): return result_primitive -def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, export_settings): +def extract_primitives(glTF, blender_mesh, blender_object, blender_vertex_groups, modifiers, export_settings): """ Extract primitives from a mesh. Polygons are triangulated and sorted by material. @@ -508,6 +546,15 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key + + armature = None + if modifiers is not None: + modifiers_dict = {m.type: m for m in modifiers} + if "ARMATURE" in modifiers_dict: + modifier = modifiers_dict["ARMATURE"] + armature = modifier.object + + # # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. # @@ -587,20 +634,20 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp vertex = blender_mesh.vertices[vertex_index] - v = convert_swizzle_location(vertex.co, export_settings) + v = convert_swizzle_location(vertex.co, armature, blender_object, export_settings) if blender_polygon.use_smooth or blender_mesh.use_auto_smooth: if blender_mesh.has_custom_normals: - n = convert_swizzle_location(blender_mesh.loops[loop_index].normal, export_settings) + n = convert_swizzle_normal_and_tangent(blender_mesh.loops[loop_index].normal, armature, blender_object, export_settings) else: - n = convert_swizzle_location(vertex.normal, export_settings) + n = convert_swizzle_normal_and_tangent(vertex.normal, armature, blender_object, export_settings) if use_tangents: - t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, export_settings) - b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, export_settings) + t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, armature, blender_object, export_settings) + b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, armature, blender_object, export_settings) else: - n = convert_swizzle_location(face_normal, export_settings) + n = convert_swizzle_normal_and_tangent(face_normal, armature, blender_object, export_settings) if use_tangents: - t = convert_swizzle_tangent(face_tangent, export_settings) - b = convert_swizzle_location(face_bitangent, export_settings) + t = convert_swizzle_tangent(face_tangent, armature, blender_object, export_settings) + b = convert_swizzle_location(face_bitangent, armature, blender_object, export_settings) if use_tangents: tv = Vector((t[0], t[1], t[2])) @@ -669,17 +716,12 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp joint_index = None - if modifiers is not None: - modifiers_dict = {m.type: m for m in modifiers} - if "ARMATURE" in modifiers_dict: - modifier = modifiers_dict["ARMATURE"] - armature = modifier.object - if armature: - skin = gltf2_blender_gather_skins.gather_skin(armature, modifier.id_data, export_settings) - for index, j in enumerate(skin.joints): - if j.name == vertex_group_name: - joint_index = index - break + if armature: + skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings) + for index, j in enumerate(skin.joints): + if j.name == vertex_group_name: + joint_index = index + break # if joint_index is not None: @@ -707,6 +749,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp blender_shape_key = blender_shape_keys[morph_index] v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co, + armature, blender_object, export_settings) # Store delta. @@ -728,7 +771,7 @@ def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, exp temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], temp_normals[blender_polygon.index * 3 + 2]) - n_morph = convert_swizzle_location(n_morph, export_settings) + n_morph = convert_swizzle_normal_and_tangent(n_morph, armature, blender_object, export_settings) # Store delta. n_morph -= n diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py index d0d2d4a5..ca79ef33 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py @@ -24,6 +24,7 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console @cached def gather_mesh(blender_mesh: bpy.types.Mesh, + blender_object: Optional[bpy.types.Object], vertex_groups: Optional[bpy.types.VertexGroups], modifiers: Optional[bpy.types.ObjectModifiers], skip_filter: bool, @@ -37,7 +38,7 @@ def gather_mesh(blender_mesh: bpy.types.Mesh, extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings), extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings), name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings), - primitives=__gather_primitives(blender_mesh, vertex_groups, modifiers, material_names, export_settings), + primitives=__gather_primitives(blender_mesh, blender_object, vertex_groups, modifiers, material_names, export_settings), weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings) ) @@ -102,12 +103,14 @@ def __gather_name(blender_mesh: bpy.types.Mesh, def __gather_primitives(blender_mesh: bpy.types.Mesh, + blender_object: Optional[bpy.types.Object], vertex_groups: Optional[bpy.types.VertexGroups], modifiers: Optional[bpy.types.ObjectModifiers], material_names: Tuple[str], export_settings ) -> List[gltf2_io.MeshPrimitive]: return gltf2_blender_gather_primitives.gather_primitives(blender_mesh, + blender_object, vertex_groups, modifiers, material_names, diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py index c0fa11ff..24564621 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py @@ -68,7 +68,10 @@ def __gather_node(blender_object, blender_scene, export_settings): translation=None, weights=__gather_weights(blender_object, export_settings) ) - node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings) + + # If node mesh is skined, transforms should be ignored at import, so no need to set them here + if node.skin is None: + node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings) if export_settings[gltf2_blender_export_keys.YUP]: if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS]: @@ -275,7 +278,18 @@ def __gather_mesh(blender_object, export_settings): skip_filter = False material_names = tuple([ms.material.name for ms in blender_object.material_slots if ms.material is not None]) + + # retrieve armature + # Because mesh data will be transforms to skeleton space, + # we can't instanciate multiple object at different location, skined by same armature + blender_object_for_skined_data = None + if export_settings[gltf2_blender_export_keys.SKINS]: + for idx, modifier in enumerate(blender_object.modifiers): + if modifier.type == 'ARMATURE': + blender_object_for_skined_data = blender_object + result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh, + blender_object_for_skined_data, vertex_groups, modifiers, skip_filter, @@ -309,13 +323,13 @@ def __gather_trans_rot_scale(blender_object, export_settings): # Decomposing matrix_local gives less accuracy, but is needed if matrix_parent_inverse is not the identity. trans, rot, sca = gltf2_blender_extract.decompose_transition(blender_object.matrix_local, export_settings) - trans = gltf2_blender_extract.convert_swizzle_location(trans, export_settings) + trans = gltf2_blender_extract.convert_swizzle_location(trans, None, None, export_settings) rot = gltf2_blender_extract.convert_swizzle_rotation(rot, export_settings) sca = gltf2_blender_extract.convert_swizzle_scale(sca, export_settings) if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection: trans = -gltf2_blender_extract.convert_swizzle_location( - blender_object.instance_collection.instance_offset, export_settings) + blender_object.instance_collection.instance_offset, None, None, export_settings) translation, rotation, scale = (None, None, None) trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \ gltf2_blender_math.round_if_near(trans[2], 0.0) @@ -350,7 +364,7 @@ def __gather_skin(blender_object, export_settings): return None # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures - return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, blender_object, export_settings) + return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings) def __gather_weights(blender_object, export_settings): diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py index 82673e5e..b146928c 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py @@ -33,6 +33,7 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console @cached def gather_primitives( blender_mesh: bpy.types.Mesh, + blender_object: Optional[bpy.types.Object], vertex_groups: Optional[bpy.types.VertexGroups], modifiers: Optional[bpy.types.ObjectModifiers], material_names: Tuple[str], @@ -45,7 +46,7 @@ def gather_primitives( """ primitives = [] - blender_primitives = __gather_cache_primitives(blender_mesh, + blender_primitives = __gather_cache_primitives(blender_mesh, blender_object, vertex_groups, modifiers, export_settings) for internal_primitive in blender_primitives: @@ -79,6 +80,7 @@ def gather_primitives( @cached def __gather_cache_primitives( blender_mesh: bpy.types.Mesh, + blender_object: Optional[bpy.types.Object], vertex_groups: Optional[bpy.types.VertexGroups], modifiers: Optional[bpy.types.ObjectModifiers], export_settings @@ -89,7 +91,7 @@ def __gather_cache_primitives( primitives = [] blender_primitives = gltf2_blender_extract.extract_primitives( - None, blender_mesh, vertex_groups, modifiers, export_settings) + None, blender_mesh, blender_object, vertex_groups, modifiers, export_settings) for internal_primitive in blender_primitives: primitive = { diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py index 71682e94..59af7d72 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py @@ -24,12 +24,11 @@ from io_scene_gltf2.blender.com import gltf2_blender_math @cached -def gather_skin(blender_object, mesh_object, export_settings): +def gather_skin(blender_object, export_settings): """ Gather armatures, bones etc into a glTF2 skin object. :param blender_object: the object which may contain a skin - :param mesh_object: the mesh object to be deformed :param export_settings: :return: a glTF2 skin object """ @@ -39,7 +38,7 @@ def gather_skin(blender_object, mesh_object, export_settings): return gltf2_io.Skin( extensions=__gather_extensions(blender_object, export_settings), extras=__gather_extras(blender_object, export_settings), - inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, mesh_object, export_settings), + inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, export_settings), joints=__gather_joints(blender_object, export_settings), name=__gather_name(blender_object, export_settings), skeleton=__gather_skeleton(blender_object, export_settings) @@ -62,7 +61,7 @@ def __gather_extensions(blender_object, export_settings): def __gather_extras(blender_object, export_settings): return None -def __gather_inverse_bind_matrices(blender_object, mesh_object, export_settings): +def __gather_inverse_bind_matrices(blender_object, export_settings): axis_basis_change = mathutils.Matrix.Identity(4) if export_settings[gltf2_blender_export_keys.YUP]: axis_basis_change = mathutils.Matrix( @@ -78,11 +77,10 @@ def __gather_inverse_bind_matrices(blender_object, mesh_object, export_settings) # traverse the matrices in the same order as the joints and compute the inverse bind matrix def __collect_matrices(bone): - matrix_world = gltf2_blender_math.multiply(blender_object.matrix_world, mesh_object.matrix_world.inverted()) inverse_bind_matrix = gltf2_blender_math.multiply( axis_basis_change, gltf2_blender_math.multiply( - matrix_world, + blender_object.matrix_world, bone.bone.matrix_local ) ).inverted() -- cgit v1.2.3