Merge pull request #1007 from ousttrue/feature/impl_morph_target_sparse_for_divided

既存の morphTarget の sparse export を整理して、divided 頂点バッファでも動くようにした
This commit is contained in:
ousttrue
2021-06-07 19:18:08 +09:00
committed by GitHub
9 changed files with 192 additions and 135 deletions

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UniGLTF
{
public static class BlendShapeExporter
{
public static gltfMorphTarget Export(glTF gltf, int gltfBuffer, Vector3[] positions, Vector3[] normals, bool useSparse)
{
var accessorCount = positions.Length;
if (normals != null && positions.Length != normals.Length)
{
throw new Exception();
}
int[] sparseIndices = default;
if (useSparse)
{
sparseIndices = Enumerable.Range(0, positions.Length).Where(x => positions[x] != Vector3.zero).ToArray();
if (sparseIndices.Length == 0)
{
// sparse 対象がすべて [0, 0, 0] の場合
// new glTFSparse
// {
// count = 0,
// }
// のようになる。
// たぶん、仕様的にはあり。
// 解釈できない場合あり。
useSparse = false;
}
}
if (useSparse)
{
if (sparseIndices == null)
{
throw new Exception();
}
// positions
var positionAccessorIndex = -1;
if (sparseIndices.Length > 0)
{
Debug.LogFormat("Sparse {0}/{1}", sparseIndices.Length, positions.Length);
var sparseIndicesViewIndex = gltf.ExtendBufferAndGetViewIndex(gltfBuffer, sparseIndices);
positionAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(gltfBuffer, accessorCount, positions, sparseIndices, sparseIndicesViewIndex, glBufferTarget.NONE);
}
// normals
var normalAccessorIndex = -1;
if (normals != null)
{
var sparseNormalIndices = Enumerable.Range(0, positions.Length).Where(x => normals[x] != Vector3.zero).ToArray();
if (sparseNormalIndices.Length > 0)
{
var sparseNormalIndicesViewIndex = gltf.ExtendBufferAndGetViewIndex(gltfBuffer, sparseNormalIndices);
normalAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(gltfBuffer, accessorCount, normals, sparseNormalIndices, sparseNormalIndicesViewIndex, glBufferTarget.NONE);
}
}
return new gltfMorphTarget
{
POSITION = positionAccessorIndex,
NORMAL = normalAccessorIndex,
};
}
else
{
// position
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(gltfBuffer, 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();
// normal
var normalAccessorIndex = -1;
if (normals != null)
{
normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(gltfBuffer, normals, glBufferTarget.ARRAY_BUFFER);
}
return new gltfMorphTarget
{
POSITION = positionAccessorIndex,
NORMAL = normalAccessorIndex,
};
}
}
}
}

View File

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

View File

@@ -15,37 +15,27 @@ namespace UniGLTF
{
public class BlendShapeBuffer
{
readonly List<Vector3> m_positions;
readonly List<Vector3> m_normals;
readonly Vector3[] m_positions;
readonly Vector3[] m_normals;
public BlendShapeBuffer(int reserve)
{
m_positions = new List<Vector3>(reserve);
m_normals = new List<Vector3>(reserve);
m_positions = new Vector3[reserve];
m_normals = new Vector3[reserve];
}
public void Push(Vector3 position, Vector3 normal)
public void Set(int index, Vector3 position, Vector3 normal)
{
m_positions.Add(position);
m_normals.Add(normal);
m_positions[index] = position;
m_normals[index] = normal;
}
public gltfMorphTarget ToGltf(glTF gltf, int bufferIndex, bool useNormal)
public gltfMorphTarget ToGltf(glTF gltf, int gltfBuffer, bool useNormal, bool useSparse)
{
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_positions.ToArray(), glBufferTarget.ARRAY_BUFFER);
gltf.accessors[positionAccessorIndex].min = m_positions.Aggregate(m_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 = m_positions.Aggregate(m_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 = -1;
if (useNormal)
{
normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_normals.ToArray(), glBufferTarget.ARRAY_BUFFER);
}
return new gltfMorphTarget
{
POSITION = positionAccessorIndex,
NORMAL = normalAccessorIndex,
};
return BlendShapeExporter.Export(gltf, gltfBuffer,
m_positions,
useNormal ? m_normals : null,
useSparse);
}
}
@@ -116,8 +106,10 @@ namespace UniGLTF
public glTFPrimitives ToGltfPrimitive(glTF gltf, int bufferIndex, int materialIndex, IEnumerable<int> indices)
{
var indicesAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, indices.Select(x => (uint)m_vertexIndexMap[x]).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 positions = m_positions.ToArray();
var positionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, positions, glBufferTarget.ARRAY_BUFFER);
var normals = m_normals.ToArray();
var normalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, normals, glBufferTarget.ARRAY_BUFFER);
var uvAccessorIndex0 = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex, m_uv.ToArray(), glBufferTarget.ARRAY_BUFFER);
int? jointsAccessorIndex = default;

View File

@@ -7,7 +7,17 @@ namespace UniGLTF
{
public static class MeshExporter_DividedVertexBuffer
{
public static (glTFMesh, Dictionary<int, int>) Export(glTF gltf, int bufferIndex,
/// <summary>
/// Divide vertex buffer(Position, Normal, UV, VertexColor, Skinning and BlendShapes) by submesh usage, then export
/// </summary>
/// <param name="gltf"></param>
/// <param name="gltfBuffer"></param>
/// <param name="unityMesh"></param>
/// <param name="unityMaterials"></param>
/// <param name="axisInverter"></param>
/// <param name="settings"></param>
/// <returns></returns>
public static (glTFMesh, Dictionary<int, int>) Export(glTF gltf, int gltfBuffer,
MeshExportInfo unityMesh, List<Material> unityMaterials,
IAxisInverter axisInverter, MeshExportSettings settings)
{
@@ -16,7 +26,7 @@ namespace UniGLTF
if (settings.ExportTangents)
{
// support しない
// no support
throw new NotImplementedException();
}
@@ -40,15 +50,14 @@ namespace UniGLTF
var indices = mesh.GetIndices(i);
var hash = new HashSet<int>(indices);
// mesh
// index の順に attributes を蓄える
// aggrigate vertex attributes
var buffer = new MeshExportUtil.VertexBuffer(indices.Length, getJointIndex);
usedIndices.Clear();
for (int k = 0; k < positions.Length; ++k)
{
if (hash.Contains(k))
{
// indices から参照される頂点だけを蓄える
// aggrigate indices
usedIndices.Add(k);
buffer.Push(k, axisInverter.InvertVector3(positions[k]), axisInverter.InvertVector3(normals[k]), uv[k].ReverseUV());
if (getJointIndex != null)
@@ -75,23 +84,25 @@ namespace UniGLTF
flipped.Add(t1);
flipped.Add(t0);
}
var gltfPrimitive = buffer.ToGltfPrimitive(gltf, bufferIndex, materialIndex, flipped);
var gltfPrimitive = buffer.ToGltfPrimitive(gltf, gltfBuffer, materialIndex, flipped);
// blendShape
// blendShape(morph target)
for (int j = 0; j < mesh.blendShapeCount; ++j)
{
var blendShape = new MeshExportUtil.BlendShapeBuffer(indices.Length);
// index の順に attributes を蓄える
// aggriage morph target
mesh.GetBlendShapeFrameVertices(j, 0, blendShapePositions, blendShapeNormals, null);
int l = 0;
foreach (var k in usedIndices)
{
blendShape.Push(
blendShape.Set(l++,
axisInverter.InvertVector3(blendShapePositions[k]),
axisInverter.InvertVector3(blendShapeNormals[k]));
}
gltfPrimitive.targets.Add(blendShape.ToGltf(gltf, bufferIndex, !settings.ExportOnlyBlendShapePosition));
gltfPrimitive.targets.Add(blendShape.ToGltf(gltf, gltfBuffer, !settings.ExportOnlyBlendShapePosition,
settings.UseSparseAccessorForMorphTarget));
}
gltfMesh.primitives.Add(gltfPrimitive);

View File

@@ -10,11 +10,12 @@ namespace UniGLTF
{
/// <summary>
/// primitive 間で vertex を共有する形で Export する。
/// UniVRM-0.71.0 以降は、MeshExporterDivided.Export もある。
///
/// UniVRM-0.71.0 までの挙動
/// * GLB/GLTF は shared(default) と divided を選択可能
/// * VRM0 は shared 仕様
/// * VRM1 は divided 仕様
///
/// UniVRM-0.71.0 以降は、MeshExporterDivided.Export もある
///
/// /// </summary>
/// <param name="gltf"></param>
/// <param name="bufferIndex"></param>
@@ -159,12 +160,13 @@ namespace UniGLTF
unityMesh.Mesh, j,
settings.UseSparseAccessorForMorphTarget,
settings.ExportOnlyBlendShapePosition, axisInverter);
if (morphTarget.POSITION < 0 && morphTarget.NORMAL < 0 && morphTarget.TANGENT < 0)
if (morphTarget.POSITION < 0)
{
// Skip empty blendShape.
// Shift blendShape's index.
continue;
}
// maybe skip
var blendShapeName = unityMesh.Mesh.GetBlendShapeName(j);
blendShapeIndexMap.Add(j, exportBlendShapes++);
targetNames.Add(blendShapeName);
@@ -199,7 +201,7 @@ namespace UniGLTF
}
static gltfMorphTarget ExportMorphTarget(glTF gltf, int bufferIndex,
Mesh mesh, int j,
Mesh mesh, int blendShapeIndex,
bool useSparseAccessorForMorphTarget,
bool exportOnlyBlendShapePosition,
IAxisInverter axisInverter)
@@ -211,106 +213,41 @@ namespace UniGLTF
var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length;
// var useNormal = usePosition && blendShapeNormals != null && blendShapeNormals.Length == blendShapeVertices.Length && !exportOnlyBlendShapePosition;
var blendShapeTangents = mesh.tangents.Select(y => (Vector3)y).ToArray();
//var useTangent = usePosition && blendShapeTangents != null && blendShapeTangents.Length == blendShapeVertices.Length;
var useTangent = false;
// var blendShapeTangents = mesh.tangents.Select(y => (Vector3)y).ToArray();
// //var useTangent = usePosition && blendShapeTangents != null && blendShapeTangents.Length == blendShapeVertices.Length;
// var useTangent = false;
var frameCount = mesh.GetBlendShapeFrameCount(j);
mesh.GetBlendShapeFrameVertices(j, frameCount - 1, blendShapeVertices, blendShapeNormals, null);
var frameCount = mesh.GetBlendShapeFrameCount(blendShapeIndex);
mesh.GetBlendShapeFrameVertices(blendShapeIndex, frameCount - 1, blendShapeVertices, blendShapeNormals, null);
var blendShapePositionAccessorIndex = -1;
var blendShapeNormalAccessorIndex = -1;
var blendShapeTangentAccessorIndex = -1;
if (useSparseAccessorForMorphTarget)
//
// invert axis
//
for (int i = 0; i < blendShapeVertices.Length; ++i)
{
var accessorCount = blendShapeVertices.Length;
var sparseIndices = Enumerable.Range(0, blendShapeVertices.Length)
.Where(x => UseSparse(
usePosition, blendShapeVertices[x],
useNormal, blendShapeNormals[x],
useTangent, blendShapeTangents[x]))
.ToArray()
;
if (sparseIndices.Length == 0)
{
usePosition = false;
useNormal = false;
useTangent = false;
}
else
{
Debug.LogFormat("Sparse {0}/{1}", sparseIndices.Length, mesh.vertexCount);
}
var sparseIndicesViewIndex = -1;
if (usePosition)
{
sparseIndicesViewIndex = gltf.ExtendBufferAndGetViewIndex(bufferIndex, sparseIndices);
blendShapeVertices = sparseIndices.Select(x => axisInverter.InvertVector3(blendShapeVertices[x])).ToArray();
blendShapePositionAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount,
blendShapeVertices,
sparseIndices, sparseIndicesViewIndex,
glBufferTarget.NONE);
}
if (useNormal)
{
blendShapeNormals = sparseIndices.Select(x => axisInverter.InvertVector3(blendShapeNormals[x])).ToArray();
blendShapeNormalAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount,
blendShapeNormals,
sparseIndices, sparseIndicesViewIndex,
glBufferTarget.NONE);
}
if (useTangent)
{
blendShapeTangents = sparseIndices.Select(x => axisInverter.InvertVector3(blendShapeTangents[x])).ToArray();
blendShapeTangentAccessorIndex = gltf.ExtendSparseBufferAndGetAccessorIndex(bufferIndex, accessorCount,
blendShapeTangents, sparseIndices, sparseIndicesViewIndex,
glBufferTarget.NONE);
}
blendShapeVertices[i] = axisInverter.InvertVector3(blendShapeVertices[i]);
}
else
for (int i = 0; i < blendShapeNormals.Length; ++i)
{
for (int i = 0; i < blendShapeVertices.Length; ++i) blendShapeVertices[i] = axisInverter.InvertVector3(blendShapeVertices[i]);
if (usePosition)
{
blendShapePositionAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex,
blendShapeVertices,
glBufferTarget.ARRAY_BUFFER);
}
if (useNormal)
{
for (int i = 0; i < blendShapeNormals.Length; ++i) blendShapeNormals[i] = axisInverter.InvertVector3(blendShapeNormals[i]);
blendShapeNormalAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex,
blendShapeNormals,
glBufferTarget.ARRAY_BUFFER);
}
if (useTangent)
{
for (int i = 0; i < blendShapeTangents.Length; ++i) blendShapeTangents[i] = axisInverter.InvertVector3(blendShapeTangents[i]);
blendShapeTangentAccessorIndex = gltf.ExtendBufferAndGetAccessorIndex(bufferIndex,
blendShapeTangents,
glBufferTarget.ARRAY_BUFFER);
}
blendShapeNormals[i] = axisInverter.InvertVector3(blendShapeNormals[i]);
}
if (blendShapePositionAccessorIndex != -1)
var positions = mesh.vertices;
for (int i = 0; i < positions.Length; ++i)
{
gltf.accessors[blendShapePositionAccessorIndex].min = blendShapeVertices.Aggregate(blendShapeVertices[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[blendShapePositionAccessorIndex].max = blendShapeVertices.Aggregate(blendShapeVertices[0], (a, b) => new Vector3(Mathf.Max(a.x, b.x), Math.Max(a.y, b.y), Mathf.Max(a.z, b.z))).ToArray();
positions[i] = axisInverter.InvertVector3(positions[i]);
}
return new gltfMorphTarget
var normals = mesh.normals;
for (int i = 0; i < normals.Length; ++i)
{
POSITION = blendShapePositionAccessorIndex,
NORMAL = blendShapeNormalAccessorIndex,
TANGENT = blendShapeTangentAccessorIndex,
};
normals[i] = axisInverter.InvertVector3(normals[i]);
}
return BlendShapeExporter.Export(gltf, bufferIndex,
blendShapeVertices,
exportOnlyBlendShapePosition && useNormal ? null : blendShapeNormals,
useSparseAccessorForMorphTarget);
}
}
}

View File

@@ -62,9 +62,9 @@ namespace UniVRM10
return "cannot migrate";
}
}
catch (Exception)
catch (Exception ex)
{
return "migration error";
return $"migration error: {ex}";
}
parser = new GltfParser();

View File

@@ -93,7 +93,15 @@ namespace UniVRM10
{
var morphTarget = src.Meshes[meshIndex].MorphTargets[i];
positions.AddRange(morphTarget.VertexBuffer.Positions.GetSpan<Vector3>());
normals.AddRange(morphTarget.VertexBuffer.Normals.GetSpan<Vector3>());
if (morphTarget.VertexBuffer.Normals != null)
{
normals.AddRange(morphTarget.VertexBuffer.Normals.GetSpan<Vector3>());
}
else
{
// fill zero
normals.AddRange(Enumerable.Range(0, morphTarget.VertexBuffer.Count).Select(x => Vector3.zero));
}
}
dst.AddBlendShapeFrame(name, 100.0f, positions.ToArray(), normals.ToArray(), null);
}

View File

@@ -126,7 +126,7 @@ namespace UniVRM10
// blendShape
for (int j = 0; j < mesh.MorphTargets.Count; ++j)
{
var blendShape = new MeshExportUtil.BlendShapeBuffer(indices.Length);
var blendShape = new MeshExportUtil.BlendShapeBuffer(usedIndices.Count);
// index の順に attributes を蓄える
var morph = mesh.MorphTargets[j];
@@ -136,15 +136,16 @@ namespace UniVRM10
{
blendShapeNormals = morph.VertexBuffer.Normals.GetSpan<UnityEngine.Vector3>();
}
int l = 0;
foreach (var k in usedIndices)
{
blendShape.Push(
blendShape.Set(l++,
blendShapePositions[k],
blendShapeNormals.HasValue ? blendShapeNormals.Value[k] : UnityEngine.Vector3.zero
);
}
gltfPrimitive.targets.Add(blendShape.ToGltf(storage.Gltf, bufferIndex, !option.removeMorphNormal));
gltfPrimitive.targets.Add(blendShape.ToGltf(storage.Gltf, bufferIndex, !option.removeMorphNormal, option.sparse));
}
yield return gltfPrimitive;

View File

@@ -49,6 +49,11 @@ namespace UniVRM10
static void ReverseVector3Array(glTF gltf, int accessorIndex, HashSet<int> used)
{
if (accessorIndex == -1)
{
return;
}
if (!used.Add(accessorIndex))
{
return;