Skip to content

ランタイム実行時に動的に追加したExpressionを認識できるようにしてほしいです #2802

Description

@nishlumi

要望

 VRMに追加するカスタムエクスプレッションですが、ランタイム実行時に動的にカスタムエクスプレッションをVRMに追加した場合にそれをアプリ側で参照できるようにしてほしいです。

経緯と概要

 拙作VRMViewMeisterで以前VRM1.0対応したときに、読み込んだVRMのSkinnedMeshRendererがもつBlendshapeが動かなくなるという問題に遭遇しました。(すべてではなく、一部。しかもVRMの制作元アプリによっても異なる)
 しかしExpression側は全部動くため、アプリ側としてどのVRMが読み込まれても、確実にBlendshapeを表情クリップとして利用できるようにするため、BlendshapeをExpressionに変換し、動的に追加しようとしました。

 しかし、実行時(ランタイム)にスクリプトから動的にExpression(元Blendshape)を追加または変更した場合(例:vrminstance.Vrm.Expression.AddClip(...) の呼び出しなど)、これらの変更は Vrm10RuntimeExpression に反映されず、ランタイム実行中のアプリではカスタムExpressionが動きませんでした。
 そのため、新しく動的に追加した表情クリップを SetWeight() 等で制御することが不可能という課題が発生しました。

 そこでこの制限を解決するため、拙作アプリでは、 Vrm10RuntimeExpression にパブリックメソッド ReloadCustomClip(Vrm10Instance target) を追加して利用しております。
 このメソッドをアプリ側の、カスタムExpressionを追加した後に呼び出すことで、現在設定されている表情ウェイト情報を失うことなく、ランタイムの表情制御状態を安全に再ロードして同期させることができるようになります。
 この仕組みを提案させていただきます。

 ご検討のほどよろしくお願いします。

提案する API

Vrm10RuntimeExpression に以下のパブリックメソッドを追加します。

/// <summary>
/// 動的に追加または変更された表情クリップ(カスタムクリップ)を反映するために表情制御システムを再ロードする。
/// </summary>
public void ReloadCustomClip(Vrm10Instance target)
{
    // 既存のマテリアル初期値を復元して破棄
    _merger?.RestoreMaterialInitialValues();
    _merger = null;

    // 最新の表情定義に基づいて再生成
    _merger = new ExpressionMerger(target.Vrm.Expression, target.transform, _isPrefabInstance);
    _keys = target.Vrm.Expression.Clips.Select(x => target.Vrm.Expression.CreateKey(x.Clip)).ToList();
    
    var oldInputWeights = _inputWeights;
    _inputWeights = _keys.ToDictionary(x => x, x => 0f);
    foreach (var key in _keys)
    {
        // リロード時の一瞬の表情カクつきを防ぐため、既に設定されていたウェイトを引き継ぐ
        if (oldInputWeights.ContainsKey(key)) _inputWeights[key] = oldInputWeights[key];
    }
    
    _actualWeights = _keys.ToDictionary(x => x, x => 0f);
    _validator = ExpressionValidatorFactory.Create(target.Vrm.Expression);
}

実際は、Vrm10RuntimeExpressionの初期処理の一部を流用しただけのものです。それらをExpressionのみ再読み込みするようにしたものです。

影響範囲は、カスタムExpressionを動的に追加する目的以外に影響はないものと想定しております。

使用例

使用例のイメージはこちらです。

利用シーンは、VRMを読み込みExpressionなどをアプリで初使用する前のタイミングを想定しています。

// 1. 実行時に動的にカスタム表情 (VRM10Expression) を作成
var myCustomExpression = ScriptableObject.CreateInstance<VRM10Expression>();
myCustomExpression.name = "MyCustomFace";
myCustomExpression.MorphTargetBindings = new MorphTargetBinding[]
{
    new MorphTargetBinding("path/to/skinned_mesh", blendShapeIndex, 1.0f)
};

// 2. Vrm10Instance の Expression に追加
vrmInstance.Vrm.Expression.AddClip(ExpressionPreset.custom, myCustomExpression);

// 3. Vrm10RuntimeExpression に変更を反映させるためにリロードを実行
vrmInstance.Runtime.Expression.ReloadCustomClip(vrmInstance);

// 4. リロード完了後、新しく追加した表情を制御可能になります
vrmInstance.Runtime.Expression.SetWeight(ExpressionKey.CreateCustom("MyCustomFace"), 1.0f);

※実際に拙作で使用中のコードを添付しております。

code_sample.txt

ユースケースとメリット

  • 動的な表情・ブレンドシェイプの登録: モデル固有のメッシュのブレンドシェイプを実行時に動的に VRM10Expression としてラップし、標準のVRM Expressionとして統一的に制御できるようになります。
  • インタラクティブなエディタ開発の容易化: VRMViewMeister などのように、ユーザーがランタイムでインタラクティブに表情を追加・編集するようなアニメーション/ポーズエディタの開発において利便性が向上します。
  • 描画上の乱れ(カクつき)を防止するリロード: 現在適用されている _inputWeights を維持したまま表情システムを再ロードするため、表情が一瞬デフォルト(真顔)に引き戻されるなどの不自然な描画の乱れを発生させずに、安全にリロードが可能です。

備考

初実装したのが2年以上前ですが最新のUniVRM向けに整備して使っています。しかし、もし現在のUniVRMに同等の考慮がすでにされている、あるいはVRMにこういう用途が不要なのであれば、本件は対応を考慮していただくことは不要と存じます。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions