/// <summary>
        /// Exports the given texture to the destination texture path and wires up the preview surface.
        /// </summary>
        /// <returns>
        /// Returns the path to the USD texture object.
        /// </returns>
        protected static string SetupTexture(Scene scene,
                                             string usdShaderPath,
                                             Material material,
                                             PreviewSurfaceSample surface,
                                             string destTexturePath,
                                             string textureName,
                                             string textureOutput)
        {
#if UNITY_EDITOR
            var srcPath = UnityEditor.AssetDatabase.GetAssetPath(material.GetTexture(textureName));
            srcPath = srcPath.Substring("Assets/".Length);
            srcPath = Application.dataPath + "/" + srcPath;
            var fileName = System.IO.Path.GetFileName(srcPath);
            var filePath = System.IO.Path.Combine(destTexturePath, fileName);

            System.IO.File.Copy(srcPath, filePath, overwrite: true);

            // Make file path baked into USD relative to scene file and use forward slashes.
            filePath = ImporterBase.MakeRelativePath(scene.FilePath, filePath);
            filePath = filePath.Replace("\\", "/");

            var uvReader = new PrimvarReaderSample <Vector2>();
            uvReader.varname.defaultValue = new TfToken("st");
            scene.Write(usdShaderPath + "/uvReader", uvReader);
            var tex = new TextureReaderSample(filePath, usdShaderPath + "/uvReader.outputs:result");
            scene.Write(usdShaderPath + "/" + textureName, tex);
            return(usdShaderPath + "/" + textureName + ".outputs:" + textureOutput);
#else
            // Not supported at run-time, too many things can go wrong
            // (can't encode compressed textures, etc).
            throw new System.Exception("Not supported at run-time");
#endif
        }
Example #2
0
        /// <summary>
        /// Ensure each layer path is expressed in the USD sub layer stack of the given scene,
        /// creating the sublayer USD files if needed.
        /// </summary>
        public void SaveLayerStack(Scene scene, string[] layerStack)
        {
            if (scene == null)
            {
                throw new NullReferenceException("Null scene provided to SaveLayerStack");
            }

            SdfSubLayerProxy subLayers = scene.Stage.GetRootLayer().GetSubLayerPaths();

            for (int i = 0; i < m_layerStack.Length; i++)
            {
                string absoluteLayerPath = m_layerStack[i];
                string relativeLayerPath = ImporterBase.MakeRelativePath(scene.FilePath, absoluteLayerPath);
                if (!System.IO.File.Exists(absoluteLayerPath))
                {
                    var newSubLayer = Scene.Create(absoluteLayerPath);
                    SetupNewSubLayer(scene, newSubLayer);
                    newSubLayer.Save();
                    newSubLayer.Close();
                }

                if (subLayers.Count(relativeLayerPath) == 0)
                {
                    subLayers.push_back(relativeLayerPath);
                }
            }

            scene.Save();
        }
Example #3
0
        /// <summary>
        /// Copy mesh data from USD to Unity with the given import options.
        /// </summary>
        public static void BuildMesh(string path,
                                     MeshSample usdMesh,
                                     GeometrySubsets geomSubsets,
                                     GameObject go,
                                     SceneImportOptions options,
                                     bool isDynamic)
        {
            var mf = ImporterBase.GetOrAddComponent <MeshFilter>(go);
            var mr = ImporterBase.GetOrAddComponent <MeshRenderer>(go);

            if (mf.sharedMesh == null)
            {
                mf.sharedMesh = new Mesh {
                    name = UniqueMeshName(go.name)
                };
            }

            // We only check if a mesh is dynamic when scene.IsPopulatingAccessMask is True. It only happens when a playable is
            // created, potentially way after mesh creation.
            if (isDynamic)
            {
                mf.sharedMesh.MarkDynamic();
            }

            BuildMesh_(path, usdMesh, mf.sharedMesh, geomSubsets, go, mr, options);
        }
        /// <summary>
        /// Copy camera data from USD to Unity with the given import options.
        /// </summary>
        public static void BuildCamera(CameraSample usdCamera,
                                       GameObject go,
                                       SceneImportOptions options)
        {
            var cam = ImporterBase.GetOrAddComponent <Camera>(go);

            usdCamera.CopyToCamera(cam, setTransform: false);
            cam.nearClipPlane *= options.scale;
            cam.farClipPlane  *= options.scale;
        }
Example #5
0
        /// <summary>
        /// Writes overrides over the given scene. The given scene is referenced into the override
        /// scene being exported.
        /// </summary>
        /// <param name="sceneInWhichToStoreTransforms"></param>
        public void ExportOverrides(Scene sceneInWhichToStoreTransforms)
        {
            var sceneToReference = this;
            var overs            = sceneInWhichToStoreTransforms;

            if (overs == null)
            {
                return;
            }

            var baseLayer = sceneToReference.GetScene();

            if (baseLayer == null)
            {
                throw new Exception("Could not open base layer: " + sceneToReference.usdFullPath);
            }

            overs.Time      = baseLayer.Time;
            overs.StartTime = baseLayer.StartTime;
            overs.EndTime   = baseLayer.EndTime;

            overs.WriteMode = Scene.WriteModes.Over;
            overs.UpAxis    = baseLayer.UpAxis;

            try
            {
                SceneExporter.Export(sceneToReference.gameObject,
                                     overs,
                                     BasisTransformation.SlowAndSafe,
                                     exportUnvarying: false,
                                     zeroRootTransform: true);

                var rel = ImporterBase.MakeRelativePath(overs.FilePath, sceneToReference.usdFullPath);
                GetFirstPrim(overs).GetReferences().AddReference(rel, GetFirstPrim(baseLayer).GetPath());
            }
            catch (System.Exception ex)
            {
                Debug.LogException(ex);
                return;
            }
            finally
            {
                if (overs != null)
                {
                    overs.Save();
                    overs.Close();
                }
            }
        }
Example #6
0
        /// <summary>
        /// Copy mesh data from USD to Unity with the given import options, setup for skinning.
        /// </summary>
        public static void BuildSkinnedMesh(string path,
                                            MeshSample usdMesh,
                                            GeometrySubsets geomSubsets,
                                            GameObject go,
                                            SceneImportOptions options)
        {
            var smr = ImporterBase.GetOrAddComponent <SkinnedMeshRenderer>(go);

            if (smr.sharedMesh == null)
            {
                smr.sharedMesh = new Mesh();
            }

            BuildMesh_(path, usdMesh, smr.sharedMesh, geomSubsets, go, smr, options);
        }
Example #7
0
        /// <summary>
        /// Build the root of a scene under which more USD data will be imported. If the handedness
        /// is changed here, no subsequent changes are required below, however the root will contain
        /// a negative scale.
        /// </summary>
        public static void BuildSceneRoot(Scene scene, Transform root, SceneImportOptions options)
        {
            var  stageRoot    = root.GetComponent <UsdAsset>();
            bool newStageRoot = false;

            if (stageRoot == null)
            {
                stageRoot             = root.gameObject.AddComponent <UsdAsset>();
                stageRoot.usdFullPath = scene.FilePath;
                newStageRoot          = true;
                ImporterBase.MoveComponentFirst(stageRoot);
                stageRoot.OptionsToState(options);
            }

            if (newStageRoot ||
                options.changeHandedness != stageRoot.LastHandedness ||
                options.scale != stageRoot.LastScale ||
                options.forceRebuild)
            {
                var localScale    = root.transform.localScale;
                var localRotation = root.transform.localRotation;

                if (options.forceRebuild)
                {
                    localScale    = Vector3.one;
                    localRotation = Quaternion.identity;
                }
                else if (!newStageRoot)
                {
                    // Undo the previous transforms.
                    UndoRootTransform(scene, stageRoot, ref localScale, ref localRotation);
                }

                stageRoot.LastScale      = options.scale;
                stageRoot.LastHandedness = options.changeHandedness;

                // Handle configurable up-axis (Y or Z).
                float invert = options.changeHandedness == BasisTransformation.FastWithNegativeScale ? -1 : 1;
                if (scene.UpAxis == Scene.UpAxes.Z)
                {
                    localRotation *= Quaternion.AngleAxis(invert * 90, Vector3.right);
                }

                if (options.changeHandedness == BasisTransformation.FastWithNegativeScale)
                {
                    // Convert from right-handed (USD) to left-handed (Unity).
                    if (scene.UpAxis == Scene.UpAxes.Z)
                    {
                        localScale.y *= -1;
                    }
                    else
                    {
                        localScale.z *= -1;
                    }
                }

                if (Mathf.Abs(options.scale - 1.0f) > 0.0001)
                {
                    // Unilaterally setting the scale here is a little wrong, since it will stomp the root
                    // object scale if set in Unity.
                    localScale *= options.scale;
                }

                root.transform.localScale    = localScale;
                root.transform.localRotation = localRotation;
            }
        }
Example #8
0
        /// <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();
        }
Example #9
0
        /// <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;
            }
        }
Example #10
0
        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
        }
Example #12
0
        /// <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;
            }
        }
        /// <summary>
        /// Exports the given texture to the destination texture path and wires up the preview surface.
        /// </summary>
        /// <returns>
        /// Returns the path to the USD texture object.
        /// </returns>
        protected static string SetupTexture(Scene scene,
                                             string usdShaderPath,
                                             Material material,
                                             PreviewSurfaceSample surface,
                                             Vector4 scale,
                                             string destTexturePath,
                                             string textureName,
                                             string textureOutput,
                                             ConversionType conversionType = ConversionType.None)
        {
            // We have to handle multiple cases here:
            // - file exists on disk
            //   - file is a supported format => can be directly copied
            //   - file is not in a supported format => need to blit / export
            // - file is only in memory
            //   - a Texture2D
            //   - a Texture
            //   - a RenderTexture
            //   - needs special care if marked as Normal Map
            //     (can probably only be detected in an Editor context, and heuristically at runtime)
            //   => need to blit / export
            // - file is not supported at all (or not yet)
            //   - a 3D texture
            //   => needs to be ignored, log Warning

            bool textureIsExported = false;

            string filePath = null;
            string fileName = null;

            var srcTexture2d = material.GetTexture(textureName);

            bool needsConversion = false;

            switch (conversionType)
            {
            case ConversionType.None:
                break;

            case ConversionType.UnpackNormal:
#if UNITY_EDITOR
                if (UnityEditor.AssetDatabase.Contains(srcTexture2d))
                {
                    // normal needs to be converted if the one on disk isn't really a normal map
                    // (e.g. created from greyscale)
                    UnityEditor.TextureImporter importer =
                        (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(
                            UnityEditor.AssetDatabase.GetAssetPath(srcTexture2d));
                    if (importer.textureType != UnityEditor.TextureImporterType.NormalMap)
                    {
                        Debug.LogWarning("Texture " + textureName + " is set as NormalMap but isn't marked as such",
                                         srcTexture2d);
                    }

                    UnityEditor.TextureImporterSettings dst = new UnityEditor.TextureImporterSettings();
                    importer.ReadTextureSettings(dst);
                    // if this NormalMap is created from greyscale we will export the NormalMap from memory.
                    if (dst.convertToNormalMap)
                    {
                        needsConversion = true;
                        break;
                    }
                }
#endif
                break;

            default:
                needsConversion = true;
                break;
            }

#if UNITY_EDITOR
            // only export from disk if there's no need to do any type of data conversion here
            if (!needsConversion)
            {
                var srcPath = UnityEditor.AssetDatabase.GetAssetPath(srcTexture2d);

                if (!string.IsNullOrEmpty(srcPath))
                {
#if UNITY_2019_2_OR_GREATER
                    // Since textures might be inside of packages for various reasons we should support that.
                    // Usually this would just be "Path.GetFullPath(srcPath)", but USD export messes with the CWD (Working Directory)
                    // and so we have to do a bit more path wrangling here.
                    if (srcPath.StartsWith("Packages"))
                    {
                        var pi = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(srcPath);
                        srcPath = pi.resolvedPath + srcPath.Substring(("Packages/" + pi.name).Length);
                    }
#endif
                    if (srcPath.StartsWith("Assets"))
                    {
                        srcPath = Application.dataPath + "/" + srcPath.Substring("Assets/".Length);
                    }

                    fileName = System.IO.Path.GetFileName(srcPath);
                    filePath = System.IO.Path.Combine(destTexturePath, fileName);

                    if (System.IO.File.Exists(srcPath))
                    {
                        // USDZ officially only supports png / jpg / jpeg
                        // https://graphics.pixar.com/usd/docs/Usdz-File-Format-Specification.html

                        var ext = System.IO.Path.GetExtension(srcPath).ToLowerInvariant();
                        if (ext == ".png" || ext == ".jpg" || ext == ".jpeg")
                        {
                            System.IO.File.Copy(srcPath, filePath, overwrite: true);
                            if (System.IO.File.Exists(filePath))
                            {
                                textureIsExported = true;
                            }
                        }
                    }
                }
            }
#endif

            if (!textureIsExported)
            {
                // Since this is a texture we can't directly export from disk, we need to blit it and output it as PNG.
                // To avoid collisions, e.g. with multiple different textures named the same, each texture gets a pseudo-random name.
                // This will also avoid collisions when exporting multiple models to the same folder, e.g. with a a RenderTexture called "RT"
                // in each of them that might look different between exports.
                // TODO Future work could, if necessary, generate a texture content hash to avoid exporting identical textures multiple times
                // (Unity's content hash isn't reliable for some types of textures unfortunately, e.g. RTs)
#if UNITY_EDITOR
                if (srcTexture2d is Texture2D)
                {
                    fileName = srcTexture2d.name + "_" + srcTexture2d.imageContentsHash.ToString();
                }
                else
                {
                    fileName = srcTexture2d.name + "_" + Random.Range(10000000, 99999999).ToString();
                }
#else
                fileName = srcTexture2d.name + "_" + Random.Range(10000000, 99999999).ToString();
#endif
                filePath = System.IO.Path.Combine(destTexturePath, fileName + ".png");

                // TODO extra care has to be taken of Normal Maps etc., since these are in a converted format in memory (for example 16 bit AG instead of 8 bit RGBA, depending on platform)
                // An example of this conversion in a shader is in Khronos' UnityGLTF implementation.
                // Basically, the blit has do be done with the right unlit conversion shader to get a proper "file-based" tangent space normal map back

                // Blit the texture and get it back to CPU
                // Note: Can't use RenderTexture.GetTemporary because that doesn't properly clear alpha channel
                bool preserveLinear = false;
                switch (conversionType)
                {
                case ConversionType.UnpackNormal:
                    preserveLinear = true;
                    break;
                }

                var rt = new RenderTexture(srcTexture2d.width, srcTexture2d.height, 0, RenderTextureFormat.ARGB32,
                                           preserveLinear ? RenderTextureReadWrite.Linear : RenderTextureReadWrite.Default);
                var resultTex2d = new Texture2D(srcTexture2d.width, srcTexture2d.height, TextureFormat.ARGB32, true,
                                                preserveLinear ? true : false);
                var activeRT = RenderTexture.active;
                try
                {
                    RenderTexture.active = rt;
                    GL.Clear(true, true, Color.clear);

                    // conversion material
                    if (_metalGlossChannelSwapMaterial == null)
                    {
                        _metalGlossChannelSwapMaterial = new Material(Shader.Find("Hidden/USD/ChannelCombiner"));
                    }

                    if (_normalChannelMaterial == null)
                    {
                        _normalChannelMaterial = new Material(Shader.Find("Hidden/USD/NormalChannel"));
                    }

                    _metalGlossChannelSwapMaterial.SetTexture("_R", srcTexture2d);
                    _metalGlossChannelSwapMaterial.SetTexture("_G", srcTexture2d);
                    _metalGlossChannelSwapMaterial.SetTexture("_B", srcTexture2d);
                    _metalGlossChannelSwapMaterial.SetTexture("_A", srcTexture2d);

                    switch (conversionType)
                    {
                    case ConversionType.None:
                        Graphics.Blit(srcTexture2d, rt);
                        break;

                    case ConversionType.SwapRASmoothnessToBGRoughness:
                        _metalGlossChannelSwapMaterial.SetVector("_Invert",
                                                                 new Vector4(0, 1, 0, 1)); // invert resulting g channel, make sure alpha is 1
                        _metalGlossChannelSwapMaterial.SetVector("_RScale", new Vector4(0, 0, 0, 0));
                        _metalGlossChannelSwapMaterial.SetVector("_GScale",
                                                                 new Vector4(0, 0, 0, 1)); // use a channel from _G texture for resulting g
                        _metalGlossChannelSwapMaterial.SetVector("_BScale",
                                                                 new Vector4(1, 0, 0, 0)); // use r channel from _B texture for resulting b
                        _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 0));

                        Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial);
                        break;

                    case ConversionType.InvertAlpha:
                        _metalGlossChannelSwapMaterial.SetVector("_Invert",
                                                                 new Vector4(0, 0, 0, 1)); // invert alpha result
                        _metalGlossChannelSwapMaterial.SetVector("_RScale",
                                                                 new Vector4(1, 0, 0, 0)); // use all color channels as-is
                        _metalGlossChannelSwapMaterial.SetVector("_GScale", new Vector4(0, 1, 0, 0));
                        _metalGlossChannelSwapMaterial.SetVector("_BScale", new Vector4(0, 0, 1, 0));
                        _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 1));

                        Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial);
                        break;

                    case ConversionType.MaskMapToORM:
                        // Input is RGBA (Metallic, Occlusion, Detail, Smoothness)
                        // Output is RGB1 (Occlusion, Roughness = 1 - Smoothness, Metallic, 1)
                        _metalGlossChannelSwapMaterial.SetVector("_Invert",
                                                                 new Vector4(0, 1, 0, 1)); // smoothness to roughness, solid alpha
                        _metalGlossChannelSwapMaterial.SetVector("_RScale", new Vector4(0, 1, 0, 0));
                        _metalGlossChannelSwapMaterial.SetVector("_GScale", new Vector4(0, 0, 0, 1));
                        _metalGlossChannelSwapMaterial.SetVector("_BScale", new Vector4(1, 0, 0, 0));
                        _metalGlossChannelSwapMaterial.SetVector("_AScale", new Vector4(0, 0, 0, 0));

                        Graphics.Blit(srcTexture2d, rt, _metalGlossChannelSwapMaterial);
                        break;

                    case ConversionType.UnpackNormal:
                        Graphics.Blit(srcTexture2d, rt, _normalChannelMaterial);
                        break;
                    }

                    resultTex2d.ReadPixels(new Rect(0, 0, srcTexture2d.width, srcTexture2d.height), 0, 0);
                    resultTex2d.Apply();

                    System.IO.File.WriteAllBytes(filePath, resultTex2d.EncodeToPNG());
                    if (System.IO.File.Exists(filePath))
                    {
                        textureIsExported = true;
                    }
                }
                finally
                {
                    RenderTexture.active = activeRT;
                    rt.Release();
                    GameObject.DestroyImmediate(rt);
                    GameObject.DestroyImmediate(resultTex2d);
                }
            }

            if (!textureIsExported)
            {
                var tmpTex2d = new Texture2D(1, 1, TextureFormat.ARGB32, true);
                try
                {
                    tmpTex2d.SetPixel(0, 0, Color.white);
                    tmpTex2d.Apply();
                    System.IO.File.WriteAllBytes(filePath, tmpTex2d.EncodeToPNG());
                    if (System.IO.File.Exists(filePath))
                    {
                        textureIsExported = true;
                    }
                }
                finally
                {
                    GameObject.DestroyImmediate(tmpTex2d);
                }
            }

            if (textureIsExported)
            {
                // Make file path baked into USD relative to scene file and use forward slashes.
                filePath = ImporterBase.MakeRelativePath(scene.FilePath, filePath);
                filePath = filePath.Replace("\\", "/");

                var uvReader = new PrimvarReaderSample <Vector2>();
                uvReader.varname.defaultValue = new TfToken("st");
                scene.Write(usdShaderPath + "/uvReader", uvReader);
                var usdTexReader = new TextureReaderSample(filePath, usdShaderPath + "/uvReader.outputs:result");
                usdTexReader.wrapS =
                    new Connectable <TextureReaderSample.WrapMode>(
                        TextureReaderSample.GetWrapMode(srcTexture2d.wrapModeU));
                usdTexReader.wrapT =
                    new Connectable <TextureReaderSample.WrapMode>(
                        TextureReaderSample.GetWrapMode(srcTexture2d.wrapModeV));
                if (scale != Vector4.one)
                {
                    usdTexReader.scale = new Connectable <Vector4>(scale);
                }

                // usdTexReader.isSRGB = new Connectable<TextureReaderSample.SRGBMode>(TextureReaderSample.SRGBMode.Auto);
                scene.Write(usdShaderPath + "/" + textureName, usdTexReader);
                return(usdShaderPath + "/" + textureName + ".outputs:" + textureOutput);
            }
            else
            {
                Debug.LogError(
                    "Texture wasn't exported: " + srcTexture2d.name + " (" + textureName + " from material " + material,
                    srcTexture2d);
                return(null);
            }
        }
Example #14
0
        public static void ExportUsdz(string usdzFilePath,
                                      GameObject root)
        {
            // Setup a temp directory for zipping up files.
            string tempDirectory  = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            var    tmpUsdName     = Path.GetFileNameWithoutExtension(usdzFilePath);
            var    di             = Directory.CreateDirectory(tempDirectory);
            var    tmpUsdFilePath = Path.Combine(tempDirectory, tmpUsdName + ".usdc");
            var    curDir         = Directory.GetCurrentDirectory();

            var supportedExtensions = new System.Collections.Generic.HashSet <string>();

            supportedExtensions.Add(".usd");
            supportedExtensions.Add(".usda");
            supportedExtensions.Add(".usdc");

            supportedExtensions.Add(".jpg");
            supportedExtensions.Add(".jpeg");
            supportedExtensions.Add(".jpe");
            supportedExtensions.Add(".jif");
            supportedExtensions.Add(".jfif");
            supportedExtensions.Add(".jfi");

            supportedExtensions.Add(".png");

            // Create the temp .usd scene, into which the data will be exported.
            var scene      = InitForSave(tmpUsdFilePath);
            var localScale = root.transform.localScale;

            try {
                try {
                    // USDZ is in centimeters.
                    root.transform.localScale = localScale * 100;

                    // Set the current working directory to the USDZ directory so the paths in USD
                    // will be relative.
                    Directory.SetCurrentDirectory(tempDirectory);

                    // Export the temp scene.
                    SceneExporter.Export(root,
                                         scene,
                                         BasisTransformation.SlowAndSafe, // Required by ARKit
                                         exportUnvarying: true,
                                         zeroRootTransform: false,
                                         exportMaterials: true);
                } finally {
                    // Undo temp scale.
                    root.transform.localScale = localScale;

                    // Flush any in-flight edits and release the scene so the file can be deleted.
                    scene.Save();
                    scene.Close();
                    scene = null;
                }

                // Copy resulting files into the USDZ archive.
                var filesToArchive = new pxr.StdStringVector();

                // According to the USDZ spec, the first file in the archive must be the primary USD file.
                filesToArchive.Add(tmpUsdFilePath);

                foreach (var fileInfo in di.GetFiles())
                {
                    if (fileInfo.Name.ToLower() == Path.GetFileName(tmpUsdFilePath).ToLower())
                    {
                        continue;
                    }
                    var relPath = ImporterBase.MakeRelativePath(tmpUsdFilePath, fileInfo.FullName);
                    var ext     = Path.GetExtension(relPath).ToLower();
                    if (!supportedExtensions.Contains(ext))
                    {
                        Debug.LogWarning("Unsupported file type in USDZ: " + relPath);
                        continue;
                    }
                    filesToArchive.Add(relPath);
                }

                // Write the USDZ file.
                pxr.UsdCs.WriteUsdZip(usdzFilePath, filesToArchive);
            } finally {
                // Clean up temp files.
                Directory.SetCurrentDirectory(curDir);
                di.Delete(recursive: true);
            }
        }