Unityでブレンドシェイプを切り替えるスクリプトを動かしたのでメモ。VRMモデルの表情をキーで切り替え

Unity
※記事内に広告が含まれています。

バーチャル3Dクリエイター神部まゆみです(*^_^*)

この記事はUnityでブレンドシェイプを切り替えるスクリプトを動かしたのでメモです。

動作確認した最新バージョンはUnity 6000.0.32、VRoid Studio v2.4.1でエクスポートしたVRM1.0とVRM0.xのモデルです。

ブレンドシェイプだけではなくアニメーション自体を切り替える方法は↓こちらの記事に書きました。

あとVRoidの場合は↓こちらの記事で表情ブレンドシェイプのパラメーターを画像付きでまとめておいたので参考になるかも。

●PRスペース●

〇まゆみマート|BOOTH

BOOTHでVRoidテクスチャやVRChat向けオブジェクトなどを販売しています。いいねしてくれると励みになります(*^-^*)

VRM1.0の場合はVRM Instanceの設定を Update Typeなし にしたら動いた

VRM0.xだとこのページのコードは問題なく動作しましたが、VRM1.0の場合はこれをしないと動かなかった。

VRM1 ブレンドシェイプ 動かない

こちらで解説してます。

ついでにブレンドシェイプ自体がない時の対処法の記事も貼っておきます↓

追記:UniVRMのバージョンの問題の可能性も?

VRM1.0でもUniVRMのバージョンを変えたらUpdate Typeなしにしなくても普通に動いたため、単にUniVRMとUnityバージョンの相性の問題かも?

UniVRM 0.128.3とUnity6000.0.32f1の組み合わせだとVRM1のLate Updateでも普通にブレンドシェイプ動きました。

vrm1 ブレンドシェイプ 動かない

いったんUniVRMを削除してみて別のバージョンを入れれば普通に動く可能性があるかも?

しかし削除すると、UniVRMのバージョンによってはマテリアル設定し直しになったことがあったから自己責任だけど…。

ダメなら仕方ないのでUpdate Type なし でやるしかないですね…。今のところ特に不具合はないけども。

追記おわり。

ブレンドシェイプを切り替えるスクリプト VRMExpressionController.cs

ChatGPT氏に書いてもらいました。

VRMExpressionController.cs

using UnityEngine;

public class VRMExpressionController : MonoBehaviour
{
    [SerializeField] private SkinnedMeshRenderer faceMeshRenderer; // Faceメッシュ
    [SerializeField] private string[] blendShapeNames;             // ブレンドシェイプ名のリスト

    private void Update()
    {
        // キー入力で表情を切り替え
        if (Input.GetKeyDown(KeyCode.Alpha1)) // "1"キーで表情1
        {
            SetExpression(blendShapeNames[0]);
            Debug.LogWarning("1キーが押されました");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2)) // "2"キーで表情2
        {
            SetExpression(blendShapeNames[1]);
            Debug.LogWarning("2キーが押されました");
        }
        // 必要に応じて他のキーも設定
    }

    private void SetExpression(string blendShapeName)
    {
        // すべてのブレンドシェイプをリセット
        for (int i = 0; i < faceMeshRenderer.sharedMesh.blendShapeCount; i++)
        {
            faceMeshRenderer.SetBlendShapeWeight(i, 0f);
        }

        // 指定したブレンドシェイプを適用
        int index = faceMeshRenderer.sharedMesh.GetBlendShapeIndex(blendShapeName);
        if (index >= 0)
        {
            faceMeshRenderer.SetBlendShapeWeight(index, 100f); // 値を100に設定(調整可能)
        }
        else
        {
            Debug.LogWarning($"ブレンドシェイプ '{blendShapeName}' が見つかりません。");
        }
    }
}

使い方はモデルにアタッチしてFaceを指定、ブレンドシェイプ名を追加する

モデルにアタッチしてスキンメッシュレンダラーのあるオブジェクトを指定、ブレンドシェイプ名を手動で追加します。

これでキー入力で表情を変えることに成功!

注意点:元の表情にプラスされる感じなので、無表情のアニメーションに使ったほうがいい

特にVRoidのJoyとかSurprisedなどの大口を開けるブレンドシェイプが設定されているアニメーションにこのスクリプトを使ってしまうと、そこから更に口を開けてしまい破綻する可能性が高いです。

なのでできるだけ無表情のアニメーションにスクリプトを使うことを推奨。

これリップシンク系のアセットを使う時も同じような問題が起きるんだけど、表情はアニメーションで表現するのか、スクリプトで動かすのか事前に決めてからアニメーション作った方が良いですね。

ブレンドシェイプを0にして初期化するスクリプトも試しに書いてみたんだけど、アニメーションがループしたら戻ってしまった。

指定した秒数で表情遷移する改良版 VRMExpressionController2.cs

上のスクリプトだと一瞬で表情が切り替わって違和感があったので、指定した秒数で表情遷移させるようにした。

using UnityEngine;
using System.Collections;

public class VRMExpressionController2 : MonoBehaviour
{
    [SerializeField] private SkinnedMeshRenderer faceMeshRenderer; // Faceメッシュ
    [SerializeField] private string[] blendShapeNames;             // ブレンドシェイプ名のリスト
    [SerializeField] private float transitionDuration = 0.5f;      // 切り替えにかかる時間(秒)

    private Coroutine transitionCoroutine;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1)) // "1"キーで表情1
        {
            StartExpressionTransition(blendShapeNames[0]);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2)) // "2"キーで表情2
        {
            StartExpressionTransition(blendShapeNames[1]);
        }
    }

    private void StartExpressionTransition(string blendShapeName)
    {
        if (transitionCoroutine != null)
        {
            StopCoroutine(transitionCoroutine);
        }
        transitionCoroutine = StartCoroutine(TransitionExpression(blendShapeName));
    }

    private IEnumerator TransitionExpression(string blendShapeName)
    {
        int newIndex = faceMeshRenderer.sharedMesh.GetBlendShapeIndex(blendShapeName);
        if (newIndex < 0)
        {
            Debug.LogWarning($"ブレンドシェイプ '{blendShapeName}' が見つかりません。");
            yield break;
        }

        // 現在のブレンドシェイプの値を取得
        float[] initialWeights = new float[faceMeshRenderer.sharedMesh.blendShapeCount];
        for (int i = 0; i < initialWeights.Length; i++)
        {
            initialWeights[i] = faceMeshRenderer.GetBlendShapeWeight(i);
        }

        float elapsedTime = 0f;
        while (elapsedTime < transitionDuration)
        {
            elapsedTime += Time.deltaTime;
            float t = elapsedTime / transitionDuration; // 0 から 1 へ補間

            // すべてのブレンドシェイプをリセット(徐々に0へ)
            for (int i = 0; i < initialWeights.Length; i++)
            {
                faceMeshRenderer.SetBlendShapeWeight(i, Mathf.Lerp(initialWeights[i], 0f, t));
            }

            // 目的のブレンドシェイプを徐々に適用
            faceMeshRenderer.SetBlendShapeWeight(newIndex, Mathf.Lerp(0f, 100f, t));

            yield return null;
        }

        // 最終的な状態を適用
        for (int i = 0; i < initialWeights.Length; i++)
        {
            faceMeshRenderer.SetBlendShapeWeight(i, 0f);
        }
        faceMeshRenderer.SetBlendShapeWeight(newIndex, 100f);
    }
}

ここで遷移間隔が指定でき、長い数値を指定するとゆっくり遷移します。

ブレンドシェイプ unity 切り替え

2025/10/12追記:複数のスキンメッシュレンダラーを指定し、キーも指定できる改良版 VRMExpressionController3.cs

これは複数のスキンメッシュレンダラーを指定出来て、キーも指定できる改良版です。

これが一番使いやすいかな?

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class MeshBlendShapeSet
{
    public SkinnedMeshRenderer meshRenderer;
    public string[] blendShapeNames;
    public KeyCode[] keys; // 各ブレンドシェイプに対応するキー
}

public class VRMExpressionController3 : MonoBehaviour
{
    [SerializeField] private List<MeshBlendShapeSet> meshBlendShapeSets = new List<MeshBlendShapeSet>();
    [SerializeField] private float transitionDuration = 0.5f;

    private Coroutine transitionCoroutine;

    private void Update()
    {
        // 各メッシュごとにキー入力をチェック
        for (int m = 0; m < meshBlendShapeSets.Count; m++)
        {
            var set = meshBlendShapeSets[m];
            for (int i = 0; i < set.keys.Length; i++)
            {
                if (Input.GetKeyDown(set.keys[i]))
                {
                    StartExpressionTransition(m, i);
                }
            }
        }
    }

    private void StartExpressionTransition(int meshIndex, int expressionIndex)
    {
        if (transitionCoroutine != null)
        {
            StopCoroutine(transitionCoroutine);
        }
        transitionCoroutine = StartCoroutine(TransitionExpression(meshIndex, expressionIndex));
    }

    private IEnumerator TransitionExpression(int meshIndex, int expressionIndex)
    {
        var set = meshBlendShapeSets[meshIndex];
        var mesh = set.meshRenderer.sharedMesh;
        int targetIndex = mesh.GetBlendShapeIndex(set.blendShapeNames[expressionIndex]);
        if (targetIndex < 0)
        {
            Debug.LogWarning($"ブレンドシェイプ '{set.blendShapeNames[expressionIndex]}' が見つかりません。");
            yield break;
        }

        float[] initialWeights = new float[mesh.blendShapeCount];
        for (int i = 0; i < mesh.blendShapeCount; i++)
            initialWeights[i] = set.meshRenderer.GetBlendShapeWeight(i);

        float elapsed = 0f;
        while (elapsed < transitionDuration)
        {
            elapsed += Time.deltaTime;
            float t = elapsed / transitionDuration;

            for (int i = 0; i < mesh.blendShapeCount; i++)
                set.meshRenderer.SetBlendShapeWeight(i, Mathf.Lerp(initialWeights[i], 0f, t));

            set.meshRenderer.SetBlendShapeWeight(targetIndex, Mathf.Lerp(0f, 100f, t));
            yield return null;
        }

        for (int i = 0; i < mesh.blendShapeCount; i++)
            set.meshRenderer.SetBlendShapeWeight(i, 0f);
        set.meshRenderer.SetBlendShapeWeight(targetIndex, 100f);
    }
}

こんな感じで指定できます。

ブレンドシェイプ 切り替え script

指定したオブジェクトに設定されてるブレンドシェイプをコンソールに出力するコード BlendShapeLister.cs

これスキンメッシュレンダラーのところに書いてあるけど、手入力だと間違うのでコピペできるように作った。

BlendShapeLister.cs

using UnityEngine;

public class BlendShapeLister : MonoBehaviour
{
    [SerializeField] private SkinnedMeshRenderer faceMeshRenderer; // Faceメッシュ

    private void Start()
    {
        if (faceMeshRenderer == null)
        {
            Debug.LogError("SkinnedMeshRendererが設定されていません!");
            return;
        }

        Mesh mesh = faceMeshRenderer.sharedMesh;
        if (mesh == null)
        {
            Debug.LogError("SkinnedMeshRendererにMeshが設定されていません!");
            return;
        }

        Debug.Log($"ブレンドシェイプの数: {mesh.blendShapeCount}");

        for (int i = 0; i < mesh.blendShapeCount; i++)
        {
            string blendShapeName = mesh.GetBlendShapeName(i);
            Debug.Log($"ブレンドシェイプ[{i}]: {blendShapeName}");
        }
    }
}

使い方:モデルにアタッチしてスキンメッシュレンダラーのあるオブジェクトを指定、実行ボタンを押す

これでコンソールに出力されるのでコピペできます。

追記:アニメーターコントローラーに不具合があると動かないことがあるっぽい?

このスクリプトたまに使うんだけど、モデルによってスムーズに動く時もあれば、モデルが違うと全く動かない時もあっておかしいなと思っていた。

どうもアニメーターファイルに不具合があると動かないことがあるっぽい?↓だと同じアニメーションを読み込んだ別のアニメーターを指定しただけで動くようになりました。

↑では普通に動いたけど、VRM Humanoid Desriptionをオフにすると動いたりした。

こんなのあるのか…?(-_-;)

あとは動いたモデルのアバターファイルとアニメーターに切り替えたら動いたとかもありましたね。

もし動かなければそのあたりをいじってみると動作するかもしれません。

アニメーターコントローラーを新規に作ったら正常に動いた

その後、アニメーターコントローラーを新規に作成して使ったら普通に動きました。

アニメーターコントローラーが壊れていたとか?ChatGPTに聞いてみたけどよくわからないっぽいです。

まぁこういうこともあるよってことで。

あとVRM Look At Headとか特定のスクリプトとは相性が悪い…

あとVRM Look At Headとは相性が悪いっぽい?Update Typeなしにしたら動いたけど。

unity ブレンドシェイプ 動かない

他にもLook Animatorっていう似たようなアセットでも動作がちょっと微妙な感じだったので、競合する可能性がありそうです。

追記:モーションキャプチャー+リップシンクと併用もできた

このスクリプトはFace以外にもシェイプキーがあるオブジェクトは全て制御できるため、↓のように赤面をキーでコントロールしたりもできます。

ちなみにこれはEVMC4UってやつとXR Animatorって言うモーションキャプチャーアプリでやりました。

んーこれ普通にシチュエーション系の動画とか作れそうだ…。

おわりに

ブレンドシェイプを切り替えられると便利なので作ってみました。

参考になればつかってみてください。

また何かあれば追記します(*^_^*)

タイトルとURLをコピーしました