リップシンク

最終更新: 2023年1月26日

リップシンクパラメータの特定

リップシンクエフェクトを使用することで、モデルに対してリップシンク動作を適用することができます。
リップシンクエフェクトを適用するために以下の処理を行います。

  • .model3.jsonファイルに記述された、リップシンクエフェクトの値を適用するパラメータとの対応付け
  • 音声入力、モーション、その他の方法でリップシンクエフェクトに対して数値を渡す

このうち、.model3.jsonファイルに記述されるリップシンクエフェクトとパラメータを対応付ける情報は、
ICubismModelSettingインターフェースを実装するCubismModelSettingJsonクラスを利用することによって取得することができます。

// C++
for (csmInt32 i = 0; i < _modelSetting->GetLipSyncParameterCount(); ++i)
{
   CubismIdHandle lipsyncParameter = _modelSetting->GetLipSyncParameterId(i);
}
// TypeScript
for(let i: number = 0; i < _modelSetting.getLipSyncParameterCount(); ++i)
{
    let lipSyncParameter = _modelSetting.getLipSyncParameterId(i);
}
// Java
for(int i = 0; i < modelSetting.getLipSyncParameterCount(); i++) {
CubismId lipSyncParameter = modelSetting.getLipSyncParameterId(i);
}

.model3.jsonファイルの中に定義を入れるには「まばたき設定」を確認してください。
Editor上でまばたき・リップシンク設定を行ってから出力すると、.model3.jsonファイルには以下のように記述されます。

{
    ... 省略 ...
       "Groups": [
                {
                        "Target": "Parameter",
                        "Name": "LipSync",
                        "Ids": [
                                "ParamMouthOpenY"
                        ]
                },
                {
                        "Target": "Parameter",
                        "Name": "EyeBlink",
                        "Ids": [
                                "ParamEyeLOpen",
                                "ParamEyeROpen"
                        ]
                }
        ]
}

リップシンクを行う3つの方法

リップシンクについては以下の3通りに大きく分類されます。

1.リアルタイムで音量を取得し、開閉度を直接指定する方式

音声レベルを何らかの方法で取得し、対象のパラメータに合わせて尺度をあわせたものを指定することにより
リアルタイムのリップシンクを実現します。

// C++
csmFloat32 value = GetAudioLevelExampleFunction(); // 直近の音量レベルを取得します。

for (csmInt32 i = 0; i < _modelSetting->GetLipSyncParameterCount(); ++i)
{
    _model->AddParameterValue(_modelSetting->GetLipSyncParameterId(i), value, 0.8f);
}
// TypeScript
let value: number = getAudioLevelExampleFunction(); // 直近の音量レベルを取得します。

for(let i: number = 0; i < _modelSetting.getLipSyncParameterCount(); ++i)
{
    _model.addParameterValue(_modelSetting.getLipSyncParameterId(i), value, 0.8);
}
// Java
float value = getAudioLevelExampleFunction(); // 直近の音量レベルを取得します
for(int i = 0; i < modelSetting.getLipSyncParameterCount(); i++){
    model.addParameterValue((modelSetting.getLipSyncParameterId(i), value, 0.8f);
}

Native(C++)のCubismModel::Update関数、Web(TypeScript), JavaのCubismModel.update関数より前でNative(C++)のCubismModel::SetParameterValue関数、Web(TypeScript), JavaのCubismModel.setParameterValue関数やNative(C++)のCubismModel::AddParameterValue関数、Web(TypeScript), JavaのCubismModel.addParameterValue関数の第2引数に直接0~1の値を設定することで口の開き具合を制御できます。

iPhone / Android2.3以降(※)は、再生中の音量をリアルタイムに取得できます。
取得した再生中の音量の値を 0..1の範囲に加工して、その値を上記の命令で設定するとリップシンクさせることができます。
(標準パラメータ設定の通り、口の開閉は 0 から 1 のパラメータで作成しているため)

設定する値は0未満、もしくは1以上でもエラーにはなりませんが、その場合リップシンクが正しく動作しない場合があります。
(※):Android2.2以前については実行時に再生中の音量を取得することはできません。
その他のプラットフォームでリアルタイムに音量が取得できるか否かは、音声再生のライブラリに依存します。

iPhoneでの取得方法:AVAudioPlayerクラス
Androidでの取得方法:Visualizerクラス

2.リップシンク用の情報を持ったモーションを使う方式

Editor上での作業でモーション自体に音声の動きを取り入れる方法です。
モーションにリップシンクのモーションを入れる方法は「BGMや音声を使用したシーンの作成」を確認してください。
再生前にNative(C++)のCubismMotion::SetEffectIds関数、またはWeb(TypeScript)、JavaのCubismMotion.setEffectIds関数を使用してリップシンク、まばたきのパラメータ群をセッティングすれば
CubismMotionインスタンスのパラメータ更新処理の時に対象のパラメータに置き換えた上でモーションを再生します。

// C++
    // .model3.jsonに記述されたまばたき用パラメータの読み込み
    csmVector<CubismIdHandle> eyeBlinkIds;
    csmInt32 eyeBlinkCount = _modelSetting->GetEyeBlinkParameterCount();
    for (csmInt32 i = 0; i < eyeBlinkCount; i++)
    {
        eyeBlinkIds.PushBack(_modelSetting->GetEyeBlinkParameterId(i));
    }

    // .model3.jsonに記述されたリップシンク用パラメータの読み込み
    csmVector<CubismIdHandle> lipSyncIds;
    csmInt32 lipSyncCount = _modelSetting->GetLipSyncParameterCount();
    for (csmInt32 i = 0; i < lipSyncCount; i++)
    {
        lipSyncIds.PushBack(_modelSetting->GetLipSyncParameterId(i));
    }

    // モーションファイルの読み込み
    csmByte* buffer;
    csmSizeInt size;
    csmString path = "example.motion3.json";
    buffer = CreateBuffer(path.GetRawString(), &size);
    CubismMotion* tmpMotion = static_cast<CubismMotion*>(LoadMotion(buffer, size, name.GetRawString()));
    DeleteBuffer(buffer, path.GetRawString());

    // 読み込んだモーションファイルへまばたき用パラメータとリップシンク用のパラメータを登録
    tmpMotion->SetEffectIds(eyeBlinkIds, lipSyncIds);
// TypeScript
    // .model3.jsonに記述されたまばたき用パラメータの読み込み
    let eyeBlinkIds: csmVector<CubismIdHandle> = new csmVector<CubismIdHandle>();
    let eyeBlinkCount: number = _modelSetting.getEyeBlinkParameterCount();
    for(let i: number = 0; i < eyeBlinkCount; i++)
    {
        eyeBlinkIds.pushBack(_modelSetting.getEyeBlinkParameterId(i));
    }

    // .model3.jsonに記述されたリップシンク用パラメータの読み込み
    let lipSyncIds: csmVector<CubismIdHandle> = new csmVector<CubismIdHandle>();
    let lipSyncCount: number = _modelSetting.getLipSyncParameterCount();
    for(let i: number = 0; i < lipSyncCount; i++)
    {
        lipSyncIds.pushBack(_modelSetting.getLipSyncParamterId(i));
    }

    // モーションファイルの読み込み
    let path: string = fileName;
    path = this._modelHomeDir + path;

    fetch(path).then(
        (response) =>
        {
            return response.arrayBuffer();
        }
    ).then(
        (arrayBuffer) =>
        {
            let buffer: ArrayBuffer = arrayBuffer;
            let size = buffer.byteLength;
            let tmpMotion = <CubismMotion>this.loadMotion(buffer, size, name);
            deleteBuffer(buffer, path);

            // 読み込みの一環として、モーションファイルへまばたき用パラメータとリップシンク用のパラメータを登録
            motion.setEffectIds(this._eyeBlinkIds, this._lipSyncIds);
        }
    );
// Java
    // .model3.jsonに記述されたまばたき用パラメータの読み込み
    List<CubismId> eyeBlinkIds;
    int eyeBlinkCount = modelSetting.getEyeBlinkParameterCount();
    for (int i = 0; i < eyeBlinkCount; i++) {
        eyeBlinkIds.add(modelSetting.getEyeBlinkParameterId(i));
    }
    // .model3.jsonに記述されたリップシンク用パラメータの読み込み
    List<CubismId> lipSyncIds;
    int lipSyncCount = modelSetting.getLipSyncParameterCount();
    for (int i = 0; i < lipSyncCount; i++) {
        lipSyncIds.add(modelSetting.getLipSyncParameterId(i));
    }
    // モーションファイルの読み込み
    byte[] buffer;
    String path = "example.motion3.json";
    buffer = createBuffer(path);
    CubismMotion tmpMotion = loadMotion(buffer);
    // 読み込んだモーションファイルへまばたき用パラメータとリップシンク用のパラメータを登録
    tmpMotion.setEffectIds(eyeBlinkIds, lipSyncIds);

3.リップシンク用の情報のみのモーションを使う方式

Native :

2で取り扱ったモーションを専用に扱うモーションマネージャを用意し、口だけをコントロールする方法です。
体や頭の動作モーションとリップシンクを分けたいときに有用です。

// C++
    /**
     * @brief ユーザーが実際に使用するモデルの実装クラス<br>
     *         モデル生成、機能コンポーネント生成、更新処理とレンダリングの呼び出しを行う。
     *
     */
    class LAppModel : public Csm::CubismUserModel
    {
    /*省略*/  

    private:    
        CubismMotionManager*    _mouthMotionManager; // <<< 追加
    };
// C++
    void LAppModel::Update()
    {
 /*省略*/  

        //-----------------------------------------------------------------
        _model->LoadParameters(); // 前回セーブされた状態をロード
        if (_motionManager->IsFinished())
        {
            // モーションの再生がない場合、待機モーションの中からランダムで再生する
            StartRandomMotion(MotionGroupIdle, PriorityIdle);
        }
        else
        {
            const csmFloat32 playSpeed = pow(2, (csmFloat32)_motionSpeed / 10.0);
            motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds * playSpeed); // モーションを更新
        }
        _mouthMotionManager->UpdateMotion(_model, deltaTimeSeconds); // <<< 追加
        _model->SaveParameters(); // 状態を保存
        //-----------------------------------------------------------------

/*省略*/  

    }
// C++
	_mouseMotionManager->StartMotionPriority(lipsyncMotion, autoDelete, priority);

Web :

2で取り扱ったモーションを専用に扱うモーションマネージャを用意し、口だけをコントロールする方法です。
体や頭の動作モーションとリップシンクを分けたいときに有用です。

// TypeScript
    export class LAppModel extends CubismUserModel {
/*省略*/

        _mouthMotionManager: CubismMotionManager; // <<< 追加
    }
// TypeScript
	public update(): void
    {
/*省略*/

        //--------------------------------------------------------------------------
        this._model.loadParameters();   // 前回セーブされた状態をロード
        if(this._motionManager.isFinished())
        {
            // モーションの再生がない場合、待機モーションの中からランダムで再生する
            this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle);
            
        }
        else
        {
            motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds);    // モーションを更新
        }
		_mouthMotionManager.udpateMotion(_model, deltaTimeSeconds); // <<< 追加
        this._model.saveParameters(); // 状態を保存
        //--------------------------------------------------------------------------

/*省略*/
    }
// TypeScript
	_mouseMotionManager.startMotionPriority(lipSyncMotion, autoDelete, priority);

Java :

2で取り扱ったモーションを専用に扱うモーションマネージャを用意し、口だけをコントロールする方式です。
体や頭の動作モーションとリップシンクを分けたいときに有用です。

// Java
/**
 * ユーザーが実際に使用するモデルの実装クラス
 * モデル生成、機能コンポーネント生成、更新処理とレンダリングの呼び出しを行う
 */
public class LAppModel extends CubismUserModel {
    // 省略

    private CubismMotionManager mouthMotionManager; // <<< 追加
}
// Java
public void update() {
/* 省略 */
    // -----------------------------
    model.loadParameters(); // 前回セーブされた状態をロード
    if (motionManager.isFinished()) {
        // モーションの再生がない場合、待機モーションの中からランダムで再生する
        startRandomMotion(LAppDefine.MotioNGroup.IDLE.getId(), LAppDefine.Priority.IDLE.getPriority());
    } else {
        final float playSpeed = Math.pow(2, motionSpeed / 10);
        isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds * playSpeed); // モーションを更新
    }
    mouthMotionManager.updateMotion(model, deltaTimeSeconds); // <<< 追加
    model.saveParameters(); // 状態を保存
    // -----------------------------
/* 省略 */
}
// Java
mouseMotionManager.startMotionPriority(lipSyncMotion, autoDelete, priority);
この記事に関するご意見・
ご要望をお聞かせください。