public static void BuildSkinnedMesh(string meshPath, string skelPath, SkeletonSample skeleton, SkelBindingSample meshBinding, GameObject go, PrimMap primMap, SceneImportOptions options) { string[] joints = meshBinding.joints; // WARNING: Do not mutate skeleton values. string[] skelJoints = skeleton.joints; bool isConstant = meshBinding.jointWeights.interpolation == PrimvarInterpolation.Constant; if (joints == null || joints.Length == 0) { if (skelJoints == null || skelJoints.Length == 0) { throw new Exception("Joints array empty: " + meshPath); } else { joints = skelJoints; } } // The mesh renderer must already exist, since hte mesh also must already exist. var smr = go.GetComponent <SkinnedMeshRenderer>(); if (!smr) { throw new Exception("Error importing " + meshPath + " SkinnnedMeshRenderer not present on GameObject"); } var mesh = smr.sharedMesh; var geomXf = meshBinding.geomBindTransform.value; // If the joints list is a different length than the bind transforms, then this is likely // a mesh using a subset of the total bones in the skeleton and the bindTransforms must be // reconstructed. var bindPoses = skeleton.bindTransforms; if (!JointsMatch(skeleton.joints, joints)) { var boneToPose = new Dictionary <string, Matrix4x4>(); bindPoses = new Matrix4x4[joints.Length]; for (int i = 0; i < skelJoints.Length; i++) { boneToPose[skelJoints[i]] = skeleton.bindTransforms[i]; } for (int i = 0; i < joints.Length; i++) { bindPoses[i] = boneToPose[joints[i]]; } } // When geomXf is identity, we can take a shortcut and just use the exact skeleton bindPoses. if (!ImporterBase.ApproximatelyEqual(geomXf, Matrix4x4.identity)) { // Note that the bind poses were transformed when the skeleton was imported, but the // geomBindTransform is per-mesh, so it must be transformed here so it is in the same space // as the bind pose. XformImporter.ImportXform(ref geomXf, options); // Make a copy only if we haven't already copied the bind poses earlier. if (bindPoses == skeleton.bindTransforms) { var newBindPoses = new Matrix4x4[skeleton.bindTransforms.Length]; Array.Copy(bindPoses, newBindPoses, bindPoses.Length); bindPoses = newBindPoses; } // Concatenate the geometry bind transform with the skeleton bind poses. for (int i = 0; i < bindPoses.Length; i++) { // The geometry transform should be applied to the points before any other transform, // hence the right hand multiply here. bindPoses[i] = bindPoses[i] * geomXf; } } mesh.bindposes = bindPoses; var bones = new Transform[joints.Length]; var sdfSkelPath = new SdfPath(skelPath); for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { jointPath = sdfSkelPath; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); jointPath = sdfSkelPath.AppendPath(jointPath); } else { jointPath = sdfSkelPath.AppendPath(jointPath); } var jointGo = primMap[jointPath]; if (!jointGo) { Debug.LogError("Error importing " + meshPath + " " + "Joint not found: " + joints[i]); continue; } bones[i] = jointGo.transform; } smr.bones = bones; int[] indices = meshBinding.jointIndices.value; float[] weights = meshBinding.jointWeights.value; // Unity 2019 supports many-bone rigs, older versions of Unity only support four bones. #if UNITY_2019 var bonesPerVertex = new NativeArray <byte>(mesh.vertexCount, Allocator.Persistent); var boneWeights1 = new NativeArray <BoneWeight1>(mesh.vertexCount * meshBinding.jointWeights.elementSize, Allocator.Persistent); for (int i = 0; i < mesh.vertexCount; i++) { int unityIndex = i * meshBinding.jointWeights.elementSize; int usdIndex = isConstant ? 0 : unityIndex; bonesPerVertex[i] = (byte)meshBinding.jointWeights.elementSize; for (int wi = 0; wi < meshBinding.jointWeights.elementSize; wi++) { var bw = boneWeights1[unityIndex + wi]; bw.boneIndex = indices[usdIndex + wi]; bw.weight = weights[usdIndex + wi]; boneWeights1[unityIndex + wi] = bw; } } mesh.SetBoneWeights(bonesPerVertex, boneWeights1); bonesPerVertex.Dispose(); boneWeights1.Dispose(); #else var boneWeights = new BoneWeight[mesh.vertexCount]; for (int i = 0; i < boneWeights.Length; i++) { // When interpolation is constant, the base usdIndex should always be zero. // When non-constant, the offset is the index times the number of weights per vertex. int usdIndex = isConstant ? 0 : i * meshBinding.jointWeights.elementSize; var boneWeight = boneWeights[i]; if (usdIndex >= indices.Length) { Debug.Log("UsdIndex out of bounds: " + usdIndex + " indices.Length: " + indices.Length + " boneWeights.Length: " + boneWeights.Length + " mesh: " + meshPath); } boneWeight.boneIndex0 = indices[usdIndex]; boneWeight.weight0 = weights[usdIndex]; if (meshBinding.jointIndices.elementSize >= 2) { boneWeight.boneIndex1 = indices[usdIndex + 1]; boneWeight.weight1 = weights[usdIndex + 1]; } if (meshBinding.jointIndices.elementSize >= 3) { boneWeight.boneIndex2 = indices[usdIndex + 2]; boneWeight.weight2 = weights[usdIndex + 2]; } if (meshBinding.jointIndices.elementSize >= 4) { boneWeight.boneIndex3 = indices[usdIndex + 3]; boneWeight.weight3 = weights[usdIndex + 3]; } // If weights are less than 1, Unity will not automatically renormalize. // If weights are greater than 1, Unity will renormalize. // Only normalize when less than one to make it easier to diff bone weights which were // round-tripped and were being normalized by Unity. float sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3; if (sum < 1) { boneWeight.weight0 /= sum; boneWeight.weight1 /= sum; boneWeight.weight2 /= sum; boneWeight.weight3 /= sum; } boneWeights[i] = boneWeight; } mesh.boneWeights = boneWeights; #endif }
public static void BuildSkinnedMesh(string meshPath, string skelPath, SkeletonSample skeleton, UsdSkelSkinningQuery skinningQuery, GameObject go, PrimMap primMap, SceneImportOptions options) { // The mesh renderer must already exist, since hte mesh also must already exist. var smr = go.GetComponent <SkinnedMeshRenderer>(); if (!smr) { throw new Exception( "Error importing " + meshPath + " SkinnnedMeshRenderer not present on GameObject" ); } // Get and validate the joint weights and indices informations. UsdGeomPrimvar jointWeights = skinningQuery.GetJointWeightsPrimvar(); UsdGeomPrimvar jointIndices = skinningQuery.GetJointIndicesPrimvar(); if (!jointWeights.IsDefined() || !jointIndices.IsDefined()) { throw new Exception("Joints information (indices and/or weights) are missing for: " + meshPath); } // TODO: Both indices and weights attributes can be animated. It's not handled yet. // TODO: Having something that convert a UsdGeomPrimvar into a PrimvarSample could help simplify this code. int[] indices = IntrinsicTypeConverter.FromVtArray((VtIntArray)jointIndices.GetAttr().Get()); int indicesElementSize = jointIndices.GetElementSize(); pxr.TfToken indicesInterpolation = jointIndices.GetInterpolation(); if (indices.Length == 0 || indicesElementSize == 0 || indices.Length % indicesElementSize != 0 || !pxr.UsdGeomPrimvar.IsValidInterpolation(indicesInterpolation)) { throw new Exception("Joint indices information are invalid or empty for: " + meshPath); } float[] weights = IntrinsicTypeConverter.FromVtArray((VtFloatArray)jointWeights.GetAttr().Get()); int weightsElementSize = jointWeights.GetElementSize(); pxr.TfToken weightsInterpolation = jointWeights.GetInterpolation(); if (weights.Length == 0 || weightsElementSize == 0 || weights.Length % weightsElementSize != 0 || !pxr.UsdGeomPrimvar.IsValidInterpolation(weightsInterpolation)) { throw new Exception("Joints weights information are invalid or empty for: " + meshPath); } // Get and validate the local list of joints. VtTokenArray jointsAttr = new VtTokenArray(); skinningQuery.GetJointOrder(jointsAttr); // If jointsAttr wasn't define, GetJointOrder return an empty array and FromVtArray as well. string[] joints = IntrinsicTypeConverter.FromVtArray(jointsAttr); // WARNING: Do not mutate skeleton values. string[] skelJoints = skeleton.joints; if (joints == null || joints.Length == 0) { if (skelJoints == null || skelJoints.Length == 0) { throw new Exception("Joints array empty: " + meshPath); } else { joints = skelJoints; } } var mesh = smr.sharedMesh; // TODO: bind transform attribute can be animated. It's not handled yet. Matrix4x4 geomXf = UnityTypeConverter.FromMatrix(skinningQuery.GetGeomBindTransform()); // If the joints list is a different length than the bind transforms, then this is likely // a mesh using a subset of the total bones in the skeleton and the bindTransforms must be // reconstructed. var bindPoses = skeleton.bindTransforms; if (!JointsMatch(skeleton.joints, joints)) { var boneToPose = new Dictionary <string, Matrix4x4>(); bindPoses = new Matrix4x4[joints.Length]; for (int i = 0; i < skelJoints.Length; i++) { boneToPose[skelJoints[i]] = skeleton.bindTransforms[i]; } for (int i = 0; i < joints.Length; i++) { bindPoses[i] = boneToPose[joints[i]]; } } // When geomXf is identity, we can take a shortcut and just use the exact skeleton bindPoses. if (!ImporterBase.ApproximatelyEqual(geomXf, Matrix4x4.identity)) { // Note that the bind poses were transformed when the skeleton was imported, but the // geomBindTransform is per-mesh, so it must be transformed here so it is in the same space // as the bind pose. XformImporter.ImportXform(ref geomXf, options); // Make a copy only if we haven't already copied the bind poses earlier. if (bindPoses == skeleton.bindTransforms) { var newBindPoses = new Matrix4x4[skeleton.bindTransforms.Length]; Array.Copy(bindPoses, newBindPoses, bindPoses.Length); bindPoses = newBindPoses; } // Concatenate the geometry bind transform with the skeleton bind poses. for (int i = 0; i < bindPoses.Length; i++) { // The geometry transform should be applied to the points before any other transform, // hence the right hand multiply here. bindPoses[i] = bindPoses[i] * geomXf; } } mesh.bindposes = bindPoses; var bones = new Transform[joints.Length]; var sdfSkelPath = new SdfPath(skelPath); for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { jointPath = sdfSkelPath; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); jointPath = sdfSkelPath.AppendPath(jointPath); } else { jointPath = sdfSkelPath.AppendPath(jointPath); } var jointGo = primMap[jointPath]; if (!jointGo) { Debug.LogError("Error importing " + meshPath + " " + "Joint not found: " + joints[i]); continue; } bones[i] = jointGo.transform; } smr.bones = bones; bool isConstant = weightsInterpolation.GetString() == pxr.UsdGeomTokens.constant; // Unity 2019 supports many-bone rigs, older versions of Unity only support four bones. #if UNITY_2019 var bonesPerVertex = new NativeArray <byte>(mesh.vertexCount, Allocator.Persistent); var boneWeights1 = new NativeArray <BoneWeight1>(mesh.vertexCount * weightsElementSize, Allocator.Persistent); for (int i = 0; i < mesh.vertexCount; i++) { int unityIndex = i * weightsElementSize; int usdIndex = isConstant ? 0 : unityIndex; bonesPerVertex[i] = (byte)weightsElementSize; for (int wi = 0; wi < weightsElementSize; wi++) { var bw = boneWeights1[unityIndex + wi]; bw.boneIndex = indices[usdIndex + wi]; bw.weight = weights[usdIndex + wi]; boneWeights1[unityIndex + wi] = bw; } } // TODO: Investigate if bone weights should be normalized before this line. mesh.SetBoneWeights(bonesPerVertex, boneWeights1); bonesPerVertex.Dispose(); boneWeights1.Dispose(); #else var boneWeights = new BoneWeight[mesh.vertexCount]; for (int i = 0; i < boneWeights.Length; i++) { // When interpolation is constant, the base usdIndex should always be zero. // When non-constant, the offset is the index times the number of weights per vertex. int usdIndex = isConstant ? 0 : i * weightsElementSize; var boneWeight = boneWeights[i]; if (usdIndex >= indices.Length) { Debug.Log("UsdIndex out of bounds: " + usdIndex + " indices.Length: " + indices.Length + " boneWeights.Length: " + boneWeights.Length + " mesh: " + meshPath); } boneWeight.boneIndex0 = indices[usdIndex]; boneWeight.weight0 = weights[usdIndex]; if (indicesElementSize >= 2) { boneWeight.boneIndex1 = indices[usdIndex + 1]; boneWeight.weight1 = weights[usdIndex + 1]; } if (indicesElementSize >= 3) { boneWeight.boneIndex2 = indices[usdIndex + 2]; boneWeight.weight2 = weights[usdIndex + 2]; } if (indicesElementSize >= 4) { boneWeight.boneIndex3 = indices[usdIndex + 3]; boneWeight.weight3 = weights[usdIndex + 3]; } // If weights are less than 1, Unity will not automatically renormalize. // If weights are greater than 1, Unity will renormalize. // Only normalize when less than one to make it easier to diff bone weights which were // round-tripped and were being normalized by Unity. float sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3; if (sum < 1) { boneWeight.weight0 /= sum; boneWeight.weight1 /= sum; boneWeight.weight2 /= sum; boneWeight.weight3 /= sum; } boneWeights[i] = boneWeight; } mesh.boneWeights = boneWeights; #endif }