Merge pull request #861 from ousttrue/fix/mesh_export_logic

VertexBufferをSubMeshで分割してエクスポートするオプション
This commit is contained in:
ousttrue
2021-04-12 18:15:09 +09:00
committed by GitHub
12 changed files with 514 additions and 273 deletions

View File

@@ -14,5 +14,7 @@ namespace UniGLTF
public bool Sparse;
public bool DropNormal;
public bool DivideVertexBuffer;
}
}

View File

@@ -36,7 +36,7 @@ namespace UniGLTF
using (var exporter = new gltfExporter(gltf, inverseAxis))
{
exporter.Prepare(go);
exporter.Export(settings, AssetTextureUtil.IsTextureEditorAsset );
exporter.Export(settings, AssetTextureUtil.IsTextureEditorAsset);
}
@@ -244,6 +244,7 @@ namespace UniGLTF
{
ExportOnlyBlendShapePosition = settings.DropNormal,
UseSparseAccessorForMorphTarget = settings.Sparse,
DivideVertexBuffer = settings.DivideVertexBuffer,
}, settings.InverseAxis);
}
}

View File

@@ -0,0 +1,35 @@
using System;
namespace UniGLTF
{
[Serializable]
public struct MeshExportSettings
{
//
// https://github.com/vrm-c/UniVRM/issues/800
//
// VertexBuffer を共有バッファ方式にする
// UniVRM-0.71.0 までの挙動
//
public bool DivideVertexBuffer;
// MorphTarget に Sparse Accessor を使う
public bool UseSparseAccessorForMorphTarget;
// MorphTarget を Position だけにする(normal とか捨てる)
public bool ExportOnlyBlendShapePosition;
// tangent を出力する
public bool ExportTangents;
public static MeshExportSettings Default => new MeshExportSettings
{
UseSparseAccessorForMorphTarget = false,
ExportOnlyBlendShapePosition = false,
DivideVertexBuffer = false,
#if GLTF_EXPORT_TANGENTS
ExportTangents = true,
#endif
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 26b97430e1698e94e9a27ab322f3eee5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,155 +3,28 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UniGLTF
{
[Serializable]
public struct MeshExportSettings
{
// MorphTarget に Sparse Accessor を使う
public bool UseSparseAccessorForMorphTarget;
// MorphTarget を Position だけにする(normal とか捨てる)
public bool ExportOnlyBlendShapePosition;
public static MeshExportSettings Default => new MeshExportSettings
{
UseSparseAccessorForMorphTarget = false,
ExportOnlyBlendShapePosition = false,
};
}
public static class MeshExporter
{
static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex, Mesh mesh, Material[] meshMaterials, BoneWeight[] boneWeights, int[] jointIndexMap,
List<Material> exportedMaterials)
{
var positions = mesh.vertices.Select(y => y.ReverseZ()).ToArray();
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, positions, glBufferTarget.ARRAY_BUFFER);
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();
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 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.normals.Select(y => y.normalized.ReverseZ()).ToArray(), glBufferTarget.ARRAY_BUFFER);
#if GLTF_EXPORT_TANGENTS
var tangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.tangents.Select(y => y.ReverseZ()).ToArray(), glBufferTarget.ARRAY_BUFFER);
#endif
var uvAccessorIndex0 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.uv.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);
var uvAccessorIndex1 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.uv2.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);
var colorAccessorIndex = -1;
var vColorState = MeshExportInfo.DetectVertexColor(mesh, meshMaterials);
if (vColorState == MeshExportInfo.VertexColorState.ExistsAndIsUsed // VColor使っている
|| vColorState == MeshExportInfo.VertexColorState.ExistsAndMixed // VColorを使っているところと使っていないところが混在(とりあえずExportする)
)
{
// UniUnlit で Multiply 設定になっている
colorAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.colors, glBufferTarget.ARRAY_BUFFER);
}
var weightAccessorIndex = -1;
var jointsAccessorIndex = -1;
if (boneWeights != null && jointIndexMap != null)
{
weightAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneWeights.Select(y => new Vector4(y.weight0, y.weight1, y.weight2, y.weight3)).ToArray(), glBufferTarget.ARRAY_BUFFER);
jointsAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, boneWeights.Select(y =>
new UShort4(
(ushort)jointIndexMap[y.boneIndex0],
(ushort)jointIndexMap[y.boneIndex1],
(ushort)jointIndexMap[y.boneIndex2],
(ushort)jointIndexMap[y.boneIndex3])
).ToArray(), glBufferTarget.ARRAY_BUFFER);
}
var attributes = new glTFAttributes
{
POSITION = positionAccessorIndex,
};
if (normalAccessorIndex != -1)
{
attributes.NORMAL = normalAccessorIndex;
}
#if GLTF_EXPORT_TANGENTS
if (tangentAccessorIndex != -1)
{
attributes.TANGENT = tangentAccessorIndex;
}
#endif
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 (var 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 (var 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 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
if (indicesAccessorIndex < 0)
{
// https://github.com/vrm-c/UniVRM/issues/664
throw new Exception();
}
var primitives = new glTFPrimitives
{
attributes = attributes,
indices = indicesAccessorIndex,
mode = 4, // triangles ?
};
if (meshMaterials != null)
{
primitives.material = exportedMaterials.IndexOf(meshMaterials[j]);
}
gltfMesh.primitives.Add(primitives);
}
return gltfMesh;
}
static glTFMesh ExportPrimitives(glTF gltf, int bufferIndex,
/// <summary>
/// primitive 間で vertex を共有する形で Export する。
///
/// UniVRM-0.71.0 までの挙動
///
/// /// </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>
static glTFMesh ExportSharedVertexBuffer(glTF gltf, int bufferIndex,
MeshWithRenderer unityMesh, List<Material> unityMaterials,
IAxisInverter axisInverter)
IAxisInverter axisInverter, MeshExportSettings settings)
{
var mesh = unityMesh.Mesh;
var materials = unityMesh.Renderer.sharedMaterials;
@@ -161,9 +34,13 @@ namespace UniGLTF
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 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.normals.Select(y => axisInverter.InvertVector3(y.normalized)).ToArray(), glBufferTarget.ARRAY_BUFFER);
#if GLTF_EXPORT_TANGENTS
var tangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.tangents.Select(axisInverter.InvertVector4()).ToArray(), glBufferTarget.ARRAY_BUFFER);
#endif
int? tangentAccessorIndex = default;
if (settings.ExportTangents)
{
tangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.tangents.Select(axisInverter.InvertVector4).ToArray(), glBufferTarget.ARRAY_BUFFER);
}
var uvAccessorIndex0 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.uv.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);
var uvAccessorIndex1 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, mesh.uv2.Select(y => y.ReverseUV()).ToArray(), glBufferTarget.ARRAY_BUFFER);
@@ -196,12 +73,12 @@ namespace UniGLTF
{
attributes.NORMAL = normalAccessorIndex;
}
#if GLTF_EXPORT_TANGENTS
if (tangentAccessorIndex != -1)
if (tangentAccessorIndex.HasValue)
{
attributes.TANGENT = tangentAccessorIndex;
attributes.TANGENT = tangentAccessorIndex.Value;
}
#endif
if (uvAccessorIndex0 != -1)
{
attributes.TEXCOORD_0 = uvAccessorIndex0;
@@ -411,18 +288,33 @@ namespace UniGLTF
};
}
public static IEnumerable<(Mesh, glTFMesh, Dictionary<int, int>)> ExportMeshes(glTF gltf, int bufferIndex,
List<MeshWithRenderer> unityMeshes, List<Material> unityMaterials,
/// <summary>
///
/// </summary>
/// <param name="mesh"></param>
/// <param name="gltf"></param>
/// <param name="bufferIndex"></param>
/// <param name="unityMesh"></param>
/// <param name="unityMaterials"></param>
/// <param name="settings"></param>
/// <param name="axisInverter"></param>
/// <returns></returns>
public static (glTFMesh mesh, Dictionary<int, int> blendShapeIndexMap) ExportMesh(glTF gltf, int bufferIndex,
MeshWithRenderer unityMesh, List<Material> unityMaterials,
MeshExportSettings settings, IAxisInverter axisInverter)
{
foreach (var unityMesh in unityMeshes)
glTFMesh gltfMesh = default;
var blendShapeIndexMap = new Dictionary<int, int>();
if (settings.DivideVertexBuffer)
{
var gltfMesh = ExportPrimitives(gltf, bufferIndex,
unityMesh, unityMaterials, axisInverter);
gltfMesh = MeshExporterDivided.Export(gltf, bufferIndex, unityMesh, unityMaterials, axisInverter, settings);
}
else
{
gltfMesh = ExportSharedVertexBuffer(gltf, bufferIndex, unityMesh, unityMaterials, axisInverter, settings);
var targetNames = new List<string>();
var blendShapeIndexMap = new Dictionary<int, int>();
int exportBlendShapes = 0;
for (int j = 0; j < unityMesh.Mesh.blendShapeCount; ++j)
{
@@ -450,66 +342,7 @@ namespace UniGLTF
}
gltf_mesh_extras_targetNames.Serialize(gltfMesh, targetNames);
yield return (unityMesh.Mesh, gltfMesh, blendShapeIndexMap);
}
}
public static (glTFMesh gltfMesh, Dictionary<int, int> blendShapeIndexMap) ExportMesh(glTF gltf, int bufferIndex, Mesh mesh, Renderer renderer, List<Material> exportedMaterials, MeshExportSettings meshExportSettings, IAxisInverter axisInverter)
{
var meshMaterials = default(Material[]);
if (renderer != null)
{
meshMaterials = renderer.sharedMaterials;
}
var boneWeights = default(BoneWeight[]);
var jointIndexMap = default(int[]);
if (renderer is SkinnedMeshRenderer skin)
{
var bones = skin.bones;
var uniqueBones = bones.Distinct().ToArray();
jointIndexMap = new int[bones.Length];
for (var i = 0; i < bones.Length; i++)
{
jointIndexMap[i] = Array.IndexOf(uniqueBones, bones[i]);
}
boneWeights = mesh.boneWeights;
}
var gltfMesh = ExportPrimitives(gltf, bufferIndex, mesh, meshMaterials, boneWeights, jointIndexMap, exportedMaterials);
var targetNames = new List<string>();
var blendShapeIndexMap = new Dictionary<int, int>();
int exportBlendShapes = 0;
for (int j = 0; j < mesh.blendShapeCount; ++j)
{
var morphTarget = ExportMorphTarget(gltf, bufferIndex,
mesh, j,
meshExportSettings.UseSparseAccessorForMorphTarget,
meshExportSettings.ExportOnlyBlendShapePosition, axisInverter);
if (morphTarget.POSITION < 0 && morphTarget.NORMAL < 0 && morphTarget.TANGENT < 0)
{
continue;
}
// maybe skip
var blendShapeName = 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);
}

View File

@@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UniGLTF
{
public static class MeshExporterDivided
{
class BlendShapeBuffer
{
readonly List<Vector3> m_positions;
readonly List<Vector3> m_normals;
public BlendShapeBuffer(int reserve)
{
m_positions = new List<Vector3>(reserve);
m_normals = new List<Vector3>(reserve);
}
public void Push(Vector3 position, Vector3 normal)
{
m_positions.Add(position);
m_normals.Add(normal);
}
public gltfMorphTarget ToGltf(glTF gltf, int bufferIndex, bool useNormal)
{
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_positions.ToArray(), glBufferTarget.ARRAY_BUFFER);
var normalAccessorIndex = -1;
if (useNormal)
{
normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_normals.ToArray(), glBufferTarget.ARRAY_BUFFER);
}
return new gltfMorphTarget
{
POSITION = positionAccessorIndex,
NORMAL = normalAccessorIndex,
};
}
}
class VertexBuffer
{
readonly List<Vector3> m_positions;
readonly List<Vector3> m_normals;
readonly List<Vector2> m_uv;
readonly Func<int, int> m_getJointIndex;
readonly List<UShort4> m_joints;
readonly List<Vector4> m_weights;
public VertexBuffer(int reserve, Func<int, int> getJointIndex)
{
m_positions = new List<Vector3>(reserve);
m_normals = new List<Vector3>(reserve);
m_uv = new List<Vector2>();
m_getJointIndex = getJointIndex;
if (m_getJointIndex != null)
{
m_joints = new List<UShort4>(reserve);
m_weights = new List<Vector4>(reserve);
}
}
public void Push(Vector3 position, Vector3 normal, Vector2 uv)
{
m_positions.Add(position);
m_normals.Add(normal);
m_uv.Add(uv);
}
public void Push(BoneWeight boneWeight)
{
m_joints.Add(new UShort4((ushort)boneWeight.boneIndex0, (ushort)boneWeight.boneIndex1, (ushort)boneWeight.boneIndex2, (ushort)boneWeight.boneIndex3));
m_weights.Add(new Vector4(boneWeight.weight0, boneWeight.weight1, boneWeight.weight2, boneWeight.weight3));
}
public glTFPrimitives ToGltf(glTF gltf, int bufferIndex, int materialIndex, IReadOnlyList<uint> indices)
{
var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.ToArray(), glBufferTarget.ELEMENT_ARRAY_BUFFER);
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_positions.ToArray(), glBufferTarget.ARRAY_BUFFER);
var normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_normals.ToArray(), glBufferTarget.ARRAY_BUFFER);
var uvAccessorIndex0 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_uv.ToArray(), glBufferTarget.ARRAY_BUFFER);
int? jointsAccessorIndex = default;
if (m_joints != null)
{
jointsAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_joints.ToArray(), glBufferTarget.ARRAY_BUFFER);
}
int? weightAccessorIndex = default;
if (m_weights != null)
{
weightAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_weights.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),
},
material = materialIndex,
mode = 4,
};
return primitive;
}
}
public static glTFMesh Export(glTF gltf, int bufferIndex,
MeshWithRenderer unityMesh, List<Material> unityMaterials,
IAxisInverter axisInverter, MeshExportSettings settings)
{
var mesh = unityMesh.Mesh;
var gltfMesh = new glTFMesh(mesh.name);
if (settings.ExportTangents)
{
// support しない
throw new NotImplementedException();
}
var positions = mesh.vertices;
var normals = mesh.normals;
var uv = mesh.uv;
var boneWeights = mesh.boneWeights;
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 usedIndices = new List<int>();
for (int i = 0; i < mesh.subMeshCount; ++i)
{
var indices = mesh.GetIndices(i);
// mesh
// index の順に attributes を蓄える
var buffer = new VertexBuffer(indices.Length, getJointIndex);
// foreach (var k in indices)
// {
// buffer.Push(axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV());
// if (getJointIndex != null)
// {
// buffer.Push(boneWeights[k]);
// }
// }
// indices から参照される頂点だけを蓄える
usedIndices.Clear();
for (int k = 0; k < positions.Length; ++k)
{
if (indices.Contains(k))
{
usedIndices.Add(k);
buffer.Push(axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV());
if (getJointIndex != null)
{
buffer.Push(boneWeights[k]);
}
}
}
var material = unityMesh.Renderer.sharedMaterials[i];
var materialIndex = -1;
if (material != null)
{
materialIndex = unityMaterials.IndexOf(material);
}
var indexMap = usedIndices.Select((used, index) => (used, index)).ToDictionary(x => x.used, x => (uint)x.index);
var flipped = new List<uint>();
for (int j = 0; j < indices.Length; j += 3)
{
flipped.Add((uint)indexMap[indices[j + 2]]);
flipped.Add((uint)indexMap[indices[j + 1]]);
flipped.Add((uint)indexMap[indices[j]]);
}
var gltfPrimitive = buffer.ToGltf(gltf, bufferIndex, materialIndex, flipped);
// blendShape
for (int j = 0; j < mesh.blendShapeCount; ++j)
{
var blendShape = new BlendShapeBuffer(indices.Length);
// index の順に attributes を蓄える
mesh.GetBlendShapeFrameVertices(j, 0, blendShapePositions, blendShapeNormals, null);
foreach (var k in usedIndices)
{
blendShape.Push(
axisInverter.InvertVector3(blendShapePositions[k]),
axisInverter.InvertVector3(blendShapeNormals[k]));
}
gltfPrimitive.targets.Add(blendShape.ToGltf(gltf, bufferIndex, !settings.ExportOnlyBlendShapePosition));
}
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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 92bc82be5ead84a429346379590f6d7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -37,6 +37,12 @@ namespace UniGLTF
}
}
/// <summary>
/// glTF は skinning の boneList の重複を許可しない
/// (unity は ok)
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public int GetJointIndex(int index)
{
if (index < 0)

View File

@@ -17,11 +17,7 @@ namespace UniGLTF
protected set;
}
public List<Mesh> Meshes
{
get;
private set;
}
public List<Mesh> Meshes { get; private set; } = new List<Mesh>();
/// <summary>
/// Mesh毎に、元のBlendShapeIndex => ExportされたBlendShapeIndex の対応を記録する
@@ -195,17 +191,17 @@ namespace UniGLTF
var unityMeshes = MeshWithRenderer.FromNodes(Nodes).Where(x => x.Mesh.vertices.Any()).ToList();
MeshBlendShapeIndexMap = new Dictionary<Mesh, Dictionary<int, int>>();
foreach (var (mesh, gltfMesh, blendShapeIndexMap) in MeshExporter.ExportMeshes(
glTF, bufferIndex, unityMeshes, Materials, meshExportSettings, m_axisInverter))
foreach (var unityMesh in unityMeshes)
{
var (gltfMesh, blendShapeIndexMap) = MeshExporter.ExportMesh(glTF, bufferIndex, unityMesh, Materials, meshExportSettings, m_axisInverter);
glTF.meshes.Add(gltfMesh);
if (!MeshBlendShapeIndexMap.ContainsKey(mesh))
Meshes.Add(unityMesh.Mesh);
if (!MeshBlendShapeIndexMap.ContainsKey(unityMesh.Mesh))
{
// 同じmeshが複数回現れた
MeshBlendShapeIndexMap.Add(mesh, blendShapeIndexMap);
MeshBlendShapeIndexMap.Add(unityMesh.Mesh, blendShapeIndexMap);
}
}
Meshes = unityMeshes.Select(x => x.Mesh).ToList();
#endregion
#region Nodes and Skins

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using UnityEngine;
namespace UniGLTF
{
@@ -11,8 +12,8 @@ namespace UniGLTF
public void AccessorTest()
{
byte[] bytes = default;
using(var ms = new MemoryStream())
using(var w = new BinaryWriter(ms))
using (var ms = new MemoryStream())
using (var w = new BinaryWriter(ms))
{
w.Write(1.0f);
w.Write(2.0f);
@@ -28,7 +29,7 @@ namespace UniGLTF
var gltf = new glTF
{
buffers=new List<glTFBuffer>
buffers = new List<glTFBuffer>
{
new glTFBuffer
{
@@ -39,7 +40,7 @@ namespace UniGLTF
new glTFBufferView{
buffer=0,
byteLength=32,
byteOffset=0,
byteOffset=0,
}
},
accessors = new List<glTFAccessor>
@@ -59,5 +60,153 @@ namespace UniGLTF
Assert.AreEqual((1.0f, 2.0f, 3.0f, 4.0f), getter(0));
Assert.AreEqual((5.0f, 6.0f, 7.0f, 8.0f), getter(1));
}
/// <summary>
/// 0 1 2
/// +-+-+
/// |A|B|
/// +-+-+
/// 5 4 3
///
/// 6 vertices
/// 4 triangles
/// 2 materials
///
/// </summary>
static (GameObject, Mesh) CreateMesh(Material[] materials)
{
var unityMesh = new Mesh();
unityMesh.vertices = new Vector3[]
{
new Vector3(0, 1, 0),
new Vector3(1, 1, 0),
new Vector3(2, 1, 0),
new Vector3(2, 0, 0),
new Vector3(1, 0, 0),
new Vector3(0, 0, 0),
};
unityMesh.uv = new Vector2[]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(2, 0),
new Vector2(2, 1),
new Vector2(1, 1),
new Vector2(0, 1),
};
unityMesh.subMeshCount = 2;
unityMesh.SetTriangles(new int[]{
0, 1, 5,
5, 1, 4,
}, 0);
unityMesh.SetTriangles(new int[]{
1, 2, 4,
4, 2, 3,
}, 1);
unityMesh.RecalculateNormals();
unityMesh.RecalculateTangents();
var go = new GameObject();
go.AddComponent<MeshRenderer>().sharedMaterials = materials;
go.AddComponent<MeshFilter>().sharedMesh = unityMesh;
return (go, unityMesh);
}
[Test]
public void SharedVertexBufferTest()
{
var glTF = new glTF();
var bytesBuffer = new ArrayByteBuffer(new byte[50 * 1024 * 1024]);
var bufferIndex = glTF.AddBuffer(bytesBuffer);
var Materials = new List<Material>{
new Material(Shader.Find("Standard")), // A
new Material(Shader.Find("Standard")), // B
};
var (go, unityMesh) = CreateMesh(Materials.ToArray());
var meshExportSettings = new MeshExportSettings
{
DivideVertexBuffer = false
};
var axisInverter = Axises.X.Create();
var (gltfMesh, blendShapeIndexMap) = MeshExporter.ExportMesh(glTF, bufferIndex, new MeshWithRenderer(go.transform), Materials, meshExportSettings, axisInverter);
{
var indices = glTF.GetIndices(gltfMesh.primitives[0].indices);
Assert.AreEqual(0, indices[0]);
Assert.AreEqual(1, indices[1]);
Assert.AreEqual(5, indices[2]);
Assert.AreEqual(5, indices[3]);
Assert.AreEqual(1, indices[4]);
Assert.AreEqual(4, indices[5]);
}
{
var indices = glTF.GetIndices(gltfMesh.primitives[1].indices);
Assert.AreEqual(1, indices[0]);
Assert.AreEqual(2, indices[1]);
Assert.AreEqual(4, indices[2]);
Assert.AreEqual(4, indices[3]);
Assert.AreEqual(2, indices[4]);
Assert.AreEqual(3, indices[5]);
}
var positions = glTF.GetArrayFromAccessor<Vector3>(gltfMesh.primitives[0].attributes.POSITION);
Assert.AreEqual(6, positions.Length);
}
[Test]
public void DividedVertexBufferTest()
{
var glTF = new glTF();
var bytesBuffer = new ArrayByteBuffer(new byte[50 * 1024 * 1024]);
var bufferIndex = glTF.AddBuffer(bytesBuffer);
var Materials = new List<Material>{
new Material(Shader.Find("Standard")), // A
new Material(Shader.Find("Standard")), // B
};
var (go, unityMesh) = CreateMesh(Materials.ToArray());
var meshExportSettings = new MeshExportSettings
{
DivideVertexBuffer = true
};
var axisInverter = Axises.X.Create();
var (gltfMesh, blendShapeIndexMap) = MeshExporter.ExportMesh(glTF, bufferIndex, new MeshWithRenderer(go.transform), Materials, meshExportSettings, axisInverter);
{
var indices = glTF.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 = glTF.GetArrayFromAccessor<Vector3>(gltfMesh.primitives[0].attributes.POSITION);
Assert.AreEqual(4, positions.Length);
}
{
var indices = glTF.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 = glTF.GetArrayFromAccessor<Vector3>(gltfMesh.primitives[1].attributes.POSITION);
Assert.AreEqual(4, positions.Length);
}
}
}
}

View File

@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using UniGLTF;
using UnityEngine;
namespace VRM
@@ -7,6 +7,7 @@ namespace VRM
[Serializable]
public class VRMExportSettings : ScriptableObject
{
/// <summary>
/// エクスポート時に強制的にT-Pose化する
/// </summary>
@@ -43,10 +44,17 @@ namespace VRM
[Tooltip("Remove blendShapeClip that preset is Unknown")]
public bool ReduceBlendshapeClip = false;
public UniGLTF.MeshExportSettings MeshExportSettings => new UniGLTF.MeshExportSettings
/// <summary>
/// Export時に頂点バッファをsubmeshで分割する。GLTF互換性
/// </summary>
[Tooltip("Divide vertex buffer. For more gltf compatibility")]
public bool DivideVertexBuffer = false;
public MeshExportSettings MeshExportSettings => new MeshExportSettings
{
UseSparseAccessorForMorphTarget = UseSparseAccessor,
ExportOnlyBlendShapePosition = OnlyBlendshapePosition,
DivideVertexBuffer = DivideVertexBuffer,
};
public GameObject Root { get; set; }

View File

@@ -36,47 +36,12 @@ namespace VRM
}
}
/// <summary>
/// エクスポート時にヒエラルキーの正規化を実施する
/// </summary>
[Tooltip("Require only first time")]
public bool PoseFreeze = true;
/// <summary>
/// エクスポート時に新しいJsonSerializerを使う
/// </summary>
[Tooltip("Use new JSON serializer")]
public bool UseExperimentalExporter = false;
/// <summary>
/// BlendShapeのシリアライズにSparseAccessorを使う
/// </summary>
[Tooltip("Use sparse accessor for blendshape. This may reduce vrm size")]
public bool UseSparseAccessor = false;
/// <summary>
/// BlendShapeのPositionのみをエクスポートする
/// </summary>
[Tooltip("UniVRM-0.54 or later can load it. Otherwise fail to load")]
public bool OnlyBlendshapePosition = false;
/// <summary>
/// エクスポート時にBlendShapeClipから参照されないBlendShapeを削除する
/// </summary>
[Tooltip("Remove blendshape that is not used from BlendShapeClip")]
public bool ReduceBlendshape = false;
/// <summary>
/// skip if BlendShapeClip.Preset == Unknown
/// </summary>
[Tooltip("Remove blendShapeClip that preset is Unknown")]
public bool ReduceBlendshapeClip = false;
CheckBoxProp m_poseFreeze;
CheckBoxProp m_useSparseAccessor;
CheckBoxProp m_onlyBlendShapePosition;
CheckBoxProp m_reduceBlendShape;
CheckBoxProp m_reduceBlendShapeClip;
CheckBoxProp m_divideVertexBuffer;
static string Msg(Options key)
{
@@ -128,15 +93,20 @@ namespace VRM
[LangMsg(Languages.ja, "T-Pose にする")]
[LangMsg(Languages.en, "Make T-Pose")]
DO_TPOSE,
[LangMsg(Languages.ja, "頂点バッファをsubmeshで分割する。GLTF互換性のため。UniVRM-0.72 からロードできる。")]
[LangMsg(Languages.en, "Divide vertex buffer by submesh。For more gltf compatibility。UniVRM-0.72 or later can load.")]
DIVIDE_VERTEX_BUFFER,
}
private void OnEnable()
{
m_poseFreeze = new CheckBoxProp(serializedObject.FindProperty(nameof(PoseFreeze)), Options.NORMALIZE);
m_useSparseAccessor = new CheckBoxProp(serializedObject.FindProperty(nameof(UseSparseAccessor)), Options.BLENDSHAPE_USE_SPARSE);
m_onlyBlendShapePosition = new CheckBoxProp(serializedObject.FindProperty(nameof(OnlyBlendshapePosition)), Options.BLENDSHAPE_EXCLUDE_NORMAL_AND_TANGENT);
m_reduceBlendShape = new CheckBoxProp(serializedObject.FindProperty(nameof(ReduceBlendshape)), Options.BLENDSHAPE_ONLY_CLIP_USE);
m_reduceBlendShapeClip = new CheckBoxProp(serializedObject.FindProperty(nameof(ReduceBlendshapeClip)), Options.BLENDSHAPE_EXCLUDE_UNKNOWN);
m_poseFreeze = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.PoseFreeze)), Options.NORMALIZE);
m_useSparseAccessor = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.UseSparseAccessor)), Options.BLENDSHAPE_USE_SPARSE);
m_onlyBlendShapePosition = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.OnlyBlendshapePosition)), Options.BLENDSHAPE_EXCLUDE_NORMAL_AND_TANGENT);
m_reduceBlendShape = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.ReduceBlendshape)), Options.BLENDSHAPE_ONLY_CLIP_USE);
m_reduceBlendShapeClip = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.ReduceBlendshapeClip)), Options.BLENDSHAPE_EXCLUDE_UNKNOWN);
m_divideVertexBuffer = new CheckBoxProp(serializedObject.FindProperty(nameof(VRMExportSettings.DivideVertexBuffer)), Options.DIVIDE_VERTEX_BUFFER);
}
public override void OnInspectorGUI()
@@ -173,6 +143,8 @@ namespace VRM
m_onlyBlendShapePosition.Draw();
m_reduceBlendShape.Draw();
m_reduceBlendShapeClip.Draw();
m_divideVertexBuffer.Draw();
serializedObject.ApplyModifiedProperties();
}
}