/// <summary> /// Reads geometry subsets if authored. If not authored, returns an empty dictionary. /// </summary> public static GeometrySubsets ReadGeomSubsets(Scene scene, string path) { var result = new GeometrySubsets(); var prim = scene.GetPrimAtPath(path); if (prim == null || prim.IsValid() == false) { return(result); } var im = new pxr.UsdGeomImageable(prim); if (im._IsValid() == false) { return(result); } pxr.UsdGeomSubsetVector subsets = pxr.UsdGeomSubset.GetGeomSubsets(im, pxr.UsdGeomTokens.face, new pxr.TfToken("materialBind")); // Cache these values to minimize garbage collector churn. var value = new pxr.VtValue(); int[] intValue = new int[0]; var defaultTime = pxr.UsdTimeCode.Default(); foreach (var subset in subsets) { if (!subset._IsValid()) { continue; } var indices = subset.GetIndicesAttr(); if (!indices.IsValid()) { continue; } if (!indices.Get(value, defaultTime)) { continue; } UnityTypeConverter.FromVtArray(value, ref intValue); result.Subsets.Add(subset.GetPath(), intValue); } return(result); }
public static void ExportSkelAnimation(ObjectContext objContext, ExportContext exportContext) { var scene = exportContext.scene; var sample = (SkelAnimationSample)objContext.sample; var go = objContext.gameObject; var boneNames = exportContext.skelSortedMap[go.transform]; var skelRoot = go.transform; sample.joints = new string[boneNames.Count]; var worldXf = new Matrix4x4[boneNames.Count]; var worldXfInv = new Matrix4x4[boneNames.Count]; string rootPath = UnityTypeConverter.GetPath(go.transform); var basisChange = Matrix4x4.identity; basisChange[2, 2] = -1; for (int i = 0; i < boneNames.Count; i++) { var bonePath = boneNames[i]; if (!exportContext.pathToBone.ContainsKey(bonePath)) { sample.joints[i] = ""; continue; } var bone = exportContext.pathToBone[bonePath]; sample.joints[i] = bonePath.Replace(rootPath + "/", ""); worldXf[i] = bone.localToWorldMatrix; if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { worldXf[i] = UnityTypeConverter.ChangeBasis(worldXf[i]); } worldXfInv[i] = worldXf[i].inverse; } var rootXf = skelRoot.localToWorldMatrix.inverse; if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { rootXf = UnityTypeConverter.ChangeBasis(rootXf); } var skelWorldTransform = UnityTypeConverter.ToGfMatrix(rootXf); pxr.VtMatrix4dArray vtJointsLS = new pxr.VtMatrix4dArray((uint)boneNames.Count); pxr.VtMatrix4dArray vtJointsWS = UnityTypeConverter.ToVtArray(worldXf); pxr.VtMatrix4dArray vtJointsWSInv = UnityTypeConverter.ToVtArray(worldXfInv); var translations = new pxr.VtVec3fArray(); var rotations = new pxr.VtQuatfArray(); sample.scales = new pxr.VtVec3hArray(); var topo = new pxr.UsdSkelTopology(UnityTypeConverter.ToVtArray(sample.joints)); pxr.UsdCs.UsdSkelComputeJointLocalTransforms(topo, vtJointsWS, vtJointsWSInv, vtJointsLS, skelWorldTransform); pxr.UsdCs.UsdSkelDecomposeTransforms( vtJointsLS, translations, rotations, sample.scales); sample.translations = UnityTypeConverter.FromVtArray(translations); sample.rotations = UnityTypeConverter.FromVtArray(rotations); scene.Write(objContext.path, sample); }
/// <summary> /// If HierInfo represents a UsdSkelRoot, reads the associated skelton joints into the /// skelJoints member. /// </summary> static void ReadSkeletonJoints(ref HierInfo skelRootInfo) { if (skelRootInfo.prim == null) { return; } var skelRoot = new UsdSkelRoot(skelRootInfo.prim); if (!skelRoot) { return; } var processed = new HashSet <SdfPath>(); foreach (UsdSkelBinding binding in skelRootInfo.skelBindings) { var skel = binding.GetSkeleton(); if (!skel) { continue; } // If the same skeleton is referenced multiple times, only process it once. if (processed.Contains(skel.GetPath())) { continue; } processed.Add(skel.GetPath()); var jointsAttr = skel.GetJointsAttr(); if (!jointsAttr) { continue; } var vtJoints = jointsAttr.Get(); if (vtJoints.IsEmpty()) { continue; } var vtStrings = UsdCs.VtValueToVtTokenArray(vtJoints); var joints = UnityTypeConverter.FromVtArray(vtStrings); var skelPath = skel.GetPath(); skelRootInfo.skelJoints = new SdfPath[joints.Length]; for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { skelRootInfo.skelJoints[i] = skelPath; continue; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); } skelRootInfo.skelJoints[i] = skelPath.AppendPath(jointPath); } } }
static void LoadPrimvars(Scene scene, Mesh unityMesh, string usdMeshPath, List <string> primvars, int[] faceVertexCounts, int[] faceVertexIndices) { if (primvars == null || primvars.Count == 0) { return; } var prim = scene.GetPrimAtPath(usdMeshPath); for (int i = 0; i < primvars.Count; i++) { var attr = prim.GetAttribute(new TfToken("primvars:" + primvars[i])); if (!attr) { continue; } // Read the raw values. VtValue val = attr.Get(0.0); if (val.IsEmpty()) { continue; } VtVec2fArray vec2fArray = UsdCs.VtValueToVtVec2fArray(val); Vector2[] values = UnityTypeConverter.FromVtArray(vec2fArray); // Unroll indexed primvars. var pv = new UsdGeomPrimvar(attr); VtIntArray vtIndices = new VtIntArray(); if (pv.GetIndices(vtIndices, 0.0)) { int[] indices = UnityTypeConverter.FromVtArray(vtIndices); values = indices.Select(idx => values[idx]).ToArray(); } // Handle primvar interpolation modes. TfToken interp = pv.GetInterpolation(); if (interp == UsdGeomTokens.constant) { Debug.Assert(values.Length == 1); var newValues = new Vector2[unityMesh.vertexCount]; for (int idx = 0; idx < values.Length; idx++) { newValues[idx] = values[0]; } values = newValues; } else if (interp == UsdGeomTokens.uniform) { Debug.Assert(values.Length == faceVertexCounts.Length); for (int faceIndex = 0; faceIndex < values.Length; faceIndex++) { var faceColor = values[faceIndex]; int idx = 0; var newValues = new Vector2[unityMesh.vertexCount]; for (int f = 0; f < faceVertexCounts[faceIndex]; f++) { int vertexInFaceIdx = faceVertexIndices[idx++]; newValues[vertexInFaceIdx] = faceColor; } values = newValues; } } else if (interp == UsdGeomTokens.faceVarying) { values = UnrollFaceVarying(unityMesh.vertexCount, values, faceVertexCounts, faceVertexIndices); } // Send them to Unity. unityMesh.SetUVs(i, values.ToList()); } }
private static void BuildMesh_(string path, MeshSample usdMesh, Mesh unityMesh, GeometrySubsets geomSubsets, GameObject go, Renderer renderer, SceneImportOptions options) { // TODO: Because this method operates on a GameObject, it must be single threaded. For this // reason, it should be extremely light weight. All computation should be completed prior to // this step, allowing heavy computations to happen in parallel. This is not currently the // case, triangulation and change of basis are non-trivial operations. Computing the mesh // bounds, normals and tangents should similarly be moved out of this function and should not // rely on the UnityEngine.Mesh API. Material mat = renderer.sharedMaterial; bool changeHandedness = options.changeHandedness == BasisTransformation.SlowAndSafe; // // Points. // if (options.meshOptions.points == ImportMode.Import && usdMesh.points != null) { if (changeHandedness) { for (int i = 0; i < usdMesh.points.Length; i++) { usdMesh.points[i] = UnityTypeConverter.ChangeBasis(usdMesh.points[i]); } } if (usdMesh.faceVertexIndices != null) { // Annoyingly, there is a circular dependency between vertices and triangles, which makes // it impossible to have a fixed update order in this function. As a result, we must clear // the triangles before setting the points, to break that dependency. unityMesh.SetTriangles(new int[0] { }, 0); } unityMesh.vertices = usdMesh.points; } // // Purpose. // // Deactivate non-geometry prims (e.g. guides, render, etc). if (usdMesh.purpose != Purpose.Default) { go.SetActive(false); } // // Mesh Topology. // // TODO: indices should not be accessed if topology is not requested, however it may be // needed for facevarying primvars; that special case should throw a warning, rather than // reading the value. int[] originalIndices = new int[usdMesh.faceVertexIndices == null ? 0 : usdMesh.faceVertexIndices.Length]; // Optimization: only do this when there are face varying primvars. if (usdMesh.faceVertexIndices != null) { Array.Copy(usdMesh.faceVertexIndices, originalIndices, originalIndices.Length); } if (options.meshOptions.topology == ImportMode.Import && usdMesh.faceVertexIndices != null) { Profiler.BeginSample("Triangulate Mesh"); if (options.meshOptions.triangulateMesh) { // Triangulate n-gons. // For best performance, triangulate off-line and skip conversion. if (usdMesh.faceVertexIndices == null) { Debug.LogWarning("Mesh had no face indices: " + UnityTypeConverter.GetPath(go.transform)); return; } if (usdMesh.faceVertexCounts == null) { Debug.LogWarning("Mesh had no face counts: " + UnityTypeConverter.GetPath(go.transform)); return; } var indices = UnityTypeConverter.ToVtArray(usdMesh.faceVertexIndices); var counts = UnityTypeConverter.ToVtArray(usdMesh.faceVertexCounts); UsdGeomMesh.Triangulate(indices, counts); UnityTypeConverter.FromVtArray(indices, ref usdMesh.faceVertexIndices); } Profiler.EndSample(); Profiler.BeginSample("Convert LeftHanded"); bool isLeftHanded = usdMesh.orientation == Orientation.LeftHanded; if (changeHandedness && !isLeftHanded || !changeHandedness && isLeftHanded) { // USD is right-handed, so the mesh needs to be flipped. // Unity is left-handed, but that doesn't matter here. for (int i = 0; i < usdMesh.faceVertexIndices.Length; i += 3) { int tmp = usdMesh.faceVertexIndices[i]; usdMesh.faceVertexIndices[i] = usdMesh.faceVertexIndices[i + 1]; usdMesh.faceVertexIndices[i + 1] = tmp; } } Profiler.EndSample(); if (usdMesh.faceVertexIndices.Length > 65535) { unityMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; } Profiler.BeginSample("Breakdown triangles for Mesh Subsets"); if (geomSubsets.Subsets.Count == 0) { unityMesh.triangles = usdMesh.faceVertexIndices; } else { unityMesh.subMeshCount = geomSubsets.Subsets.Count; int subsetIndex = 0; foreach (var kvp in geomSubsets.Subsets) { int[] faceIndices = kvp.Value; int[] triangleIndices = new int[faceIndices.Length * 3]; for (int i = 0; i < faceIndices.Length; i++) { triangleIndices[i * 3 + 0] = usdMesh.faceVertexIndices[faceIndices[i] * 3 + 0]; triangleIndices[i * 3 + 1] = usdMesh.faceVertexIndices[faceIndices[i] * 3 + 1]; triangleIndices[i * 3 + 2] = usdMesh.faceVertexIndices[faceIndices[i] * 3 + 2]; } unityMesh.SetTriangles(triangleIndices, subsetIndex); subsetIndex++; } } Profiler.EndSample(); } // // Extent / Bounds. // bool hasBounds = usdMesh.extent.size.x > 0 || usdMesh.extent.size.y > 0 || usdMesh.extent.size.z > 0; if (ShouldImport(options.meshOptions.boundingBox) && hasBounds) { Profiler.BeginSample("Import Bounds"); if (changeHandedness) { usdMesh.extent.center = UnityTypeConverter.ChangeBasis(usdMesh.extent.center); usdMesh.extent.extents = UnityTypeConverter.ChangeBasis(usdMesh.extent.extents); } unityMesh.bounds = usdMesh.extent; Profiler.EndSample(); } else if (ShouldCompute(options.meshOptions.boundingBox)) { Profiler.BeginSample("Calculate Bounds"); unityMesh.RecalculateBounds(); Profiler.EndSample(); } // // Normals. // if (usdMesh.normals != null && ShouldImport(options.meshOptions.normals)) { Profiler.BeginSample("Import Normals"); if (changeHandedness) { for (int i = 0; i < usdMesh.points.Length; i++) { usdMesh.normals[i] = UnityTypeConverter.ChangeBasis(usdMesh.normals[i]); } } // If more normals than verts, assume face-varying. if (usdMesh.normals.Length > usdMesh.points.Length) { usdMesh.normals = UnrollFaceVarying(usdMesh.points.Length, usdMesh.normals, usdMesh.faceVertexCounts, originalIndices); } unityMesh.normals = usdMesh.normals; Profiler.EndSample(); } else if (ShouldCompute(options.meshOptions.normals)) { Profiler.BeginSample("Calculate Normals"); unityMesh.RecalculateNormals(); Profiler.EndSample(); } // // Tangents. // if (usdMesh.tangents != null && ShouldImport(options.meshOptions.tangents)) { Profiler.BeginSample("Import Tangents"); if (changeHandedness) { for (int i = 0; i < usdMesh.points.Length; i++) { var w = usdMesh.tangents[i].w; var t = UnityTypeConverter.ChangeBasis(usdMesh.tangents[i]); usdMesh.tangents[i] = new Vector4(t.x, t.y, t.z, w); } } unityMesh.tangents = usdMesh.tangents; Profiler.EndSample(); } else if (ShouldCompute(options.meshOptions.tangents)) { Profiler.BeginSample("Calculate Tangents"); unityMesh.RecalculateTangents(); Profiler.EndSample(); } // // Display Color. // if (ShouldImport(options.meshOptions.color) && usdMesh.colors != null && usdMesh.colors.Length > 0) { Profiler.BeginSample("Import Display Color"); // NOTE: The following color conversion assumes PlayerSettings.ColorSpace == Linear. // For best performance, convert color space to linear off-line and skip conversion. if (usdMesh.colors.Length == 1) { // Constant color can just be set on the material. if (options.useDisplayColorAsFallbackMaterial && options.materialImportMode != MaterialImportMode.None) { mat = options.materialMap.InstantiateSolidColor(usdMesh.colors[0].gamma); } } else if (usdMesh.colors.Length == usdMesh.points.Length) { // Vertex colors map on to verts. // TODO: move the conversion to C++ and use the color management API. for (int i = 0; i < usdMesh.colors.Length; i++) { usdMesh.colors[i] = usdMesh.colors[i]; } unityMesh.colors = usdMesh.colors; } else if (usdMesh.colors.Length == usdMesh.faceVertexCounts.Length) { // Uniform colors, one per face. // Unroll face colors into vertex colors. This is not strictly correct, but it's much faster // than the fully correct solution. var colors = new Color[unityMesh.vertexCount]; int idx = 0; try { for (int faceIndex = 0; faceIndex < usdMesh.colors.Length; faceIndex++) { var faceColor = usdMesh.colors[faceIndex]; for (int f = 0; f < usdMesh.faceVertexCounts[faceIndex]; f++) { int vertexInFaceIdx = originalIndices[idx++]; colors[vertexInFaceIdx] = faceColor; } } unityMesh.colors = colors; } catch (Exception ex) { Debug.LogException(new Exception("Failed loading uniform/per-face colors at " + path, ex)); } } else if (usdMesh.colors.Length > usdMesh.points.Length) { try { usdMesh.colors = UnrollFaceVarying(unityMesh.vertexCount, usdMesh.colors, usdMesh.faceVertexCounts, originalIndices); for (int i = 0; i < usdMesh.colors.Length; i++) { usdMesh.colors[i] = usdMesh.colors[i]; } unityMesh.colors = usdMesh.colors; } catch (Exception ex) { Debug.LogException( new Exception("Error unrolling Face-Varying colors at <" + path + ">", ex)); } } else { Debug.LogWarning("Uniform (color per face) display color not supported"); } Profiler.EndSample(); } // should import color // // UVs / Texture Coordinates. // // TODO: these should also be driven by the UV privmars required by the bound shader. Profiler.BeginSample("Import UV Sets"); ImportUv(path, unityMesh, 0, usdMesh.st, usdMesh.indices, usdMesh.faceVertexCounts, originalIndices, options.meshOptions.texcoord0, go); ImportUv(path, unityMesh, 0, usdMesh.uv, null, usdMesh.faceVertexCounts, originalIndices, options.meshOptions.texcoord0, go); ImportUv(path, unityMesh, 1, usdMesh.uv2, null, usdMesh.faceVertexCounts, originalIndices, options.meshOptions.texcoord1, go); ImportUv(path, unityMesh, 2, usdMesh.uv3, null, usdMesh.faceVertexCounts, originalIndices, options.meshOptions.texcoord2, go); ImportUv(path, unityMesh, 3, usdMesh.uv4, null, usdMesh.faceVertexCounts, originalIndices, options.meshOptions.texcoord3, go); Profiler.EndSample(); Profiler.BeginSample("Request Material Bindings"); // // Materials. // if (options.materialImportMode != MaterialImportMode.None) { if (mat == null) { mat = options.materialMap.InstantiateSolidColor(Color.white); } if (unityMesh.subMeshCount == 1) { renderer.sharedMaterial = mat; if (options.ShouldBindMaterials) { options.materialMap.RequestBinding(path, (scene, boundMat, primvars) => BindMat(scene, unityMesh, boundMat, renderer, path, primvars, usdMesh.faceVertexCounts, originalIndices)); } } else { var mats = new Material[unityMesh.subMeshCount]; for (int i = 0; i < mats.Length; i++) { mats[i] = mat; } renderer.sharedMaterials = mats; if (options.ShouldBindMaterials) { Debug.Assert(geomSubsets.Subsets.Count == unityMesh.subMeshCount); var subIndex = 0; foreach (var kvp in geomSubsets.Subsets) { int idx = subIndex++; options.materialMap.RequestBinding(kvp.Key, (scene, boundMat, primvars) => BindMat(scene, unityMesh, boundMat, renderer, idx, path, primvars, usdMesh.faceVertexCounts, originalIndices)); } } } } Profiler.EndSample(); // // Lightmap UV Unwrapping. // #if UNITY_EDITOR if (options.meshOptions.generateLightmapUVs) { #if !UNITY_2018_3_OR_NEWER if (unityMesh.indexFormat == UnityEngine.Rendering.IndexFormat.UInt32) { Debug.LogWarning("Skipping prim " + path + " due to large IndexFormat (UInt32) bug in older vesrsions of Unity"); return; } #endif Profiler.BeginSample("Unwrap Lightmap UVs"); var unwrapSettings = new UnityEditor.UnwrapParam(); unwrapSettings.angleError = options.meshOptions.unwrapAngleError; unwrapSettings.areaError = options.meshOptions.unwrapAngleError; unwrapSettings.hardAngle = options.meshOptions.unwrapHardAngle; // Convert pixels to unitless UV space, which is what unwrapSettings uses internally. unwrapSettings.packMargin = options.meshOptions.unwrapPackMargin / 1024.0f; UnityEditor.Unwrapping.GenerateSecondaryUVSet(unityMesh, unwrapSettings); Profiler.EndSample(); } #else if (options.meshOptions.generateLightmapUVs) { Debug.LogWarning("Lightmap UVs were requested to be generated, but cannot be generated outside of the editor"); } #endif }
static void ReadSkeleton(ref HierInfo info) { if (info.prim == null) { return; } var skelRoot = new UsdSkelRoot(info.prim); if (!skelRoot) { return; } var skelRel = info.prim.GetRelationship(UsdSkelTokens.skelSkeleton); if (!skelRel) { return; } SdfPathVector targets = skelRel.GetForwardedTargets(); if (targets == null || targets.Count == 0) { return; } var skelPrim = info.prim.GetStage().GetPrimAtPath(targets[0]); if (!skelPrim) { return; } var skel = new UsdSkelSkeleton(skelPrim); if (!skel) { return; } var jointsAttr = skel.GetJointsAttr(); if (!jointsAttr) { return; } var vtJoints = jointsAttr.Get(); if (vtJoints.IsEmpty()) { return; } var vtStrings = UsdCs.VtValueToVtTokenArray(vtJoints); var joints = UnityTypeConverter.FromVtArray(vtStrings); var skelPath = skelPrim.GetPath(); info.skelJoints = new SdfPath[joints.Length]; for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { info.skelJoints[i] = skelPath; continue; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); } info.skelJoints[i] = skelPath.AppendPath(jointPath); } }