mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-08-22 18:45:06 -05:00
VrmMeshIntegratorWizard
This commit is contained in:
@@ -13,6 +13,20 @@ namespace UniGLTF.MeshUtility
|
||||
|
||||
public static class GameObjectTypeUtility
|
||||
{
|
||||
public static Object GetPrefab(this GameObject instance)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(instance)))
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
return PrefabUtility.GetCorrespondingObjectFromSource(instance);
|
||||
#else
|
||||
return PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static GameObjectType GetGameObjectType(this GameObject go)
|
||||
{
|
||||
if (go == null)
|
||||
|
||||
@@ -343,8 +343,13 @@ namespace UniGLTF
|
||||
}
|
||||
|
||||
public static UnityPath FromAsset(UnityEngine.Object asset)
|
||||
{
|
||||
return new UnityPath(AssetDatabase.GetAssetPath(asset));
|
||||
{
|
||||
var assetPath = AssetDatabase.GetAssetPath(asset);
|
||||
if (string.IsNullOrEmpty(assetPath))
|
||||
{
|
||||
throw new System.ArgumentNullException();
|
||||
}
|
||||
return new UnityPath(assetPath);
|
||||
}
|
||||
|
||||
public void ImportAsset()
|
||||
|
||||
@@ -25,11 +25,11 @@ namespace VRM
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!r.Renderer.EnableForExport())
|
||||
{
|
||||
validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended));
|
||||
return false;
|
||||
}
|
||||
// if (!r.Renderer.EnableForExport())
|
||||
// {
|
||||
// validation = Validation.Error($"{name}.Renderer is not active", ValidationContext.Create(extended));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
validation = default;
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniGLTF;
|
||||
using UniGLTF.MeshUtility;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
|
||||
/// </summary>
|
||||
public static class VRMMeshIntegratorUtility
|
||||
{
|
||||
public static void FollowBlendshapeRendererChange(List<MeshIntegrationResult> results, GameObject root, string assetFolder)
|
||||
{
|
||||
var proxy = root.GetComponent<VRMBlendShapeProxy>();
|
||||
if (proxy == null || proxy.BlendShapeAvatar == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var result = results.FirstOrDefault(x => x.SourceSkinnedMeshRenderers.Count > 0);
|
||||
if (result == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var rendererDict = new Dictionary<string, SkinnedMeshRenderer>();
|
||||
foreach (var x in result.SourceSkinnedMeshRenderers)
|
||||
{
|
||||
rendererDict.Add(x.transform.RelativePathFrom(root.transform), x);
|
||||
}
|
||||
var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform);
|
||||
|
||||
// copy
|
||||
var clips = new List<BlendShapeClip>();
|
||||
foreach (var src in proxy.BlendShapeAvatar.Clips)
|
||||
{
|
||||
if (src == null) continue;
|
||||
|
||||
var clip = BlendShapeClip.Instantiate(src);
|
||||
clips.Add(clip);
|
||||
for (var i = 0; i < clip.Values.Length; ++i)
|
||||
{
|
||||
var val = clip.Values[i];
|
||||
if (rendererDict.ContainsKey(val.RelativePath))
|
||||
{
|
||||
var srcRenderer = rendererDict[val.RelativePath];
|
||||
var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index);
|
||||
var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name);
|
||||
|
||||
val.RelativePath = dstPath;
|
||||
val.Index = newIndex;
|
||||
}
|
||||
|
||||
clip.Values[i] = val;
|
||||
}
|
||||
|
||||
var assetPath = $"{assetFolder}/{clip.name}.asset";
|
||||
Debug.Log($"write: {assetPath}");
|
||||
AssetDatabase.CreateAsset(clip, assetPath);
|
||||
}
|
||||
|
||||
{
|
||||
// create blendshape avatar & replace
|
||||
var blendShapeAvatar = BlendShapeAvatar.CreateInstance<BlendShapeAvatar>();
|
||||
blendShapeAvatar.Clips.AddRange(clips);
|
||||
var assetPath = $"{assetFolder}/blendshape.asset";
|
||||
AssetDatabase.CreateAsset(blendShapeAvatar, assetPath);
|
||||
proxy.BlendShapeAvatar = blendShapeAvatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8eebeb093136b7f429c0e9e7295816b3
|
||||
guid: fb47e24fc1463584fa0b6b685d75f25e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -221,90 +221,103 @@ namespace VRM
|
||||
|
||||
void OnWizardUpdate()
|
||||
{
|
||||
// helpString = "Set target gameobject `in scene`. Prefab not supported.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefab に対する操作として実装されている
|
||||
/// </summary>
|
||||
/// 2022.05 仕様変更
|
||||
///
|
||||
/// * scene or prefab どっちでも動作する
|
||||
/// * backup するのではなく 変更した copy を作成する。元は変えない
|
||||
/// * 実行すると mesh, blendshape, blendShape を新規に作成する
|
||||
/// * scene のときは新しいヒエラルキーが出現する
|
||||
/// * prefab のときは新しいヒエラルキーを prefab 保存して、scene の方を削除して終了する
|
||||
///
|
||||
void Integrate()
|
||||
{
|
||||
var prefabPath = AssetDatabase.GetAssetPath(m_root);
|
||||
Debug.Log(prefabPath);
|
||||
var path = EditorUtility.SaveFilePanel("save prefab", Path.GetDirectoryName(prefabPath), m_root.name, "prefab");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
String folder = "Assets";
|
||||
var prefab = m_root.GetPrefab();
|
||||
if (prefab != null)
|
||||
{
|
||||
return;
|
||||
folder = AssetDatabase.GetAssetPath(prefab);
|
||||
Debug.Log(folder);
|
||||
}
|
||||
|
||||
var assetPath = UniGLTF.UnityPath.FromFullpath(path);
|
||||
if (!assetPath.IsUnderAssetsFolder)
|
||||
// 新規で作成されるアセットはすべてこのフォルダの中に作る。上書きチェックはしない
|
||||
var assetFolder = EditorUtility.SaveFolderPanel("select asset save folder", Path.GetDirectoryName(folder), "VrmIntegrated");
|
||||
var unityPath = UniGLTF.UnityPath.FromFullpath(assetFolder);
|
||||
if (!unityPath.IsUnderAssetsFolder)
|
||||
{
|
||||
Debug.LogWarning($"{path} is not asset path");
|
||||
EditorUtility.DisplayDialog("asset folder", "Target folder must be in the `Assets` folder", "cancel");
|
||||
return;
|
||||
}
|
||||
assetFolder = unityPath.Value;
|
||||
|
||||
var copy = GameObject.Instantiate(m_root);
|
||||
|
||||
// 統合
|
||||
var excludes = m_excludes.Where(x => x.Exclude).Select(x => x.Mesh);
|
||||
var results = Integrate(copy, excludes, m_separateByBlendShape);
|
||||
|
||||
// Backup Exists
|
||||
VrmPrefabUtility.BackupVrmPrefab(m_root);
|
||||
// 統合前のMeshを非表示にして、統合した結果をヒエラルキーに追加する
|
||||
DeactivateOldRendererAndAddIntegrated(copy, results);
|
||||
|
||||
Undo.RecordObject(m_root, "Mesh Integration");
|
||||
var instance = VrmPrefabUtility.InstantiatePrefab(m_root);
|
||||
var clips = new List<BlendShapeClip>();
|
||||
var proxy = instance.GetComponent<VRMBlendShapeProxy>();
|
||||
if (proxy != null && proxy.BlendShapeAvatar != null)
|
||||
// write mesh asset
|
||||
foreach (var result in results)
|
||||
{
|
||||
clips.AddRange(proxy.BlendShapeAvatar.Clips);
|
||||
}
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
Undo.RecordObject(clip, "Mesh Integration");
|
||||
var childAssetPath = $"{assetFolder}/{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}";
|
||||
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
|
||||
AssetDatabase.CreateAsset(result.IntegratedRenderer.sharedMesh, childAssetPath);
|
||||
}
|
||||
|
||||
_Integrate(instance, excludes, assetPath, clips);
|
||||
// 統合した結果を反映した BlendShapeClip を作成して置き換える
|
||||
VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(results, copy, assetFolder);
|
||||
|
||||
// destroy source renderers
|
||||
UnityEngine.Object.DestroyImmediate(instance);
|
||||
}
|
||||
|
||||
void _Integrate(GameObject instance, IEnumerable<Mesh> excludes, UniGLTF.UnityPath assetPath, List<BlendShapeClip> clips)
|
||||
{
|
||||
// Execute
|
||||
var results = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
|
||||
if (m_separateByBlendShape)
|
||||
// reset firstperson
|
||||
var firstperson = copy.GetComponent<VRMFirstPerson>();
|
||||
if (firstperson != null)
|
||||
{
|
||||
var withoutBlendShape = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes);
|
||||
if (withoutBlendShape.IntegratedRenderer != null)
|
||||
firstperson.Reset();
|
||||
}
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
// prefab
|
||||
var prefabPath = $"{assetFolder}/VrmIntegrated.prefab";
|
||||
Debug.Log(prefabPath);
|
||||
PrefabUtility.SaveAsPrefabAsset(copy, prefabPath, out bool success);
|
||||
if (!success)
|
||||
{
|
||||
results.Add(withoutBlendShape);
|
||||
throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {prefabPath}");
|
||||
}
|
||||
|
||||
var onlyBlendShape = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes);
|
||||
if (onlyBlendShape.IntegratedRenderer != null)
|
||||
{
|
||||
results.Add(onlyBlendShape);
|
||||
VRMMeshIntegratorUtility.FollowBlendshapeRendererChange(clips, onlyBlendShape, instance);
|
||||
}
|
||||
// destroy scene
|
||||
UnityEngine.Object.DestroyImmediate(copy);
|
||||
}
|
||||
else
|
||||
{
|
||||
var integrated = MeshIntegratorUtility.Integrate(instance, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes);
|
||||
if (integrated.IntegratedRenderer != null)
|
||||
{
|
||||
results.Add(integrated);
|
||||
}
|
||||
// do nothing. keep scene
|
||||
}
|
||||
|
||||
DeactivateOldRendererAndAddIntegrated(instance, results, assetPath,
|
||||
UniGLTF.UnityPath.FromUnityPath(AssetDatabase.GetAssetPath(m_root)).Equals(assetPath));
|
||||
}
|
||||
|
||||
static void DeactivateOldRendererAndAddIntegrated(GameObject instance, List<MeshIntegrationResult> results, UniGLTF.UnityPath assetPath,
|
||||
bool applyToPrefab)
|
||||
static List<UniGLTF.MeshUtility.MeshIntegrationResult> Integrate(GameObject root, IEnumerable<Mesh> excludes, bool separateByBlendShape)
|
||||
{
|
||||
// TODO: add integrated
|
||||
var results = new List<UniGLTF.MeshUtility.MeshIntegrationResult>();
|
||||
if (separateByBlendShape)
|
||||
{
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithoutBlendShape, excludes: excludes));
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.OnlyWithBlendShape, excludes: excludes));
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(MeshIntegratorUtility.Integrate(root, onlyBlendShapeRenderers: MeshEnumerateOption.All, excludes: excludes));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 古いMeshを disable にし、新しい統合済み mesh を追加する
|
||||
/// </summary>
|
||||
static void DeactivateOldRendererAndAddIntegrated(GameObject root, List<MeshIntegrationResult> results)
|
||||
{
|
||||
// disable source renderer
|
||||
foreach (var result in results)
|
||||
{
|
||||
@@ -321,31 +334,12 @@ namespace VRM
|
||||
}
|
||||
}
|
||||
|
||||
// Add integrated
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.IntegratedRenderer == null) continue;
|
||||
|
||||
// Add integrated
|
||||
result.IntegratedRenderer.transform.SetParent(instance.transform, false);
|
||||
|
||||
// save as asset mesh
|
||||
var childAssetPath = assetPath.Parent.Child($"{result.IntegratedRenderer.gameObject.name}{ASSET_SUFFIX}");
|
||||
Debug.LogFormat("CreateAsset: {0}", childAssetPath);
|
||||
childAssetPath.CreateAsset(result.IntegratedRenderer.sharedMesh);
|
||||
Undo.RegisterCreatedObjectUndo(result.IntegratedRenderer.gameObject, "Integrate Renderers");
|
||||
}
|
||||
|
||||
// Apply to Prefab
|
||||
if (applyToPrefab)
|
||||
{
|
||||
VrmPrefabUtility.ApplyChangesToPrefab(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(instance, assetPath.Value, out bool success);
|
||||
if (!success)
|
||||
if (result.IntegratedRenderer != null)
|
||||
{
|
||||
throw new System.Exception($"PrefabUtility.SaveAsPrefabAsset: {assetPath}");
|
||||
result.IntegratedRenderer.transform.SetParent(root.transform, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using UniGLTF;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UniGLTF.MeshUtility;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace VRM
|
||||
|
||||
public static void ApplyChangesToPrefab(GameObject instance)
|
||||
{
|
||||
var prefab = GetPrefab(instance);
|
||||
var prefab = instance.GetPrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
return;
|
||||
@@ -46,15 +47,6 @@ namespace VRM
|
||||
PrefabUtility.SaveAsPrefabAssetAndConnect(instance, path, InteractionMode.AutomatedAction);
|
||||
}
|
||||
|
||||
static Object GetPrefab(GameObject instance)
|
||||
{
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
return PrefabUtility.GetCorrespondingObjectFromSource(instance);
|
||||
#else
|
||||
return PrefabUtility.GetPrefabParent(go);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VRM prefab を ${prefab_dir}/MeshIntegratorBackup/ に複製する。
|
||||
///
|
||||
@@ -64,18 +56,21 @@ namespace VRM
|
||||
///
|
||||
/// が複製される。
|
||||
/// </summary>
|
||||
/// <param name="rootPrefab"></param>
|
||||
public static void BackupVrmPrefab(GameObject rootPrefab)
|
||||
/// <param name="go"></param>
|
||||
public static void BackupVrmPrefab(GameObject go)
|
||||
{
|
||||
var proxy = rootPrefab.GetComponent<VRMBlendShapeProxy>();
|
||||
var prefab = go.GetGameObjectType() == GameObjectType.AssetPrefab ? go : PrefabUtility.GetCorrespondingObjectFromSource(go);
|
||||
var prefabPath = UnityPath.FromAsset(prefab);
|
||||
|
||||
var proxy = go.GetComponent<VRMBlendShapeProxy>();
|
||||
|
||||
var srcAvatar = proxy.BlendShapeAvatar;
|
||||
var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, rootPrefab);
|
||||
var dstAvatar = (BlendShapeAvatar)BackupAsset(srcAvatar, prefabPath);
|
||||
|
||||
var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, rootPrefab));
|
||||
var clipMapper = srcAvatar.Clips.ToDictionary(x => x, x => (BlendShapeClip)BackupAsset(x, prefabPath));
|
||||
dstAvatar.Clips = clipMapper.Values.ToList();
|
||||
|
||||
var dstPrefab = BackupAsset(rootPrefab, rootPrefab);
|
||||
var dstPrefab = BackupAsset(prefab, prefabPath);
|
||||
var dstInstance = InstantiatePrefab(dstPrefab);
|
||||
dstInstance.GetComponent<VRMBlendShapeProxy>().BlendShapeAvatar = dstAvatar;
|
||||
ApplyChangesToPrefab(dstInstance);
|
||||
@@ -89,12 +84,12 @@ namespace VRM
|
||||
/// <param name="rootPrefab"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
private static T BackupAsset<T>(T asset, GameObject rootPrefab) where T : UnityEngine.Object
|
||||
private static T BackupAsset<T>(T asset, UnityPath prefabPath) where T : UnityEngine.Object
|
||||
{
|
||||
var srcAssetPath = UnityPath.FromAsset(asset);
|
||||
var assetName = srcAssetPath.FileName;
|
||||
|
||||
var backupPath = UnityPath.FromAsset(rootPrefab).Parent.Child(BACKUP_DIR);
|
||||
var backupPath = prefabPath.Parent.Child(BACKUP_DIR);
|
||||
backupPath.EnsureFolder();
|
||||
var dstAssetPath = backupPath.Child(assetName);
|
||||
|
||||
|
||||
@@ -91,6 +91,12 @@ namespace VRM
|
||||
var rendererComponents = transform.GetComponentsInChildren<Renderer>();
|
||||
foreach (var renderer in rendererComponents)
|
||||
{
|
||||
// renderer が !enabled/!activeSelf なのがロード中なのか否か区別がつかないような気がするので
|
||||
// チェックしない。
|
||||
// if(!renderer.enabled)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
var flags = new RendererFirstPersonFlags
|
||||
{
|
||||
Renderer = renderer,
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniGLTF;
|
||||
using UniGLTF.MeshUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM
|
||||
{
|
||||
/// <summary>
|
||||
/// Meshを統合し、統合後のMeshのBlendShapeの変化をVRMのBlendShapeClipに反映する
|
||||
/// </summary>
|
||||
public static class VRMMeshIntegratorUtility
|
||||
{
|
||||
public static void FollowBlendshapeRendererChange(List<BlendShapeClip> clips, MeshIntegrationResult result, GameObject root)
|
||||
{
|
||||
if (clips == null || result == null || result.IntegratedRenderer == null || root == null) return;
|
||||
|
||||
var rendererDict = result.SourceSkinnedMeshRenderers
|
||||
.ToDictionary(x => x.transform.RelativePathFrom(root.transform), x => x);
|
||||
|
||||
var dstPath = result.IntegratedRenderer.transform.RelativePathFrom(root.transform);
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip == null) continue;
|
||||
|
||||
for (var i = 0; i < clip.Values.Length; ++i)
|
||||
{
|
||||
var val = clip.Values[i];
|
||||
if (rendererDict.ContainsKey(val.RelativePath))
|
||||
{
|
||||
var srcRenderer = rendererDict[val.RelativePath];
|
||||
var name = srcRenderer.sharedMesh.GetBlendShapeName(val.Index);
|
||||
var newIndex = result.IntegratedRenderer.sharedMesh.GetBlendShapeIndex(name);
|
||||
|
||||
val.RelativePath = dstPath;
|
||||
val.Index = newIndex;
|
||||
}
|
||||
|
||||
clip.Values[i] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user