パラメータ操作
最終更新: 2023年1月26日
CubismIdHandle
Cubismでパラメータを最終的に特定するには、パラメータの並びであるIndexを得る必要があります。
IDで特定する場合には、パラメータの文字列であるIDを照合してくことになります。
Native及びWebのFrameworkでは照合の計算コストを削減するためにCubismIdHandleという型を用意しました。
Javaでは型を新たに定義することができないため、別の対応を行っています。
Native
CubismIdHandle型の実態はCubismIdクラスのポインタ型で、管理クラスの関数CubismIdManager::GetId関数によって取得できます。
CubismIdManager::GetId関数は同じ文字列であれば同じポインタを返すため、
CubismIdHandle同士を比較し同じポインタアドレスであれば、同じ文字列である保証ができます。
CubismIdManagerインスタンスにはstaticであるCubismFramework::GetIdManager関数でアクセスできます。
// C++ CubismIdHandle idA = CubismFramework::GetIdManager()->GetId("ParamAngleX") CubismIdHandle idB = CubismFramework::GetIdManager()->GetId("ParamAngleX") CubismIdHandle idC = CubismFramework::GetIdManager()->GetId("ParamAngleY") csmBool ab = (idA == idB); //true csmBool bc = (idB == idC); //false
値の操作関数ではIndexによるアクセスと、CubismIdHandleによるアクセスの二つの方法が用意されています。
Web
CubismIdHandle型の実態はCubismIdクラスのオブジェクト型で、管理クラスの関数CubismIdManager.getId関数によって取得できます。
CubismIdManager.getId関数は同じ文字列であれば同じインスタンスを返すため、
CubismIdHandle同士を比較し同じオブジェクトであれば、同じ文字列である保証ができます。
CubismIdManagerインスタンスにはstaticであるCubismFramework.getIdManager関数でアクセスできます。
値の操作関数ではIndexによるアクセスと、CubismIdHandleによるアクセスの二つの方法が用意されています。
// TypeScript let idA: CubismIdHandle = CubismFramework.getIdManager().getId("ParamAngleX"); let idB: CubismIdHandle = CubismFramework.getIdManager().getId("ParamAngleX"); let idC: CubismIdHandle = CubismFramework.getIdManager().getId("ParamAngleY"); let ab = idA.isEqual(idB); //true let bc = idB.isEqual(idC); //false
Java
SDK for Javaでは上記2つとは異なり型定義ができないため、CubismIdHandle型は存在しません。
管理クラスの関数CubismIdManager.getId関数ではCubismId型を返します。
CubismIdはequals関数をオーバーライドしているため、CubismId同士をequals関数で比較すれば同じ文字列である保証ができます。
CubismIdManagerインスタンスにはstaticであるCubismFramework.getIdManager関数でアクセスできます。
// Java CubismId idA = CubismFramework.getIdManager().getId("ParamAngleX"); CubismId idB = CubismFramework.getIdManager().getId("ParamAngleX"); CubismId idC = CubismFramework.getIdManager().getId("ParamAngleY"); boolean ab = idA.equals(idB); // true boolean bc = idB.equals(idC); // false
値の操作関数ではIndexによるアクセスと、CubismIdによるアクセスの二つの方法が用意され
ています。
パラメータを設定する
パラメータは、通常ではモーションから再生されますが直接値を指定することもできます。
パラメータを操作する関数は、以下の通り、適用する値の計算方法ごとに3種類あります。
1.値を上書きする
以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::SetParameterValue関数
- Web(TypeScript)のCubismModel.setParameterValueById関数
- JavaのCubismModel.setParameterValue関数
第一引数にパラメータのIDかインデックス,第二引数に値、第三引数に影響度を設定します。
影響度は省略可能で、その場合は1になります。
例えば0.5にすると前の値の影響を50%残したまま設定することになります。
例
// C++ _model->SetParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 30.0f, 1.0f);
// TypeScript _model.setParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 30.0, 1.0);
// Java model.setParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 30.0f, 1.0f);
2. 現在の値に加算する
以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::AddParameterValue関数
- Web(TypeScript)のCubismModel.addParameterValueById関数
- JavaのCubismModel.addParameterValue関数
引数は以下のいずれかの関数と同じです。
- Native(C++)のCubismModel::SetParameterValue関数
- Web(TypeScript)のCubismModel.setParameterValueById関数
- JavaのCubismModel.setParameterValue関数
この関数で設定した値は、そのパラメータに現在設定されている値に加算されます。
例
// C++ _model->AddParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 1.0f, 1.0f);
// TypeScript _model.addParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 1.0, 1.0);
// Java model.addParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 1.0f, 1.0f);
3. 現在の値に乗算する
以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::MultiplyParameterValue関数
- Web(TypeScript)のCubismModel.multiplyParameterValueById関数
- JavaのCubismModel.multiplyParameterValue関数
引数は以下のいずれかの関数と同じです。
- CubismModel::MultiplyParameterValue関数
- Web(TypeScript)のCubismModel.multiplyParameterValueById関数
- JavaのCubismModel.multiplyParameterValue関数
この関数で設定した値は、そのパラメータに現在設定されている値に乗算されます。
例
// C++ _model->MultiplyParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 2.0f, 1.0f);
// TypeScript _model.multiplyParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 2.0, 1.0);
// Java model.multiplyParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 2.0f, 1.0f);
また、現在のパラメータ値の取得には以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::GetParameterValue関数
- Web(TypeScript)のCubismModel.getParameterValueById関数
- JavaのCubismModel.getParameterValue関数
パラメータのインデックスによる指定
パラメータのID指定には、文字列から生成するID型で指定する方法とインデックスで指定する方法があります。
インデックスをあらかじめ取得するなどキャッシュした上でインデックスを使ったほうが高速なため、呼び出し頻度が多い場合はインデックスの使用をおすすめします。
パラメータのインデックスは以下のいずれかの関数で取得できます。
- Native(C++)のCubismModel::GetParameterIndex関数
- Web(TypeScript)のCubismModel.getParameterIndex関数
- JavaのCubismModel.getParameterIndex関数
例
// C++ //初期化時 csmInt32 paramAngleX; paramAngleX = _model->GetParameterIndex(CubismFramework::GetIdManager()->GetId("ParamAngleX")); //パラメータの設定時 _model->SetParameterValue( paramAngleX, 30.0f , 1.0f);
// TypeScript // 初期化時 let paramAngleX: number; paramAngleX = _model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // パラメータの設定時 _model.setParameterValueByIndex(paramAngleX, 30.0, 1.0);
// Java // 初期化時 int paramAngleXIndex; paramAngleXIndex = model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // パラメータの設定時 model.setParameterValue(paramAngleXIndex, 30.0f, 1.0f);
パラメータの値を保存、復元する
モデルの現在のパラメータ値を一時的に保存する場合は以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::SaveParameters関数
- Web(TypeScript)のCubismModel.saveParameters関数
- JavaのCubismModel.saveParameters関数
一時保存したモデルのパラメータ値を復元するには以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::LoadParameters関数
- Web(TypeScript)のCubismModel.loadParameters関数
- JavaのCubismModel.loadParameters関数
例
// C++ //初期化時 csmInt32 paramAngleX; paramAngleX = _model->GetParameterIndex(CubismFramework::GetIdManager()->GetId("ParamAngleX")); // パラメータを30に設定 _model->SetParameterValue( paramAngleX, 30.0f ,1.0f ); //現在のすべてのパラメータ値を一時保存 _model->SaveParameters(); // 0に設定 _model->SetParameterValue( paramAngleX, 0.0f ,1.0f ); //output: value = 0 printf("value = %f",_model->GetParameterValue( paramAngleX ) ); //前回のsaveParam時の状態を復元 _model->LoadParameters(); //output: value = 30 printf("value = %f",_model->GetParameterValue( paramAngleX ) );
// TypeScript // 初期化時 let paramAngleX: number; paramAngleX = _model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // パラメータを30に設定 _model.setParameterValueByIndex(paramAngleX, 30.0, 1.0); // 現在のすべてのパラメータ値を一時保存 _model.saveParameters(); // 0に設定 _model.setParameterValue(paramAngleX, 0.0, 1.0); // output: value = 0 LAppPal.printLog("value = {0}", _model.getParameterValueByIndex(paramAngleX)); // 前回のsaveParam時の状態を復元 _model.loadParameters(); // output: value = 30 LAppPal.printLog("value = {0}", _model.getParameterValueByIndex(paramAngleX));
// Java // 初期化時 int paramAngleXIndex; paramAngleXIndex = model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // パラメータを30に設定 model.setParameterValue(paramAngleXIndex, 30.0f, 1.0f); // 現在の全てのパラメータ値を一時保存 model.saveParameters(); // 0に設定 model.setParameterValue(paramAngleXIndex, 0.0f, 1.0f); // output: value = 0 LAppPal.printLog("value = " + model.getParametetValue(paramAngleXIndex)); // 前回のsaveParameters時の状態を復元 model.loadParameters(); // output: value = 30 LAppPal.printLog("value = " + model.getParameterValue(paramAngleXIndex));
LAppModel::UpdateにおけるSaveParameters、LoadParametersの使い方
APIとしてのSaveParameters、LoadParametersは値の保存と呼び出しを行うものですが、
実際のLAppModel::Update関数ではLoadParametersを呼び出して前回状況へリセットを行い、
モーションの再生を適用してから、SaveParametersを実行しています。
// C++ void LAppModel::Update() { const csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime(); _userTimeSeconds += deltaTimeSeconds; _dragManager->Update(deltaTimeSeconds); _dragX = _dragManager->GetX(); _dragY = _dragManager->GetY(); csmBool motionUpdated = false; //----------------------------------------------------------------- _model->LoadParameters(); if (_motionManager->IsFinished()) { StartRandomMotion(MotionGroupIdle, PriorityIdle); } else { motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); } _model->SaveParameters(); //----------------------------------------------------------------- if (!motionUpdated) { if (_eyeBlink != NULL) { _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // Set } } if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // Add Set Mult } _model->AddParameterValue(_idParamAngleX, _dragX * 30); _model->AddParameterValue(_idParamAngleY, _dragY * 30); _model->AddParameterValue(_idParamAngleZ, _dragX * _dragY * -30); _model->AddParameterValue(_idParamBodyAngleX, _dragX * 10); _model->AddParameterValue(_idParamEyeBallX, _dragX); _model->AddParameterValue(_idParamEyeBallY, _dragY); if (_breath != NULL) { _breath->UpdateParameters(_model, deltaTimeSeconds); // Add } if (_physics != NULL) { _physics->Evaluate(_model, deltaTimeSeconds); // Set } if (_lipSync) { csmFloat32 value = 0; for (csmUint32 i = 0; i < _lipSyncIds.GetSize(); ++i) { _model->AddParameterValue(_lipSyncIds[i], value, 0.8f); } } if (_pose != NULL) { _pose->UpdateParameters(_model, deltaTimeSeconds); // パラメータへの操作はなし } _model->Update(); }
// TypeScript public update(): void { if(this._state != LoadStep.CompleteSetup) return; const deltaTimeSeconds: number = LAppPal.getDeltaTime(); this._userTimeSeconds += deltaTimeSeconds; this._dragManager.update(deltaTimeSeconds); this._dragX = this._dragManager.getX(); this._dragY = this._dragManager.getY(); let motionUpdated = false; //-------------------------------------------------------------------------- this._model.loadParameters(); if(this._motionManager.isFinished()) { this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle); } else { motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds); } this._model.saveParameters(); //-------------------------------------------------------------------------- if(!motionUpdated) { if(this._eyeBlink != null) { this._eyeBlink.updateParameters(this._model, deltaTimeSeconds); // Set } } if(this._expressionManager != null) { this._expressionManager.updateMotion(this._model, deltaTimeSeconds); // Add Set Mult } this._model.addParameterValueById(this._idParamAngleX, this._dragX * 30); this._model.addParameterValueById(this._idParamAngleY, this._dragY * 30); this._model.addParameterValueById(this._idParamAngleZ, this._dragX * this._dragY * -30); this._model.addParameterValueById(this._idParamBodyAngleX, this._dragX * 10); this._model.addParameterValueById(this._idParamEyeBallX, this._dragX); this._model.addParameterValueById(this._idParamEyeBallY, this._dragY); if(this._breath != null) { this._breath.updateParameters(this._model, deltaTimeSeconds); // Add } if(this._physics != null) { this._physics.evaluate(this._model, deltaTimeSeconds); // Set } if(this._lipsync) { let value: number = 0; for(let i: number = 0; i < this._lipSyncIds.getSize(); ++i) { this._model.addParameterValueById(this._lipSyncIds.at(i), value, 0.8); } } if(this._pose != null) { this._pose.updateParameters(this._model, deltaTimeSeconds); // パラメータへの操作はなし } this._model.update(); }
// Java public void update() { final float deltaTimeSeconds = LAppPal.getDeltaTime(); userTimeSeconds += deltaTimeSeconds; dragManager.update(deltaTimeSeconds); dragX = dragManager.getX(); dragY = dragManager.getY(); boolean isMotionUpdated = false; //-------------------------------------------------------------- model.loadParameters(); // モーションの再生がない場合、待機モーションの中からランダムで再生する if (motionManager.isFinished()) { startRandomMotion(LAppDefine.MotionGroup.IDLE.getId(), LAppDefine.Priority.IDLE.getPriority()); } else { isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds); } model.saveParameters(); // ------------------------------------------------------------- // メインモーションの更新がないときだけまばたきする if (!isMotionUpdated) { if (eyeBlink != null) { eyeBlink.updateParameters(model, deltaTimeSeconds); // Set } } if (expressionManager != null) { expressionManager.updateMotion(model, deltaTimeSeconds); // Add Set Mult } // ドラッグによる顔の向きの調整 model.addParameterValue(idParamAngleX, dragX * 30); model.addParameterValue(idParamAngleY, dragY * 30); model.addParameterValue(idParamAngleZ, dragX * dragY * (-30)); // ドラッグによる体の向きの調整 model.addParameterValue(idParamBodyAngleX, dragX * 10); // ドラッグによる目の向きの調整 model.addParameterValue(idParamEyeBallX, dragX); model.addParameterValue(idParamEyeBallY, dragY); if (breath != null) { breath.updateParameters(model, deltaTimeSeconds); // Add } if (physics != null) { physics.evaluate(model, deltaTimeSeconds); // Set } if (lipSync) { // リアルタイムでリップシンクを行う場合、システムから音量を取得して0~1の範囲で値を入力します float value = 0.0f; for (CubismId lipSyncId : lipSyncIds) { model.addParameterValue(lipSyncId, value, 0.8f); } } if (pose != null) { pose.updateParameters(model, deltaTimeSeconds); // パラメータへの操作はなし } model.update(); }
この方法の目的は、モーションが再生されていなかったり、モーション再生によって指定のなかったパラメータへ
Updateの他の操作が入る前の値で上書きすることで、加算・乗算計算のベースを作り出すことにあります。
この機能がない場合はモーションで指定のないパラメータへAddを行うと、更新ごとに値がAddされて範囲外へ値が出ることになります。
モーション再生前のLoadのみであっても加算・乗算のベースは作成できますが、
モーション再生後のSaveが入ることによりモーションの最後の状態を保持できるようになります。
パーツの不透明度を取得・設定する
パーツの不透明度の設定は以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::SetPartOpacity関数
- Web(TypeScript)のCubismModel.setPartOpacityById関数
- JavaのCubismModel.setPartOpactiy関数
パーツの不透明度の取得は以下のいずれかの関数を使用します。
- Native(C++)のCubismModel::GetPartOpacity関数
- Web(TypeScript)のCubismModel.getPartOpacityById関数
- JavaのCubismModel.getPartOpacity関数
例
// C++ // 顔パーツの不透明度を0.5に設定する _model->SetPartOpacity( CubismFramework::GetIdManager()->GetId("PartArmLB001"),0.5f );
// TypeScript // 顔パーツの不透明度を0.5に設定する _model.setPartOpacityById(CubismFramework.getIdManager().getId("PartArmLB001"), 0.5);
// Java // 顔パーツの不透明度を0.5に設定する model.setPartOpacity(CubismFramework.getIdManager().getId("PartArmLB001"), 0.5f);
パラメータの適用タイミングとコスト
パラメータの設定時にはパラメータ値を書き換えるだけで頂点計算は行いません。
パラメータ変更後に以下のいずれかの関数で頂点が計算されます。
- Native(C++)のCubismModel::Updatel関数
- Web(TypeScript)のCubismModel.updatel関数
- JavaのCubismModel.updatel関数
その後、以下のいずれかの関数でパラメータ適用後のモデルが描画されます。
- Native(C++)のCubismRenderer::DrawModel関数
- Web(TypeScript)のCubismRenderer.drawModel関数
- JavaのCubismRenderer.drawModel関数
パラメータの計算順序の重要性
パラメータ操作には上書き、加算、乗算の3タイプがあります。
乗算の計算を最初に行っても後の加算や上書きには影響しません。
上書きを最後に行うとそれまでの計算結果がすべて無視されます。
上書き、加算、乗算と適用していくのが一般的です。
プログラムコンポーネントなどとして計算内容が確認しづらい状況では
どの操作がどの計算を行うか把握し、順序をもってコンポーネントの適用順を設定する必要があります。
上にあるGifに示した目の開きの違いが、どのようなコードの違いで発生するかを見ていきます。
SDKに含まれるサンプルモデルのharuに対してまばたきと表情の適用を行っている例になります。
表情設定に関しては目の開閉を2倍に見開くf06.exp3.jsonを再生している状態です。
計算順序だけが問題のためC++のコードのみを掲載します。
上書き動作の自動まばたきの後に、乗算動作の表情設定を行うAのUpdate
// C++ // まばたき if (!motionUpdated) { if (_eyeBlink != NULL) { //j/ メインモーションの更新がないとき _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 目パチ } } if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化) }
// TypeScript // まばたき if (!motionUpdated) { if (this._eyeBlink != NULL) { //j/ メインモーションの更新がないとき this._eyeBlink->UpdateParameters(this._model, deltaTimeSeconds); // 目パチ } } if (this._expressionManager != NULL) { this._expressionManager.updateMotion(this._model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化) }
乗算動作の表情設定の後に、上書き動作の自動まばたきを行うBのUpdate
BのUpdate (Native)
// C++ if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化) } // まばたき if (!motionUpdated) { if (_eyeBlink != NULL) { //j/ メインモーションの更新がないとき _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 目パチ } }
// TypeScript if (this._expressionManager != NULL) { this._expressionManager.updateMotion(this._model, deltaTimeSeconds); // 表情でパラメータ更新(相対変化) } // まばたき if (!motionUpdated) { if (this._eyeBlink != NULL) { //j/ メインモーションの更新がないとき this._eyeBlink.updateParameters(this._model, deltaTimeSeconds); // 目パチ } }
Aではまばたきの後に表情の効果で目が大きく見開かれていること確認できます。
一方でBの計算ではまばたきに上書きされ標準の目の開閉に抑えられてしまっています。
計算の順番が違うだけでモデルが表現するニュアンスは大きく異なってしまいます。
モーションや表情、機能の計算順序はデザイナとも共有しておきましょう。