Exemplo n.º 1
0
        public void TextureNameUniqueness()
        {
            var data = new ExportingGltfData();
            var gltf = data.Gltf;

            gltf.asset.version = "2.0";
            gltf.textures.Add(new glTFTexture
            {
                name   = "FooBar",
                source = 0,
            });
            gltf.textures.Add(new glTFTexture
            {
                name   = "foobar",
                source = 1,
            });
            gltf.images.Add(new glTFImage
            {
                name = "HogeFuga",
            });
            gltf.images.Add(new glTFImage
            {
                name = "hogefuga",
            });

            var parser = new GlbLowLevelParser("Test", data.ToGlbBytes());

            using (var parsed = parser.Parse())
            {
                Assert.AreEqual("FooBar", parsed.GLTF.textures[0].name);
                // NOTE: 大文字小文字が違うだけの名前は、同一としてみなされ、Suffix が付く。
                Assert.AreEqual("foobar__UNIGLTF__DUPLICATED__2", parsed.GLTF.textures[1].name);
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// gltf に texture を足す
        ///
        /// * textures
        /// * samplers
        /// * images
        /// * bufferViews
        ///
        /// を更新し、textures の index を返す
        ///
        /// もっとも根本の Exporter クラスのみが呼び出すべきである。
        /// 他の拡張機能などが呼び出すべきではない。
        ///
        /// </summary>
        /// <returns>gltf texture index</returns>
        public static int PushGltfTexture(ExportingGltfData data, Texture2D texture, ColorSpace textureColorSpace, ITextureSerializer textureSerializer)
        {
            var bytesWithMime = textureSerializer.ExportBytesWithMime(texture, textureColorSpace);

            // add view
            var viewIndex = data.ExtendBufferAndGetViewIndex(bytesWithMime.bytes);

            // add image
            var imageIndex = data.GLTF.images.Count;

            data.GLTF.images.Add(new glTFImage
            {
                name       = TextureImportName.RemoveSuffix(texture.name),
                bufferView = viewIndex,
                mimeType   = bytesWithMime.mime,
            });

            // add sampler
            var samplerIndex = data.GLTF.samplers.Count;
            var sampler      = TextureSamplerUtil.Export(texture);

            data.GLTF.samplers.Add(sampler);

            // add texture
            var textureIndex = data.GLTF.textures.Count;

            data.GLTF.textures.Add(new glTFTexture
            {
                sampler = samplerIndex,
                source  = imageIndex,
            });

            return(textureIndex);
        }
Exemplo n.º 3
0
        protected override void ExportPath(string path)
        {
            var ext   = Path.GetExtension(path).ToLower();
            var isGlb = false;

            switch (ext)
            {
            case ".glb": isGlb = true; break;

            case ".gltf": isGlb = false; break;

            default: throw new System.Exception();
            }

            var progress = 0;

            EditorUtility.DisplayProgressBar("export gltf", path, progress);
            try
            {
                var data = new ExportingGltfData();
                using (var exporter = new gltfExporter(data, Settings, new EditorProgress()))
                {
                    exporter.Prepare(State.ExportRoot);
                    exporter.Export(new EditorTextureSerializer());
                }

                if (isGlb)
                {
                    var bytes = data.ToGlbBytes();
                    File.WriteAllBytes(path, bytes);
                }
                else
                {
                    var(json, buffer0) = data.ToGltf(path);

                    {
                        // write JSON without BOM
                        var encoding = new System.Text.UTF8Encoding(false);
                        File.WriteAllText(path, json, encoding);
                    }

                    {
                        // write to buffer0 local folder
                        var dir        = Path.GetDirectoryName(path);
                        var bufferPath = Path.Combine(dir, buffer0.uri);
                        File.WriteAllBytes(bufferPath, data.BinBytes.ToArray());
                    }
                }

                if (path.StartsWithUnityAssetPath())
                {
                    AssetDatabase.ImportAsset(path.ToUnityRelativePath());
                    AssetDatabase.Refresh();
                }
            }
            finally
            {
                EditorUtility.ClearProgressBar();
            }
        }
Exemplo n.º 4
0
        public void UniGLTFSimpleSceneTest()
        {
            var go = CreateSimpleScene();

            // export
            var data = new ExportingGltfData();

            string json = null;

            using (var exporter = new gltfExporter(data, new GltfExportSettings()))
            {
                exporter.Prepare(go);
                exporter.Export(new EditorTextureSerializer());

                // remove empty buffer
                data.GLTF.buffers.Clear();

                json = data.GLTF.ToJson();
            }

            // parse
            var parsed = GltfData.CreateFromExportForTest(data);

            // import
            using (var context = new ImporterContext(parsed))
                using (var loaded = context.Load())
                {
                    AssertAreEqual(go.transform, loaded.transform);
                }
        }
Exemplo n.º 5
0
 public gltfMorphTarget ToGltf(ExportingGltfData data, bool useNormal, bool useSparse)
 {
     return(BlendShapeExporter.Export(data,
                                      m_positions,
                                      useNormal ? m_normals : null,
                                      useSparse));
 }
Exemplo n.º 6
0
        static Byte[] Export(GameObject root)
        {
            var data = new ExportingGltfData();

            using (var exporter = new gltfExporter(data, new GltfExportSettings()))
            {
                exporter.Prepare(root);
                exporter.Export(new EditorTextureSerializer());
            }
            return(data.ToGlbBytes());
        }
Exemplo n.º 7
0
        public void DividedVertexBufferTest()
        {
            var data = new ExportingGltfData(50 * 1024 * 1024);

            var Materials = new List <Material> {
                new Material(Shader.Find("Standard")), // A
                new Material(Shader.Find("Standard")), // B
            };

            var(go, mesh) = CreateMesh(Materials.ToArray());
            var meshExportSettings = new GltfExportSettings
            {
                DivideVertexBuffer = true
            };
            var axisInverter = Axes.X.Create();

            var unityMesh = MeshExportList.Create(go);

            var(gltfMesh, blendShapeIndexMap) = meshExportSettings.DivideVertexBuffer
                ? MeshExporter_DividedVertexBuffer.Export(data, unityMesh, Materials, axisInverter, meshExportSettings)
                : MeshExporter_SharedVertexBuffer.Export(data, unityMesh, Materials, axisInverter, meshExportSettings)
            ;

            var parsed = GltfData.CreateFromGltfDataForTest(data.GLTF, data.BinBytes);
            {
                var indices = parsed.GetIndices(gltfMesh.primitives[0].indices);
                Assert.AreEqual(0, indices[0]);
                Assert.AreEqual(1, indices[1]);
                Assert.AreEqual(3, indices[2]);
                Assert.AreEqual(3, indices[3]);
                Assert.AreEqual(1, indices[4]);
                Assert.AreEqual(2, indices[5]);
            }
            {
                var positions = parsed.GetArrayFromAccessor <Vector3>(gltfMesh.primitives[0].attributes.POSITION);
                Assert.AreEqual(4, positions.Length);
            }

            {
                var indices = parsed.GetIndices(gltfMesh.primitives[1].indices);
                Assert.AreEqual(0, indices[0]);
                Assert.AreEqual(1, indices[1]);
                Assert.AreEqual(3, indices[2]);
                Assert.AreEqual(3, indices[3]);
                Assert.AreEqual(1, indices[4]);
                Assert.AreEqual(2, indices[5]);
            }
            {
                var positions = parsed.GetArrayFromAccessor <Vector3>(gltfMesh.primitives[1].attributes.POSITION);
                Assert.AreEqual(4, positions.Length);
            }
        }
        static gltfMorphTarget ExportMorphTarget(ExportingGltfData data,
                                                 Mesh mesh, int blendShapeIndex,
                                                 bool useSparseAccessorForMorphTarget,
                                                 bool exportOnlyBlendShapePosition,
                                                 IAxisInverter axisInverter)
        {
            var blendShapeVertices = mesh.vertices;
            var usePosition        = blendShapeVertices != null && blendShapeVertices.Length > 0;

            var blendShapeNormals = mesh.normals;
            var useNormal         = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length;
            // var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length && !exportOnlyBlendShapePosition;

            // var blendShapeTangents = mesh.tangents.Select(y => (Vector3)y).ToArray();
            // //var useTangent = usePosition && blendShapeTangents != null && blendShapeTangents.Length == blendShapeVertices.Length;
            // var useTangent = false;

            var frameCount = mesh.GetBlendShapeFrameCount(blendShapeIndex);

            mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameCount - 1, blendShapeVertices, blendShapeNormals, null);

            //
            // invert axis
            //
            for (int i = 0; i < blendShapeVertices.Length; ++i)
            {
                blendShapeVertices[i] = axisInverter.InvertVector3(blendShapeVertices[i]);
            }
            for (int i = 0; i < blendShapeNormals.Length; ++i)
            {
                blendShapeNormals[i] = axisInverter.InvertVector3(blendShapeNormals[i]);
            }

            var positions = mesh.vertices;

            for (int i = 0; i < positions.Length; ++i)
            {
                positions[i] = axisInverter.InvertVector3(positions[i]);
            }

            var normals = mesh.normals;

            for (int i = 0; i < normals.Length; ++i)
            {
                normals[i] = axisInverter.InvertVector3(normals[i]);
            }

            return(BlendShapeExporter.Export(data,
                                             blendShapeVertices,
                                             exportOnlyBlendShapePosition && useNormal ? null : blendShapeNormals,
                                             useSparseAccessorForMorphTarget));
        }
Exemplo n.º 9
0
        int AddViewTo(ExportingGltfData data, int bufferIndex,
                      int offset = 0, int count = 0)
        {
            var stride = Stride;

            if (count == 0)
            {
                count = Count;
            }
            var slice = Bytes.GetSubArray(offset * stride, count * stride);

            return(data.AppendToBuffer(slice));
        }
Exemplo n.º 10
0
            public glTFPrimitives ToGltfPrimitive(ExportingGltfData data, int materialIndex, IEnumerable <int> indices)
            {
                var indicesAccessorIndex  = data.ExtendBufferAndGetAccessorIndex(indices.Select(x => (uint)m_vertexIndexMap[x]).ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
                var positions             = m_positions.ToArray();
                var positionAccessorIndex = data.ExtendBufferAndGetAccessorIndex(positions, glBufferTarget.ARRAY_BUFFER);

                AccessorsBounds.UpdatePositionAccessorsBounds(data.Gltf.accessors[positionAccessorIndex], positions);

                var normals             = m_normals.ToArray();
                var normalAccessorIndex = data.ExtendBufferAndGetAccessorIndex(normals, glBufferTarget.ARRAY_BUFFER);
                var uvAccessorIndex0    = data.ExtendBufferAndGetAccessorIndex(m_uv.ToArray(), glBufferTarget.ARRAY_BUFFER);

                int?jointsAccessorIndex = default;

                if (m_joints != null)
                {
                    jointsAccessorIndex = data.ExtendBufferAndGetAccessorIndex(m_joints.ToArray(), glBufferTarget.ARRAY_BUFFER);
                }
                int?weightAccessorIndex = default;

                if (m_weights != null)
                {
                    weightAccessorIndex = data.ExtendBufferAndGetAccessorIndex(m_weights.ToArray(), glBufferTarget.ARRAY_BUFFER);
                }

                int?vertexColorIndex = default;

                if (m_color.Count == m_positions.Count)
                {
                    vertexColorIndex = data.ExtendBufferAndGetAccessorIndex(m_color.ToArray(), glBufferTarget.ARRAY_BUFFER);
                }

                var primitive = new glTFPrimitives
                {
                    indices    = indicesAccessorIndex,
                    attributes = new glTFAttributes
                    {
                        POSITION   = positionAccessorIndex,
                        NORMAL     = normalAccessorIndex,
                        TEXCOORD_0 = uvAccessorIndex0,
                        JOINTS_0   = jointsAccessorIndex.GetValueOrDefault(-1),
                        WEIGHTS_0  = weightAccessorIndex.GetValueOrDefault(-1),
                        COLOR_0    = vertexColorIndex.GetValueOrDefault(-1),
                    },
                    material = materialIndex,
                    mode     = 4,
                };

                return(primitive);
            }
Exemplo n.º 11
0
        int AddAccessorTo(ExportingGltfData data, int viewIndex,
                          Action <NativeArray <byte>, glTFAccessor> minMax = null,
                          int offset = 0, int count = 0)
        {
            var gltf          = data.Gltf;
            var accessorIndex = gltf.accessors.Count;
            var accessor      = CreateGltfAccessor(viewIndex, count, offset * Stride);

            if (minMax != null)
            {
                minMax(Bytes, accessor);
            }
            gltf.accessors.Add(accessor);
            return(accessorIndex);
        }
Exemplo n.º 12
0
        public void MeshHasNoRendererTest()
        {
            var go = new GameObject("mesh_has_no_renderer");

            try
            {
                {
                    var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                    cube.transform.SetParent(go.transform);
                    UnityEngine.Object.DestroyImmediate(cube.GetComponent <MeshRenderer>());
                }

                // export
                var    data = new ExportingGltfData();
                var    gltf = data.GLTF;
                string json;
                using (var exporter = new gltfExporter(data, new GltfExportSettings()))
                {
                    exporter.Prepare(go);
                    exporter.Export(new EditorTextureSerializer());

                    json = gltf.ToJson();
                }

                Assert.AreEqual(0, gltf.meshes.Count);
                Assert.AreEqual(1, gltf.nodes.Count);
                Assert.AreEqual(-1, gltf.nodes[0].mesh);

                // import
                {
                    var parsed = GltfData.CreateFromExportForTest(data);
                    using (var context = new ImporterContext(parsed))
                        using (var loaded = context.Load())
                        {
                            Assert.AreEqual(1, loaded.transform.GetChildren().Count());
                            {
                                var child = loaded.transform.GetChild(0);
                                Assert.IsNull(child.GetSharedMesh());
                            }
                        }
                }
            }
            finally
            {
                GameObject.DestroyImmediate(go);
            }
        }
        private static Texture2D AssignTextureToMaterialPropertyAndExportAndExtract(Texture2D srcTex, string srcImageName, string propertyName)
        {
            // Prepare
            var root = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var mat  = new Material(Shader.Find("Standard"));

            mat.SetTexture(propertyName, srcTex);
            root.GetComponent <MeshRenderer>().sharedMaterial = mat;

            // Export glTF
            var data = new ExportingGltfData();

            using (var exporter = new gltfExporter(data, new GltfExportSettings
            {
                InverseAxis = Axes.X,
                ExportOnlyBlendShapePosition = false,
                UseSparseAccessorForMorphTarget = false,
                DivideVertexBuffer = false,
            }))
            {
                exporter.Prepare(root);
                exporter.Export(new EditorTextureSerializer());
            }
            var gltf = data.GLTF;

            Assert.AreEqual(1, gltf.images.Count);
            var exportedImage = gltf.images[0];

            Assert.AreEqual("image/png", exportedImage.mimeType);
            Assert.AreEqual(srcImageName, exportedImage.name);

            UnityEngine.Object.DestroyImmediate(mat);
            UnityEngine.Object.DestroyImmediate(root);

            var parsed = GltfData.CreateFromGltfDataForTest(gltf, data.BinBytes);

            // Extract Image to Texture2D
            var exportedBytes   = parsed.GetBytesFromBufferView(exportedImage.bufferView).ToArray();
            var exportedTexture = new Texture2D(2, 2, TextureFormat.ARGB32, mipChain: false, linear: false);

            Assert.IsTrue(exportedTexture.LoadImage(exportedBytes)); // Always true ?
            Assert.AreEqual(srcTex.width, exportedTexture.width);
            Assert.AreEqual(srcTex.height, exportedTexture.height);

            return(exportedTexture);
        }
Exemplo n.º 14
0
        public void Export(ExportingGltfData _data, GameObject Copy, List <Transform> Nodes)
        {
            var clips = GetAnimationClips(Copy);

            foreach (AnimationClip clip in clips)
            {
                var animationWithCurve = AnimationExporter.Export(clip, Copy.transform, Nodes);

                foreach (var kv in animationWithCurve.SamplerMap)
                {
                    var sampler = animationWithCurve.Animation.samplers[kv.Key];

                    var inputAccessorIndex = _data.ExtendBufferAndGetAccessorIndex(kv.Value.Input);
                    sampler.input = inputAccessorIndex;

                    var outputAccessorIndex = _data.ExtendBufferAndGetAccessorIndex(kv.Value.Output);
                    sampler.output = outputAccessorIndex;

                    // modify accessors
                    var outputAccessor = _data.Gltf.accessors[outputAccessorIndex];
                    var channel        = animationWithCurve.Animation.channels.First(x => x.sampler == kv.Key);
                    switch (glTFAnimationTarget.GetElementCount(channel.target.path))
                    {
                    case 1:
                        outputAccessor.type = "SCALAR";
                        //outputAccessor.count = ;
                        break;

                    case 3:
                        outputAccessor.type   = "VEC3";
                        outputAccessor.count /= 3;
                        break;

                    case 4:
                        outputAccessor.type   = "VEC4";
                        outputAccessor.count /= 4;
                        break;

                    default:
                        throw new NotImplementedException();
                    }
                }
                animationWithCurve.Animation.name = clip.name;
                _data.Gltf.animations.Add(animationWithCurve.Animation);
            }
        }
Exemplo n.º 15
0
        public gltfExporter(ExportingGltfData data, GltfExportSettings settings, IProgress <ExportProgress> progress = null)
        {
            _data = data;

            _gltf.extensionsUsed.AddRange(ExtensionUsed);

            _gltf.asset = new glTFAssets
            {
                generator = "UniGLTF-" + UniGLTFVersion.VERSION,
                version   = "2.0",
            };

            m_settings = settings;
            if (m_settings == null)
            {
                // default
                m_settings = new GltfExportSettings();
            }
        }
Exemplo n.º 16
0
        void BufferTest(int init, params int[] size)
        {
            var initBytes = init == 0 ? null : new byte[init];
            var storage   = new ArrayByteBuffer(initBytes);
            var data      = new ExportingGltfData();
            // var buffer = new glTFBuffer(storage);

            var values = new List <byte>();
            int offset = 0;

            foreach (var x in size)
            {
                var nums = Enumerable.Range(offset, x).Select(y => (Byte)y).ToArray();
                values.AddRange(nums);
                var bytes = new ArraySegment <Byte>(nums);
                offset += x;
                data.ExtendBufferAndGetView(bytes, glBufferTarget.NONE);
            }

            Assert.AreEqual(values.Count, data.GLTF.buffers[0].byteLength);
            Assert.True(Enumerable.SequenceEqual(values, data.BinBytes.ToArray()));
        }
Exemplo n.º 17
0
        public void ExportingNullMeshTest()
        {
            var validator = ScriptableObject.CreateInstance <MeshExportValidator>();
            var root      = new GameObject("root");

            try
            {
                {
                    var child = GameObject.CreatePrimitive(PrimitiveType.Cube);
                    child.transform.SetParent(root.transform);
                    // remove MeshFilter
                    Component.DestroyImmediate(child.GetComponent <MeshFilter>());
                }

                {
                    var child = GameObject.CreatePrimitive(PrimitiveType.Cube);
                    child.transform.SetParent(root.transform);
                    // set null
                    child.GetComponent <MeshFilter>().sharedMesh = null;
                }

                // validate
                validator.SetRoot(root, new GltfExportSettings(), new DefualtBlendShapeExportFilter());
                var vs = validator.Validate(root);
                Assert.True(vs.All(x => x.CanExport));

                // export
                var    data = new ExportingGltfData();
                var    gltf = data.GLTF;
                string json;
                using (var exporter = new gltfExporter(data, new GltfExportSettings()))
                {
                    exporter.Prepare(root);
                    exporter.Export(new EditorTextureSerializer());

                    json = gltf.ToJson();
                }

                Assert.AreEqual(0, gltf.meshes.Count);
                Assert.AreEqual(2, gltf.nodes.Count);
                Assert.AreEqual(-1, gltf.nodes[0].mesh);
                Assert.AreEqual(-1, gltf.nodes[1].mesh);

                // import
                {
                    var parsed = GltfData.CreateFromExportForTest(data);
                    using (var context = new ImporterContext(parsed))
                        using (var loaded = context.Load())
                        {
                            Assert.AreEqual(2, loaded.transform.GetChildren().Count());

                            {
                                var child = loaded.transform.GetChild(0);
                                Assert.IsNull(child.GetSharedMesh());
                            }

                            {
                                var child = loaded.transform.GetChild(1);
                                Assert.IsNull(child.GetSharedMesh());
                            }
                        }
                }
            }
            finally
            {
                GameObject.DestroyImmediate(root);
                ScriptableObject.DestroyImmediate(validator);
            }
        }
Exemplo n.º 18
0
        public void SameMeshButDifferentMaterialExport()
        {
            var go = new GameObject("same_mesh");

            try
            {
                var shader = Shader.Find("Unlit/Color");

                var cubeA = GameObject.CreatePrimitive(PrimitiveType.Cube);
                {
                    cubeA.transform.SetParent(go.transform);
                    var material = new Material(shader);
                    material.name  = "red";
                    material.color = Color.red;
                    cubeA.GetComponent <Renderer>().sharedMaterial = material;
                }

                {
                    var cubeB = GameObject.Instantiate(cubeA);
                    cubeB.transform.SetParent(go.transform);
                    var material = new Material(shader);
                    material.color = Color.blue;
                    material.name  = "blue";
                    cubeB.GetComponent <Renderer>().sharedMaterial = material;

                    Assert.AreEqual(cubeB.GetComponent <MeshFilter>().sharedMesh, cubeA.GetComponent <MeshFilter>().sharedMesh);
                }

                // export
                var data = new ExportingGltfData();
                var gltf = data.GLTF;
                var json = default(string);
                using (var exporter = new gltfExporter(data, new GltfExportSettings()))
                {
                    exporter.Prepare(go);
                    exporter.Export(new EditorTextureSerializer());

                    json = gltf.ToJson();
                }

                Assert.AreEqual(2, gltf.meshes.Count);

                var red = gltf.materials[gltf.meshes[0].primitives[0].material];
                Assert.AreEqual(new float[] { 1, 0, 0, 1 }, red.pbrMetallicRoughness.baseColorFactor);

                var blue = gltf.materials[gltf.meshes[1].primitives[0].material];
                Assert.AreEqual(new float[] { 0, 0, 1, 1 }, blue.pbrMetallicRoughness.baseColorFactor);

                Assert.AreEqual(2, gltf.nodes.Count);

                Assert.AreNotEqual(gltf.nodes[0].mesh, gltf.nodes[1].mesh);

                // import
                {
                    var parsed = GltfData.CreateFromExportForTest(data);
                    using (var context = new ImporterContext(parsed))
                        using (var loaded = context.Load())
                        {
                            var importedRed         = loaded.transform.GetChild(0);
                            var importedRedMaterial = importedRed.GetComponent <Renderer>().sharedMaterial;
                            Assert.AreEqual("red", importedRedMaterial.name);
                            Assert.AreEqual(Color.red, importedRedMaterial.color);

                            var importedBlue         = loaded.transform.GetChild(1);
                            var importedBlueMaterial = importedBlue.GetComponent <Renderer>().sharedMaterial;
                            Assert.AreEqual("blue", importedBlueMaterial.name);
                            Assert.AreEqual(Color.blue, importedBlueMaterial.color);
                        }
                }

                // import new version
                {
                    var parsed = GltfData.CreateFromExportForTest(data);
                    using (var context = new ImporterContext(parsed))
                        using (var loaded = context.Load())
                        {
                            var importedRed         = loaded.transform.GetChild(0);
                            var importedRedMaterial = importedRed.GetComponent <Renderer>().sharedMaterial;
                            Assert.AreEqual("red", importedRedMaterial.name);
                            Assert.AreEqual(Color.red, importedRedMaterial.color);

                            var importedBlue         = loaded.transform.GetChild(1);
                            var importedBlueMaterial = importedBlue.GetComponent <Renderer>().sharedMaterial;
                            Assert.AreEqual("blue", importedBlueMaterial.name);
                            Assert.AreEqual(Color.blue, importedBlueMaterial.color);
                        }
                }
            }
            finally
            {
                GameObject.DestroyImmediate(go);
            }
        }
Exemplo n.º 19
0
        public void GlTFToJsonTest()
        {
            var data = new ExportingGltfData();

            using (var exporter = new gltfExporter(data, new GltfExportSettings()))
            {
                exporter.Prepare(CreateSimpleScene());
                exporter.Export(new EditorTextureSerializer());
            }

            var expected = data.GLTF.ToJson().ParseAsJson();

            expected.AddKey(Utf8String.From("meshes"));
            expected.AddValue(default(ArraySegment <byte>), ValueNodeType.Array);
            expected["meshes"].AddValue(default(ArraySegment <byte>), ValueNodeType.Object);

            var mesh = expected["meshes"][0];

            mesh.AddKey(Utf8String.From("name"));
            mesh.AddValue(Utf8String.From(JsonString.Quote("test")).Bytes, ValueNodeType.String);
            mesh.AddKey(Utf8String.From("primitives"));
            mesh.AddValue(default(ArraySegment <byte>), ValueNodeType.Array);
            mesh["primitives"].AddValue(default(ArraySegment <byte>), ValueNodeType.Object);

            var primitive = mesh["primitives"][0];

            primitive.AddKey(Utf8String.From("mode"));
            primitive.AddValue(Utf8String.From("0").Bytes, ValueNodeType.Integer);
            primitive.AddKey(Utf8String.From("indices"));
            primitive.AddValue(Utf8String.From("0").Bytes, ValueNodeType.Integer);
            primitive.AddKey(Utf8String.From("material"));
            primitive.AddValue(Utf8String.From("0").Bytes, ValueNodeType.Integer);
            primitive.AddKey(Utf8String.From("attributes"));
            primitive.AddValue(default(ArraySegment <byte>), ValueNodeType.Object);
            primitive["attributes"].AddKey(Utf8String.From("POSITION"));
            primitive["attributes"].AddValue(Utf8String.From("0").Bytes, ValueNodeType.Integer);
            primitive.AddKey(Utf8String.From("targets"));
            primitive.AddValue(default(ArraySegment <byte>), ValueNodeType.Array);
            primitive["targets"].AddValue(default(ArraySegment <byte>), ValueNodeType.Object);
            primitive["targets"][0].AddKey(Utf8String.From("POSITION"));
            primitive["targets"][0].AddValue(Utf8String.From("1").Bytes, ValueNodeType.Integer);
            primitive["targets"].AddValue(default(ArraySegment <byte>), ValueNodeType.Object);
            primitive["targets"][1].AddKey(Utf8String.From("POSITION"));
            primitive["targets"][1].AddValue(Utf8String.From("2").Bytes, ValueNodeType.Integer);
            primitive["targets"][1].AddKey(Utf8String.From("TANGENT"));
            primitive["targets"][1].AddValue(Utf8String.From("0").Bytes, ValueNodeType.Integer);

            data.GLTF.meshes.Add(new glTFMesh("test")
            {
                primitives = new List <glTFPrimitives>
                {
                    new glTFPrimitives
                    {
                        indices    = 0,
                        attributes = new glTFAttributes
                        {
                            POSITION = 0,
                            TANGENT  = -1 // should be removed
                        },
                        targets = new List <gltfMorphTarget>
                        {
                            new gltfMorphTarget
                            {
                                POSITION = 1,
                                TANGENT  = -1 // should be removed
                            },
                            new gltfMorphTarget
                            {
                                POSITION = 2,
                                TANGENT  = 0
                            }
                        }
                    }
                }
            });
            var actual = data.GLTF.ToJson().ParseAsJson();

            Assert.AreEqual(expected, actual);
        }
Exemplo n.º 20
0
        public int AddAccessorTo(ExportingGltfData data, int bufferIndex,
                                 // GltfBufferTargetType targetType,
                                 bool useSparse,
                                 Action <NativeArray <byte>, glTFAccessor> minMax = null,
                                 int offset = 0, int count = 0)
        {
            if (ComponentType == AccessorValueType.FLOAT &&
                AccessorType == AccessorVectorType.VEC3
                )
            {
                var values = GetSpan <Vector3>();
                // 巨大ポリゴンのモデル対策にValueTupleの型をushort -> uint へ
                var sparseValuesWithIndex = new List <ValueTuple <int, Vector3> >();
                for (int i = 0; i < values.Length; ++i)
                {
                    var v = values[i];
                    if (v != Vector3.zero)
                    {
                        sparseValuesWithIndex.Add((i, v));
                    }
                }

                //var status = $"{sparseIndices.Count * 14}/{values.Length * 12}";
                if (useSparse &&
                    sparseValuesWithIndex.Count > 0 && // avoid empty sparse
                    sparseValuesWithIndex.Count * 16 < values.Length * 12)
                {
                    // use sparse
                    using (var sparseIndexBin = new NativeArray <byte>(sparseValuesWithIndex.Count * 4, Allocator.Persistent))
                        using (var sparseValueBin = new NativeArray <byte>(sparseValuesWithIndex.Count * 12, Allocator.Persistent))
                        {
                            var sparseIndexSpan = sparseIndexBin.Reinterpret <Int32>(1);
                            var sparseValueSpan = sparseValueBin.Reinterpret <Vector3>(1);

                            for (int i = 0; i < sparseValuesWithIndex.Count; ++i)
                            {
                                var(index, value)  = sparseValuesWithIndex[i];
                                sparseIndexSpan[i] = index;
                                sparseValueSpan[i] = value;
                            }

                            var sparseIndexView = data.AppendToBuffer(sparseIndexBin);
                            var sparseValueView = data.AppendToBuffer(sparseValueBin);

                            var accessorIndex = data.Gltf.accessors.Count;
                            var accessor      = new glTFAccessor
                            {
                                componentType = (glComponentType)ComponentType,
                                type          = AccessorType.ToString(),
                                count         = Count,
                                byteOffset    = -1,
                                sparse        = new glTFSparse
                                {
                                    count   = sparseValuesWithIndex.Count,
                                    indices = new glTFSparseIndices
                                    {
                                        componentType = (glComponentType)AccessorValueType.UNSIGNED_INT,
                                        bufferView    = sparseIndexView,
                                    },
                                    values = new glTFSparseValues
                                    {
                                        bufferView = sparseValueView,
                                    },
                                }
                            };
                            if (minMax != null)
                            {
                                minMax(sparseValueBin, accessor);
                            }
                            data.Gltf.accessors.Add(accessor);
                            return(accessorIndex);
                        }
                }
            }

            var viewIndex = AddViewTo(data, bufferIndex, offset, count);

            return(AddAccessorTo(data, viewIndex, minMax, 0, count));
        }
Exemplo n.º 21
0
        public static gltfMorphTarget Export(ExportingGltfData data, Vector3[] positions, Vector3[] normals, bool useSparse)
        {
            var accessorCount = positions.Length;

            if (normals != null && positions.Length != normals.Length)
            {
                throw new Exception();
            }

            int[] sparseIndices = default;
            if (useSparse)
            {
                sparseIndices = Enumerable.Range(0, positions.Length).Where(x => positions[x] != Vector3.zero).ToArray();
                if (sparseIndices.Length == 0)
                {
                    // sparse 対象がすべて [0, 0, 0] の場合
                    // new glTFSparse
                    // {
                    //     count = 0,
                    // }
                    // のようになる。
                    // たぶん、仕様的にはあり。
                    // 解釈できない場合あり。
                    useSparse = false;
                }
            }

            if (useSparse)
            {
                if (sparseIndices == null)
                {
                    throw new Exception();
                }

                // positions
                var positionAccessorIndex = -1;
                if (sparseIndices.Length > 0)
                {
                    var sparseIndicesViewIndex = data.ExtendBufferAndGetViewIndex(sparseIndices);
                    positionAccessorIndex = data.ExtendSparseBufferAndGetAccessorIndex(accessorCount,
                                                                                       sparseIndices.Select(x => positions[x]).ToArray(), sparseIndices, sparseIndicesViewIndex,
                                                                                       glBufferTarget.NONE);
                }

                // normals
                var normalAccessorIndex = -1;
                if (normals != null)
                {
                    var sparseNormalIndices = Enumerable.Range(0, positions.Length).Where(x => normals[x] != Vector3.zero).ToArray();
                    if (sparseNormalIndices.Length > 0)
                    {
                        var sparseNormalIndicesViewIndex = data.ExtendBufferAndGetViewIndex(sparseNormalIndices);
                        normalAccessorIndex = data.ExtendSparseBufferAndGetAccessorIndex(accessorCount,
                                                                                         sparseNormalIndices.Select(x => normals[x]).ToArray(), sparseNormalIndices, sparseNormalIndicesViewIndex,
                                                                                         glBufferTarget.NONE);
                    }
                }

                return(new gltfMorphTarget
                {
                    POSITION = positionAccessorIndex,
                    NORMAL = normalAccessorIndex,
                });
            }
            else
            {
                // position
                var positionAccessorIndex = data.ExtendBufferAndGetAccessorIndex(positions, glBufferTarget.ARRAY_BUFFER);
                data.GLTF.accessors[positionAccessorIndex].min = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Min(a.x, b.x), Math.Min(a.y, b.y), Mathf.Min(a.z, b.z))).ToArray();
                data.GLTF.accessors[positionAccessorIndex].max = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Max(a.x, b.x), Math.Max(a.y, b.y), Mathf.Max(a.z, b.z))).ToArray();

                // normal
                var normalAccessorIndex = -1;
                if (normals != null)
                {
                    normalAccessorIndex = data.ExtendBufferAndGetAccessorIndex(normals, glBufferTarget.ARRAY_BUFFER);
                }

                return(new gltfMorphTarget
                {
                    POSITION = positionAccessorIndex,
                    NORMAL = normalAccessorIndex,
                });
            }
        }
        /// <summary>
        /// Divide vertex buffer(Position, Normal, UV, VertexColor, Skinning and BlendShapes) by submesh usage, then export
        /// </summary>
        /// <param name="gltf"></param>
        /// <param name="gltfBuffer"></param>
        /// <param name="unityMesh"></param>
        /// <param name="unityMaterials"></param>
        /// <param name="axisInverter"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static (glTFMesh, Dictionary <int, int>) Export(ExportingGltfData data,
                                                               MeshExportInfo unityMesh, List <Material> unityMaterials,
                                                               IAxisInverter axisInverter, GltfExportSettings settings)
        {
            var mesh     = unityMesh.Mesh;
            var gltfMesh = new glTFMesh(mesh.name);

            if (settings.ExportTangents)
            {
                // no support
                throw new NotImplementedException();
            }

            var positions   = mesh.vertices;
            var normals     = mesh.normals;
            var uv          = mesh.uv;
            var boneWeights = mesh.boneWeights;

            if (boneWeights.All(x => x.weight0 == 0 && x.weight1 == 0 && x.weight2 == 0 && x.weight3 == 0))
            {
                boneWeights = null;
            }
            var colors = mesh.colors;

            Func <int, int> getJointIndex = null;

            if (boneWeights != null && boneWeights.Length == positions.Length)
            {
                getJointIndex = unityMesh.GetJointIndex;
            }

            Vector3[] blendShapePositions = new Vector3[mesh.vertexCount];
            Vector3[] blendShapeNormals   = new Vector3[mesh.vertexCount];

            var vColorState       = VertexColorUtility.DetectVertexColor(mesh, unityMaterials);
            var exportVertexColor = (
                (settings.KeepVertexColor && mesh.colors != null && mesh.colors.Length == mesh.vertexCount) || // vertex color を残す設定
                vColorState == VertexColorState.ExistsAndIsUsed || // VColor使っている
                vColorState == VertexColorState.ExistsAndMixed    // VColorを使っているところと使っていないところが混在(とりあえずExportする)
                );

            var usedIndices = new List <int>();

            for (int i = 0; i < mesh.subMeshCount; ++i)
            {
                var indices = mesh.GetIndices(i);
                var hash    = new HashSet <int>(indices);

                // aggregate vertex attributes
                var buffer = new MeshExportUtil.VertexBuffer(indices.Length, getJointIndex);
                usedIndices.Clear();
                for (int k = 0; k < positions.Length; ++k)
                {
                    if (hash.Contains(k))
                    {
                        // aggregate indices
                        usedIndices.Add(k);
                        buffer.PushVertex(k,
                                          axisInverter.InvertVector3(positions[k]), // POSITION
                                          axisInverter.InvertVector3(normals[k]),   // NORMAL
                                          uv[k].ReverseUV()                         // UV
                                          );
                        if (getJointIndex != null)
                        {
                            buffer.PushBoneWeight(boneWeights[k]);
                        }
                        if (exportVertexColor)
                        {
                            buffer.PushColor(colors[k]);
                        }
                    }
                }

                var material      = unityMesh.Materials[i];
                var materialIndex = -1;
                if (material != null)
                {
                    materialIndex = unityMaterials.IndexOf(material);
                }

                var flipped = new List <int>();
                for (int j = 0; j < indices.Length; j += 3)
                {
                    var t0 = indices[j];
                    var t1 = indices[j + 1];
                    var t2 = indices[j + 2];
                    flipped.Add(t2);
                    flipped.Add(t1);
                    flipped.Add(t0);
                }
                var gltfPrimitive = buffer.ToGltfPrimitive(data, materialIndex, flipped);

                // blendShape(morph target)
                for (int j = 0; j < mesh.blendShapeCount; ++j)
                {
                    var blendShape = new MeshExportUtil.BlendShapeBuffer(usedIndices.Count);

                    // aggriage morph target
                    mesh.GetBlendShapeFrameVertices(j, 0, blendShapePositions, blendShapeNormals, null);
                    int l = 0;
                    foreach (var k in usedIndices)
                    {
                        blendShape.Set(l++,
                                       axisInverter.InvertVector3(blendShapePositions[k]),
                                       axisInverter.InvertVector3(blendShapeNormals[k]));
                    }

                    gltfPrimitive.targets.Add(blendShape.ToGltf(data, !settings.ExportOnlyBlendShapePosition,
                                                                settings.UseSparseAccessorForMorphTarget));
                }

                gltfMesh.primitives.Add(gltfPrimitive);
            }

            var targetNames = Enumerable.Range(0, mesh.blendShapeCount).Select(x => mesh.GetBlendShapeName(x)).ToArray();

            gltf_mesh_extras_targetNames.Serialize(gltfMesh, targetNames);

            return(gltfMesh, Enumerable.Range(0, mesh.blendShapeCount).ToDictionary(x => x, x => x));
        }
Exemplo n.º 23
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="path"></param>
        /// <param name="settings"></param>
        /// <param name="destroy">作業が終わったらDestoryするべき一時オブジェクト</param>
        static byte[] Export(GameObject exportRoot, VRMMetaObject meta,
                             VRMExportSettings settings,
                             List <GameObject> destroy)
        {
            var target = exportRoot;

            // 常にコピーする。シーンを変化させない
            target = GameObject.Instantiate(target);
            destroy.Add(target);

            var metaBehaviour = target.GetComponent <VRMMeta>();

            if (metaBehaviour == null)
            {
                metaBehaviour      = target.AddComponent <VRMMeta>();
                metaBehaviour.Meta = meta;
            }
            if (metaBehaviour.Meta == null)
            {
                // 来ないはず
                throw new Exception("meta required");
            }

            {
                // copy元
                var animator         = exportRoot.GetComponent <Animator>();
                var beforeTransforms = exportRoot.GetComponentsInChildren <Transform>();
                // copy先
                var afterTransforms = target.GetComponentsInChildren <Transform>();
                // copy先のhumanoidBoneのリストを得る
                var bones           = (HumanBodyBones[])Enum.GetValues(typeof(HumanBodyBones));
                var humanTransforms = bones
                                      .Where(x => x != HumanBodyBones.LastBone)
                                      .Select(x => animator.GetBoneTransform(x))
                                      .Where(x => x != null)
                                      .Select(x => afterTransforms[Array.IndexOf(beforeTransforms, x)]) // copy 先を得る
                                      .ToArray();

                var nameCount = target.GetComponentsInChildren <Transform>()
                                .GroupBy(x => x.name)
                                .ToDictionary(x => x.Key, x => x.Count());
                foreach (var t in target.GetComponentsInChildren <Transform>())
                {
                    if (humanTransforms.Contains(t))
                    {
                        // keep original name
                        continue;
                    }

                    if (nameCount[t.name] > 1)
                    {
                        // 重複するボーン名をリネームする
                        ForceUniqueName(t, nameCount);
                    }
                }
            }

            // 正規化
            if (settings.PoseFreeze)
            {
                // BoneNormalizer.Execute は Copy を作って正規化する。UNDO無用
                target = VRMBoneNormalizer.Execute(target, settings.ForceTPose);
                destroy.Add(target);
            }

            var fp = target.GetComponent <VRMFirstPerson>();

            // 元のBlendShapeClipに変更を加えないように複製
            var proxy = target.GetComponent <VRMBlendShapeProxy>();

            if (proxy != null)
            {
                var copyBlendShapeAvatar = CopyBlendShapeAvatar(proxy.BlendShapeAvatar, settings.ReduceBlendshapeClip);
                proxy.BlendShapeAvatar = copyBlendShapeAvatar;

                // BlendShape削減
                if (settings.ReduceBlendshape)
                {
                    foreach (SkinnedMeshRenderer smr in target.GetComponentsInChildren <SkinnedMeshRenderer>())
                    {
                        // 未使用のBlendShapeを間引く
                        ReplaceMesh(target, smr, copyBlendShapeAvatar);
                    }
                }
            }

            // 出力
            var sw   = System.Diagnostics.Stopwatch.StartNew();
            var data = new UniGLTF.ExportingGltfData();

            using (var exporter = new VRMExporter(data, settings.MeshExportSettings))
            {
                exporter.Prepare(target);
                exporter.Export(new EditorTextureSerializer());
            }
            var bytes = data.ToGlbBytes();

            Debug.LogFormat("Export elapsed {0}", sw.Elapsed);
            return(bytes);
        }
Exemplo n.º 24
0
 public static GltfData CreateFromExportForTest(ExportingGltfData data)
 {
     return(CreateFromGltfDataForTest(data.Gltf, data.BinBytes));
 }
        /// <summary>
        /// primitive 間で vertex を共有する形で Export する。
        /// UniVRM-0.71.0 以降は、MeshExporterDivided.Export もある。
        ///
        /// * GLB/GLTF は shared(default) と divided を選択可能
        /// * VRM0 は shared 仕様
        /// * VRM1 は divided 仕様
        ///
        /// /// </summary>
        /// <param name="gltf"></param>
        /// <param name="bufferIndex"></param>
        /// <param name="unityMesh"></param>
        /// <param name="unityMaterials"></param>
        /// <param name="axisInverter"></param>
        /// <param name="settings"></param>
        /// <returns></returns>
        public static (glTFMesh, Dictionary <int, int> blendShapeIndexMap) Export(ExportingGltfData data,
                                                                                  MeshExportInfo unityMesh, List <Material> unityMaterials,
                                                                                  IAxisInverter axisInverter, GltfExportSettings settings)
        {
            var mesh                  = unityMesh.Mesh;
            var materials             = unityMesh.Materials;
            var positions             = mesh.vertices.Select(axisInverter.InvertVector3).ToArray();
            var positionAccessorIndex = data.ExtendBufferAndGetAccessorIndex(positions, glBufferTarget.ARRAY_BUFFER);

            data.GLTF.accessors[positionAccessorIndex].min = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Min(a.x, b.x), Math.Min(a.y, b.y), Mathf.Min(a.z, b.z))).ToArray();
            data.GLTF.accessors[positionAccessorIndex].max = positions.Aggregate(positions[0], (a, b) => new Vector3(Mathf.Max(a.x, b.x), Math.Max(a.y, b.y), Mathf.Max(a.z, b.z))).ToArray();

            var normalAccessorIndex = data.ExtendBufferAndGetAccessorIndex(mesh.normals.Select(y => axisInverter.InvertVector3(y.normalized)).ToArray(), glBufferTarget.ARRAY_BUFFER);

            int?tangentAccessorIndex = default;

            if (settings.ExportTangents)
            {
                tangentAccessorIndex = data.ExtendBufferAndGetAccessorIndex(mesh.tangents.Select(axisInverter.InvertVector4).ToArray(), glBufferTarget.ARRAY_BUFFER);
            }

            var uvAccessorIndex0 = data.ExtendBufferAndGetAccessorIndex(mesh.uv.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);
            var uvAccessorIndex1 = data.ExtendBufferAndGetAccessorIndex(mesh.uv2.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);

            var colorAccessorIndex = -1;

            var vColorState = VertexColorUtility.DetectVertexColor(mesh, materials);

            if (vColorState == VertexColorState.ExistsAndIsUsed || // VColor使っている
                vColorState == VertexColorState.ExistsAndMixed // VColorを使っているところと使っていないところが混在(とりあえずExportする)
                )
            {
                // UniUnlit で Multiply 設定になっている
                colorAccessorIndex = data.ExtendBufferAndGetAccessorIndex(mesh.colors, glBufferTarget.ARRAY_BUFFER);
            }

            var boneweights         = mesh.boneWeights;
            var weightAccessorIndex = data.ExtendBufferAndGetAccessorIndex(boneweights.Select(y => new Vector4(y.weight0, y.weight1, y.weight2, y.weight3)).ToArray(), glBufferTarget.ARRAY_BUFFER);
            var jointsAccessorIndex = data.ExtendBufferAndGetAccessorIndex(boneweights.Select(y =>
                                                                                              new UShort4(
                                                                                                  (ushort)unityMesh.GetJointIndex(y.boneIndex0),
                                                                                                  (ushort)unityMesh.GetJointIndex(y.boneIndex1),
                                                                                                  (ushort)unityMesh.GetJointIndex(y.boneIndex2),
                                                                                                  (ushort)unityMesh.GetJointIndex(y.boneIndex3))
                                                                                              ).ToArray(), glBufferTarget.ARRAY_BUFFER);

            var attributes = new glTFAttributes
            {
                POSITION = positionAccessorIndex,
            };

            if (normalAccessorIndex != -1)
            {
                attributes.NORMAL = normalAccessorIndex;
            }

            if (tangentAccessorIndex.HasValue)
            {
                attributes.TANGENT = tangentAccessorIndex.Value;
            }

            if (uvAccessorIndex0 != -1)
            {
                attributes.TEXCOORD_0 = uvAccessorIndex0;
            }
            if (uvAccessorIndex1 != -1)
            {
                attributes.TEXCOORD_1 = uvAccessorIndex1;
            }
            if (colorAccessorIndex != -1)
            {
                attributes.COLOR_0 = colorAccessorIndex;
            }
            if (weightAccessorIndex != -1)
            {
                attributes.WEIGHTS_0 = weightAccessorIndex;
            }
            if (jointsAccessorIndex != -1)
            {
                attributes.JOINTS_0 = jointsAccessorIndex;
            }

            var gltfMesh = new glTFMesh(mesh.name);
            var indices  = new List <uint>();

            for (int j = 0; j < mesh.subMeshCount; ++j)
            {
                indices.Clear();

                var triangles = mesh.GetIndices(j);
                if (triangles.Length == 0)
                {
                    // https://github.com/vrm-c/UniVRM/issues/664
                    continue;
                }

                for (int i = 0; i < triangles.Length; i += 3)
                {
                    var i0 = triangles[i];
                    var i1 = triangles[i + 1];
                    var i2 = triangles[i + 2];

                    // flip triangle
                    indices.Add((uint)i2);
                    indices.Add((uint)i1);
                    indices.Add((uint)i0);
                }

                var indicesAccessorIndex = data.ExtendBufferAndGetAccessorIndex(indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
                if (indicesAccessorIndex < 0)
                {
                    // https://github.com/vrm-c/UniVRM/issues/664
                    throw new Exception();
                }

                if (j >= materials.Length)
                {
                    Debug.LogWarningFormat("{0}.materials is not enough", unityMesh.Mesh.name);
                    break;
                }

                gltfMesh.primitives.Add(new glTFPrimitives
                {
                    attributes = attributes,
                    indices    = indicesAccessorIndex,
                    mode       = 4, // triangles ?
                    material   = unityMaterials.IndexOf(materials[j])
                });
            }

            var blendShapeIndexMap = new Dictionary <int, int>();

            {
                var targetNames = new List <string>();

                int exportBlendShapes = 0;
                for (int j = 0; j < unityMesh.Mesh.blendShapeCount; ++j)
                {
                    var morphTarget = ExportMorphTarget(data,
                                                        unityMesh.Mesh, j,
                                                        settings.UseSparseAccessorForMorphTarget,
                                                        settings.ExportOnlyBlendShapePosition, axisInverter);
                    if (morphTarget.POSITION < 0)
                    {
                        // Skip empty blendShape.
                        // Shift blendShape's index.
                        continue;
                    }

                    var blendShapeName = unityMesh.Mesh.GetBlendShapeName(j);
                    blendShapeIndexMap.Add(j, exportBlendShapes++);
                    targetNames.Add(blendShapeName);

                    //
                    // all primitive has same blendShape
                    //
                    for (int k = 0; k < gltfMesh.primitives.Count; ++k)
                    {
                        gltfMesh.primitives[k].targets.Add(morphTarget);
                    }
                }

                gltf_mesh_extras_targetNames.Serialize(gltfMesh, targetNames);
            }

            return(gltfMesh, blendShapeIndexMap);
        }