diff --git a/Assets/UniGLTF/Editor/UniGLTF/GltfExportSettings.cs b/Assets/UniGLTF/Editor/UniGLTF/GltfExportSettings.cs index 843e47a5e..54101e083 100644 --- a/Assets/UniGLTF/Editor/UniGLTF/GltfExportSettings.cs +++ b/Assets/UniGLTF/Editor/UniGLTF/GltfExportSettings.cs @@ -14,5 +14,7 @@ namespace UniGLTF public bool Sparse; public bool DropNormal; + + public bool DivideVertexBuffer; } } diff --git a/Assets/UniGLTF/Editor/UniGLTF/GltfExportWindow.cs b/Assets/UniGLTF/Editor/UniGLTF/GltfExportWindow.cs index 4842878dc..d8e22e5c4 100644 --- a/Assets/UniGLTF/Editor/UniGLTF/GltfExportWindow.cs +++ b/Assets/UniGLTF/Editor/UniGLTF/GltfExportWindow.cs @@ -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); } } diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs new file mode 100644 index 000000000..4af2f6290 --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs @@ -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 + }; + } +} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs.meta b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs.meta new file mode 100644 index 000000000..78ce31750 --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExportSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 26b97430e1698e94e9a27ab322f3eee5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs index 314f2a8a6..7b0cd7cfb 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporter.cs @@ -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 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(); - 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, + /// + /// primitive 間で vertex を共有する形で Export する。 + /// + /// UniVRM-0.71.0 までの挙動 + /// + /// /// + /// + /// + /// + /// + /// + /// + /// + static glTFMesh ExportSharedVertexBuffer(glTF gltf, int bufferIndex, MeshWithRenderer unityMesh, List 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)> ExportMeshes(glTF gltf, int bufferIndex, - List unityMeshes, List unityMaterials, + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static (glTFMesh mesh, Dictionary blendShapeIndexMap) ExportMesh(glTF gltf, int bufferIndex, + MeshWithRenderer unityMesh, List unityMaterials, MeshExportSettings settings, IAxisInverter axisInverter) { - foreach (var unityMesh in unityMeshes) + glTFMesh gltfMesh = default; + var blendShapeIndexMap = new Dictionary(); + 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(); - var blendShapeIndexMap = new Dictionary(); 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 blendShapeIndexMap) ExportMesh(glTF gltf, int bufferIndex, Mesh mesh, Renderer renderer, List 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(); - - var blendShapeIndexMap = new Dictionary(); - 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); } diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs new file mode 100644 index 000000000..3b876f87a --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs @@ -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 m_positions; + readonly List m_normals; + + public BlendShapeBuffer(int reserve) + { + m_positions = new List(reserve); + m_normals = new List(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 m_positions; + readonly List m_normals; + readonly List m_uv; + + readonly Func m_getJointIndex; + readonly List m_joints; + readonly List m_weights; + + public VertexBuffer(int reserve, Func getJointIndex) + { + m_positions = new List(reserve); + m_normals = new List(reserve); + m_uv = new List(); + + m_getJointIndex = getJointIndex; + if (m_getJointIndex != null) + { + m_joints = new List(reserve); + m_weights = new List(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 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 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 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(); + 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(); + 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; + } + } +} diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs.meta b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs.meta new file mode 100644 index 000000000..6a31e5d50 --- /dev/null +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshExporterDivided.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92bc82be5ead84a429346379590f6d7d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs index 9c67c42cb..1ee16c1e8 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/MeshWithRenderer.cs @@ -37,6 +37,12 @@ namespace UniGLTF } } + /// + /// glTF は skinning の boneList の重複を許可しない + /// (unity は ok) + /// + /// + /// public int GetJointIndex(int index) { if (index < 0) diff --git a/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs b/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs index 3f54a66c5..0fa288dc3 100644 --- a/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs +++ b/Assets/UniGLTF/Runtime/UniGLTF/IO/gltfExporter.cs @@ -17,11 +17,7 @@ namespace UniGLTF protected set; } - public List Meshes - { - get; - private set; - } + public List Meshes { get; private set; } = new List(); /// /// 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>(); - 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 diff --git a/Assets/UniGLTF/Tests/UniGLTF/MeshTests.cs b/Assets/UniGLTF/Tests/UniGLTF/MeshTests.cs index 840fd67f7..9f4e6a112 100644 --- a/Assets/UniGLTF/Tests/UniGLTF/MeshTests.cs +++ b/Assets/UniGLTF/Tests/UniGLTF/MeshTests.cs @@ -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 + buffers = new List { new glTFBuffer { @@ -39,7 +40,7 @@ namespace UniGLTF new glTFBufferView{ buffer=0, byteLength=32, - byteOffset=0, + byteOffset=0, } }, accessors = new List @@ -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)); } + + /// + /// 0 1 2 + /// +-+-+ + /// |A|B| + /// +-+-+ + /// 5 4 3 + /// + /// 6 vertices + /// 4 triangles + /// 2 materials + /// + /// + 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().sharedMaterials = materials; + go.AddComponent().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{ + 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(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{ + 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(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(gltfMesh.primitives[1].attributes.POSITION); + Assert.AreEqual(4, positions.Length); + } + } } } diff --git a/Assets/VRM/Editor/Format/VRMExportSettings.cs b/Assets/VRM/Editor/Format/VRMExportSettings.cs index 2e0f2e89e..66cba297d 100644 --- a/Assets/VRM/Editor/Format/VRMExportSettings.cs +++ b/Assets/VRM/Editor/Format/VRMExportSettings.cs @@ -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 { + /// /// エクスポート時に強制的にT-Pose化する /// @@ -43,10 +44,17 @@ namespace VRM [Tooltip("Remove blendShapeClip that preset is Unknown")] public bool ReduceBlendshapeClip = false; - public UniGLTF.MeshExportSettings MeshExportSettings => new UniGLTF.MeshExportSettings + /// + /// Export時に頂点バッファをsubmeshで分割する。GLTF互換性 + /// + [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; } diff --git a/Assets/VRM/Editor/Format/VRMExportSettingsEditor.cs b/Assets/VRM/Editor/Format/VRMExportSettingsEditor.cs index 6600a4a4b..3e883b92a 100644 --- a/Assets/VRM/Editor/Format/VRMExportSettingsEditor.cs +++ b/Assets/VRM/Editor/Format/VRMExportSettingsEditor.cs @@ -36,47 +36,12 @@ namespace VRM } } - /// - /// エクスポート時にヒエラルキーの正規化を実施する - /// - [Tooltip("Require only first time")] - public bool PoseFreeze = true; - - /// - /// エクスポート時に新しいJsonSerializerを使う - /// - [Tooltip("Use new JSON serializer")] - public bool UseExperimentalExporter = false; - - /// - /// BlendShapeのシリアライズにSparseAccessorを使う - /// - [Tooltip("Use sparse accessor for blendshape. This may reduce vrm size")] - public bool UseSparseAccessor = false; - - /// - /// BlendShapeのPositionのみをエクスポートする - /// - [Tooltip("UniVRM-0.54 or later can load it. Otherwise fail to load")] - public bool OnlyBlendshapePosition = false; - - /// - /// エクスポート時にBlendShapeClipから参照されないBlendShapeを削除する - /// - [Tooltip("Remove blendshape that is not used from BlendShapeClip")] - public bool ReduceBlendshape = false; - - /// - /// skip if BlendShapeClip.Preset == Unknown - /// - [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(); } }