separate file

This commit is contained in:
ousttrue
2023-10-26 16:06:53 +09:00
parent 4e18060786
commit 16d17a7951
8 changed files with 498 additions and 436 deletions

View File

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UniGLTF.MeshUtility
{
class BlendShapeReport
{
string m_name;
int m_count;
struct BlendShapeStat
{
public int Index;
public string Name;
public int VertexCount;
public int NormalCount;
public int TangentCount;
public override string ToString()
{
return string.Format("[{0}]{1}: {2}, {3}, {4}\n", Index, Name, VertexCount, NormalCount, TangentCount);
}
}
List<BlendShapeStat> m_stats = new List<BlendShapeStat>();
public int Count
{
get { return m_stats.Count; }
}
public BlendShapeReport(Mesh mesh)
{
m_name = mesh.name;
m_count = mesh.vertexCount;
}
public void SetCount(int index, string name, int v, int n, int t)
{
m_stats.Add(new BlendShapeStat
{
Index = index,
Name = name,
VertexCount = v,
NormalCount = n,
TangentCount = t,
});
}
public override string ToString()
{
return String.Format("NormalizeSkinnedMesh: {0}({1}verts)\n{2}",
m_name,
m_count,
String.Join("", m_stats.Select(x => x.ToString()).ToArray()));
}
}
}

View File

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

View File

@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VRMShaders;
namespace UniGLTF.MeshUtility
{
public static class BoneNormalizer
{
public delegate Avatar CreateAvatarFunc(GameObject original, GameObject normalized, Dictionary<Transform, Transform> boneMap);
public static (GameObject, Dictionary<Transform, Transform>) CreateNormalizedHierarchy(GameObject go,
bool removeScaling = true,
bool removeRotation = true)
@@ -74,430 +70,6 @@ namespace UniGLTF.MeshUtility
}
}
class BlendShapeReport
{
string m_name;
int m_count;
struct BlendShapeStat
{
public int Index;
public string Name;
public int VertexCount;
public int NormalCount;
public int TangentCount;
public override string ToString()
{
return string.Format("[{0}]{1}: {2}, {3}, {4}\n", Index, Name, VertexCount, NormalCount, TangentCount);
}
}
List<BlendShapeStat> m_stats = new List<BlendShapeStat>();
public int Count
{
get { return m_stats.Count; }
}
public BlendShapeReport(Mesh mesh)
{
m_name = mesh.name;
m_count = mesh.vertexCount;
}
public void SetCount(int index, string name, int v, int n, int t)
{
m_stats.Add(new BlendShapeStat
{
Index = index,
Name = name,
VertexCount = v,
NormalCount = n,
TangentCount = t,
});
}
public override string ToString()
{
return String.Format("NormalizeSkinnedMesh: {0}({1}verts)\n{2}",
m_name,
m_count,
String.Join("", m_stats.Select(x => x.ToString()).ToArray()));
}
}
/// <summary>
/// index が 有効であれば、setter に weight を渡す。無効であれば setter に 0 を渡す。
/// </summary>
/// <param name="indexMap"></param>
/// <param name="srcIndex"></param>
/// <param name="weight"></param>
/// <param name="setter"></param>
static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action<int, float> setter)
{
if (srcIndex < 0 || srcIndex >= indexMap.Length)
{
// ありえるかどうかわからないが BoneWeight.boneIndexN に変な値が入っている.
setter(0, 0);
return false;
}
var dstIndex = indexMap[srcIndex];
if (dstIndex != -1)
{
// 有効なindex。weightをコピーする
setter(dstIndex, weight);
return true;
}
else
{
// 無効なindex。0でクリアする
setter(0, 0);
return false;
}
}
/// <summary>
/// BoneWeight[] src から新しいボーンウェイトを作成する。
/// </summary>
/// <param name="src">変更前のBoneWeight[]</param>
/// <param name="boneMap">新旧のボーンの対応表。新しい方は無効なボーンが除去されてnullの部分がある</param>
/// <param name="srcBones">変更前のボーン配列</param>
/// <param name="dstBones">変更後のボーン配列。除去されたボーンがある場合、変更前より短い</param>
/// <returns></returns>
public static BoneWeight[] MapBoneWeight(BoneWeight[] src,
Dictionary<Transform, Transform> boneMap,
Transform[] srcBones,
Transform[] dstBones
)
{
// 処理前後の index の対応表を作成する
var indexMap = new int[srcBones.Length];
for (int i = 0; i < srcBones.Length; ++i)
{
var srcBone = srcBones[i];
if (srcBone == null)
{
// 元のボーンが無い
indexMap[i] = -1;
Debug.LogWarningFormat("bones[{0}] is null", i);
}
else
{
if (boneMap.TryGetValue(srcBone, out Transform dstBone))
{
// 対応するボーンが存在する
var dstIndex = Array.IndexOf(dstBones, dstBone);
if (dstIndex == -1)
{
// ありえない。バグ
throw new Exception();
}
indexMap[i] = dstIndex;
}
else
{
// 先のボーンが無い
indexMap[i] = -1;
Debug.LogWarningFormat("{0} is removed", srcBone.name);
}
}
}
// 新しいBoneWeightを作成する
var newBoneWeights = new BoneWeight[src.Length];
for (int i = 0; i < src.Length; ++i)
{
BoneWeight srcBoneWeight = src[i];
// 0
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex0, srcBoneWeight.weight0, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex0 = newIndex;
newBoneWeights[i].weight0 = newWeight;
});
// 1
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex1, srcBoneWeight.weight1, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex1 = newIndex;
newBoneWeights[i].weight1 = newWeight;
});
// 2
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex2, srcBoneWeight.weight2, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex2 = newIndex;
newBoneWeights[i].weight2 = newWeight;
});
// 3
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex3, srcBoneWeight.weight3, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex3 = newIndex;
newBoneWeights[i].weight3 = newWeight;
});
}
return newBoneWeights;
}
/// <summary>
/// srcのSkinnedMeshRendererを正規化して、dstにアタッチする
/// </summary>
/// <param name="src">正規化前のSkinnedMeshRendererのTransform</param>
/// <param name="dst">正規化後のSkinnedMeshRendererのTransform</param>
/// <param name="boneMap">正規化前のボーンから正規化後のボーンを得る</param>
static void NormalizeSkinnedMesh(Transform src, Transform dst, Dictionary<Transform, Transform> boneMap)
{
var srcRenderer = src.GetComponent<SkinnedMeshRenderer>();
if (srcRenderer == null
|| !srcRenderer.enabled
|| srcRenderer.sharedMesh == null
|| srcRenderer.sharedMesh.vertexCount == 0)
{
// 有効なSkinnedMeshRendererが無かった
return;
}
var srcMesh = srcRenderer.sharedMesh;
var originalSrcMesh = srcMesh;
// 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る
var dstBones = srcRenderer.bones
.Where(x => x != null && boneMap.ContainsKey(x))
.Select(x => boneMap[x])
.ToArray();
var hasBoneWeight = srcRenderer.bones != null && srcRenderer.bones.Length > 0;
if (!hasBoneWeight)
{
// Before bake, bind no weight bones
//Debug.LogFormat("no weight: {0}", srcMesh.name);
srcMesh = srcMesh.Copy(true);
var bw = new BoneWeight
{
boneIndex0 = 0,
boneIndex1 = 0,
boneIndex2 = 0,
boneIndex3 = 0,
weight0 = 1.0f,
weight1 = 0.0f,
weight2 = 0.0f,
weight3 = 0.0f,
};
srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray();
srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity };
srcRenderer.rootBone = srcRenderer.transform;
dstBones = new[] { boneMap[srcRenderer.transform] };
srcRenderer.bones = new[] { srcRenderer.transform };
srcRenderer.sharedMesh = srcMesh;
}
// BakeMesh
var mesh = srcMesh.Copy(false);
mesh.name = srcMesh.name + ".baked";
srcRenderer.BakeMesh(mesh);
var blendShapeValues = new Dictionary<int, float>();
for (int i = 0; i < srcMesh.blendShapeCount; i++)
{
var val = srcRenderer.GetBlendShapeWeight(i);
if (val > 0) blendShapeValues.Add(i, val);
}
// 新しい骨格のボーンウェイトを作成する
mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, srcRenderer.bones, dstBones);
// recalc bindposes
mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray();
//var m = src.localToWorldMatrix; // include scaling
var m = default(Matrix4x4);
m.SetTRS(Vector3.zero, src.rotation, Vector3.one); // rotation only
mesh.ApplyMatrix(m);
//
// BlendShapes
//
// clear blendShape always
var backcup = new List<float>();
for (int i = 0; i < srcMesh.blendShapeCount; ++i)
{
backcup.Add(srcRenderer.GetBlendShapeWeight(i));
srcRenderer.SetBlendShapeWeight(i, 0);
}
var meshVertices = mesh.vertices;
var meshNormals = mesh.normals;
var meshTangents = Array.Empty<Vector3>();
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray();
}
var originalBlendShapePositions = new Vector3[meshVertices.Length];
var originalBlendShapeNormals = new Vector3[meshVertices.Length];
var originalBlendShapeTangents = new Vector3[meshVertices.Length];
var report = new BlendShapeReport(srcMesh);
var blendShapeMesh = new Mesh();
for (int i = 0; i < srcMesh.blendShapeCount; ++i)
{
// check blendShape
srcRenderer.sharedMesh.GetBlendShapeFrameVertices(i, 0, originalBlendShapePositions, originalBlendShapeNormals, originalBlendShapeTangents);
var hasVertices = originalBlendShapePositions.Count(x => x != Vector3.zero);
var hasNormals = originalBlendShapeNormals.Count(x => x != Vector3.zero);
var hasTangents = 0;
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
hasTangents = originalBlendShapeTangents.Count(x => x != Vector3.zero);
}
var name = srcMesh.GetBlendShapeName(i);
if (string.IsNullOrEmpty(name))
{
name = String.Format("{0}", i);
}
report.SetCount(i, name, hasVertices, hasNormals, hasTangents);
srcRenderer.SetBlendShapeWeight(i, 100.0f);
srcRenderer.BakeMesh(blendShapeMesh);
if (blendShapeMesh.vertices.Length != mesh.vertices.Length)
{
throw new Exception("different vertex count");
}
var value = blendShapeValues.ContainsKey(i) ? blendShapeValues[i] : 0;
srcRenderer.SetBlendShapeWeight(i, value);
Vector3[] vertices = blendShapeMesh.vertices;
for (int j = 0; j < vertices.Length; ++j)
{
if (originalBlendShapePositions[j] == Vector3.zero)
{
vertices[j] = Vector3.zero;
}
else
{
vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j];
}
}
Vector3[] normals = blendShapeMesh.normals;
for (int j = 0; j < normals.Length; ++j)
{
if (originalBlendShapeNormals[j] == Vector3.zero)
{
normals[j] = Vector3.zero;
}
else
{
normals[j] = m.MultiplyVector(normals[j].normalized) - meshNormals[j];
}
}
Vector3[] tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray();
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
for (int j = 0; j < tangents.Length; ++j)
{
if (originalBlendShapeTangents[j] == Vector3.zero)
{
tangents[j] = Vector3.zero;
}
else
{
tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j];
}
}
}
var frameCount = srcMesh.GetBlendShapeFrameCount(i);
for (int f = 0; f < frameCount; f++)
{
var weight = srcMesh.GetBlendShapeFrameWeight(i, f);
try
{
mesh.AddBlendShapeFrame(name,
weight,
vertices,
hasNormals > 0 ? normals : null,
hasTangents > 0 ? tangents : null
);
}
catch (Exception)
{
Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}",
mesh.name,
srcMesh.GetBlendShapeName(i)
);
throw;
}
}
}
if (report.Count > 0)
{
Debug.LogFormat("{0}", report.ToString());
}
var dstRenderer = dst.gameObject.AddComponent<SkinnedMeshRenderer>();
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
if (srcRenderer.rootBone != null)
{
if (boneMap.TryGetValue(srcRenderer.rootBone, out Transform found))
{
dstRenderer.rootBone = found;
}
}
dstRenderer.bones = dstBones;
dstRenderer.sharedMesh = mesh;
if (!hasBoneWeight)
{
// restore bones
srcRenderer.bones = new Transform[] { };
srcRenderer.sharedMesh = originalSrcMesh;
}
// restore blendshape weights
for (int i = 0; i < backcup.Count; ++i)
{
srcRenderer.SetBlendShapeWeight(i, backcup[i]);
}
}
/// <summary>
///
/// </summary>
/// <param name="src"></param>
/// <param name="dst"></param>
static void NormalizeNoneSkinnedMesh(Transform src, Transform dst)
{
var srcFilter = src.GetComponent<MeshFilter>();
if (srcFilter == null
|| srcFilter.sharedMesh == null
|| srcFilter.sharedMesh.vertexCount == 0)
{
return;
}
var srcRenderer = src.GetComponent<MeshRenderer>();
if (srcRenderer == null || !srcRenderer.enabled)
{
return;
}
// Meshに乗っているボーンの姿勢を適用する
var dstFilter = dst.gameObject.AddComponent<MeshFilter>();
var dstMesh = srcFilter.sharedMesh.Copy(false);
dstMesh.ApplyRotationAndScale(src.localToWorldMatrix);
dstFilter.sharedMesh = dstMesh;
// Materialをコピー
var dstRenderer = dst.gameObject.AddComponent<MeshRenderer>();
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
}
/// <summary>
/// 回転とスケールを除去したヒエラルキーのコピーを作成する(MeshをBakeする)
@@ -506,12 +78,15 @@ namespace UniGLTF.MeshUtility
/// <param name="bakeCurrentBlendShape">BlendShapeを0クリアするか否か。false の場合 BlendShape の現状を Bake する</param>
/// <param name="createAvatar">Avatarを作る関数</param>
/// <returns></returns>
public static (GameObject, Dictionary<Transform, Transform>) Execute(GameObject go)
public static (GameObject, Dictionary<Transform, Transform>) NormalizeHierarchyFreezeMesh(GameObject go,
bool removeScaling = true,
bool removeRotation = true
)
{
//
// 正規化されたヒエラルキーを作る
//
var (normalized, boneMap) = CreateNormalizedHierarchy(go);
var (normalized, boneMap) = CreateNormalizedHierarchy(go, removeScaling, removeRotation);
//
// 各メッシュから回転・スケールを取り除いてBinding行列を再計算する
@@ -524,9 +99,42 @@ namespace UniGLTF.MeshUtility
continue;
}
NormalizeSkinnedMesh(src, dst, boneMap);
{
// SkinnedMeshRenderer
var srcRenderer = src.GetComponent<SkinnedMeshRenderer>();
var (mesh, dstBones) = MeshFreezer.NormalizeSkinnedMesh(srcRenderer, boneMap, dst.localToWorldMatrix);
if (mesh != null)
{
var dstRenderer = dst.gameObject.AddComponent<SkinnedMeshRenderer>();
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
if (srcRenderer.rootBone != null)
{
if (boneMap.TryGetValue(srcRenderer.rootBone, out Transform found))
{
dstRenderer.rootBone = found;
}
}
dstRenderer.bones = dstBones;
dstRenderer.sharedMesh = mesh;
}
}
NormalizeNoneSkinnedMesh(src, dst);
{
// MeshRenderer
var srcRenderer = src.GetComponent<MeshRenderer>();
if (srcRenderer != null)
{
var dstMesh = MeshFreezer.NormalizeNoneSkinnedMesh(srcRenderer);
if (dstMesh != null)
{
var dstFilter = dst.gameObject.AddComponent<MeshFilter>();
dstFilter.sharedMesh = dstMesh;
// Materialをコピー
var dstRenderer = dst.gameObject.AddComponent<MeshRenderer>();
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
}
}
}
}
return (normalized, boneMap);

View File

@@ -0,0 +1,375 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using VRMShaders;
namespace UniGLTF.MeshUtility
{
public static class MeshFreezer
{
/// <summary>
/// index が 有効であれば、setter に weight を渡す。無効であれば setter に 0 を渡す。
/// </summary>
/// <param name="indexMap"></param>
/// <param name="srcIndex"></param>
/// <param name="weight"></param>
/// <param name="setter"></param>
static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action<int, float> setter)
{
if (srcIndex < 0 || srcIndex >= indexMap.Length)
{
// ありえるかどうかわからないが BoneWeight.boneIndexN に変な値が入っている.
setter(0, 0);
return false;
}
var dstIndex = indexMap[srcIndex];
if (dstIndex != -1)
{
// 有効なindex。weightをコピーする
setter(dstIndex, weight);
return true;
}
else
{
// 無効なindex。0でクリアする
setter(0, 0);
return false;
}
}
/// <summary>
/// BoneWeight[] src から新しいボーンウェイトを作成する。
/// </summary>
/// <param name="src">変更前のBoneWeight[]</param>
/// <param name="boneMap">新旧のボーンの対応表。新しい方は無効なボーンが除去されてnullの部分がある</param>
/// <param name="srcBones">変更前のボーン配列</param>
/// <param name="dstBones">変更後のボーン配列。除去されたボーンがある場合、変更前より短い</param>
/// <returns></returns>
public static BoneWeight[] MapBoneWeight(BoneWeight[] src,
Dictionary<Transform, Transform> boneMap,
Transform[] srcBones,
Transform[] dstBones
)
{
// 処理前後の index の対応表を作成する
var indexMap = new int[srcBones.Length];
for (int i = 0; i < srcBones.Length; ++i)
{
var srcBone = srcBones[i];
if (srcBone == null)
{
// 元のボーンが無い
indexMap[i] = -1;
Debug.LogWarningFormat("bones[{0}] is null", i);
}
else
{
if (boneMap.TryGetValue(srcBone, out Transform dstBone))
{
// 対応するボーンが存在する
var dstIndex = Array.IndexOf(dstBones, dstBone);
if (dstIndex == -1)
{
// ありえない。バグ
throw new Exception();
}
indexMap[i] = dstIndex;
}
else
{
// 先のボーンが無い
indexMap[i] = -1;
Debug.LogWarningFormat("{0} is removed", srcBone.name);
}
}
}
// 新しいBoneWeightを作成する
var newBoneWeights = new BoneWeight[src.Length];
for (int i = 0; i < src.Length; ++i)
{
BoneWeight srcBoneWeight = src[i];
// 0
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex0, srcBoneWeight.weight0, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex0 = newIndex;
newBoneWeights[i].weight0 = newWeight;
});
// 1
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex1, srcBoneWeight.weight1, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex1 = newIndex;
newBoneWeights[i].weight1 = newWeight;
});
// 2
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex2, srcBoneWeight.weight2, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex2 = newIndex;
newBoneWeights[i].weight2 = newWeight;
});
// 3
CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex3, srcBoneWeight.weight3, (newIndex, newWeight) =>
{
newBoneWeights[i].boneIndex3 = newIndex;
newBoneWeights[i].weight3 = newWeight;
});
}
return newBoneWeights;
}
/// <summary>
///
/// </summary>
/// <param name="src"></param>
/// <param name="boneMap">正規化前のボーンから正規化後のボーンを得る</param>
/// <param name="dstLocalToWorldMatrix"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static (Mesh, Transform[]) NormalizeSkinnedMesh(
SkinnedMeshRenderer src,
Dictionary<Transform, Transform> boneMap,
Matrix4x4 dstLocalToWorldMatrix)
{
if (src == null
|| !src.enabled
|| src.sharedMesh == null
|| src.sharedMesh.vertexCount == 0)
{
// 有効なSkinnedMeshRendererが無かった
return default;
}
var srcMesh = src.sharedMesh;
var originalSrcMesh = srcMesh;
// 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る
var dstBones = src.bones
.Where(x => x != null && boneMap.ContainsKey(x))
.Select(x => boneMap[x])
.ToArray();
var hasBoneWeight = src.bones != null && src.bones.Length > 0;
if (!hasBoneWeight)
{
// Before bake, bind no weight bones
//Debug.LogFormat("no weight: {0}", srcMesh.name);
srcMesh = srcMesh.Copy(true);
var bw = new BoneWeight
{
boneIndex0 = 0,
boneIndex1 = 0,
boneIndex2 = 0,
boneIndex3 = 0,
weight0 = 1.0f,
weight1 = 0.0f,
weight2 = 0.0f,
weight3 = 0.0f,
};
srcMesh.boneWeights = Enumerable.Range(0, srcMesh.vertexCount).Select(x => bw).ToArray();
srcMesh.bindposes = new Matrix4x4[] { Matrix4x4.identity };
src.rootBone = src.transform;
dstBones = new[] { boneMap[src.transform] };
src.bones = new[] { src.transform };
src.sharedMesh = srcMesh;
}
// BakeMesh
var mesh = srcMesh.Copy(false);
mesh.name = srcMesh.name + ".baked";
src.BakeMesh(mesh);
// 新しい骨格のボーンウェイトを作成する
mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, src.bones, dstBones);
// recalc bindposes
mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dstLocalToWorldMatrix).ToArray();
//var m = src.localToWorldMatrix; // include scaling
var m = default(Matrix4x4);
m.SetTRS(Vector3.zero, src.transform.rotation, Vector3.one); // rotation only
mesh.ApplyMatrix(m);
//
// BlendShapes
//
CopyBlendShapes(src, srcMesh, mesh, m);
if (!hasBoneWeight)
{
// restore bones
src.bones = new Transform[] { };
src.sharedMesh = originalSrcMesh;
}
return (mesh, dstBones);
}
private static void CopyBlendShapes(SkinnedMeshRenderer src, Mesh srcMesh, Mesh mesh, Matrix4x4 m)
{
var blendShapeValues = new Dictionary<int, float>();
for (int i = 0; i < srcMesh.blendShapeCount; i++)
{
var val = src.GetBlendShapeWeight(i);
if (val > 0) blendShapeValues.Add(i, val);
}
// clear blendShape always
var backcup = new List<float>();
for (int i = 0; i < srcMesh.blendShapeCount; ++i)
{
backcup.Add(src.GetBlendShapeWeight(i));
src.SetBlendShapeWeight(i, 0);
}
var meshVertices = mesh.vertices;
var meshNormals = mesh.normals;
var meshTangents = Array.Empty<Vector3>();
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
meshTangents = mesh.tangents.Select(x => (Vector3)x).ToArray();
}
var originalBlendShapePositions = new Vector3[meshVertices.Length];
var originalBlendShapeNormals = new Vector3[meshVertices.Length];
var originalBlendShapeTangents = new Vector3[meshVertices.Length];
var report = new BlendShapeReport(srcMesh);
var blendShapeMesh = new Mesh();
for (int i = 0; i < srcMesh.blendShapeCount; ++i)
{
// check blendShape
src.sharedMesh.GetBlendShapeFrameVertices(i, 0, originalBlendShapePositions, originalBlendShapeNormals, originalBlendShapeTangents);
var hasVertices = originalBlendShapePositions.Count(x => x != Vector3.zero);
var hasNormals = originalBlendShapeNormals.Count(x => x != Vector3.zero);
var hasTangents = 0;
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
hasTangents = originalBlendShapeTangents.Count(x => x != Vector3.zero);
}
var name = srcMesh.GetBlendShapeName(i);
if (string.IsNullOrEmpty(name))
{
name = String.Format("{0}", i);
}
report.SetCount(i, name, hasVertices, hasNormals, hasTangents);
src.SetBlendShapeWeight(i, 100.0f);
src.BakeMesh(blendShapeMesh);
if (blendShapeMesh.vertices.Length != mesh.vertices.Length)
{
throw new Exception("different vertex count");
}
var value = blendShapeValues.ContainsKey(i) ? blendShapeValues[i] : 0;
src.SetBlendShapeWeight(i, value);
Vector3[] vertices = blendShapeMesh.vertices;
for (int j = 0; j < vertices.Length; ++j)
{
if (originalBlendShapePositions[j] == Vector3.zero)
{
vertices[j] = Vector3.zero;
}
else
{
vertices[j] = m.MultiplyPoint(vertices[j]) - meshVertices[j];
}
}
Vector3[] normals = blendShapeMesh.normals;
for (int j = 0; j < normals.Length; ++j)
{
if (originalBlendShapeNormals[j] == Vector3.zero)
{
normals[j] = Vector3.zero;
}
else
{
normals[j] = m.MultiplyVector(normals[j].normalized) - meshNormals[j];
}
}
Vector3[] tangents = blendShapeMesh.tangents.Select(x => (Vector3)x).ToArray();
if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT)
{
for (int j = 0; j < tangents.Length; ++j)
{
if (originalBlendShapeTangents[j] == Vector3.zero)
{
tangents[j] = Vector3.zero;
}
else
{
tangents[j] = m.MultiplyVector(tangents[j]) - meshTangents[j];
}
}
}
var frameCount = srcMesh.GetBlendShapeFrameCount(i);
for (int f = 0; f < frameCount; f++)
{
var weight = srcMesh.GetBlendShapeFrameWeight(i, f);
try
{
mesh.AddBlendShapeFrame(name,
weight,
vertices,
hasNormals > 0 ? normals : null,
hasTangents > 0 ? tangents : null
);
}
catch (Exception)
{
Debug.LogErrorFormat("fail to mesh.AddBlendShapeFrame {0}.{1}",
mesh.name,
srcMesh.GetBlendShapeName(i)
);
throw;
}
}
}
if (report.Count > 0)
{
Debug.LogFormat("{0}", report.ToString());
}
// restore blendshape weights
for (int i = 0; i < backcup.Count; ++i)
{
src.SetBlendShapeWeight(i, backcup[i]);
}
}
public static Mesh NormalizeNoneSkinnedMesh(MeshRenderer srcRenderer)
{
if (srcRenderer == null || !srcRenderer.enabled)
{
return default;
}
var srcFilter = srcRenderer.GetComponent<MeshFilter>();
if (srcFilter == null
|| srcFilter.sharedMesh == null
|| srcFilter.sharedMesh.vertexCount == 0)
{
return default;
}
var dstMesh = srcFilter.sharedMesh.Copy(false);
// Meshに乗っているボーンの姿勢を適用する
dstMesh.ApplyRotationAndScale(srcRenderer.transform.localToWorldMatrix);
return dstMesh;
}
}
}

View File

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

View File

@@ -67,7 +67,7 @@ namespace VRM
}
// 正規化されたヒエラルキーを作る
var (normalized, bMap) = BoneNormalizer.Execute(go);
var (normalized, bMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go);
// 新しいヒエラルキーからAvatarを作る
var newAvatar = UniHumanoid.AvatarDescription.CreateAvatarForCopyHierarchy(

View File

@@ -59,7 +59,7 @@ namespace VRM
map.Add(null, new GameObject("null"));
// map.Add(new GameObject("c"), null); // ありえないので Exception にしてある
var boneWeights = map.CreateBoneWeight(64).ToArray();
var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
var newBoneWeight = MeshFreezer.MapBoneWeight(boneWeights, map.Map,
map.SrcBones.ToArray(), map.DstBones.ToArray());
// 正常系
@@ -75,7 +75,7 @@ namespace VRM
map.Add(null, new GameObject("null"));
// map.Add(new GameObject("c"), null); // ありえないので Exception にしてある
var boneWeights = map.CreateBoneWeight(64).ToArray();
var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map,
var newBoneWeight = MeshFreezer.MapBoneWeight(boneWeights, map.Map,
map.SrcBones.ToArray(), map.DstBones.ToArray());
// 4 つめが 0 になる

View File

@@ -137,7 +137,7 @@ namespace UniVRM10
}
// 正規化されたヒエラルキーを作る
var (normalized, boneMap) = BoneNormalizer.CreateNormalizedHierarchy(go,
var (normalized, boneMap) = BoneNormalizer.NormalizeHierarchyFreezeMesh(go,
removeScaling: FreezeScaling,
removeRotation: FreezeRotation);
@@ -151,6 +151,7 @@ namespace UniVRM10
// TODO: write back normalized transform to boneMap
// TODO: integration
foreach (var group in MeshIntegrationGroups)
{
foreach (var renderer in group.Renderers)
@@ -158,6 +159,8 @@ namespace UniVRM10
}
}
// TODO: split
}
}
}