Exemple #1
0
        private ExportResults ExportHelper(
            SceneStatePayload payload,
            string outputFile,
            bool binary,
            bool doExtras,
            int gltfVersion,
            bool allowHttpUri)
        {
            // TODO: Ownership of this temp directory is sloppy.
            // Payload and export share the same dir and we assume that the exporter:
            // 1. will not write files whose names conflict with payload's
            // 2. will clean up the entire directory when done
            // This works, as long as the payload isn't used for more than one export (it currently isn't)
            using (var exporter = new GlTF_ScriptableExporter(payload.temporaryDirectory, gltfVersion))
            {
                exporter.AllowHttpUri = allowHttpUri;
                try
                {
                    m_exporter        = exporter;
                    exporter.G.binary = binary;

                    exporter.BeginExport(outputFile);
                    exporter.SetMetadata(payload.generator, copyright: null);
                    if (doExtras)
                    {
                        SetExtras(exporter, payload);
                    }

                    if (payload.env.skyCubemap != null)
                    {
                        // Add the skybox texture to the export.
                        string texturePath     = ExportUtils.GetTexturePath(payload.env.skyCubemap);
                        string textureFilename = Path.GetFileName(texturePath);
                        exporter.G.extras["TB_EnvironmentSkybox"] =
                            ExportFileReference.CreateLocal(texturePath, textureFilename);
                    }

                    WriteObjectsAndConnections(exporter, payload);

                    string[] exportedFiles = exporter.EndExport();
                    return(new ExportResults
                    {
                        success = true,
                        exportedFiles = exportedFiles,
                        numTris = exporter.NumTris
                    });
                }
                catch (InvalidOperationException e)
                {
                    OutputWindowScript.Error("glTF export failed", e.Message);
                    // TODO: anti-pattern. Let the exception bubble up so caller can log it properly
                    // Actually, InvalidOperationException is now somewhat expected in experimental, since
                    // the gltf exporter does not check IExportableMaterial.SupportsDetailedMaterialInfo.
                    // But we still want the logging for standalone builds.
                    Debug.LogException(e);
                    return(new ExportResults {
                        success = false
                    });
                }
                catch (IOException e)
                {
                    OutputWindowScript.Error("glTF export failed", e.Message);
                    return(new ExportResults {
                        success = false
                    });
                }
                finally
                {
                    payload.Destroy();
                    // The lifetime of ExportGlTF, GlTF_ScriptableExporter, and GlTF_Globals instances
                    // is identical. This is solely to be pedantic.
                    m_exporter = null;
                }
            }
        }
Exemple #2
0
        public static SceneStatePayload GetExportPayloadForGameObject(GameObject gameObject,
                                                                      AxisConvention axes,
                                                                      Environment env)
        {
            var payload = new SceneStatePayload(axes);

            payload.env.skyCubemap = env == null ? null : env.m_RenderSettings.m_SkyboxCubemap;
            payload.lights         = new ExportUtils.LightsPayload();
            // unused test code
#if TEST_ENVIRONMENTS_WITH_SCENE_LIGHTS
            {
                var lights = payload.lights;
                lights.ambientColor = RenderSettings.ambientLight;

#if true
                // Scene lights from Standard enviroment
                lights.elements.Add(new ExportUtils.Light {
                    name       = "SceneLight0",
                    id         = 0,
                    type       = LightType.Directional,
                    lightColor = new Color32(198, 208, 253),
                    xform      = ExportUtils.ChangeBasisNonUniformScale(
                        payload, Matrix4x4.TRS(
                            new Vector3(0f, 0.21875f, -.545875f),
                            Quaternion.Euler(60, 0, 26),
                            Vector3.one))
                });

                lights.elements.Add(new ExportUtils.Light {
                    name       = "SceneLight1",
                    id         = 1,
                    type       = LightType.Directional,
                    lightColor = new Color32(109, 107, 88),
                    xform      = ExportUtils.ChangeBasisNonUniformScale(
                        payload,
                        Matrix4x4.TRS(
                            new Vector3(0f, 0.21875f, -.545875f),
                            Quaternion.Euler(140, 0, 40),
                            Vector3.one)),
                });
#else
                // Scene lights from Dress Form environment
                lights.elements.Add(new ExportUtils.Light {
                    name       = "SceneLight0",
                    id         = 0,
                    type       = LightType.Directional,
                    lightColor = new Color32(255, 234, 198),
                    xform      = ExportUtils.ChangeBasisNonUniformScale(
                        payload, Matrix4x4.TRS(
                            new Vector3(0f, 0.7816665f, -.220875f),
                            Quaternion.Euler(50, 42, 26),
                            Vector3.one))
                });

                lights.elements.Add(new ExportUtils.Light {
                    name       = "SceneLight1",
                    id         = 1,
                    type       = LightType.Directional,
                    lightColor = new Color32(91, 90, 74),
                    xform      = ExportUtils.ChangeBasisNonUniformScale(
                        payload,
                        Matrix4x4.TRS(
                            new Vector3(0f, .636532f, -.6020539f),
                            Quaternion.Euler(140, 47, 40),
                            Vector3.one)),
                });
#endif
            }
#endif

            // The determinant can be used to detect if the basis-change has a mirroring.
            // This matters because a mirroring turns the triangles inside-out, requiring
            // us to flip their winding to preserve the surface orientation.
            bool reverseTriangleWinding = (ExportUtils.GetBasisMatrix(payload).determinant < 0);

            BuildModelMeshesFromGameObject(gameObject, payload, reverseTriangleWinding);
            return(payload);
        }
Exemple #3
0
        // Get a material for the given mesh. If a _MainTex texture exists, reference to it from
        // UnityEditor.AssetBase.
        static IExportableMaterial GetMaterialForMeshData(MeshData data)
        {
            // Create parameter dictionaries.
            Dictionary <string, string>  textureUris  = new Dictionary <string, string>();
            Dictionary <string, Vector2> textureSizes = new Dictionary <string, Vector2>();
            Dictionary <string, Vector3> vectorParams = new Dictionary <string, Vector3>();
            Dictionary <string, float>   floatParams  = new Dictionary <string, float>();
            Dictionary <string, Color>   colorParams  = new Dictionary <string, Color>();

            // Get material for the mesh.
            Material  mat     = data.renderer.sharedMaterial;
            Texture2D mainTex = (Texture2D)mat.GetTexture("_MainTex");
            string    uriBase = "";

            if (mainTex)
            {
                string texturePath = ExportUtils.GetTexturePath(mainTex);
                textureUris.Add("BaseColorTex", ExportUtils.kTempPrefix + texturePath);
                Vector2 uvScale  = mat.GetTextureScale("_MainTex");
                Vector2 uvOffset = mat.GetTextureOffset("_MainTex");
                colorParams.Add("UvAdjust", new Color(uvScale.x, uvScale.y, uvOffset.x, uvOffset.y));
            }
            else
            {
                textureUris.Add("BaseColorTex", ExportUtils.kBuiltInPrefix + "whiteTextureMap.png");
                colorParams.Add("UvAdjust", new Color(1.0f, 1.0f, 0.0f, 0.0f));
            }

            // Check for secondary light map texture.
            bool      hasLightMap = false;
            Texture2D lightMapTex = null;

            if (mat.HasProperty("_DetailAlbedoMap"))
            {
                lightMapTex = (Texture2D)mat.GetTexture("_DetailAlbedoMap");
            }
            if (lightMapTex == null && mat.HasProperty("_LightMap"))
            {
                lightMapTex = (Texture2D)mat.GetTexture("_LightMap");
            }
            if (lightMapTex)
            {
                hasLightMap = true;
                string texturePath = ExportUtils.GetTexturePath(lightMapTex);
                textureUris.Add("LightMapTex", ExportUtils.kTempPrefix + texturePath);
            }
            else
            {
                textureUris.Add("LightMapTex", ExportUtils.kBuiltInPrefix + "whiteTextureMap.png");
            }

            // As much as possible, this should mimic the way that GltfMaterialConverter.ConvertMaterial()
            // converts materials from gltf2 to UnityMaterial methods.
            if (mat.HasProperty("_Color"))
            {
                colorParams.Add("BaseColorFactor", mat.color);
            }
            else
            {
                colorParams.Add("BaseColorFactor", Color.black);
            }

            // Create the exportable material.
            BrushDescriptor exportDescriptor =
                hasLightMap ?
                Resources.Load <BrushDescriptor>("Brushes/Poly/Environments/EnvironmentDiffuseLightMap") :
                Resources.Load <BrushDescriptor>("Brushes/Poly/Environments/EnvironmentDiffuse");

            return(new DynamicExportableMaterial(
                       // Things that come from the template
                       blendMode: exportDescriptor.BlendMode,
                       vertexLayout: exportDescriptor.VertexLayout,
                       vertShaderUri: exportDescriptor.VertShaderUri,
                       fragShaderUri: exportDescriptor.FragShaderUri,
                       enableCull: exportDescriptor.EnableCull,
                       // Things that vary
                       durableName: data.renderer.gameObject.GetInstanceID().ToString(),
                       emissiveFactor: 0.0f,
                       // TODO: Should the export texture be the main pbr texture?
                       hasExportTexture: false,
                       exportTextureFilename: null,
                       uriBase: uriBase,
                       textureUris: textureUris,
                       textureSizes: textureSizes,
                       floatParams: floatParams,
                       vectorParams: vectorParams,
                       colorParams: colorParams));
        }
Exemple #4
0
        // Helper for GetModelMeshPayloads() -- this is the level 4 iteration.
        // Pass:
        //   mesh / meshMaterials - the Mesh to generate geometry for
        //   payload - the payload being exported
        //   prefab - the owner "prefab"
        //   meshIndex - the index of this Mesh within its owner "prefab"
        static IEnumerable <PrefabGeometry> GetPrefabGameObjMeshes(
            Mesh mesh, Material[] meshMaterials,
            SceneStatePayload payload, Model prefab, int meshIndex)
        {
            if (meshMaterials.Length != mesh.subMeshCount)
            {
                throw new ArgumentException("meshMaterials length");
            }
            string exportName = prefab.GetExportName();

            IExportableMaterial[] exportableMaterials = meshMaterials.Select(
                mat => prefab.GetExportableMaterial(mat)).ToArray();
            VertexLayout?[] layouts = exportableMaterials.Select(iem => iem?.VertexLayout).ToArray();
            // TODO(b/142396408)
            // Is it better to write color/texcoord that we don't need, just to satisfy the layout?
            // Or should we remove color/texcoord from the layout if the Mesh doesn't have them?
            // Right now we do the former. I think the fix should probably be in the collector;
            // it shouldn't return an exportableMaterial that is a superset of what the mesh contains.
            GeometryPool[] subMeshPools = GeometryPool.FromMesh(
                mesh, layouts, fallbackColor: Color.white, useFallbackTexcoord: true);

            for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
            {
                // See if this material is a Blocks material.
                Material mat = meshMaterials[subMeshIndex];

                // At import time, ImportMaterialCollector now ensures that _every_ UnityEngine.Material
                // imported also comes with an IExportableMaterial of some kind (BrushDescriptor for
                // Blocks/TB, and DynamicExportableMaterial for everything else). This lookup shouldn't fail.
                IExportableMaterial exportableMaterial = exportableMaterials[subMeshIndex];
                if (exportableMaterial == null)
                {
                    Debug.LogWarning($"Model {prefab.HumanName} has a non-exportable material {mat.name}");
                    continue;
                }

                GeometryPool geo = subMeshPools[subMeshIndex];
                // TODO(b/140634751): lingering sanity-checking; we can probably remove this for M24
                if (geo.Layout.bUseColors)
                {
                    Debug.Assert(geo.m_Colors.Count == geo.NumVerts);
                }

                // Important: This transform should only be applied once per prefab, since the pools are
                // shared among all the instances, and cannot include any mesh-local transformations.
                // If the pools share data (which is currently impossible), then additionally
                // the transform should only be applied once to each set of vertex data.
                ExportUtils.ConvertUnitsAndChangeBasis(geo, payload);

                if (payload.reverseWinding)
                {
                    // There are many ways of reversing winding; choose the one that matches Unity's fbx flip
                    ExportUtils.ReverseTriangleWinding(geo, 1, 2);
                }

                yield return(new PrefabGeometry
                {
                    model = prefab,
                    name = exportName + "_" + meshIndex + "_" + subMeshIndex,
                    pool = geo,
                    exportableMaterial = exportableMaterial,
                });
            }
        }
Exemple #5
0
        private static void BuildModelsAsModelMeshes(
            SceneStatePayload payload, IEnumerable <ModelWidget> modelWidgets)
        {
            // This is a quadruple-nested loop that's triply flattened into a single IEnumerable.
            // The structure of the data is is:
            // 1. There are 1+ Models (which I'll call "prefab" because Model is a loaded term here)
            // 2. Each "prefab" is used by 1+ ModelWidgets ("instance")
            // 3. Each ModelWidget, and the "prefab", contains 1+ GameObjects (which has a single Mesh)
            // 4. Each Mesh has 1+ unity submeshes (almost always exactly 1)
            //    These are converted to ModelMeshPayload
            //
            // The resulting ModelMeshPayloads are a mixture of data from all 4 levels, for example
            // - ModelMeshPayload.model comes from level 1
            // - ModelMeshPayload.modelId comes from level 2
            // - ModelMeshPayload.xform comes from level 3
            // - ModelMeshPayload.pool comes from level 4
            //
            // Orthogonal to this nesting, some data comes from the "prefab" and some from the "instance",
            // since we don't want to have to convert a mesh into GeometryPool once per instance.
            // So we iterate level 3 for the Model once, up front; then each ModelWidget level 3 is
            // a parallel iteration with that Model's pre-computed iteration.
            foreach (var group in modelWidgets.GroupBy(widget => widget.Model))
            {
                Model prefab = group.Key;
                // Each element represents a GameObject in the "prefab"
                List <PrefabGeometry[]> prefabObjs = GetPrefabGameObjs(payload, prefab).ToList();

                foreach ((ModelWidget widget, int widgetIndex) in group.WithIndex())
                {
                    Matrix4x4 widgetXform = ExportUtils.ChangeBasis(widget.transform, payload);
                    // Acts like our AsScene[], AsCanvas[] accessors
                    var AsWidget = new TransformExtensions.RelativeAccessor(widget.transform);

                    // Verify that it's okay to parallel-iterate over the prefab and instance gameObjs.
                    // It should be, unless one or the other has mucked with their m_MeshChildren.
                    MeshFilter[] instanceObjs = widget.GetMeshes();
                    if (prefabObjs.Count != instanceObjs.Length)
                    {
                        Debug.LogError($"Bad Model instance: {prefabObjs.Count} {instanceObjs.Length}");
                        // TODO: check order as well, somehow
                        continue;
                    }

                    // Parallel iterate, taking shared things (GeometryPool, material, name) from the
                    // "prefab" and xforms, unique ids, etc from the "instance".
                    for (int objIndex = 0; objIndex < prefabObjs.Count; ++objIndex)
                    {
                        PrefabGeometry[] prefabSubMeshes = prefabObjs[objIndex];
                        GameObject       instanceObj     = instanceObjs[objIndex].gameObject;
                        Matrix4x4        localXform      = ExportUtils.ChangeBasis(AsWidget[instanceObj.transform], payload);

                        int       objId = payload.idGenerator.GetIdFromInstanceId(instanceObj);
                        Matrix4x4 xform = ExportUtils.ChangeBasis(instanceObj.transform, payload);
                        foreach (PrefabGeometry prefabSubMesh in prefabSubMeshes)
                        {
                            payload.modelMeshes.Add(new ExportUtils.ModelMeshPayload(
                                                        payload.groupIdMapping.GetId(widget.Group))
                            {
                                // Copied from "prefab"
                                model              = prefabSubMesh.model,
                                geometry           = prefabSubMesh.pool,
                                exportableMaterial = prefabSubMesh.exportableMaterial,
                                geometryName       = prefabSubMesh.name,
                                // Unique to instance
                                legacyUniqueName = $"{prefabSubMesh.name}_i{objId}",
                                nodeName         = $"{prefabSubMesh.name}_{widgetIndex}",
                                parentXform      = widgetXform,
                                localXform       = localXform,
                                modelId          = widgetIndex,
                                xform            = xform,
                            });
                        }
                    }
                }
            }
        }
Exemple #6
0
        internal static FbxSurfaceMaterial CreateFbxMaterial(
            FbxExportGlobals G, string meshNamespace, IExportableMaterial exportableMaterial,
            HashSet <string> createdMaterialNames)
        {
            string materialName;

            if (exportableMaterial is BrushDescriptor)
            {
                // Toolkit uses this guid (in "N" format) to look up a BrushDescriptor.
                // See Toolkit's ModelImportSettings.GetDescriptorForStroke
                materialName = $"{exportableMaterial.UniqueName:N}_{meshNamespace}_{exportableMaterial.DurableName}";
            }
            else if (exportableMaterial is DynamicExportableMaterial dem)
            {
                // Comes from {fbx,obj,gltf,...} import from {Poly, Media Library}
                // This is a customized version of a BrushDescriptor -- almost certainly
                // Pbr{Blend,Opaque}{Double,Single}Sided with maybe an added texture and
                // some of its params customized.
                // TBT will merge the material created by Unity for this FbxMaterial with
                // the premade material it has for the parent guid.
                materialName = $"{dem.Parent.m_Guid:N}_{meshNamespace}_{dem.DurableName}";
            }
            else
            {
                Debug.LogWarning($"Unknown class {exportableMaterial.GetType().Name}");
                materialName = $"{meshNamespace}_{exportableMaterial.DurableName}";
            }
            // If only ExportFbx were a non-static class we could merge it with FbxExportGlobals
            materialName = ExportUtils.CreateUniqueName(materialName, createdMaterialNames);

            FbxSurfaceLambert material = FbxSurfaceLambert.Create(G.m_scene, materialName);

            material.Ambient.Set(new FbxDouble3(0, 0, 0));
            material.Diffuse.Set(new FbxDouble3(1.0, 1.0, 1.0));
            if (exportableMaterial.EmissiveFactor > 0)
            {
                material.EmissiveFactor.Set(exportableMaterial.EmissiveFactor);
                material.Emissive.Set(new FbxDouble3(1.0, 1.0, 1.0));
            }
            if (exportableMaterial.BlendMode != ExportableMaterialBlendMode.None)
            {
                var blendMode = FbxProperty.Create(material, Globals.FbxStringDT, "BlendMode");
                switch (exportableMaterial.BlendMode)
                {
                case ExportableMaterialBlendMode.AlphaMask:
                    blendMode.SetString(new FbxString("AlphaMask"));
                    material.TransparencyFactor.Set(0.2);
                    break;

                case ExportableMaterialBlendMode.AdditiveBlend:
                    blendMode.SetString(new FbxString("AdditiveBlend"));
                    break;
                }
            }

            // Export the texture
            if (exportableMaterial.HasExportTexture())
            {
                // This is not perfectly unique, but it is good enough for fbx export
                // better would be to use <durable>_<guid> but that's ugly, and nobody uses
                // the textures anyway, so... let's leave well enough alone for now.
                string albedoTextureName = exportableMaterial.DurableName;
                var    fullTextureDir    = Path.Combine(G.m_outputDir, kRelativeTextureDir);
                if (!Directory.Exists(fullTextureDir))
                {
                    if (!FileUtils.InitializeDirectoryWithUserError(fullTextureDir))
                    {
                        throw new IOException("Cannot write textures");
                    }
                }
                string   src             = exportableMaterial.GetExportTextureFilename();
                var      textureFileName = albedoTextureName + ".png";
                var      textureFilePath = Path.Combine(fullTextureDir, textureFileName);
                FileInfo srcInfo         = new FileInfo(src);
                if (srcInfo.Exists && !new FileInfo(textureFilePath).Exists)
                {
                    srcInfo.CopyTo(textureFilePath);
                }

                FbxFileTexture texture = FbxFileTexture.Create(G.m_scene, albedoTextureName + "_texture");
                texture.SetFileName(textureFilePath);
                texture.SetTextureUse(FbxTexture.ETextureUse.eStandard);
                texture.SetMappingType(FbxTexture.EMappingType.eUV);
                texture.SetMaterialUse(FbxFileTexture.EMaterialUse.eModelMaterial);
                texture.UVSet.Set(new FbxString("uv0"));
                material.Diffuse.ConnectSrcObject(texture);
                material.TransparentColor.ConnectSrcObject(texture);
            }
            else
            {
                foreach (var kvp in exportableMaterial.TextureUris)
                {
                    string parameterName = kvp.Key;
                    string textureUri    = kvp.Value;
                    if (ExportFileReference.IsHttp(textureUri))
                    {
                        // fbx can't deal with http references to textures
                        continue;
                    }
                    ExportFileReference fileRef = ExportFileReference.GetOrCreateSafeLocal(
                        G.m_disambiguationContext, textureUri, exportableMaterial.UriBase,
                        $"{meshNamespace}_{Path.GetFileName(textureUri)}");
                    AddTextureToMaterial(G, fileRef, material, parameterName);
                }
            }
            return(material);
        }