using System; using System.Collections.Generic; using System.IO; using GLTF.Schema; using UnityEngine; using UnityGLTF.Extensions; namespace UnityGLTF { public class GLTFSceneExporter { private Transform[] _rootTransforms; private GLTFRoot _root; private BufferId _bufferId; private GLTF.Schema.Buffer _buffer; private BinaryWriter _bufferWriter; private List _images; private List _textures; private List _materials; protected struct PrimKey { public UnityEngine.Mesh Mesh; public UnityEngine.Material Material; } private readonly Dictionary _primOwner = new Dictionary(); private readonly Dictionary _meshToPrims = new Dictionary(); public bool ExportNames = true; /// /// Create a GLTFExporter that exports out a transform /// /// Root transform of object to export public GLTFSceneExporter(Transform[] rootTransforms) { _rootTransforms = rootTransforms; _root = new GLTFRoot{ Accessors = new List(), Asset = new Asset { Version = "2.0" }, Buffers = new List(), BufferViews = new List(), Images = new List(), Materials = new List(), Meshes = new List(), Nodes = new List(), Samplers = new List(), Scenes = new List(), Textures = new List(), }; _images = new List(); _materials = new List(); _textures = new List(); _buffer = new GLTF.Schema.Buffer(); _bufferId = new BufferId { Id = _root.Buffers.Count, Root = _root }; _root.Buffers.Add(_buffer); } /// /// Gets the root object of the exported GLTF /// /// Root parsed GLTF Json public GLTFRoot GetRoot() { return _root; } /// /// Specifies the path and filename for the GLTF Json and binary /// /// File path for saving the GLTF and binary files /// The name of the GLTF file public void SaveGLTFandBin(string path, string fileName) { var binFile = File.Create(Path.Combine(path, fileName + ".bin")); _bufferWriter = new BinaryWriter(binFile); _root.Scene = ExportScene(fileName, _rootTransforms); _buffer.Uri = fileName + ".bin"; _buffer.ByteLength = (int)_bufferWriter.BaseStream.Length; var gltfFile = File.CreateText(Path.Combine(path, fileName + ".gltf")); _root.Serialize(gltfFile); #if WINDOWS_UWP gltfFile.Dispose(); binFile.Dispose(); #else gltfFile.Close(); binFile.Close(); #endif foreach (var image in _images) { Debug.Log(image.name); var renderTexture = RenderTexture.GetTemporary(image.width, image.height); Graphics.Blit(image, renderTexture); RenderTexture.active = renderTexture; var exportTexture = new Texture2D(image.width, image.height); exportTexture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); exportTexture.Apply(); File.WriteAllBytes(Path.Combine(path, image.name + ".png"), exportTexture.EncodeToPNG()); } } private SceneId ExportScene(string name, Transform[] rootObjTransforms) { var scene = new Scene(); if (ExportNames) { scene.Name = name; } scene.Nodes = new List(rootObjTransforms.Length); foreach (var transform in rootObjTransforms) { scene.Nodes.Add(ExportNode(transform)); } _root.Scenes.Add(scene); return new SceneId { Id = _root.Scenes.Count - 1, Root = _root }; } private NodeId ExportNode(Transform nodeTransform) { var node = new Node(); if (ExportNames) { node.Name = nodeTransform.name; } node.SetUnityTransform(nodeTransform); var id = new NodeId { Id = _root.Nodes.Count, Root = _root }; _root.Nodes.Add(node); // children that are primitives get put in a mesh GameObject[] primitives, nonPrimitives; FilterPrimitives(nodeTransform, out primitives, out nonPrimitives); if (primitives.Length > 0) { node.Mesh = ExportMesh(nodeTransform.name, primitives); // associate unity meshes with gltf mesh id foreach (var prim in primitives) { var filter = prim.GetComponent(); var renderer = prim.GetComponent(); _primOwner[new PrimKey {Mesh = filter.sharedMesh, Material = renderer.sharedMaterial}] = node.Mesh; } } // children that are not primitives get added as child nodes if (nonPrimitives.Length > 0) { node.Children = new List(nonPrimitives.Length); foreach(var child in nonPrimitives) { node.Children.Add(ExportNode(child.transform)); } } return id; } private void FilterPrimitives(Transform transform, out GameObject[] primitives, out GameObject[] nonPrimitives) { var childCount = transform.childCount; var prims = new List(childCount+1); var nonPrims = new List(childCount); // add another primitive if the root object also has a mesh if (transform.gameObject.GetComponent() != null && transform.gameObject.GetComponent() != null) { prims.Add(transform.gameObject); } for (var i = 0; i < childCount; i++) { var go = transform.GetChild(i).gameObject; if (IsPrimitive(go)) prims.Add(go); else nonPrims.Add(go); } primitives = prims.ToArray(); nonPrimitives = nonPrims.ToArray(); } private static bool IsPrimitive(GameObject gameObject) { /* * Primitives have the following properties: * - have no children * - have no non-default local transform properties * - have MeshFilter and MeshRenderer components */ return gameObject.transform.childCount == 0 && gameObject.transform.localPosition == Vector3.zero && gameObject.transform.localRotation == Quaternion.identity && gameObject.transform.localScale == Vector3.one && gameObject.GetComponent() != null && gameObject.GetComponent() != null; } private MeshId ExportMesh(string name, GameObject[] primitives) { // check if this set of primitives is already a mesh MeshId existingMeshId = null; var key = new PrimKey(); foreach (var prim in primitives) { var filter = prim.GetComponent(); var renderer = prim.GetComponent(); key.Mesh = filter.sharedMesh; key.Material = renderer.sharedMaterial; MeshId tempMeshId; if (_primOwner.TryGetValue(key, out tempMeshId) && (existingMeshId == null || tempMeshId == existingMeshId)) { existingMeshId = tempMeshId; } else { existingMeshId = null; break; } } // if so, return that mesh id if(existingMeshId != null) return existingMeshId; // if not, create new mesh and return its id var mesh = new GLTF.Schema.Mesh(); if (ExportNames) { mesh.Name = name; } mesh.Primitives = new List(primitives.Length); foreach (var prim in primitives) { mesh.Primitives.AddRange(ExportPrimitive(prim)); } var id = new MeshId { Id = _root.Meshes.Count, Root = _root }; _root.Meshes.Add(mesh); return id; } // a mesh *might* decode to multiple prims if there are submeshes private MeshPrimitive[] ExportPrimitive(GameObject gameObject) { var filter = gameObject.GetComponent(); var meshObj = filter.sharedMesh; var renderer = gameObject.GetComponent(); var materialsObj = renderer.sharedMaterials; var prims = new MeshPrimitive[meshObj.subMeshCount]; // don't export any more accessors if this mesh is already exported MeshPrimitive[] primVariations; if (_meshToPrims.TryGetValue(meshObj, out primVariations) && meshObj.subMeshCount == primVariations.Length) { for (var i = 0; i < primVariations.Length; i++) { prims[i] = primVariations[i].Clone(); prims[i].Material = ExportMaterial(materialsObj[i]); } return prims; } AccessorId aPosition = null, aNormal = null, aTangent = null, aTexcoord0 = null, aTexcoord1 = null, aColor0 = null; aPosition = ExportAccessor(InvertZ(meshObj.vertices)); if (meshObj.normals.Length != 0) aNormal = ExportAccessor(InvertZ(meshObj.normals)); if (meshObj.tangents.Length != 0) aTangent = ExportAccessor(InvertW(meshObj.tangents)); if (meshObj.uv.Length != 0) aTexcoord0 = ExportAccessor(FlipY(meshObj.uv)); if (meshObj.uv2.Length != 0) aTexcoord1 = ExportAccessor(FlipY(meshObj.uv2)); if (meshObj.colors.Length != 0) aColor0 = ExportAccessor(meshObj.colors); MaterialId lastMaterialId = null; for (var submesh = 0; submesh < meshObj.subMeshCount; submesh++) { var primitive = new MeshPrimitive(); var triangles = meshObj.GetTriangles(submesh); primitive.Indices = ExportAccessor(FlipFaces(triangles), true); primitive.Attributes = new Dictionary(); primitive.Attributes.Add(SemanticProperties.POSITION, aPosition); if (aNormal != null) primitive.Attributes.Add(SemanticProperties.NORMAL, aNormal); if (aTangent != null) primitive.Attributes.Add(SemanticProperties.TANGENT, aTangent); if (aTexcoord0 != null) primitive.Attributes.Add(SemanticProperties.TexCoord(0), aTexcoord0); if (aTexcoord1 != null) primitive.Attributes.Add(SemanticProperties.TexCoord(1), aTexcoord1); if (aColor0 != null) primitive.Attributes.Add(SemanticProperties.Color(0), aColor0); if (submesh < materialsObj.Length) { primitive.Material = ExportMaterial(materialsObj[submesh]); lastMaterialId = primitive.Material; } else { primitive.Material = lastMaterialId; } prims[submesh] = primitive; } _meshToPrims[meshObj] = prims; return prims; } private MaterialId ExportMaterial(UnityEngine.Material materialObj) { MaterialId id = GetMaterialId(_root, materialObj); if (id != null) { return id; } var material = new GLTF.Schema.Material(); if (ExportNames) { material.Name = materialObj.name; } if (materialObj.HasProperty("_Cutoff")) { material.AlphaCutoff = materialObj.GetFloat("_Cutoff"); } switch (materialObj.GetTag("RenderType", false, "")) { case "TransparentCutout": material.AlphaMode = AlphaMode.MASK; break; case "Transparent": material.AlphaMode = AlphaMode.BLEND; break; default: material.AlphaMode = AlphaMode.OPAQUE; break; } material.DoubleSided = materialObj.HasProperty("_Cull") && materialObj.GetInt("_Cull") == (float)UnityEngine.Rendering.CullMode.Off; if (materialObj.HasProperty("_EmissionColor")) { material.EmissiveFactor = materialObj.GetColor("_EmissionColor").ToNumericsColor(); } if (materialObj.HasProperty("_EmissionMap")) { var emissionTex = materialObj.GetTexture("_EmissionMap"); if (emissionTex != null) { material.EmissiveTexture = ExportTextureInfo(emissionTex); ExportTextureTransform(material.EmissiveTexture, materialObj, "_EmissionMap"); } } if (materialObj.HasProperty("_BumpMap")) { var normalTex = materialObj.GetTexture("_BumpMap"); if (normalTex != null) { material.NormalTexture = ExportNormalTextureInfo(normalTex, materialObj); ExportTextureTransform(material.NormalTexture, materialObj, "_BumpMap"); } } if (materialObj.HasProperty("_OcclusionMap")) { var occTex = materialObj.GetTexture("_OcclusionMap"); if (occTex != null) { material.OcclusionTexture = ExportOcclusionTextureInfo(occTex, materialObj); ExportTextureTransform(material.OcclusionTexture, materialObj, "_OcclusionMap"); } } switch (materialObj.shader.name) { case "Standard": case "GLTF/GLTFStandard": material.PbrMetallicRoughness = ExportPBRMetallicRoughness(materialObj); break; case "GLTF/GLTFConstant": material.CommonConstant = ExportCommonConstant(materialObj); break; } _materials.Add(materialObj); id = new MaterialId { Id = _root.Materials.Count, Root = _root }; _root.Materials.Add(material); return id; } private void ExportTextureTransform(TextureInfo def, UnityEngine.Material mat, string texName) { Vector2 offset = mat.GetTextureOffset(texName); Vector2 scale = mat.GetTextureScale(texName); if (offset == Vector2.zero && scale == Vector2.one) return; if (_root.ExtensionsUsed == null) { _root.ExtensionsUsed = new List( new string[] { ExtTextureTransformExtensionFactory.EXTENSION_NAME } ); } else if (!_root.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME)) { _root.ExtensionsUsed.Add(ExtTextureTransformExtensionFactory.EXTENSION_NAME); } if (def.Extensions == null) def.Extensions = new Dictionary(); def.Extensions[ExtTextureTransformExtensionFactory.EXTENSION_NAME] = new ExtTextureTransformExtension( new GLTF.Math.Vector2(offset.x, -offset.y), new GLTF.Math.Vector2(scale.x, scale.y), 0 // TODO: support UV channels ); } private NormalTextureInfo ExportNormalTextureInfo(UnityEngine.Texture texture, UnityEngine.Material material) { var info = new NormalTextureInfo(); info.Index = ExportTexture(texture); if (material.HasProperty("_BumpScale")) { info.Scale = material.GetFloat("_BumpScale"); } return info; } private OcclusionTextureInfo ExportOcclusionTextureInfo(UnityEngine.Texture texture, UnityEngine.Material material) { var info = new OcclusionTextureInfo(); info.Index = ExportTexture(texture); if (material.HasProperty("_OcclusionStrength")) { info.Strength = material.GetFloat("_OcclusionStrength"); } return info; } private PbrMetallicRoughness ExportPBRMetallicRoughness(UnityEngine.Material material) { var pbr = new PbrMetallicRoughness(); if (material.HasProperty("_Color")) { pbr.BaseColorFactor = material.GetColor("_Color").ToNumericsColor(); } if (material.HasProperty("_MainTex")) { var mainTex = material.GetTexture("_MainTex"); if (mainTex != null) { pbr.BaseColorTexture = ExportTextureInfo(mainTex); ExportTextureTransform(pbr.BaseColorTexture, material, "_MainTex"); } } if (material.HasProperty("_Metallic")) { pbr.MetallicFactor = material.GetFloat("_Metallic"); } if (material.HasProperty("_Roughness")) { pbr.RoughnessFactor = material.GetFloat("_Roughness"); } else if (material.HasProperty("_Glossiness")) { pbr.RoughnessFactor = 1 - material.GetFloat("_Glossiness"); } if (material.HasProperty("_MetallicRoughnessMap")) { var mrTex = material.GetTexture("_MetallicRoughnessMap"); if (mrTex != null) { pbr.MetallicRoughnessTexture = ExportTextureInfo(mrTex); ExportTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicRoughnessMap"); } } else if (material.HasProperty("_MetallicGlossMap")) { var mgTex = material.GetTexture("_MetallicGlossMap"); if (mgTex != null) { pbr.MetallicRoughnessTexture = ExportTextureInfo(mgTex); ExportTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicGlossMap"); } } return pbr; } private MaterialCommonConstant ExportCommonConstant(UnityEngine.Material materialObj) { if (_root.ExtensionsUsed == null) { _root.ExtensionsUsed = new List(new string[] { "KHR_materials_common" }); } else if(!_root.ExtensionsUsed.Contains("KHR_materials_common")) _root.ExtensionsUsed.Add("KHR_materials_common"); var constant = new MaterialCommonConstant(); if (materialObj.HasProperty("_AmbientFactor")) { constant.AmbientFactor = materialObj.GetColor("_AmbientFactor").ToNumericsColor(); } if (materialObj.HasProperty("_LightMap")) { var lmTex = materialObj.GetTexture("_LightMap"); if (lmTex != null) { constant.LightmapTexture = ExportTextureInfo(lmTex); ExportTextureTransform(constant.LightmapTexture, materialObj, "_LightMap"); } } if (materialObj.HasProperty("_LightFactor")) { constant.LightmapFactor = materialObj.GetColor("_LightFactor").ToNumericsColor(); } return constant; } private TextureInfo ExportTextureInfo(UnityEngine.Texture texture) { var info = new TextureInfo(); info.Index = ExportTexture(texture); return info; } private TextureId ExportTexture(UnityEngine.Texture textureObj) { TextureId id = GetTextureId(_root, textureObj); if (id != null) { return id; } var texture = new GLTF.Schema.Texture(); //If texture name not set give it a unique name using count if (textureObj.name == "") { textureObj.name = (_root.Textures.Count + 1).ToString(); } if (ExportNames) { texture.Name = textureObj.name; } texture.Source = ExportImage(textureObj); texture.Sampler = ExportSampler(textureObj); _textures.Add(textureObj); id = new TextureId { Id = _root.Textures.Count, Root = _root }; _root.Textures.Add(texture); return id; } private ImageId ExportImage(UnityEngine.Texture texture) { ImageId id = GetImageId(_root, texture); if(id != null) { return id; } var image = new Image(); if (ExportNames) { image.Name = texture.name; } _images.Add(texture as Texture2D); image.Uri = Uri.EscapeUriString(texture.name + ".png"); id = new ImageId { Id = _root.Images.Count, Root = _root }; _root.Images.Add(image); return id; } private SamplerId ExportSampler(UnityEngine.Texture texture) { var samplerId = GetSamplerId(_root, texture); if (samplerId != null) return samplerId; var sampler = new Sampler(); if (texture.wrapMode == TextureWrapMode.Clamp) { sampler.WrapS = GLTF.Schema.WrapMode.ClampToEdge; sampler.WrapT = GLTF.Schema.WrapMode.ClampToEdge; } else { sampler.WrapS = GLTF.Schema.WrapMode.Repeat; sampler.WrapT = GLTF.Schema.WrapMode.Repeat; } if(texture.filterMode == FilterMode.Point) { sampler.MinFilter = MinFilterMode.NearestMipmapNearest; sampler.MagFilter = MagFilterMode.Nearest; } else if(texture.filterMode == FilterMode.Bilinear) { sampler.MinFilter = MinFilterMode.NearestMipmapLinear; sampler.MagFilter = MagFilterMode.Linear; } else { sampler.MinFilter = MinFilterMode.LinearMipmapLinear; sampler.MagFilter = MagFilterMode.Linear; } samplerId = new SamplerId { Id = _root.Samplers.Count, Root = _root }; _root.Samplers.Add(sampler); return samplerId; } private Vector2[] FlipY(Vector2[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].y = 1 - arr[i].y; } return arr; } private Vector3[] InvertZ(Vector3[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].z = -arr[i].z; } return arr; } private Vector4[] InvertW(Vector4[] arr) { var len = arr.Length; for(var i = 0; i < len; i++) { arr[i].w = -arr[i].w; } return arr; } private int[] FlipFaces(int[] arr) { var triangles = new int[arr.Length]; for (int i = 0; i < arr.Length; i += 3) { triangles[i + 2] = arr[i]; triangles[i + 1] = arr[i + 1]; triangles[i] = arr[i + 2]; } return triangles; } private AccessorId ExportAccessor(int[] arr, bool isIndices = false) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.SCALAR; int min = arr[0]; int max = arr[0]; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur < min) { min = cur; } if (cur > max) { max = cur; } } var byteOffset = _bufferWriter.BaseStream.Position; if (max <= byte.MaxValue && min >= byte.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedByte; foreach (var v in arr) { _bufferWriter.Write((byte)v); } } else if (max <= sbyte.MaxValue && min >= sbyte.MinValue && !isIndices) { accessor.ComponentType = GLTFComponentType.Byte; foreach (var v in arr) { _bufferWriter.Write((sbyte)v); } } else if (max <= short.MaxValue && min >= short.MinValue && !isIndices) { accessor.ComponentType = GLTFComponentType.Short; foreach (var v in arr) { _bufferWriter.Write((short)v); } } else if (max <= ushort.MaxValue && min >= ushort.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedShort; foreach (var v in arr) { _bufferWriter.Write((ushort)v); } } else if (min >= uint.MinValue) { accessor.ComponentType = GLTFComponentType.UnsignedInt; foreach (var v in arr) { _bufferWriter.Write((uint)v); } } else { accessor.ComponentType = GLTFComponentType.Float; foreach (var v in arr) { _bufferWriter.Write((float)v); } } accessor.Min = new List { min }; accessor.Max = new List { max }; var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector2[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC2; float minX = arr[0].x; float minY = arr[0].y; float maxX = arr[0].x; float maxY = arr[0].y; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } } accessor.Min = new List { minX, minY }; accessor.Max = new List { maxX, maxY }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector3[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC3; float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } } accessor.Min = new List { minX, minY, minZ }; accessor.Max = new List { maxX, maxY, maxZ }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); _bufferWriter.Write(vec.z); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(Vector4[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC4; float minX = arr[0].x; float minY = arr[0].y; float minZ = arr[0].z; float minW = arr[0].w; float maxX = arr[0].x; float maxY = arr[0].y; float maxZ = arr[0].z; float maxW = arr[0].w; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.x < minX) { minX = cur.x; } if (cur.y < minY) { minY = cur.y; } if (cur.z < minZ) { minZ = cur.z; } if (cur.w < minW) { minW = cur.w; } if (cur.x > maxX) { maxX = cur.x; } if (cur.y > maxY) { maxY = cur.y; } if (cur.z > maxZ) { maxZ = cur.z; } if (cur.w > maxW) { maxW = cur.w; } } accessor.Min = new List { minX, minY, minZ, minW }; accessor.Max = new List { maxX, maxY, maxZ, maxW }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var vec in arr) { _bufferWriter.Write(vec.x); _bufferWriter.Write(vec.y); _bufferWriter.Write(vec.z); _bufferWriter.Write(vec.w); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private AccessorId ExportAccessor(UnityEngine.Color[] arr) { var count = arr.Length; if (count == 0) { throw new Exception("Accessors can not have a count of 0."); } var accessor = new Accessor(); accessor.ComponentType = GLTFComponentType.Float; accessor.Count = count; accessor.Type = GLTFAccessorAttributeType.VEC4; float minR = arr[0].r; float minG = arr[0].g; float minB = arr[0].b; float minA = arr[0].a; float maxR = arr[0].r; float maxG = arr[0].g; float maxB = arr[0].b; float maxA = arr[0].a; for (var i = 1; i < count; i++) { var cur = arr[i]; if (cur.r < minR) { minR = cur.r; } if (cur.g < minG) { minG = cur.g; } if (cur.b < minB) { minB = cur.b; } if (cur.a < minA) { minA = cur.a; } if (cur.r > maxR) { maxR = cur.r; } if (cur.g > maxG) { maxG = cur.g; } if (cur.b > maxB) { maxB = cur.b; } if (cur.a > maxA) { maxA = cur.a; } } accessor.Min = new List { minR, minG, minB, minA }; accessor.Max = new List { maxR, maxG, maxB, maxA }; var byteOffset = _bufferWriter.BaseStream.Position; foreach (var color in arr) { _bufferWriter.Write(color.r); _bufferWriter.Write(color.g); _bufferWriter.Write(color.b); _bufferWriter.Write(color.a); } var byteLength = _bufferWriter.BaseStream.Position - byteOffset; accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); var id = new AccessorId { Id = _root.Accessors.Count, Root = _root }; _root.Accessors.Add(accessor); return id; } private BufferViewId ExportBufferView(int byteOffset, int byteLength) { var bufferView = new BufferView { Buffer = _bufferId, ByteOffset = byteOffset, ByteLength = byteLength, }; var id = new BufferViewId { Id = _root.BufferViews.Count, Root = _root }; _root.BufferViews.Add(bufferView); return id; } public MaterialId GetMaterialId(GLTFRoot root, UnityEngine.Material materialObj) { for (var i = 0; i < _materials.Count; i++) { if (_materials[i] == materialObj) { return new MaterialId { Id = i, Root = root }; } } return null; } public TextureId GetTextureId(GLTFRoot root, UnityEngine.Texture textureObj) { for (var i = 0; i < _textures.Count; i++) { if (_textures[i] == textureObj) { return new TextureId { Id = i, Root = root }; } } return null; } public ImageId GetImageId(GLTFRoot root, UnityEngine.Texture imageObj) { for (var i = 0; i < _images.Count; i++) { if (_images[i] == imageObj) { return new ImageId { Id = i, Root = root }; } } return null; } public SamplerId GetSamplerId(GLTFRoot root, UnityEngine.Texture textureObj) { for (var i = 0; i < root.Samplers.Count; i++) { bool filterIsNearest = root.Samplers[i].MinFilter == MinFilterMode.Nearest || root.Samplers[i].MinFilter == MinFilterMode.NearestMipmapNearest || root.Samplers[i].MinFilter == MinFilterMode.LinearMipmapNearest; bool filterIsLinear = root.Samplers[i].MinFilter == MinFilterMode.Linear || root.Samplers[i].MinFilter == MinFilterMode.NearestMipmapLinear; bool filterMatched = textureObj.filterMode == FilterMode.Point && filterIsNearest || textureObj.filterMode == FilterMode.Bilinear && filterIsLinear || textureObj.filterMode == FilterMode.Trilinear && root.Samplers[i].MinFilter == MinFilterMode.LinearMipmapLinear; bool wrapMatched = textureObj.wrapMode == TextureWrapMode.Clamp && root.Samplers[i].WrapS == GLTF.Schema.WrapMode.ClampToEdge || textureObj.wrapMode == TextureWrapMode.Repeat && root.Samplers[i].WrapS != GLTF.Schema.WrapMode.ClampToEdge; if (filterMatched && wrapMatched) { return new SamplerId { Id = i, Root = root }; } } return null; } } }