public static void ExportSkeleton(ObjectContext objContext, ExportContext exportContext) { var scene = exportContext.scene; var sample = (SkeletonSample)objContext.sample; var boneNames = exportContext.skelSortedMap[objContext.gameObject.transform]; sample.joints = new string[boneNames.Count]; sample.bindTransforms = new Matrix4x4[boneNames.Count]; sample.restTransforms = new Matrix4x4[boneNames.Count]; string rootPath = UnityTypeConverter.GetPath(objContext.gameObject.transform); sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(rootPath).IsRootPrimPath(), exportContext.basisTransform); int i = 0; foreach (string bonePath in boneNames) { if (string.IsNullOrEmpty(bonePath)) { sample.joints[i] = ""; i++; continue; } var bone = exportContext.pathToBone[bonePath]; if (bonePath == rootPath) { sample.joints[i] = "/"; } else { sample.joints[i] = bonePath.Replace(rootPath + "/", ""); } // TODO: When the bone bind transform contains the geomBindTransform from USD import, it // will be mixed into each bone. This transform should be saved in some way and removed // when exported as a skeleton. sample.bindTransforms[i] = exportContext.bindPoses[bone].inverse; sample.restTransforms[i] = XformExporter.GetLocalTransformMatrix( bone, false, false, exportContext.basisTransform); if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.bindTransforms[i] = UnityTypeConverter.ChangeBasis(sample.bindTransforms[i]); // The restTransforms will get a change of basis from GetLocalTransformMatrix(). } i++; } scene.Write(objContext.path, sample); // Stop Skeleton from rendering bones in usdview by default. var im = new pxr.UsdGeomImageable(scene.GetPrimAtPath(objContext.path)); im.CreatePurposeAttr().Set(pxr.UsdGeomTokens.guide); }
/// <summary> /// Imports a matrix transform, correctly handling the change of basis. /// </summary> public static void ImportXform(ref Matrix4x4 mat, SceneImportOptions options) { if (options.changeHandedness == BasisTransformation.FastWithNegativeScale) { return; } mat = UnityTypeConverter.ChangeBasis(mat); }
public static void ExportSkelRoot(ObjectContext objContext, ExportContext exportContext) { var sample = (SkelRootSample)objContext.sample; // Compute bounds for the root, required by USD. bool first = true; // Ensure the bounds are computed in root-local space. // This is required because USD expects the extent to be a local bound for the SkelRoot. var oldParent = objContext.gameObject.transform.parent; objContext.gameObject.transform.SetParent(null, worldPositionStays: false); try { foreach (var r in objContext.gameObject.GetComponentsInChildren <Renderer>()) { if (first) { // Ensure the bounds object starts growing from the first valid child bounds. first = false; sample.extent = r.bounds; } else { sample.extent.Encapsulate(r.bounds); } } } finally { // Restore the root parent. objContext.gameObject.transform.SetParent(oldParent, worldPositionStays: false); } // Convert handedness if needed. if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); } // Convert the transform var path = new pxr.SdfPath(objContext.path); // If exporting for Z-Up, rotate the world. bool correctZUp = exportContext.scene.UpAxis == Scene.UpAxes.Z; sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, correctZUp, path.IsRootPrimPath(), exportContext.basisTransform); exportContext.scene.Write(objContext.path, sample); }
public static void ExportSkelRoot(ObjectContext objContext, ExportContext exportContext) { var sample = (SkelRootSample)objContext.sample; var bindings = ((string[])objContext.additionalData); if (bindings != null) { sample.skeleton = bindings[0]; if (bindings.Length > 1) { sample.animationSource = bindings[1]; } } // Compute bounds for the root, required by USD. bool first = true; foreach (var r in objContext.gameObject.GetComponentsInChildren <Renderer>()) { if (first) { // Ensure the bounds object starts growing from the first valid child bounds. first = false; sample.extent = r.bounds; } else { sample.extent.Encapsulate(r.bounds); } } // Convert the bounds from worldspace to local space. // This is required because USD expects the extent to be a local bound for the SkelRoot. var xf = objContext.gameObject.transform; sample.extent.min = xf.worldToLocalMatrix.MultiplyPoint(sample.extent.min); sample.extent.max = xf.worldToLocalMatrix.MultiplyPoint(sample.extent.max); if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.extent.min = UnityTypeConverter.ChangeBasis(sample.extent.min); sample.extent.max = UnityTypeConverter.ChangeBasis(sample.extent.max); } exportContext.scene.Write(objContext.path, sample); }
public static Matrix4x4 GetLocalTransformMatrix( Transform tr, bool correctZUp, bool isRootPrim, BasisTransformation conversionType) { var localRot = tr.localRotation; bool fastConvert = conversionType == BasisTransformation.FastWithNegativeScale; if (correctZUp && isRootPrim) { float invert = fastConvert ? 1 : -1; localRot = localRot * Quaternion.AngleAxis(invert * 90, Vector3.right); } var mat = Matrix4x4.TRS(tr.localPosition, localRot, tr.localScale); // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of // basis is required. There are shortcuts, but this is fully general. // // Here we can either put a partial conversion at the root (fast & dangerous) or convert the // entire hierarchy, along with the points, normals and triangle winding. The benefit of the // full conversion is that there are no negative scales left in the hierarchy. // // Note that this is the correct partial conversion for the root transforms, however the // camera and light matrices must contain the other half of the conversion // (e.g. mat * basisChangeInverse). if (fastConvert && isRootPrim) { // Partial change of basis. var basisChange = Matrix4x4.identity; // Invert the forward vector. basisChange[2, 2] = -1; mat = basisChange * mat; } else if (!fastConvert) { // Full change of basis. mat = UnityTypeConverter.ChangeBasis(mat); } return(mat); }
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); }
static void ExportMesh(ObjectContext objContext, ExportContext exportContext, Mesh mesh, Material sharedMaterial, Material[] sharedMaterials, bool exportMeshPose = true) { string path = objContext.path; if (mesh == null) { Debug.LogWarning("Null mesh for: " + path, objContext.gameObject); return; } #if UNITY_EDITOR if (!CanReadMesh(mesh)) { #else if (!mesh.isReadable) { #endif Debug.LogError( "Mesh is not readable: " + objContext.path + ". To fix this, enable read/write in the inspector for the source asset that you are attempting to export.", objContext.gameObject); return; } var scene = exportContext.scene; bool unvarying = scene.Time == null; bool slowAndSafeConversion = exportContext.basisTransform == BasisTransformation.SlowAndSafe; var sample = (MeshSample)objContext.sample; var go = objContext.gameObject; if (mesh.bounds.center == Vector3.zero && mesh.bounds.extents == Vector3.zero) { mesh.RecalculateBounds(); } sample.extent = mesh.bounds; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of // basis is required. There are shortcuts, but this is fully general. sample.ConvertTransform(); sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); } // Only export the mesh topology on the first frame. if (unvarying) { // TODO: Technically a mesh could be the root transform, which is not handled correctly here. // It should have the same logic for root prims as in ExportXform. sample.transform = XformExporter.GetLocalTransformMatrix( go.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(path).IsRootPrimPath(), exportContext.basisTransform); sample.normals = mesh.normals; sample.points = mesh.vertices; sample.tangents = mesh.tangents; sample.colors = mesh.colors; if (sample.colors != null && sample.colors.Length == 0) { sample.colors = null; } if ((sample.colors == null || sample.colors.Length == 0) && (sharedMaterial != null && sharedMaterial.HasProperty("_Color"))) { sample.colors = new Color[1]; sample.colors[0] = sharedMaterial.color.linear; } // Gah. There is no way to inspect a meshes UVs. sample.st = mesh.uv; // Set face vertex counts and indices. var tris = mesh.triangles; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change // of basis is required. There are shortcuts, but this is fully general. for (int i = 0; i < sample.points.Length; i++) { sample.points[i] = UnityTypeConverter.ChangeBasis(sample.points[i]); if (sample.normals != null && sample.normals.Length == sample.points.Length) { sample.normals[i] = UnityTypeConverter.ChangeBasis(sample.normals[i]); } if (sample.tangents != null && sample.tangents.Length == sample.points.Length) { var w = sample.tangents[i].w; var t = UnityTypeConverter.ChangeBasis(sample.tangents[i]); sample.tangents[i] = new Vector4(t.x, t.y, t.z, w); } } for (int i = 0; i < tris.Length; i += 3) { var t = tris[i]; tris[i] = tris[i + 1]; tris[i + 1] = t; } } sample.SetTriangles(tris); UnityEngine.Profiling.Profiler.BeginSample("USD: Mesh Write"); scene.Write(path, sample); UnityEngine.Profiling.Profiler.EndSample(); // TODO: this is a bit of a half-measure, we need real support for primvar interpolation. // Set interpolation based on color count. if (sample.colors != null && sample.colors.Length == 1) { pxr.UsdPrim usdPrim = scene.GetPrimAtPath(path); var colorPrimvar = new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayColor)); colorPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); var opacityPrimvar = new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayOpacity)); opacityPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); } string usdMaterialPath; if (exportContext.exportMaterials && sharedMaterial != null) { if (!exportContext.matMap.TryGetValue(sharedMaterial, out usdMaterialPath)) { Debug.LogError("Invalid material bound for: " + path); } else { MaterialSample.Bind(scene, path, usdMaterialPath); } } // In USD subMeshes are represented as UsdGeomSubsets. // When there are multiple subMeshes, convert them into UsdGeomSubsets. if (mesh.subMeshCount > 1) { // Build a table of face indices, used to convert the subMesh triangles to face indices. var faceTable = new Dictionary <Vector3, int>(); for (int i = 0; i < tris.Length; i += 3) { if (!slowAndSafeConversion) { faceTable.Add(new Vector3(tris[i], tris[i + 1], tris[i + 2]), i / 3); } else { // Under slow and safe export, index 0 and 1 are swapped. // This swap will not be present in the subMesh indices, so must be undone here. faceTable.Add(new Vector3(tris[i + 1], tris[i], tris[i + 2]), i / 3); } } var usdPrim = scene.GetPrimAtPath(path); var usdGeomMesh = new pxr.UsdGeomMesh(usdPrim); // Process each subMesh and create a UsdGeomSubset of faces this subMesh targets. for (int si = 0; si < mesh.subMeshCount; si++) { int[] indices = mesh.GetTriangles(si); int[] faceIndices = new int[indices.Length / 3]; for (int i = 0; i < indices.Length; i += 3) { faceIndices[i / 3] = faceTable[new Vector3(indices[i], indices[i + 1], indices[i + 2])]; } var vtIndices = UnityTypeConverter.ToVtArray(faceIndices); var subset = pxr.UsdGeomSubset.CreateUniqueGeomSubset( usdGeomMesh, // The object of which this subset belongs. m_subMeshesToken, // An arbitrary name for the subset. pxr.UsdGeomTokens.face, // Indicator that these represent face indices vtIndices, // The actual face indices. m_materialBindToken // familyName = "materialBind" ); if (exportContext.exportMaterials) { if (si >= sharedMaterials.Length || !sharedMaterials[si] || !exportContext.matMap.TryGetValue(sharedMaterials[si], out usdMaterialPath)) { Debug.LogWarning("Invalid material bound for: " + path + "\n" + (si >= sharedMaterials.Length ? "More submeshes than materials assigned." : (!sharedMaterials[si] ? "Submesh " + si + " has null material" : "ExportMap can't map material"))); } else { MaterialSample.Bind(scene, subset.GetPath(), usdMaterialPath); } } } } } else { // Only write the transform when animating. var meshSample = new MeshSampleBase(); meshSample.extent = sample.extent; meshSample.transform = XformExporter.GetLocalTransformMatrix( go.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(path).IsRootPrimPath(), exportContext.basisTransform); if (exportMeshPose) { meshSample.points = mesh.vertices; // Set face vertex counts and indices. var tris = mesh.triangles; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change // of basis is required. There are shortcuts, but this is fully general. for (int i = 0; i < meshSample.points.Length; i++) { meshSample.points[i] = UnityTypeConverter.ChangeBasis(meshSample.points[i]); } for (int i = 0; i < tris.Length; i += 3) { var t = tris[i]; tris[i] = tris[i + 1]; tris[i + 1] = t; } } sample.SetTriangles(tris); } UnityEngine.Profiling.Profiler.BeginSample("USD: Mesh Write"); scene.Write(path, meshSample); UnityEngine.Profiling.Profiler.EndSample(); } } }
/// <summary> /// Copy sphere data from USD to Unity with the given import options. /// </summary> /// <param name="skinnedMesh"> /// Whether the Cube to build is skinned or not. This will allow to determine which Renderer to create /// on the GameObject (MeshRenderer or SkinnedMeshRenderer). Default value is false (not skinned). /// </param> public static void BuildSphere(SphereSample usdSphere, GameObject go, SceneImportOptions options, bool skinnedMesh = false) { Material mat = null; var sphereGo = GameObject.CreatePrimitive(PrimitiveType.Sphere); var unityMesh = sphereGo.GetComponent <MeshFilter>().sharedMesh; GameObject.DestroyImmediate(sphereGo); // Because Unity only handle a sphere with a default size, the custom size of it is define by the localScale // transform. This also need to be taken into account while computing the Unity extent of the mesh (see bellow). // This is doable because xformable data are always handled before mesh data, so go.transform already // contains any transform of the geometry. float size = (float)usdSphere.radius * 2; go.transform.localScale = go.transform.localScale * size; bool changeHandedness = options.changeHandedness == BasisTransformation.SlowAndSafe; bool hasBounds = usdSphere.extent.size.x > 0 || usdSphere.extent.size.y > 0 || usdSphere.extent.size.z > 0; if (ShouldImport(options.meshOptions.boundingBox) && hasBounds) { if (changeHandedness) { usdSphere.extent.center = UnityTypeConverter.ChangeBasis(usdSphere.extent.center); // Divide the extent by the size of the cube. A custom size of the extent is define by // the localScale transform (see above). usdSphere.extent.extents = UnityTypeConverter.ChangeBasis(usdSphere.extent.extents) / size; } unityMesh.bounds = usdSphere.extent; } else if (ShouldCompute(options.meshOptions.boundingBox)) { unityMesh.RecalculateBounds(); } if (usdSphere.colors != null && ShouldImport(options.meshOptions.color)) { // NOTE: The following color conversion assumes PlayerSettings.ColorSpace == Linear. // For best performance, convert color space to linear off-line and skip conversion. if (usdSphere.colors.Length == 1) { // Constant color can just be set on the material. mat = options.materialMap.InstantiateSolidColor(usdSphere.colors[0].gamma); } else { // TODO: Improve logging by adding the path to the sphere prim. This would require that SphereSample // (and SampleBase class in general) allow to get the UsdPrim back and it's path in the stage. Debug.LogWarning( "Only constant color are supported for sphere: (can't handle " + usdSphere.colors.Length + " color values)" ); } } if (mat == null) { mat = options.materialMap.InstantiateSolidColor(Color.white); } // Create Unity mesh. // TODO: This code is a duplicate of the CubeImporter code. It requires refactoring. Renderer renderer; if (skinnedMesh) { SkinnedMeshRenderer skinnedRenderer = ImporterBase.GetOrAddComponent <SkinnedMeshRenderer>(go); if (skinnedRenderer.sharedMesh == null) { skinnedRenderer.sharedMesh = Mesh.Instantiate(unityMesh); } renderer = skinnedRenderer; } else { renderer = ImporterBase.GetOrAddComponent <MeshRenderer>(go); MeshFilter meshFilter = ImporterBase.GetOrAddComponent <MeshFilter>(go); if (meshFilter.sharedMesh == null) { meshFilter.sharedMesh = Mesh.Instantiate(unityMesh); } } if (unityMesh.subMeshCount == 1) { renderer.sharedMaterial = mat; } else { var mats = new Material[unityMesh.subMeshCount]; for (int i = 0; i < mats.Length; i++) { mats[i] = mat; } renderer.sharedMaterials = mats; } }
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 }
/// <summary> /// Copy cube data from USD to Unity with the given import options. /// </summary> /// <param name="skinnedMesh"> /// Whether the Cube to build is skinned or not. This will allow to determine which Renderer to create /// on the GameObject (MeshRenderer or SkinnedMeshRenderer). Default value is false (not skinned). /// </param> public static void BuildCube(CubeSample usdCube, GameObject go, SceneImportOptions options, bool skinnedMesh = false) { Material mat = null; var cubeGo = GameObject.CreatePrimitive(PrimitiveType.Cube); var unityMesh = cubeGo.GetComponent <MeshFilter>().sharedMesh; GameObject.DestroyImmediate(cubeGo); // Because Unity only handle a cube with a default size, the custom size of it is define by the localScale // transform. This also need to be taken into account while computing the Unity extent of the mesh (see bellow). // This is doable because xformable data are always handled before mesh data, so go.transform already // contains any transform of the geometry. float size = (float)usdCube.size; go.transform.localScale = go.transform.localScale * size; bool changeHandedness = options.changeHandedness == BasisTransformation.SlowAndSafe; bool hasBounds = usdCube.extent.size.x > 0 || usdCube.extent.size.y > 0 || usdCube.extent.size.z > 0; if (ShouldImport(options.meshOptions.boundingBox) && hasBounds) { if (changeHandedness) { usdCube.extent.center = UnityTypeConverter.ChangeBasis(usdCube.extent.center); // Divide the extent by the size of the cube. A custom size of the extent is define by // the localScale transform (see above). usdCube.extent.extents = UnityTypeConverter.ChangeBasis(usdCube.extent.extents) / size; } unityMesh.bounds = usdCube.extent; } else if (ShouldCompute(options.meshOptions.boundingBox)) { unityMesh.RecalculateBounds(); } if (usdCube.colors != null && ShouldImport(options.meshOptions.color)) { // NOTE: The following color conversion assumes PlayerSettings.ColorSpace == Linear. // For best performance, convert color space to linear off-line and skip conversion. if (usdCube.colors.Length == 1) { // Constant color can just be set on the material. mat = options.materialMap.InstantiateSolidColor(usdCube.colors[0].gamma); Debug.Log("constant colors assigned"); } else if (usdCube.colors.Length == 6) { // Uniform colors to verts. // Note that USD cubes have 6 uniform colors and Unity cube mesh has 24 (6*4) // TODO: move the conversion to C++ and use the color management API. Debug.Log(unityMesh.vertexCount); for (int i = 0; i < usdCube.colors.Length; i++) { usdCube.colors[i] = usdCube.colors[i]; } var unityColors = new Color[24]; // Front:0, Back:1, Top:2, Bottom:3, Right:4, Left:5 unityColors[0] = usdCube.colors[0]; // front bottom right unityColors[1] = usdCube.colors[0]; // front bottom left unityColors[2] = usdCube.colors[0]; // front top right unityColors[3] = usdCube.colors[0]; // front top left unityColors[4] = usdCube.colors[2]; // top back right unityColors[5] = usdCube.colors[2]; // top back left unityColors[6] = usdCube.colors[1]; // back bottom right unityColors[7] = usdCube.colors[1]; // back bottom left unityColors[8] = usdCube.colors[2]; // top front right unityColors[9] = usdCube.colors[2]; // top front left unityColors[10] = usdCube.colors[1]; // back top right unityColors[11] = usdCube.colors[1]; // back top left unityColors[12] = usdCube.colors[3]; // Bottom back right unityColors[13] = usdCube.colors[3]; // Bottom front right unityColors[14] = usdCube.colors[3]; // Bottom front left unityColors[15] = usdCube.colors[3]; // Bottom back left unityColors[16] = usdCube.colors[5]; // left front bottom unityColors[17] = usdCube.colors[5]; // left front top unityColors[18] = usdCube.colors[5]; // left back top unityColors[19] = usdCube.colors[5]; // left back bottom unityColors[20] = usdCube.colors[4]; // right back bottom unityColors[21] = usdCube.colors[4]; // right back top unityColors[22] = usdCube.colors[4]; // right front top unityColors[23] = usdCube.colors[4]; // right front bottom unityMesh.colors = unityColors; } else if (usdCube.colors.Length == 24) { // Face varying colors to verts. // Note that USD cubes have 24 face varying colors and Unity cube mesh has 24 (6*4) // TODO: move the conversion to C++ and use the color management API. Debug.Log(unityMesh.vertexCount); for (int i = 0; i < usdCube.colors.Length; i++) { usdCube.colors[i] = usdCube.colors[i]; } // USD order: front, back, top, bottom, right, left var unityColors = new Color[24]; unityColors[0] = usdCube.colors[3]; // front bottom right unityColors[1] = usdCube.colors[2]; // front bottom left unityColors[2] = usdCube.colors[0]; // front top right unityColors[3] = usdCube.colors[1]; // front top left unityColors[4] = usdCube.colors[8 + 1]; // top back right unityColors[5] = usdCube.colors[8 + 2]; // top back left unityColors[6] = usdCube.colors[4 + 3]; // back bottom right unityColors[7] = usdCube.colors[4 + 0]; // back bottom left unityColors[8] = usdCube.colors[8 + 0]; // top front right unityColors[9] = usdCube.colors[8 + 3]; // top front left unityColors[10] = usdCube.colors[4 + 2]; // back top right unityColors[11] = usdCube.colors[4 + 1]; // back top left unityColors[12] = usdCube.colors[12 + 1]; // Bottom back right unityColors[13] = usdCube.colors[12 + 2]; // Bottom front right unityColors[14] = usdCube.colors[12 + 3]; // Bottom front left unityColors[15] = usdCube.colors[12 + 0]; // Bottom back left unityColors[16] = usdCube.colors[20 + 1]; // left front bottom unityColors[17] = usdCube.colors[20 + 2]; // left front top unityColors[18] = usdCube.colors[20 + 3]; // left back top unityColors[19] = usdCube.colors[20 + 0]; // left back bottom unityColors[20] = usdCube.colors[16 + 2]; // right back bottom unityColors[21] = usdCube.colors[16 + 3]; // right back top unityColors[22] = usdCube.colors[16 + 0]; // right front top unityColors[23] = usdCube.colors[16 + 1]; // right front bottom unityMesh.colors = unityColors; } else if (usdCube.colors.Length == 8) { // Vertex colors map on to verts. // Note that USD cubes have 8 verts but Unity cube mesh has 24 (6*4) // TODO: move the conversion to C++ and use the color management API. Debug.Log(unityMesh.vertexCount); for (int i = 0; i < usdCube.colors.Length; i++) { usdCube.colors[i] = usdCube.colors[i]; } // USD order: front (top-right -> ccw) // back (bottom-left -> ccw (from back perspective)) var unityColors = new Color[24]; unityColors[0] = usdCube.colors[3]; // front bottom right unityColors[1] = usdCube.colors[2]; // front bottom left unityColors[2] = usdCube.colors[0]; // front top right unityColors[3] = usdCube.colors[1]; // front top left unityColors[4] = usdCube.colors[6]; // top back right unityColors[5] = usdCube.colors[5]; // top back left unityColors[6] = usdCube.colors[7]; // back bottom right unityColors[7] = usdCube.colors[4]; // back bottom left unityColors[8] = usdCube.colors[0]; // top front right unityColors[9] = usdCube.colors[1]; // top front left unityColors[10] = usdCube.colors[6]; // back top right unityColors[11] = usdCube.colors[5]; // back top left unityColors[12] = usdCube.colors[7]; // Bottom back right unityColors[13] = usdCube.colors[3]; // Bottom front right unityColors[14] = usdCube.colors[2]; // Bottom front left unityColors[15] = usdCube.colors[4]; // Bottom back left unityColors[16] = usdCube.colors[2]; // left front bottom unityColors[17] = usdCube.colors[1]; // left front top unityColors[18] = usdCube.colors[5]; // left back top unityColors[19] = usdCube.colors[4]; // left back bottom unityColors[20] = usdCube.colors[7]; // right back bottom unityColors[21] = usdCube.colors[6]; // right back top unityColors[22] = usdCube.colors[0]; // right front top unityColors[23] = usdCube.colors[3]; // right front bottom unityMesh.colors = unityColors; Debug.Log("vertex colors assigned"); } else { // FaceVarying and uniform both require breaking up the mesh and are not yet handled in // this example. Debug.LogWarning("Uniform (color per face) and FaceVarying (color per vert per face) " + "display color not supported in this example"); } } if (mat == null) { mat = options.materialMap.InstantiateSolidColor(Color.white); } // Create Unity mesh. Renderer renderer; if (skinnedMesh) { SkinnedMeshRenderer skinnedRenderer = ImporterBase.GetOrAddComponent <SkinnedMeshRenderer>(go); if (skinnedRenderer.sharedMesh == null) { skinnedRenderer.sharedMesh = Mesh.Instantiate(unityMesh); } renderer = skinnedRenderer; } else { renderer = ImporterBase.GetOrAddComponent <MeshRenderer>(go); MeshFilter meshFilter = ImporterBase.GetOrAddComponent <MeshFilter>(go); if (meshFilter.sharedMesh == null) { meshFilter.sharedMesh = Mesh.Instantiate(unityMesh); } } if (unityMesh.subMeshCount == 1) { renderer.sharedMaterial = mat; } else { var mats = new Material[unityMesh.subMeshCount]; for (int i = 0; i < mats.Length; i++) { mats[i] = mat; } renderer.sharedMaterials = mats; } }