Created
February 14, 2026 00:12
-
-
Save Yuu6883/9e2c70cc4272dfdaa7bbd1cab24c3e4f to your computer and use it in GitHub Desktop.
AnimeStudio patch for GLB export instead of OBJ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public static bool ExportMesh(AssetItem item, string exportPath) | |
| { | |
| const int GL_ARRAY_BUFFER = 34962; | |
| const int GL_ELEMENT_ARRAY_BUFFER = 34963; | |
| const int GL_FLOAT = 5126; | |
| const int GL_UNSIGNED_INT = 5125; | |
| var m_Mesh = (Mesh)item.Asset; | |
| if (m_Mesh.m_VertexCount <= 0) | |
| return false; | |
| if (!TryExportFile(exportPath, item, ".glb", out var exportFullPath)) | |
| return false; | |
| if (m_Mesh.m_Vertices == null || m_Mesh.m_Vertices.Length == 0) | |
| return false; | |
| var binStream = new MemoryStream(); | |
| var binWriter = new BinaryWriter(binStream); | |
| var bufferViews = new List<object>(); | |
| var accessors = new List<object>(); | |
| var attributes = new Dictionary<string, int>(); | |
| // ---------- Helper ---------- | |
| int WriteBuffer<T>( | |
| T[] data, | |
| int target, | |
| int componentType, | |
| int count, | |
| string type, | |
| string name = null) | |
| where T : unmanaged | |
| { | |
| if (typeof(T) != typeof(float) && typeof(T) != typeof(uint)) | |
| throw new InvalidOperationException( | |
| $"Unsupported type {typeof(T)}. Only float and uint allowed."); | |
| int byteOffset = (int)binStream.Position; | |
| if (typeof(T) == typeof(float)) | |
| foreach (float v in (float[])(object)data) | |
| binWriter.Write(v); | |
| else | |
| foreach (uint v in (uint[])(object)data) | |
| binWriter.Write(v); | |
| int byteLength = data.Length * 4; | |
| int bufferViewIndex = bufferViews.Count; | |
| bufferViews.Add(new | |
| { | |
| buffer = 0, | |
| byteOffset, | |
| byteLength, | |
| target | |
| }); | |
| int accessorIndex = accessors.Count; | |
| accessors.Add(new | |
| { | |
| bufferView = bufferViewIndex, | |
| componentType, | |
| count, | |
| type, | |
| name | |
| }); | |
| return accessorIndex; | |
| } | |
| // ---------- Attributes ---------- | |
| attributes["POSITION"] = WriteBuffer( | |
| m_Mesh.m_Vertices, | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| "VEC3" | |
| ); | |
| if (m_Mesh.m_Normals != null) | |
| attributes["NORMAL"] = WriteBuffer( | |
| m_Mesh.m_Normals, | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| "VEC3" | |
| ); | |
| if (m_Mesh.m_Tangents != null) | |
| attributes["TANGENT"] = WriteBuffer( | |
| m_Mesh.m_Tangents, | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| "VEC4" | |
| ); | |
| if (m_Mesh.m_Colors != null) | |
| attributes["COLOR_0"] = WriteBuffer( | |
| m_Mesh.m_Colors, | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| "VEC4" | |
| ); | |
| float[][] uvs = | |
| { | |
| m_Mesh.m_UV0, m_Mesh.m_UV1, m_Mesh.m_UV2, m_Mesh.m_UV3, | |
| m_Mesh.m_UV4, m_Mesh.m_UV5, m_Mesh.m_UV6, m_Mesh.m_UV7 | |
| }; | |
| for (int i = 0; i < uvs.Length; i++) | |
| { | |
| if (uvs[i] != null) | |
| { | |
| var type = (uvs[i].Length / m_Mesh.m_VertexCount) switch { 4 => "VEC4", 3 => "VEC3", 2 => "VEC2", _ => "VEC2" }; | |
| if (type == "VEC2") | |
| attributes[$"TEXCOORD_{i}"] = WriteBuffer( | |
| uvs[i], | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| type, | |
| $"CHANNEL_{i}" | |
| ); | |
| else | |
| attributes[$"_CUSTOM_UV_{i}"] = WriteBuffer( | |
| uvs[i], | |
| GL_ARRAY_BUFFER, | |
| GL_FLOAT, | |
| m_Mesh.m_VertexCount, | |
| type, | |
| $"CHANNEL_{i}" | |
| ); | |
| } | |
| } | |
| Logger.Info($"mesh skin array length: {m_Mesh.m_Skin?.Count}"); | |
| Logger.Info($"mesh blendshape array length: {m_Mesh.m_Shapes?.channels?.Count}"); | |
| // ---------- Indices (optional) ---------- | |
| int? indexAccessor = null; | |
| if (m_Mesh.m_Indices != null && m_Mesh.m_Indices.Count > 0) | |
| { | |
| indexAccessor = WriteBuffer( | |
| m_Mesh.m_Indices.ToArray(), | |
| GL_ELEMENT_ARRAY_BUFFER, | |
| GL_UNSIGNED_INT, | |
| m_Mesh.m_Indices.Count, | |
| "SCALAR" | |
| ); | |
| } | |
| // ---------- BIN padding (zeros) ---------- | |
| while ((binStream.Position & 3) != 0) | |
| binWriter.Write((byte)0); | |
| byte[] bin = binStream.ToArray(); | |
| // ---------- glTF JSON ---------- | |
| var primitive = new Dictionary<string, object> | |
| { | |
| ["attributes"] = attributes | |
| }; | |
| if (indexAccessor.HasValue) | |
| primitive["indices"] = indexAccessor.Value; | |
| var gltf = new | |
| { | |
| asset = new { version = "2.0", generator = "GLBExporterFromAnimeStudio" }, | |
| buffers = new[] { new { byteLength = bin.Length } }, | |
| bufferViews, | |
| accessors, | |
| meshes = new[] { new { primitives = new[] { primitive } } }, | |
| nodes = new[] { new { mesh = 0 } }, | |
| scenes = new[] { new { nodes = new[] { 0 } } }, | |
| scene = 0 | |
| }; | |
| var settings = new JsonSerializerSettings(); | |
| settings.Converters.Add(new StringEnumConverter()); | |
| string json = JsonConvert.SerializeObject(gltf, Formatting.Indented, settings); | |
| Logger.Info($"GLB json: {json}"); | |
| // ---------- JSON padding (spaces) ---------- | |
| int jsonByteLen = Encoding.UTF8.GetByteCount(json); | |
| int padded = (jsonByteLen + 3) & ~3; | |
| if (padded != jsonByteLen) | |
| json += new string(' ', padded - jsonByteLen); | |
| byte[] jsonBytes = Encoding.UTF8.GetBytes(json); | |
| // ---------- Write GLB ---------- | |
| using var fs = File.Create(exportFullPath); | |
| using var bw = new BinaryWriter(fs); | |
| bw.Write(0x46546C67); // "glTF" | |
| bw.Write(2); | |
| bw.Write(12 + 8 + jsonBytes.Length + 8 + bin.Length); | |
| // JSON chunk | |
| bw.Write(jsonBytes.Length); | |
| bw.Write(0x4E4F534A); // "JSON" | |
| bw.Write(jsonBytes); | |
| // BIN chunk | |
| bw.Write(bin.Length); | |
| bw.Write(0x004E4942); // "BIN" | |
| bw.Write(bin); | |
| return true; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment