/// <summary> /// nesessaryShapeKeysで指定されたシェイプキーから法線・接線を削除し、それ以外のシェイプキーは削除します。 /// </summary> /// <param name="mesh"></param> /// <param name="nesessaryShapeKeys"></param> /// <returns></returns> internal static void CleanUpShapeKeys(Mesh mesh, IEnumerable <string> nesessaryShapeKeys) { var shapeKeys = SkinnedMeshUtility.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents: false); mesh.ClearBlendShapes(); foreach (var name in nesessaryShapeKeys) { var shapeKey = shapeKeys.FirstOrDefault(key => key.Name == name); if (shapeKey == null) { continue; } mesh.AddBlendShapeFrame( shapeKey.Name, BlendShapeReplacer.MaxBlendShapeFrameWeight, shapeKey.Positions.ToArray(), shapeKey.Normals.ToArray(), shapeKey.Tangents.ToArray() ); } }
protected override bool DrawWizardGUI() { this.isValid = true; if (VRChatUtility.SDKVersion == null) { EditorGUILayout.HelpBox(_("VRChat SDK2 or SDK3 has not been imported."), MessageType.Error); this.isValid = false; return(true); } if (!this.prefabOrInstance.GetComponent <Animator>().isHuman) { EditorGUILayout.HelpBox(_("This is not humanoid."), MessageType.Error); this.isValid = false; return(true); } if (Application.unityVersion != VRChatUtility.SDKSupportedUnityVersion) { EditorGUILayout.HelpBox(string.Format( _("Unity {0} is running. If you are using a different version than {1}, VRChat SDK might not work correctly. Recommended using Unity downloaded from {2} ."), Application.unityVersion, VRChatUtility.SDKSupportedUnityVersion, VRChatUtility.DownloadURL ), MessageType.Warning); } if (this.expressions == null) { this.shapeKeyNames = this.prefabOrInstance.GetComponentsInChildren <SkinnedMeshRenderer>() .Select(renderer => renderer.sharedMesh) .Where(mesh => mesh != null) .SelectMany(mesh => SkinnedMeshUtility.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents: false)) .Select(shapeKey => shapeKey.Name) .Distinct(); (this.animations, this.expressions) = VRChatUtility.DetectVRChatExpressions(this.prefabOrInstance, this.shapeKeyNames); this.animationNames = new[] { "-" }.Concat(this.animations.Select(animation => animation.name)).ToArray(); this.maybeBlinkShapeKeyNames = this.expressions .Where(expression => VRChatToVRMWizard.PresetFieldPairs.ContainsKey(expression.Key) && VRChatToVRMWizard.PresetFieldPairs[expression.Key] == nameof(VRChatExpressionBinding.ShapeKeyNames)) .SelectMany(expression => expression.Value.ShapeKeyNames) .Concat(VRChatUtility.DetectBlinkShapeKeyNames(this.shapeKeyNames)) .Distinct() .Take(VRChatToVRMWizard.UnityEditorMaxMultiSelectCount) .ToArray(); this.expressionPresetFlagPairs = VRChatToVRMWizard.PresetFieldPairs .ToDictionary( presetFieldPair => presetFieldPair.Key, presetFieldPair => this.expressions.ContainsKey(presetFieldPair.Key) ? (VRChatToVRMWizard.PresetFieldPairs[presetFieldPair.Key] == nameof(VRChatExpressionBinding.AnimationClip) ? 1 + this.animations.ToList().IndexOf(this.expressions[presetFieldPair.Key].AnimationClip) : VRChatToVRMWizard.ToFlags(this.maybeBlinkShapeKeyNames, this.expressions[presetFieldPair.Key].ShapeKeyNames)) : 0 ); this.metaEditor = Editor.CreateEditor(this.meta); } EditorGUILayout.LabelField("Expressions", EditorStyles.boldLabel); foreach (var(preset, field) in VRChatToVRMWizard.PresetFieldPairs) { this.expressionPresetFlagPairs[preset] = VRChatToVRMWizard.PresetFieldPairs[preset] == nameof(VRChatExpressionBinding.AnimationClip) ? EditorGUILayout.Popup( preset.ToString(), this.expressionPresetFlagPairs[preset], this.animationNames ) : EditorGUILayout.MaskField( preset.ToString(), this.expressionPresetFlagPairs[preset], this.maybeBlinkShapeKeyNames ); } this.metaEditor.OnInspectorGUI(); return(true); }
/// <summary> /// VRChatアバターインスタンスからVRMインスタンスへ変換します。 /// </summary> /// <param name="instance">ヒエラルキー上のGameObject。</param> /// <param name="presetVRChatBindingPairs">各表情への割り当て。</param> internal static void Convert( string outputPath, GameObject instance, VRMMetaObject meta, IDictionary <ExpressionPreset, VRChatExpressionBinding> presetVRChatBindingPairs ) { GameObject clone = null, normalized = null; try { var rootObjectName = instance.name; clone = Object.Instantiate(instance); // 非表示のオブジェクト・コンポーネントを削除 // TODO: アクティブ・非アクティブの切り替えをシェイプキーに変換する VRChatToVRMConverter.RemoveInactiveObjectsAndDisabledComponents(clone); // 表情とシェイプキー名の組み合わせを取得 var presetShapeKeyNameWeightPairsPairs = presetVRChatBindingPairs.ToDictionary( presetVRChatBindingPair => presetVRChatBindingPair.Key, presetVRChatBindingPair => VRChatExpressionsReplacer.ExtractShapeKeyNames(presetVRChatBindingPair.Value) ); // VRM設定1 var temporaryFolder = UnityPath.FromUnityPath(VRChatToVRMConverter.TemporaryFolderPath); temporaryFolder.EnsureFolder(); var temporaryPrefabPath = temporaryFolder.Child(VRChatToVRMConverter.TemporaryPrefabFileName).Value; VRMInitializer.Initialize(temporaryPrefabPath, clone); VRChatToVRMConverter.SetFirstPersonOffset(clone); VRChatToVRMConverter.SetLookAtBoneApplyer(clone); var sourceAndDestination = clone.GetComponent <Animator>(); if (DynamicBones.IsImported()) { DynamicBonesToVRMSpringBonesConverter.Convert( source: sourceAndDestination, destination: sourceAndDestination ); VRChatToVRMConverter.RemoveUnusedColliderGroups(clone); } // 正規化 normalized = VRMBoneNormalizer.Execute(clone, forceTPose: true); // 全メッシュ結合 var combinedRenderer = CombineMeshesAndSubMeshes.Combine( normalized, notCombineRendererObjectNames: new List <string>(), destinationObjectName: "vrm-mesh", savingAsAsset: false ); // 使用していないシェイプキーの削除 SkinnedMeshUtility.CleanUpShapeKeys(combinedRenderer.sharedMesh, presetShapeKeyNameWeightPairsPairs .SelectMany(presetShapeKeyNameWeightPairsPair => presetShapeKeyNameWeightPairsPair.Value.Keys) .Distinct()); // シェイプキーの分離 Utilities.MeshUtility.SeparationProcessing(normalized); // マテリアルの設定・アセットとして保存 VRChatToVRMConverter.ReplaceShaders(normalized, temporaryPrefabPath); // GameObject・メッシュなどをアセットとして保存 (アセットとして存在しないと正常にエクスポートできない) normalized.name = rootObjectName; var animator = normalized.GetComponent <Animator>(); animator.avatar = Duplicator.CreateObjectToFolder(animator.avatar, temporaryPrefabPath); meta.name = "Meta"; normalized.GetComponent <VRMMeta>().Meta = Duplicator.CreateObjectToFolder(meta, temporaryPrefabPath); foreach (var renderer in normalized.GetComponentsInChildren <SkinnedMeshRenderer>()) { renderer.sharedMesh.name = renderer.name; renderer.sharedMesh = Duplicator.CreateObjectToFolder(renderer.sharedMesh, temporaryPrefabPath); } // VRM設定2 VRChatToVRMConverter.SetFirstPersonRenderers(normalized); // 表情の設定 VRChatExpressionsReplacer.SetExpressions(normalized, presetShapeKeyNameWeightPairsPairs); var prefab = PrefabUtility .SaveAsPrefabAssetAndConnect(normalized, temporaryPrefabPath, InteractionMode.AutomatedAction); // エクスポート AssetDatabase.SaveAssets(); File.WriteAllBytes( outputPath, VRMEditorExporter.Export(prefab, meta: null, ScriptableObject.CreateInstance <VRMExportSettings>()) ); } catch (Exception exception) { ErrorDialog.Open(exception); throw; } finally { if (clone != null) { Object.DestroyImmediate(clone); } if (normalized != null) { Object.DestroyImmediate(normalized); } AssetDatabase.DeleteAsset("Assets/VRMConverterTemporary"); } }
/// <summary> /// リップシンクの設定を行います。 /// </summary> /// <remarks> /// <see cref="BlendShapePreset.A"/>、<see cref="BlendShapePreset.I"/>、<see cref="BlendShapePreset.O"/>が /// 単一のフレームを持つシェイプキーが存在しない場合、設定を行いません。 /// 生成するシェイプキー名と同じシェイプキーが存在する場合、それを利用します。 /// </remarks> /// <param name="avatar"></param> /// <param name="clips"></param> /// <param name="useShapeKeyNormalsAndTangents"></param> private static void SetLipSync( GameObject avatar, IEnumerable <VRMBlendShapeClip> clips, bool useShapeKeyNormalsAndTangents ) { Transform transform = avatar.transform.Find(VRChatUtility.AutoBlinkMeshPath); var renderer = transform.GetComponent <SkinnedMeshRenderer>(); Mesh mesh = renderer.sharedMesh; foreach (var preset in new[] { BlendShapePreset.A, BlendShapePreset.I, BlendShapePreset.O }) { if (!clips.FirstOrDefault(c => c.Preset == preset)) { return; } } IEnumerable <BlendShape> shapeKeys = SkinnedMeshUtility.GetAllShapeKeys(mesh, useShapeKeyNormalsAndTangents); foreach (var(newName, values) in BlendShapeReplacer.VisemeShapeKeyNamesAndValues) { if (mesh.GetBlendShapeIndex(newName) != -1) { continue; } Vector3[] deltaVertices = null; foreach (Vector3[] vertices in values.SelectMany(presetAndWeight => BlendShapeReplacer.SubtractNeutralShapeKeyValues(clips.First(clip => clip.Preset == presetAndWeight.Key).ShapeKeyValues, clips) .Select(shapeKeyNameAndWeight => shapeKeys .First(shapeKey => shapeKey.Name == shapeKeyNameAndWeight.Key) .Positions .Select(vertix => vertix * (shapeKeyNameAndWeight.Value / VRMUtility.MaxBlendShapeBindingWeight * presetAndWeight.Value)) .ToArray() ) )) { if (deltaVertices == null) { deltaVertices = vertices; continue; } for (var i = 0; i < deltaVertices.Length; i++) { deltaVertices[i] += vertices[i]; } } mesh.AddBlendShapeFrame( newName, BlendShapeReplacer.MaxBlendShapeFrameWeight, deltaVertices, null, null ); } EditorUtility.SetDirty(mesh); var avatarDescriptor #if VRC_SDK_VRCSDK3 = avatar.GetComponent <VRC_AvatarDescriptor>(); avatarDescriptor.lipSync = VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape;