パカパカパカリンコタウン

黒歴史や生き恥を切り売り

clusterにVRChatで使っているミルティナちゃんをアップロードしようとして手こずったメモ

私は普段VRChatでミルティナちゃんをケモ改変して使ってます。

このたび、clusterに出向く用事ができたので、アバターアップロードのためVRM化をしてたのですが、

VRMにしてアップロードしてみると・・・

失敗!!

 

ポリゴン数はまぁオーバーしているだろうなと思っていましたが、

morphTargetっていうのはなんぞやと。

Blenderでいうシェイプキー、UnityでいうBlendShape相当のものとのこと。

 

booth.pm

 

ミルティナちゃんは表情調整シェイプキーだけでも、658種類あるからそれでオーバーじゃん!

 

 

教えてもらった方法としては、こちら。

 

AAO: Avatar Optimizer

https://vpm.anatawa12.com/avatar-optimizer/ja/#installation

 

 

アバターのオブジェクトにAddComponentから、「AAOTraceAndOptimize」をアタッチして、

ModularAvatarのManualBakeAvatarを押せば

なんか色々統合されたのができている!


なんかBlendShapesも減って、統合??されたものになってる!

すごい!

 

AAO Trace and OptimizeつけてModularAvatarのManual Bake Avatarすれば、いろいろ適応されたアバターが別で出来上がるの知らなかった・・・

 

そしてポリゴンを減らすのは、服や靴をはぎ取って対応しました。

悲しいね。でもケモだから靴は履いてなくてもいいよね。

 

この生成されたアバターをVRMでエクスポートして、

clusterにアップロード!!

 

できた!

なんか表示されている画像がぐちゃぐちゃ!!

 

 

でも一応ちゃんとVRで動かした感じ大丈夫そう。

 

余談ですが、BlendShapesを削除できるの知らなかったので、教えてもらうまではChatGPTに削除するスクリプト作らせてそれで対処してました。

 

以下がBlendShapesを削除するスクリプト。

 

using UnityEditor;
using UnityEngine;

public static class RemoveBlendShapesByPrefix
{
    // 削除したい先頭文字列。大文字小文字は無視します。
    private static readonly string[] RemovePrefixes =
    {
        "tongue",
        "brow",
        "shrink",
        "mouth",
        "tooth",
        "pupil",
        "eye_under",
        "eyebrow",
    };

    [MenuItem("Tools/Avatar/Remove BlendShapes By Prefix From Selected")]
    private static void Remove()
    {
        var go = Selection.activeGameObject;
        if (go == null)
        {
            Debug.LogError("SkinnedMeshRenderer が付いたオブジェクトを選択してください。");
            return;
        }

        var smr = go.GetComponent<SkinnedMeshRenderer>();
        if (smr == null || smr.sharedMesh == null)
        {
            Debug.LogError("選択中オブジェクトに SkinnedMeshRenderer / sharedMesh がありません。");
            return;
        }

        var src = smr.sharedMesh;
        var dst = Object.Instantiate(src);
        dst.name = src.name + "_RemovedSelectedBlendShapes";

        int vertexCount = src.vertexCount;

        var deltaVertices = new Vector3[vertexCount];
        var deltaNormals = new Vector3[vertexCount];
        var deltaTangents = new Vector3[vertexCount];

        dst.ClearBlendShapes();

        int kept = 0;
        int removed = 0;

        for (int shapeIndex = 0; shapeIndex < src.blendShapeCount; shapeIndex++)
        {
            string shapeName = src.GetBlendShapeName(shapeIndex);

            if (ShouldRemove(shapeName))
            {
                Debug.Log($"Remove BlendShape: {shapeName}");
                removed++;
                continue;
            }

            int frameCount = src.GetBlendShapeFrameCount(shapeIndex);

            for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
            {
                float frameWeight = src.GetBlendShapeFrameWeight(shapeIndex, frameIndex);

                src.GetBlendShapeFrameVertices(
                    shapeIndex,
                    frameIndex,
                    deltaVertices,
                    deltaNormals,
                    deltaTangents
                );

                dst.AddBlendShapeFrame(
                    shapeName,
                    frameWeight,
                    deltaVertices,
                    deltaNormals,
                    deltaTangents
                );
            }

            kept++;
        }

        string path = EditorUtility.SaveFilePanelInProject(
            "Save Mesh Without Selected BlendShapes",
            dst.name + ".asset",
            "asset",
            "保存先を選んでください"
        );

        if (string.IsNullOrEmpty(path))
        {
            Object.DestroyImmediate(dst);
            return;
        }

        AssetDatabase.CreateAsset(dst, path);
        AssetDatabase.SaveAssets();

        Undo.RecordObject(smr, "Remove BlendShapes By Prefix");
        smr.sharedMesh = dst;
        EditorUtility.SetDirty(smr);

        Debug.Log($"BlendShape削除完了: mesh={src.name}, kept={kept}, removed={removed}, before={src.blendShapeCount}, after={dst.blendShapeCount}");
        Debug.Log($"Saved: {path}");
    }

    private static bool ShouldRemove(string shapeName)
    {
        string lower = shapeName.ToLowerInvariant();

        foreach (string prefix in RemovePrefixes)
        {
            if (lower.StartsWith(prefix.ToLowerInvariant()))
            {
                return true;
            }
        }

        return false;
    }
}

 

 

あとポリゴンを減らすスクリプト

 

using UnityEditor;
using UnityEngine;
using UnityMeshSimplifier;

public static class SimplifySelectedSkinnedMesh
{
    // 0.85 = 15%くらい削る目安。まずは弱め推奨。
    private const float Quality = 0.85f;

    [MenuItem("Tools/Avatar/Simplify Selected SkinnedMesh")]
    private static void Simplify()
    {
        var go = Selection.activeGameObject;
        if (go == null)
        {
            Debug.LogError("SkinnedMeshRenderer が付いたオブジェクトを選択してください。");
            return;
        }

        var smr = go.GetComponent<SkinnedMeshRenderer>();
        if (smr == null || smr.sharedMesh == null)
        {
            Debug.LogError("選択中オブジェクトに SkinnedMeshRenderer / sharedMesh がありません。");
            return;
        }

        var src = smr.sharedMesh;

        Debug.Log($"Before: {src.name}, triangles={GetTriangleCount(src)}, blendShapes={src.blendShapeCount}");

        var simplifier = new MeshSimplifier();
        simplifier.Initialize(src);

        // 穴あき防止寄りの設定
        var options = SimplificationOptions.Default;
        options.PreserveBorderEdges = true;
        options.PreserveUVSeamEdges = true;
        options.PreserveUVFoldoverEdges = true;
        simplifier.SimplificationOptions = options;

        simplifier.SimplifyMesh(Quality);

        var dst = simplifier.ToMesh();
        dst.name = src.name + "_Simplified_" + Mathf.RoundToInt(Quality * 100);

        string path = EditorUtility.SaveFilePanelInProject(
            "Save Simplified Mesh",
            dst.name + ".asset",
            "asset",
            "保存先を選んでください"
        );

        if (string.IsNullOrEmpty(path))
        {
            Object.DestroyImmediate(dst);
            return;
        }

        AssetDatabase.CreateAsset(dst, path);
        AssetDatabase.SaveAssets();

        Undo.RecordObject(smr, "Replace Skinned Mesh");
        smr.sharedMesh = dst;
        EditorUtility.SetDirty(smr);

        Debug.Log($"After: {dst.name}, triangles={GetTriangleCount(dst)}, blendShapes={dst.blendShapeCount}");
        Debug.Log($"Simplified mesh saved: {path}");
    }

    private static int GetTriangleCount(Mesh mesh)
    {
        int count = 0;
        for (int i = 0; i < mesh.subMeshCount; i++)
        {
            count += mesh.GetTriangles(i).Length / 3;
        }
        return count;
    }
}