Skip to content

Instantly share code, notes, and snippets.

@Yuu6883
Created February 14, 2026 00:12
Show Gist options
  • Select an option

  • Save Yuu6883/9e2c70cc4272dfdaa7bbd1cab24c3e4f to your computer and use it in GitHub Desktop.

Select an option

Save Yuu6883/9e2c70cc4272dfdaa7bbd1cab24c3e4f to your computer and use it in GitHub Desktop.
AnimeStudio patch for GLB export instead of OBJ
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