SkelBindingSample ReadUsdWeights(Scene scene) { if (scene == null) { return(null); } try { var sample = new SkelBindingSample(); scene.Read(m_usdMeshPath, sample); return(sample); } catch (System.Exception ex) { Debug.LogException(ex); return(null); } }
void OnEnable() { InitUsd.Initialize(); if (string.IsNullOrEmpty(m_usdMeshPath)) { m_usdMeshPath = UnityTypeConverter.GetPath(transform); } var scene = GetScene(); var binding = ReadUsdWeights(scene); string skelRootPath; var skeleton = ReadUsdSkeleton(scene, out skelRootPath); if (binding == null) { binding = new SkelBindingSample(); } var mesh = GetComponent <SkinnedMeshRenderer>().sharedMesh; // Process classic four-bone weights first. var sb = new System.Text.StringBuilder(); #if UNITY_2019_1_OR_NEWER var bonesPerVert = mesh.GetBonesPerVertex(); int weightsPerBone = 0; foreach (int count in bonesPerVert) { weightsPerBone = weightsPerBone > count ? weightsPerBone : count; } var boneWeights = mesh.GetAllBoneWeights(); sb.AppendLine("Many-bone indices: (" + boneWeights.Length + " * 4)"); int bone = 0; int bi = 0; int wi = 0; foreach (var weight in boneWeights) { if (wi == 0) { sb.Append("i: " + bone + " ["); } sb.Append(weight.boneIndex + GetUsdBoneData(bi, wi, binding.jointIndices) + ","); wi++; if (wi == weightsPerBone) { sb.Append("]\n"); bi++; wi = 0; } if (bonesPerVert[bi] != weightsPerBone) { // TODO: Unity supports a variable number of weights per bone, but USD does not. // Therefore, the number of weights may be greater in USD than in Unity. Currently // the way this works does not correctly handle that case. Debug.LogWarning("Unity bone count issue, see code comment for details."); } bone++; } Debug.Log(sb.ToString()); bone = 0; bi = 0; wi = 0; sb = new System.Text.StringBuilder(); sb.AppendLine("Many-bone weights: (" + boneWeights.Length + " * 4)"); foreach (var weight in boneWeights) { if (wi == 0) { sb.Append("i: " + bone + " ["); } sb.Append(weight.weight + GetUsdBoneData(bi, wi, binding.jointWeights) + ","); wi++; if (wi == weightsPerBone) { sb.Append("]\n"); bi++; wi = 0; } bone++; } Debug.Log(sb.ToString()); #else sb.AppendLine("Legacy 4-bone indices: (" + mesh.boneWeights.Length + " * 4)"); int bone = 0; foreach (var weight in mesh.boneWeights) { sb.Append("["); sb.Append(weight.boneIndex0 + GetUsdBoneData(bone, 0, binding.jointIndices) + ","); sb.Append(weight.boneIndex1 + GetUsdBoneData(bone, 1, binding.jointIndices) + ","); sb.Append(weight.boneIndex2 + GetUsdBoneData(bone, 2, binding.jointIndices) + ","); sb.Append(weight.boneIndex3 + GetUsdBoneData(bone, 3, binding.jointIndices) + "]\n"); bone++; } Debug.Log(sb.ToString()); bone = 0; sb = new System.Text.StringBuilder(); sb.AppendLine("Legacy 4-bone weights: (" + mesh.boneWeights.Length + " * 4)"); foreach (var weight in mesh.boneWeights) { sb.Append("["); sb.Append(weight.weight0 + GetUsdBoneData(bone, 0, binding.jointWeights) + ","); sb.Append(weight.weight1 + GetUsdBoneData(bone, 1, binding.jointWeights) + ","); sb.Append(weight.weight2 + GetUsdBoneData(bone, 2, binding.jointWeights) + ","); sb.Append(weight.weight3 + GetUsdBoneData(bone, 3, binding.jointWeights) + "]\n"); bone++; } Debug.Log(sb.ToString()); #endif sb = new System.Text.StringBuilder(); var bones = GetComponent <SkinnedMeshRenderer>().bones; var rootBone = GetComponent <SkinnedMeshRenderer>().rootBone; var root = UnityTypeConverter.GetPath(rootBone); sb.AppendLine("Bones: (" + bones.Length + ")"); sb.AppendLine("Root Bone: " + root); int i = 0; foreach (var boneXf in bones) { sb.AppendLine(UnityTypeConverter.GetPath(boneXf)); if (binding.joints != null) { sb.AppendLine(root + "\\" + binding.joints[i++] + "\n"); } } Debug.Log(sb.ToString()); sb = new System.Text.StringBuilder(); sb.AppendLine("Bind Transforms: (" + mesh.bindposes.Length + ")"); i = -1; var options = new SceneImportOptions(); options.changeHandedness = m_basisTransform; foreach (var boneXf in bones) { i++; var bindPose = mesh.bindposes[i]; var bonePath = UnityTypeConverter.GetPath(boneXf); sb.AppendLine("Pose[" + i + "] " + bonePath); sb.AppendLine(bindPose.ToString()); if (skeleton.bindTransforms != null) { if (string.IsNullOrEmpty(skelRootPath)) { continue; } bonePath = bonePath.Substring(skelRootPath.Length); bonePath = bonePath.TrimStart('/'); foreach (var joint in skeleton.joints) { if (joint == bonePath) { var usdMat = skeleton.bindTransforms[i]; XformImporter.ImportXform(ref usdMat, options); sb.AppendLine(usdMat.ToString() + "\n"); bonePath = null; break; } } if (string.IsNullOrEmpty(bonePath)) { continue; } sb.Append("Bone not found in USD: " + bonePath + "\n\n"); } } Debug.Log(sb.ToString()); }
/// <summary> /// Rebuilds the USD scene as Unity GameObjects, with a limited budget per update. /// </summary> public static IEnumerator BuildScene(Scene scene, GameObject root, SceneImportOptions importOptions, PrimMap primMap, float targetFrameMilliseconds, bool composingSubtree) { var timer = new System.Diagnostics.Stopwatch(); var usdPrimRoot = new pxr.SdfPath(importOptions.usdRootPath); // Setting an arbitrary fudge factor of 20% is very non-scientific, however it's better than // nothing. The correct way to hit a deadline is to predict how long each iteration actually // takes and then return early if the estimated time is over budget. float targetTime = targetFrameMilliseconds * .8f; timer.Start(); // Reconstruct the USD hierarchy as Unity GameObjects. // A PrimMap is returned for tracking the USD <-> Unity mapping. Profiler.BeginSample("USD: Build Hierarchy"); if (importOptions.importHierarchy || importOptions.forceRebuild) { // When a USD file is fully RE-imported, all exsiting USD data must be removed. The old // assumption was that the root would never have much more than the UsdAsset component // itself, however it's now clear that the root may also have meaningful USD data added // too. // // TODO(jcowles): This feels like a workaround. What we really want here is an "undo" // process for changes made to the root GameObject. For example, to clean up non-USD // components which may have been added (e.g. what if a mesh is imported to the root? // currently the MeshRenderer etc will remain after re-import). RemoveComponent <UsdAssemblyRoot>(root); RemoveComponent <UsdVariantSet>(root); RemoveComponent <UsdModelRoot>(root); RemoveComponent <UsdLayerStack>(root); RemoveComponent <UsdPayload>(root); RemoveComponent <UsdPrimSource>(root); primMap.Clear(); HierarchyBuilder.BuildGameObjects(scene, root, usdPrimRoot, scene.Find(usdPrimRoot.ToString(), "UsdSchemaBase"), primMap, importOptions); } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } Profiler.BeginSample("USD: Post Process Hierarchy"); foreach (var processor in root.GetComponents <IImportPostProcessHierarchy>()) { try { processor.PostProcessHierarchy(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(ex); } } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // Pre-process UsdSkelRoots. // var skelRoots = new List <pxr.UsdSkelRoot>(); if (importOptions.importSkinning) { Profiler.BeginSample("USD: Process UsdSkelRoots"); foreach (var path in primMap.SkelRoots) { try { var skelRootPrim = scene.GetPrimAtPath(path); if (!skelRootPrim) { Debug.LogWarning("SkelRoot prim not found: " + path); continue; } var skelRoot = new pxr.UsdSkelRoot(skelRootPrim); if (!skelRoot) { Debug.LogWarning("SkelRoot prim not SkelRoot type: " + path); continue; } skelRoots.Add(skelRoot); GameObject go = primMap[path]; ImporterBase.GetOrAddComponent <Animator>(go, true); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error pre-processing SkelRoot <" + path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Import known prim types. // // Materials. Profiler.BeginSample("USD: Build Materials"); if (importOptions.ShouldBindMaterials) { foreach (var pathAndSample in scene.ReadAll <MaterialSample>(primMap.Materials)) { try { var mat = MaterialImporter.BuildMaterial(scene, pathAndSample.path, pathAndSample.sample, importOptions); if (mat != null) { importOptions.materialMap[pathAndSample.path] = mat; } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing material <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } } Profiler.EndSample(); // // Start threads. // ReadAllJob <XformSample> readXforms; if (importOptions.importTransforms) { readXforms = new ReadAllJob <XformSample>(scene, primMap.Xforms); #if UNITY_2018_1_OR_NEWER readXforms.Schedule(primMap.Xforms.Length, 4); #else readXforms.Run(); #endif } if (importOptions.importMeshes) { ActiveMeshImporter.BeginReading(scene, primMap); } #if UNITY_2018_1_OR_NEWER JobHandle.ScheduleBatchedJobs(); #endif // Xforms. // // Note that we are specifically filtering on XformSample, not Xformable, this way only // Xforms are processed to avoid doing that work redundantly. if (importOptions.importTransforms) { Profiler.BeginSample("USD: Build Xforms"); foreach (var pathAndSample in readXforms) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.SkelRoots)) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } if (importOptions.importSkinning) { foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.Skeletons)) { try { if (pathAndSample.path == usdPrimRoot) { // Never read the xform from the USD root, that will be authored in Unity. continue; } GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } } Profiler.EndSample(); } // Meshes. if (importOptions.importMeshes) { Profiler.BeginSample("USD: Build Meshes"); IEnumerator it = ActiveMeshImporter.Import(scene, primMap, importOptions); while (it.MoveNext()) { if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); // Cubes. Profiler.BeginSample("USD: Build Cubes"); foreach (var pathAndSample in scene.ReadAll <CubeSample>(primMap.Cubes)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CubeImporter.BuildCube(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing cube <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // Cameras. if (importOptions.importCameras) { Profiler.BeginSample("USD: Cameras"); foreach (var pathAndSample in scene.ReadAll <CameraSample>(primMap.Cameras)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); // The camera has many value-type parameters that need to be handled correctly when not // not animated. For now, only the camera transform will animate, until this is fixed. if (scene.AccessMask == null || scene.IsPopulatingAccessMask) { CameraImporter.BuildCamera(pathAndSample.sample, go, importOptions); } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing camera <" + pathAndSample.path + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // Build out masters for instancing. Profiler.BeginSample("USD: Build Instances"); foreach (var masterRootPath in primMap.GetMasterRootPaths()) { try { Transform masterRootXf = primMap[masterRootPath].transform; // Transforms if (importOptions.importTransforms) { Profiler.BeginSample("USD: Build Xforms"); foreach (var pathAndSample in scene.ReadAll <XformSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } foreach (var pathAndSample in scene.ReadAll <XformSample>(primMap.Skeletons)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing xform <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } // Meshes. if (importOptions.importMeshes) { Profiler.BeginSample("USD: Build Meshes"); foreach (var pathAndSample in scene.ReadAll <MeshSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); var subsets = MeshImporter.ReadGeomSubsets(scene, pathAndSample.path); MeshImporter.BuildMesh(pathAndSample.path, pathAndSample.sample, subsets, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing mesh <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); // Cubes. Profiler.BeginSample("USD: Build Cubes"); foreach (var pathAndSample in scene.ReadAll <CubeSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CubeImporter.BuildCube(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing cube <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } // Cameras. if (importOptions.importCameras) { Profiler.BeginSample("USD: Build Cameras"); foreach (var pathAndSample in scene.ReadAll <CameraSample>(masterRootPath)) { try { GameObject go = primMap[pathAndSample.path]; NativeImporter.ImportObject(scene, go, scene.GetPrimAtPath(pathAndSample.path), importOptions); XformImporter.BuildXform(pathAndSample.path, pathAndSample.sample, go, importOptions, scene); CameraImporter.BuildCamera(pathAndSample.sample, go, importOptions); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing camera <" + pathAndSample.path + ">", ex)); } } Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing master <" + masterRootPath + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } // Instances. Profiler.EndSample(); // // Post-process dependencies: materials and bones. // Profiler.BeginSample("USD: Process Material Bindings"); try { // TODO: Currently ProcessMaterialBindings runs too long and will go over budget for any // large scene. However, pulling the loop into this code feels wrong in terms of // responsibilities. // Process all material bindings in a single vectorized request. MaterialImporter.ProcessMaterialBindings(scene, importOptions); } catch (System.Exception ex) { Debug.LogException(new ImportException("Failed in ProcessMaterialBindings", ex)); } Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // SkinnedMesh bone bindings. // if (importOptions.importSkinning) { Profiler.BeginSample("USD: Build Skeletons"); var skeletonSamples = new Dictionary <pxr.SdfPath, SkeletonSample>(); foreach (var skelRoot in skelRoots) { try { var bindings = new pxr.UsdSkelBindingVector(); if (!primMap.SkelBindings.TryGetValue(skelRoot.GetPath(), out bindings)) { Debug.LogWarning("No bindings found skelRoot: " + skelRoot.GetPath()); } if (bindings.Count == 0) { Debug.LogWarning("No bindings found skelRoot: " + skelRoot.GetPath()); } foreach (var skelBinding in bindings) { // The SkelRoot will likely have a skeleton binding, but it's inherited, so the bound // skeleton isn't actually known until it's queried from the binding. Still, we would // like not to reprocess skeletons redundantly, so skeletons are cached into a // dictionary. Profiler.BeginSample("Build Bind Transforms"); var skelPath = skelBinding.GetSkeleton().GetPath(); SkeletonSample skelSample = null; if (!skeletonSamples.TryGetValue(skelPath, out skelSample)) { skelSample = new SkeletonSample(); Profiler.BeginSample("Read Skeleton"); scene.Read(skelPath, skelSample); Profiler.EndSample(); skeletonSamples.Add(skelPath, skelSample); // Unity uses the inverse bindTransform, since that's actually what's needed for // skinning. Do that once here, so each skinned mesh doesn't need to do it // redundantly. SkeletonImporter.BuildBindTransforms(skelPath, skelSample, importOptions); var bindXforms = new pxr.VtMatrix4dArray(); var prim = scene.GetPrimAtPath(skelPath); var skel = new pxr.UsdSkelSkeleton(prim); Profiler.BeginSample("Get SkelQuery"); pxr.UsdSkelSkeletonQuery skelQuery = primMap.SkelCache.GetSkelQuery(skel); Profiler.EndSample(); Profiler.BeginSample("Get JointWorldBind Transforms"); if (!skelQuery.GetJointWorldBindTransforms(bindXforms)) { throw new ImportException("Failed to compute binding trnsforms for <" + skelPath + ">"); } Profiler.EndSample(); SkeletonImporter.BuildDebugBindTransforms(skelSample, primMap[skelPath], importOptions); } Profiler.EndSample(); if (importOptions.importSkinWeights) { // // Apply skinning weights to each skinned mesh. // Profiler.BeginSample("Apply Skin Weights"); foreach (var skinningQuery in skelBinding.GetSkinningTargetsAsVector()) { var meshPath = skinningQuery.GetPrim().GetPath(); try { var skelBindingSample = new SkelBindingSample(); var goMesh = primMap[meshPath]; scene.Read(meshPath, skelBindingSample); Profiler.BeginSample("Build Skinned Mesh"); SkeletonImporter.BuildSkinnedMesh( meshPath, skelPath, skelSample, skelBindingSample, goMesh, primMap, importOptions); Profiler.EndSample(); // In terms of performance, this is almost free. goMesh.GetComponent <SkinnedMeshRenderer>().rootBone = primMap[skelPath].transform.GetChild(0); } catch (System.Exception ex) { Debug.LogException(new ImportException("Error skinning mesh: " + meshPath, ex)); } } Profiler.EndSample(); } } } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing SkelRoot <" + skelRoot.GetPath() + ">", ex)); } } // foreach SkelRoot Profiler.EndSample(); if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // // Bone transforms. // Profiler.BeginSample("USD: Pose Bones"); foreach (var pathAndSample in skeletonSamples) { var skelPath = pathAndSample.Key; try { var prim = scene.GetPrimAtPath(skelPath); var skel = new pxr.UsdSkelSkeleton(prim); pxr.UsdSkelSkeletonQuery skelQuery = primMap.SkelCache.GetSkelQuery(skel); var joints = skelQuery.GetJointOrder(); var restXforms = new pxr.VtMatrix4dArray(); var time = scene.Time.HasValue ? scene.Time.Value : pxr.UsdTimeCode.Default(); Profiler.BeginSample("Compute Joint Local Transforms"); if (!skelQuery.ComputeJointLocalTransforms(restXforms, time, atRest: false)) { throw new ImportException("Failed to compute bind trnsforms for <" + skelPath + ">"); } Profiler.EndSample(); Profiler.BeginSample("Build Bones"); for (int i = 0; i < joints.size(); i++) { var jointPath = scene.GetSdfPath(joints[i]); if (joints[i] == "/") { jointPath = skelPath; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new System.Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new pxr.SdfPath(joints[i].ToString().TrimStart('/')); jointPath = skelPath.AppendPath(jointPath); } else { jointPath = skelPath.AppendPath(jointPath); } var goBone = primMap[jointPath]; Profiler.BeginSample("Convert Matrix"); var restXform = UnityTypeConverter.FromMatrix(restXforms[i]); Profiler.EndSample(); Profiler.BeginSample("Build Bone"); SkeletonImporter.BuildSkeletonBone(skelPath, goBone, restXform, joints, importOptions); Profiler.EndSample(); } Profiler.EndSample(); } catch (System.Exception ex) { Debug.LogException( new ImportException("Error processing SkelRoot <" + skelPath + ">", ex)); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Apply instancing. // if (importOptions.importSceneInstances) { Profiler.BeginSample("USD: Build Scene-Instances"); try { // Build scene instances. InstanceImporter.BuildSceneInstances(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(new ImportException("Failed in BuildSceneInstances", ex)); } Profiler.EndSample(); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } // Build point instances. if (importOptions.importPointInstances) { Profiler.BeginSample("USD: Build Point-Instances"); // TODO: right now all point instancer data is read, but we only need prototypes and indices. foreach (var pathAndSample in scene.ReadAll <PointInstancerSample>()) { try { GameObject instancerGo = primMap[pathAndSample.path]; // Now build the point instances. InstanceImporter.BuildPointInstances(scene, primMap, pathAndSample.path, pathAndSample.sample, instancerGo, importOptions); } catch (System.Exception ex) { Debug.LogError("Error processing point instancer <" + pathAndSample.path + ">: " + ex.Message); } if (ShouldYield(targetTime, timer)) { yield return(null); ResetTimer(timer); } } Profiler.EndSample(); } // // Apply root transform corrections. // Profiler.BeginSample("USD: Build Root Transforms"); if (!composingSubtree) { if (!root) { // There is no single root, // Apply root transform corrections to all imported root prims. foreach (KeyValuePair <pxr.SdfPath, GameObject> kvp in primMap) { if (kvp.Key.IsRootPrimPath() && kvp.Value != null) { // The root object at which the USD scene will be reconstructed. // It may need a Z-up to Y-up conversion and a right- to left-handed change of basis. XformImporter.BuildSceneRoot(scene, kvp.Value.transform, importOptions); } } } else { // There is only one root, apply a single transform correction. XformImporter.BuildSceneRoot(scene, root.transform, importOptions); } } Profiler.EndSample(); Profiler.BeginSample("USD: Post Process Components"); foreach (var processor in root.GetComponents <IImportPostProcessComponents>()) { try { processor.PostProcessComponents(primMap, importOptions); } catch (System.Exception ex) { Debug.LogException(ex); } } Profiler.EndSample(); }
static void ExportSkelWeights(Scene scene, string path, Mesh unityMesh, Transform rootBone, Transform[] bones, string skeletonPath) { var sample = new SkelBindingSample(); sample.geomBindTransform.value = Matrix4x4.identity; sample.joints = new string[bones.Length]; if (!string.IsNullOrEmpty(skeletonPath)) { sample.skeleton.targetPaths = new string[] { skeletonPath }; } int b = 0; var rootPath = UnityTypeConverter.GetPath(rootBone); foreach (Transform bone in bones) { var bonePath = UnityTypeConverter.GetPath(bone); if (bonePath == rootPath) { sample.joints[b++] = "/"; } else { sample.joints[b++] = bonePath.Replace(rootPath + "/", ""); } } int i = 0; int w = 0; b = 0; #if UNITY_2019 var bonesPerVertex = unityMesh.GetBonesPerVertex(); var unityBoneWeights = unityMesh.GetAllBoneWeights(); byte maxWeightCount = 0; foreach (var c in bonesPerVertex) { maxWeightCount = maxWeightCount > c ? maxWeightCount : c; } sample.jointIndices.value = new int[bonesPerVertex.Length * maxWeightCount]; sample.jointIndices.elementSize = maxWeightCount; sample.jointIndices.interpolation = PrimvarInterpolation.Vertex; sample.jointWeights.value = new float[bonesPerVertex.Length * maxWeightCount]; sample.jointWeights.elementSize = maxWeightCount; sample.jointWeights.interpolation = PrimvarInterpolation.Vertex; foreach (var weightCount in bonesPerVertex) { for (int j = 0; j < weightCount; j++) { var bw = unityBoneWeights[b++]; sample.jointIndices.value[i++] = bw.boneIndex; sample.jointWeights.value[w++] = bw.weight; } // Unity allows a variable number of weights per bone, but we've made the array square, // which means we may need to skip a few indicies, if this vert doesn't use the max number // of weights. i += maxWeightCount - weightCount; w += maxWeightCount - weightCount; } #else var unityBoneWeights = unityMesh.boneWeights; if (unityBoneWeights.Length == 0) { Debug.LogWarning("Found zero bone weights at: " + path); return; } sample.jointIndices.value = new int[unityBoneWeights.Length * 4]; sample.jointIndices.elementSize = 4; sample.jointIndices.interpolation = PrimvarInterpolation.Vertex; sample.jointWeights.value = new float[unityBoneWeights.Length * 4]; sample.jointWeights.elementSize = 4; sample.jointWeights.interpolation = PrimvarInterpolation.Vertex; foreach (var bone in unityBoneWeights) { sample.jointIndices.value[i++] = bone.boneIndex0; sample.jointIndices.value[i++] = bone.boneIndex1; sample.jointIndices.value[i++] = bone.boneIndex2; sample.jointIndices.value[i++] = bone.boneIndex3; sample.jointWeights.value[w++] = bone.weight0; sample.jointWeights.value[w++] = bone.weight1; sample.jointWeights.value[w++] = bone.weight2; sample.jointWeights.value[w++] = bone.weight3; } #endif scene.Write(path, sample); }
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 }