mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-12 22:10:11 -05:00
add VRM scripts
This commit is contained in:
200
Scripts/SkinnedMeshUtility/Editor/BoneMeshEraserWizard.cs
Normal file
200
Scripts/SkinnedMeshUtility/Editor/BoneMeshEraserWizard.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(BoneMeshEraser.EraseBone))]
|
||||
public class EraseBoneDrawer : PropertyDrawer
|
||||
{
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
//EditorGUI.BeginProperty(position, label, property);
|
||||
|
||||
var leftWidth = 0.6f;
|
||||
var rightWidth = 1.0f - leftWidth;
|
||||
|
||||
var leftSide = new Rect(position.x, position.y, position.width * leftWidth, position.height);
|
||||
var rightSide = new Rect(position.width * leftWidth, position.y, position.width * rightWidth, position.height);
|
||||
{
|
||||
EditorGUI.PropertyField(leftSide, property.FindPropertyRelative("Bone"), new GUIContent("", ""));
|
||||
EditorGUI.PropertyField(rightSide, property.FindPropertyRelative("Erase"));
|
||||
}
|
||||
|
||||
//EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var height = base.GetPropertyHeight(property, label);
|
||||
return height;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoneMeshEraserWizard : ScriptableWizard
|
||||
{
|
||||
const string ASSET_SUFFIX = ".asset";
|
||||
|
||||
[SerializeField]
|
||||
SkinnedMeshRenderer m_skinnedMesh;
|
||||
|
||||
[SerializeField]
|
||||
Animator m_animator;
|
||||
|
||||
[SerializeField]
|
||||
Transform EraseRoot;
|
||||
|
||||
[SerializeField]
|
||||
BoneMeshEraser.EraseBone[] m_eraseBones;
|
||||
|
||||
[MenuItem("GameObject/UnityEditorScripts/BoneMeshEraser Wizard")]
|
||||
static void CreateWizard()
|
||||
{
|
||||
ScriptableWizard.DisplayWizard<BoneMeshEraserWizard>("BoneMeshEraser", "Erase triangles by bone", "Erase");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
var root = Selection.activeGameObject;
|
||||
if (root != null)
|
||||
{
|
||||
m_animator = root.GetComponent<Animator>();
|
||||
m_skinnedMesh = root.GetComponent<SkinnedMeshRenderer>();
|
||||
OnValidate();
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
//Debug.Log("OnValidate");
|
||||
if (m_skinnedMesh == null)
|
||||
{
|
||||
m_eraseBones = new BoneMeshEraser.EraseBone[] { };
|
||||
return;
|
||||
}
|
||||
|
||||
if (EraseRoot == null)
|
||||
{
|
||||
if (m_animator != null)
|
||||
{
|
||||
EraseRoot = m_animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
//Debug.LogFormat("head: {0}", EraseRoot);
|
||||
}
|
||||
}
|
||||
|
||||
m_eraseBones = m_skinnedMesh.bones.Select(x =>
|
||||
{
|
||||
var eb = new BoneMeshEraser.EraseBone
|
||||
{
|
||||
Bone = x,
|
||||
};
|
||||
|
||||
if (EraseRoot != null)
|
||||
{
|
||||
// 首の子孫を消去
|
||||
if (eb.Bone.Ancestor().Any(y => y == EraseRoot))
|
||||
{
|
||||
//Debug.LogFormat("erase {0}", x);
|
||||
eb.Erase = true;
|
||||
}
|
||||
}
|
||||
|
||||
return eb;
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
void OnWizardUpdate()
|
||||
{
|
||||
helpString = "select target skinnedMesh and animator";
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int IndexOf(Transform[] list, Transform target)
|
||||
{
|
||||
for (int i = 0; i < list.Length; ++i)
|
||||
{
|
||||
if (list[i] == target)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer _Erase(GameObject go)
|
||||
{
|
||||
if (go == null)
|
||||
{
|
||||
Debug.LogWarning("select root object in hierarchy");
|
||||
return null;
|
||||
}
|
||||
if (m_skinnedMesh == null)
|
||||
{
|
||||
Debug.LogWarning("no skinnedmesh");
|
||||
return null;
|
||||
}
|
||||
|
||||
var bones = m_skinnedMesh.bones;
|
||||
var eraseBones = m_eraseBones
|
||||
.Where(x => x.Erase)
|
||||
.Select(x => bones.IndexOf(x.Bone))
|
||||
.ToArray();
|
||||
|
||||
var meshNode = new GameObject("BoneMeshEraser");
|
||||
meshNode.transform.SetParent(go.transform, false);
|
||||
|
||||
var erased = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
erased.sharedMesh = BoneMeshEraser.CreateErasedMesh(m_skinnedMesh.sharedMesh, eraseBones);
|
||||
erased.sharedMaterials = m_skinnedMesh.sharedMaterials;
|
||||
erased.bones = m_skinnedMesh.bones;
|
||||
|
||||
return erased;
|
||||
}
|
||||
|
||||
void Erase()
|
||||
{
|
||||
var go = Selection.activeGameObject;
|
||||
var renderer = _Erase(go);
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// save mesh to Assets
|
||||
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
|
||||
var prefab = PrefabUtility.GetPrefabParent(go);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}{2}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(renderer.sharedMesh, assetPath);
|
||||
}
|
||||
|
||||
void OnWizardCreate()
|
||||
{
|
||||
//Debug.Log("OnWizardCreate");
|
||||
Erase();
|
||||
|
||||
// close
|
||||
}
|
||||
|
||||
void OnWizardOtherButton()
|
||||
{
|
||||
//Debug.Log("OnWizardOtherButton");
|
||||
Erase();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 881b00db73f639c48a3f043a775fa61a
|
||||
timeCreated: 1518503829
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
238
Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs
Normal file
238
Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UniHumanoid;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
public static class BoneNormalizer
|
||||
{
|
||||
/// <summary>
|
||||
/// 回転とスケールを除去したヒエラルキーをコピーする
|
||||
/// </summary>
|
||||
/// <param name="src"></param>
|
||||
/// <param name="dst"></param>
|
||||
static void CopyAndBuild(Transform src, Transform dst, Dictionary<Transform, Transform> boneMap)
|
||||
{
|
||||
boneMap[src] = dst;
|
||||
|
||||
foreach (Transform child in src)
|
||||
{
|
||||
var dstChild = new GameObject(child.name);
|
||||
dstChild.transform.SetParent(dst);
|
||||
dstChild.transform.position = child.position; // copy position only
|
||||
|
||||
//dstChild.AddComponent<UniHumanoid.BoneGizmoDrawer>();
|
||||
|
||||
CopyAndBuild(child, dstChild.transform, boneMap);
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<Transform> Traverse(this Transform t)
|
||||
{
|
||||
yield return t;
|
||||
foreach (Transform child in t)
|
||||
{
|
||||
foreach (var x in child.Traverse())
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GameObject Execute(GameObject go, Dictionary<Transform, Transform> boneMap)
|
||||
{
|
||||
//
|
||||
// T-Poseにする
|
||||
//
|
||||
{
|
||||
var animator = go.GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
{
|
||||
throw new ArgumentException("Animator with avatar is required");
|
||||
}
|
||||
|
||||
var avatar = animator.avatar;
|
||||
if (avatar == null)
|
||||
{
|
||||
throw new ArgumentException("avatar is required");
|
||||
}
|
||||
|
||||
if (!avatar.isValid)
|
||||
{
|
||||
throw new ArgumentException("invalid avatar");
|
||||
}
|
||||
|
||||
if (!avatar.isHuman)
|
||||
{
|
||||
throw new ArgumentException("avatar is not human");
|
||||
}
|
||||
|
||||
HumanPoseTransfer.SetTPose(avatar, go.transform);
|
||||
}
|
||||
|
||||
//
|
||||
// 回転・スケールの無いヒエラルキーをコピーする
|
||||
//
|
||||
var normalized = new GameObject(go.name + "(normalized)");
|
||||
normalized.transform.position = go.transform.position;
|
||||
|
||||
CopyAndBuild(go.transform, normalized.transform, boneMap);
|
||||
|
||||
//
|
||||
// 新しいヒエラルキーからAvatarを作る
|
||||
//
|
||||
{
|
||||
var src = go.GetComponent<Animator>();
|
||||
|
||||
var map = Enum.GetValues(typeof(HumanBodyBones))
|
||||
.Cast<HumanBodyBones>()
|
||||
.Where(x => x != HumanBodyBones.LastBone)
|
||||
.Select(x => new { Key = x, Value = src.GetBoneTransform(x) })
|
||||
.Where(x => x.Value != null)
|
||||
.ToDictionary(x => x.Key, x => boneMap[x.Value])
|
||||
;
|
||||
|
||||
var animator = normalized.AddComponent<Animator>();
|
||||
var vrmHuman = go.GetComponent<VRMHumanoidDescription>();
|
||||
var avatarDescription = AvatarDescription.Create();
|
||||
if (vrmHuman != null && vrmHuman.Description != null)
|
||||
{
|
||||
avatarDescription.armStretch = vrmHuman.Description.armStretch;
|
||||
avatarDescription.legStretch = vrmHuman.Description.legStretch;
|
||||
avatarDescription.upperArmTwist = vrmHuman.Description.upperArmTwist;
|
||||
avatarDescription.lowerArmTwist = vrmHuman.Description.lowerArmTwist;
|
||||
avatarDescription.upperLegTwist = vrmHuman.Description.upperLegTwist;
|
||||
avatarDescription.lowerLegTwist = vrmHuman.Description.lowerLegTwist;
|
||||
avatarDescription.feetSpacing = vrmHuman.Description.feetSpacing;
|
||||
avatarDescription.hasTranslationDoF = vrmHuman.Description.hasTranslationDoF;
|
||||
}
|
||||
avatarDescription.SetHumanBones(map);
|
||||
var avatar = avatarDescription.CreateAvatar(normalized.transform);
|
||||
|
||||
avatar.name = go.name + ".normalized";
|
||||
animator.avatar = avatar;
|
||||
|
||||
var humanPoseTransfer = normalized.AddComponent<HumanPoseTransfer>();
|
||||
humanPoseTransfer.Avatar = avatar;
|
||||
}
|
||||
|
||||
//
|
||||
// 各メッシュから回転・スケールを取り除いてBinding行列を再計算する
|
||||
//
|
||||
foreach (var src in go.transform.Traverse())
|
||||
{
|
||||
var dst = boneMap[src];
|
||||
|
||||
{
|
||||
//
|
||||
// SkinnedMesh
|
||||
//
|
||||
var srcRenderer = src.GetComponent<SkinnedMeshRenderer>();
|
||||
if (srcRenderer != null && srcRenderer.enabled)
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
var srcMesh = srcRenderer.sharedMesh;
|
||||
mesh.name = srcMesh.name + ".baked";
|
||||
srcRenderer.BakeMesh(mesh);
|
||||
|
||||
//var m = src.localToWorldMatrix;
|
||||
var m = default(Matrix4x4);
|
||||
m.SetTRS(Vector3.zero, src.rotation, Vector3.one);
|
||||
|
||||
mesh.vertices = mesh.vertices.Select(x => m.MultiplyPoint(x)).ToArray();
|
||||
mesh.normals = mesh.normals.Select(x => m.MultiplyVector(x).normalized).ToArray();
|
||||
|
||||
mesh.uv = srcMesh.uv;
|
||||
mesh.tangents = srcMesh.tangents;
|
||||
mesh.subMeshCount = srcMesh.subMeshCount;
|
||||
for (int i = 0; i < srcMesh.subMeshCount; ++i)
|
||||
{
|
||||
mesh.SetIndices(srcMesh.GetIndices(i), srcMesh.GetTopology(i), i);
|
||||
}
|
||||
mesh.boneWeights = srcMesh.boneWeights;
|
||||
|
||||
for (int i = 0; i < srcMesh.blendShapeCount; ++i)
|
||||
{
|
||||
var vertices = srcMesh.vertices;
|
||||
var normals = srcMesh.normals;
|
||||
var tangents = (srcMesh.tangents != null && srcMesh.tangents.Any())
|
||||
? srcMesh.tangents.Select(x => (Vector3)x).ToArray()
|
||||
: null
|
||||
;
|
||||
srcMesh.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents);
|
||||
|
||||
var name = srcMesh.GetBlendShapeName(i);
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = String.Format("{0}", i);
|
||||
}
|
||||
|
||||
var s = src.lossyScale.x;
|
||||
try
|
||||
{
|
||||
mesh.AddBlendShapeFrame(name,
|
||||
srcMesh.GetBlendShapeFrameWeight(i, 0),
|
||||
vertices.Select(x => m.MultiplyPoint(x) * s).ToArray(),
|
||||
normals.Select(x => m.MultiplyVector(x).normalized).ToArray(),
|
||||
tangents
|
||||
);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.LogWarningFormat("{0}.{1}", mesh.name, srcMesh.GetBlendShapeName(i));
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// recalc bindposes
|
||||
var bones = srcRenderer.bones.Select(x => boneMap[x]).ToArray();
|
||||
mesh.bindposes = bones.Select(x =>
|
||||
x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray();
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
var dstRenderer = dst.gameObject.AddComponent<SkinnedMeshRenderer>();
|
||||
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
|
||||
dstRenderer.sharedMesh = mesh;
|
||||
dstRenderer.bones = bones;
|
||||
if (srcRenderer.rootBone != null)
|
||||
{
|
||||
dstRenderer.rootBone = boneMap[srcRenderer.rootBone];
|
||||
}
|
||||
if (!bones.Any() && srcRenderer.rootBone != null)
|
||||
{
|
||||
dstRenderer.rootBone = boneMap[srcRenderer.rootBone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//
|
||||
// not SkinnedMesh
|
||||
//
|
||||
var srcFilter = src.GetComponent<MeshFilter>();
|
||||
if (srcFilter != null)
|
||||
{
|
||||
var srcRenderer = src.GetComponent<MeshRenderer>();
|
||||
if (srcRenderer!=null && srcRenderer.enabled)
|
||||
{
|
||||
var dstFilter = dst.gameObject.AddComponent<MeshFilter>();
|
||||
dstFilter.sharedMesh = srcFilter.sharedMesh;
|
||||
|
||||
var dstRenderer = dst.gameObject.AddComponent<MeshRenderer>();
|
||||
dstRenderer.sharedMaterials = srcRenderer.sharedMaterials;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs.meta
Normal file
13
Scripts/SkinnedMeshUtility/Editor/BoneNormalizer.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b330ec419f98af14687c302638922ab0
|
||||
timeCreated: 1519379418
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
359
Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs
Normal file
359
Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs
Normal file
@@ -0,0 +1,359 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// 複数のメッシュをまとめる
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public static class MeshIntegrator
|
||||
{
|
||||
const string MENU_KEY = "GameObject/UnityEditorScripts/MeshIntegrator";
|
||||
const int MENU_PRIORITY = 0;
|
||||
const string ASSET_SUFFIX = ".mesh.asset";
|
||||
const string ASSET_WITH_BLENDSHAPE_SUFFIX = ".blendshape.asset";
|
||||
|
||||
[MenuItem(MENU_KEY, true, MENU_PRIORITY)]
|
||||
private static bool ExportValidate()
|
||||
{
|
||||
return Selection.activeObject != null && Selection.activeObject is GameObject;
|
||||
}
|
||||
|
||||
[MenuItem(MENU_KEY, false, MENU_PRIORITY)]
|
||||
private static void ExportFromMenu()
|
||||
{
|
||||
var go = Selection.activeObject as GameObject;
|
||||
|
||||
Integrate(go);
|
||||
}
|
||||
|
||||
public static SkinnedMeshRenderer Integrate(GameObject go)
|
||||
{
|
||||
var without_blendshape = _Integrate(go, false);
|
||||
if (without_blendshape == null)
|
||||
{
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
// save mesh to Assets
|
||||
var assetPath = string.Format("{0}{1}", go.name, ASSET_SUFFIX);
|
||||
var prefab = PrefabUtility.GetPrefabParent(go);
|
||||
if (prefab != null)
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(prefab);
|
||||
assetPath = string.Format("{0}/{1}{2}",
|
||||
Path.GetDirectoryName(prefabPath),
|
||||
Path.GetFileNameWithoutExtension(prefabPath),
|
||||
ASSET_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
Debug.LogFormat("CreateAsset: {0}", assetPath);
|
||||
AssetDatabase.CreateAsset(without_blendshape.sharedMesh, assetPath);
|
||||
return without_blendshape;
|
||||
}
|
||||
|
||||
struct SubMesh
|
||||
{
|
||||
public List<int> Indices;
|
||||
public Material Material;
|
||||
}
|
||||
|
||||
class BlendShape
|
||||
{
|
||||
public int VertexOffset;
|
||||
public string Name;
|
||||
public float FrameWeight;
|
||||
public Vector3[] Positions;
|
||||
public Vector3[] Normals;
|
||||
public Vector3[] Tangents;
|
||||
}
|
||||
|
||||
class Integrator
|
||||
{
|
||||
public List<SkinnedMeshRenderer> Renderers { get; private set; }
|
||||
public List<Vector3> Positions { get; private set; }
|
||||
public List<Vector3> Normals { get; private set; }
|
||||
public List<Vector2> UV { get; private set; }
|
||||
public List<Vector4> Tangents { get; private set; }
|
||||
public List<BoneWeight> BoneWeights { get; private set; }
|
||||
|
||||
public List<SubMesh> SubMeshes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public List<Matrix4x4> BindPoses { get; private set; }
|
||||
public List<Transform> Bones { get; private set; }
|
||||
|
||||
public List<BlendShape> BlendShapes { get; private set; }
|
||||
public void AddBlendShapesToMesh(Mesh mesh)
|
||||
{
|
||||
Dictionary<string, BlendShape> map = new Dictionary<string, BlendShape>();
|
||||
|
||||
foreach (var x in BlendShapes)
|
||||
{
|
||||
BlendShape bs = null;
|
||||
if (!map.TryGetValue(x.Name, out bs))
|
||||
{
|
||||
bs = new BlendShape();
|
||||
bs.Positions = Positions.ToArray();
|
||||
bs.Normals = Normals.ToArray();
|
||||
bs.Tangents = Tangents.Select(y => (Vector3)y).ToArray();
|
||||
bs.Name = x.Name;
|
||||
bs.FrameWeight = x.FrameWeight;
|
||||
map.Add(x.Name, bs);
|
||||
}
|
||||
|
||||
var j = x.VertexOffset;
|
||||
for (int i = 0; i < x.Positions.Length; ++i, ++j)
|
||||
{
|
||||
bs.Positions[j] = x.Positions[i];
|
||||
bs.Normals[j] = x.Normals[i];
|
||||
bs.Tangents[j] = x.Tangents[i];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in map)
|
||||
{
|
||||
//Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key);
|
||||
mesh.AddBlendShapeFrame(kv.Key, kv.Value.FrameWeight,
|
||||
kv.Value.Positions, kv.Value.Normals, kv.Value.Tangents);
|
||||
}
|
||||
}
|
||||
|
||||
public Integrator()
|
||||
{
|
||||
Renderers = new List<SkinnedMeshRenderer>();
|
||||
|
||||
Positions = new List<Vector3>();
|
||||
Normals = new List<Vector3>();
|
||||
UV = new List<Vector2>();
|
||||
Tangents = new List<Vector4>();
|
||||
BoneWeights = new List<BoneWeight>();
|
||||
|
||||
SubMeshes = new List<SubMesh>();
|
||||
|
||||
BindPoses = new List<Matrix4x4>();
|
||||
Bones = new List<Transform>();
|
||||
|
||||
BlendShapes = new List<BlendShape>();
|
||||
}
|
||||
|
||||
static BoneWeight AddBoneIndexOffset(BoneWeight bw, int boneIndexOffset)
|
||||
{
|
||||
if (bw.weight0 > 0) bw.boneIndex0 += boneIndexOffset;
|
||||
if (bw.weight1 > 0) bw.boneIndex1 += boneIndexOffset;
|
||||
if (bw.weight2 > 0) bw.boneIndex2 += boneIndexOffset;
|
||||
if (bw.weight3 > 0) bw.boneIndex3 += boneIndexOffset;
|
||||
return bw;
|
||||
}
|
||||
|
||||
public void Push(SkinnedMeshRenderer renderer)
|
||||
{
|
||||
var mesh = renderer.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
||||
return;
|
||||
}
|
||||
|
||||
Renderers.Add(renderer);
|
||||
|
||||
var indexOffset = Positions.Count;
|
||||
var boneIndexOffset = Bones.Count;
|
||||
|
||||
Positions.AddRange(mesh.vertices);
|
||||
Normals.AddRange(mesh.normals);
|
||||
UV.AddRange(mesh.uv);
|
||||
Tangents.AddRange(mesh.tangents);
|
||||
|
||||
if (mesh.vertexCount == mesh.boneWeights.Length)
|
||||
{
|
||||
BoneWeights.AddRange(mesh.boneWeights.Select(x => AddBoneIndexOffset(x, boneIndexOffset)).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => new BoneWeight()).ToArray());
|
||||
}
|
||||
|
||||
BindPoses.AddRange(mesh.bindposes);
|
||||
Bones.AddRange(renderer.bones);
|
||||
|
||||
for (int i = 0; i < mesh.subMeshCount; ++i)
|
||||
{
|
||||
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
||||
var mat = renderer.sharedMaterials[i];
|
||||
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
||||
if (sameMaterialSubMeshIndex >= 0)
|
||||
{
|
||||
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
SubMeshes.Add(new SubMesh
|
||||
{
|
||||
Indices = indices.ToList(),
|
||||
Material = mat,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mesh.blendShapeCount; ++i)
|
||||
{
|
||||
var positions = (Vector3[])mesh.vertices.Clone();
|
||||
var normals = (Vector3[])mesh.normals.Clone();
|
||||
var tangents = mesh.tangents.Select(x => (Vector3)x).ToArray();
|
||||
|
||||
mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents);
|
||||
BlendShapes.Add(new BlendShape
|
||||
{
|
||||
VertexOffset = indexOffset,
|
||||
FrameWeight = mesh.GetBlendShapeFrameWeight(i, 0),
|
||||
Name = mesh.GetBlendShapeName(i),
|
||||
Positions = positions,
|
||||
Normals = normals,
|
||||
Tangents = tangents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<Transform> Traverse(Transform parent)
|
||||
{
|
||||
if (parent.gameObject.activeSelf)
|
||||
{
|
||||
yield return parent;
|
||||
|
||||
foreach (Transform child in parent)
|
||||
{
|
||||
foreach (var x in Traverse(child))
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public IEnumerable<SkinnedMeshRenderer> EnumerateRenderer(Transform root, bool hasBlendShape)
|
||||
{
|
||||
foreach (var x in Traverse(root))
|
||||
{
|
||||
var renderer = x.GetComponent<SkinnedMeshRenderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
if (renderer.sharedMesh != null)
|
||||
{
|
||||
if (renderer.gameObject.activeSelf)
|
||||
{
|
||||
if (renderer.sharedMesh.blendShapeCount > 0 == hasBlendShape)
|
||||
{
|
||||
yield return renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<Transform> Ancestors(Transform self)
|
||||
{
|
||||
yield return self;
|
||||
|
||||
if (self.parent != null)
|
||||
{
|
||||
foreach (var x in Ancestors(self.parent))
|
||||
{
|
||||
yield return x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SkinnedMeshRenderer _Integrate(GameObject go, bool hasBlendShape)
|
||||
{
|
||||
var meshNode = new GameObject();
|
||||
if (hasBlendShape)
|
||||
{
|
||||
meshNode.name = "MeshIntegrator(BlendShape)";
|
||||
}
|
||||
else
|
||||
{
|
||||
meshNode.name = "MeshIntegrator";
|
||||
}
|
||||
meshNode.transform.SetParent(go.transform, false);
|
||||
|
||||
var renderers = EnumerateRenderer(go.transform, hasBlendShape).ToArray();
|
||||
|
||||
// Root objectを選出する
|
||||
var root = renderers.Select(x => x.rootBone != null ? x.rootBone : x.transform)
|
||||
.Select(x => Ancestors(x).Reverse().ToArray())
|
||||
.Aggregate((a, b) =>
|
||||
{
|
||||
int i = 0;
|
||||
for(; i<a.Length && i<b.Length; ++i)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return a.Take(i).ToArray();
|
||||
})
|
||||
.Last()
|
||||
;
|
||||
Debug.LogFormat("root bone: {0}", root.name);
|
||||
|
||||
// レンダラから情報を集める
|
||||
var integrator = new Integrator();
|
||||
foreach(var x in renderers)
|
||||
{
|
||||
integrator.Push(x);
|
||||
}
|
||||
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "integrated";
|
||||
|
||||
if (integrator.Positions.Count > ushort.MaxValue)
|
||||
{
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
Debug.LogFormat("exceed 65535 vertices: {0}", integrator.Positions.Count);
|
||||
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
#else
|
||||
throw new NotImplementedException(String.Format("exceed 65535 vertices: {0}", integrator.Positions.Count.ToString()));
|
||||
#endif
|
||||
}
|
||||
|
||||
mesh.vertices = integrator.Positions.ToArray();
|
||||
mesh.normals = integrator.Normals.ToArray();
|
||||
mesh.uv = integrator.UV.ToArray();
|
||||
mesh.tangents = integrator.Tangents.ToArray();
|
||||
mesh.boneWeights = integrator.BoneWeights.ToArray();
|
||||
mesh.subMeshCount = integrator.SubMeshes.Count;
|
||||
for (var i = 0; i < integrator.SubMeshes.Count; ++i)
|
||||
{
|
||||
mesh.SetIndices(integrator.SubMeshes[i].Indices.ToArray(), MeshTopology.Triangles, i);
|
||||
}
|
||||
mesh.bindposes = integrator.BindPoses.ToArray();
|
||||
|
||||
if (hasBlendShape)
|
||||
{
|
||||
integrator.AddBlendShapesToMesh(mesh);
|
||||
}
|
||||
|
||||
var integrated = meshNode.AddComponent<SkinnedMeshRenderer>();
|
||||
integrated.sharedMesh = mesh;
|
||||
integrated.sharedMaterials = integrator.SubMeshes.Select(x => x.Material).ToArray();
|
||||
integrated.bones = integrator.Bones.ToArray();
|
||||
|
||||
return integrated;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs.meta
Normal file
13
Scripts/SkinnedMeshUtility/Editor/MeshIntegrator.cs.meta
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 648920943c26bde4b920119bdbb38f70
|
||||
timeCreated: 1518194696
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
174
Scripts/SkinnedMeshUtility/Editor/MeshIntegratorWizard.cs
Normal file
174
Scripts/SkinnedMeshUtility/Editor/MeshIntegratorWizard.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
#pragma warning disable 0414, 0649
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
public class MeshIntegratorWizard : ScriptableWizard
|
||||
{
|
||||
[SerializeField]
|
||||
GameObject m_root;
|
||||
|
||||
[SerializeField]
|
||||
Material[] m_uniqueMaterials;
|
||||
|
||||
[Serializable]
|
||||
struct MaterialKey
|
||||
{
|
||||
public string Shader;
|
||||
public KeyValuePair<string, object>[] Properties;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if(!(obj is MaterialKey))
|
||||
{
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
var key = (MaterialKey)obj;
|
||||
|
||||
return Shader == key.Shader
|
||||
&& Properties.SequenceEqual(key.Properties)
|
||||
;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct MaterialList
|
||||
{
|
||||
public Material[] Materials;
|
||||
|
||||
public MaterialList(Material[] list)
|
||||
{
|
||||
Materials = list;
|
||||
}
|
||||
}
|
||||
[SerializeField]
|
||||
MaterialList[] m_duplicateMaterials;
|
||||
|
||||
[Header("Result")]
|
||||
public Mesh integrated;
|
||||
|
||||
[MenuItem("GameObject/UnityEditorScripts/MeshInregrator Wizard")]
|
||||
static void CreateWizard()
|
||||
{
|
||||
ScriptableWizard.DisplayWizard<MeshIntegratorWizard>("MeshIntegrator", "Integrate and close window", "Integrate");
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_root = Selection.activeGameObject;
|
||||
OnValidate();
|
||||
}
|
||||
|
||||
static object GetPropertyValue(Shader shader, int i, Material m)
|
||||
{
|
||||
var propType = ShaderUtil.GetPropertyType(shader, i);
|
||||
switch (propType)
|
||||
{
|
||||
case ShaderUtil.ShaderPropertyType.Color:
|
||||
return m.GetColor(ShaderUtil.GetPropertyName(shader, i));
|
||||
|
||||
case ShaderUtil.ShaderPropertyType.Range:
|
||||
case ShaderUtil.ShaderPropertyType.Float:
|
||||
return m.GetFloat(ShaderUtil.GetPropertyName(shader, i));
|
||||
|
||||
case ShaderUtil.ShaderPropertyType.Vector:
|
||||
return m.GetVector(ShaderUtil.GetPropertyName(shader, i));
|
||||
|
||||
case ShaderUtil.ShaderPropertyType.TexEnv:
|
||||
return m.GetTexture(ShaderUtil.GetPropertyName(shader, i));
|
||||
|
||||
default:
|
||||
throw new NotImplementedException(propType.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
static MaterialKey GetMaterialKey(Material m)
|
||||
{
|
||||
var key = new MaterialKey
|
||||
{
|
||||
Shader = m.shader.name,
|
||||
};
|
||||
|
||||
key.Properties = Enumerable.Range(0, ShaderUtil.GetPropertyCount(m.shader))
|
||||
.Select(x => new KeyValuePair<string, object>(
|
||||
ShaderUtil.GetPropertyName(m.shader, x),
|
||||
GetPropertyValue(m.shader, x, m))
|
||||
)
|
||||
.OrderBy(x => x.Key)
|
||||
.ToArray()
|
||||
;
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
Debug.Log("OnValidate");
|
||||
if (m_root == null)
|
||||
{
|
||||
m_uniqueMaterials = new Material[] { };
|
||||
m_duplicateMaterials = new MaterialList[] { };
|
||||
return;
|
||||
}
|
||||
|
||||
m_uniqueMaterials = MeshIntegrator.EnumerateRenderer(m_root.transform, false)
|
||||
.SelectMany(x => x.sharedMaterials)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
m_duplicateMaterials = m_uniqueMaterials
|
||||
.GroupBy(x => GetMaterialKey(x), x => x)
|
||||
.Select(x => new MaterialList(x.ToArray()))
|
||||
.Where(x => x.Materials.Length > 1)
|
||||
.ToArray()
|
||||
;
|
||||
}
|
||||
|
||||
void OnWizardUpdate()
|
||||
{
|
||||
helpString = "select target mesh root";
|
||||
}
|
||||
|
||||
void Integrate()
|
||||
{
|
||||
if (m_root == null)
|
||||
{
|
||||
Debug.LogWarning("no root object");
|
||||
return;
|
||||
}
|
||||
|
||||
var renderer = MeshIntegrator.Integrate(m_root);
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
integrated = renderer.sharedMesh;
|
||||
}
|
||||
|
||||
void OnWizardCreate()
|
||||
{
|
||||
Debug.Log("OnWizardCreate");
|
||||
Integrate();
|
||||
|
||||
// close
|
||||
}
|
||||
|
||||
void OnWizardOtherButton()
|
||||
{
|
||||
Debug.Log("OnWizardOtherButton");
|
||||
Integrate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 744f66c7013807b4cae1d38d2ecfff38
|
||||
timeCreated: 1518503829
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user