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); } }
/// <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); }
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(); } }
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); } }
public gltfMorphTarget ToGltf(ExportingGltfData data, bool useNormal, bool useSparse) { return(BlendShapeExporter.Export(data, m_positions, useNormal ? m_normals : null, useSparse)); }
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()); }
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)); }
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)); }
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); }
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); }
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); }
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); } }
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(); } }
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())); }
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); } }
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); } }
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); }
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)); }
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)); }
/// <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); }
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); }