参数操作
最終更新: 2022年12月8日
CubismIdHandle
为了通过Cubism最终识别参数,需要得到一个参数列表的Index。
用通过ID识别时,将对参数字符串的ID进行核对。
在Native和Web的Framework中,我们准备了一个名为CubismIdHandle的类型来降低匹配的计算成本。
由于无法在Java中定义新类型,我们采用了另一种方法。
Native
CubismIdHandle类型的实际状态是CubismId类的指针类型,可以通过管理类的函数CubismIdManager::GetId函数获取。
若CubismIdManager::GetId函数是相同的字符串,则返回相同的指针,
因此,如果CubismIdHandle相互比较,并且指针地址相同,则可以保证它们是相同的字符串。
您可以使用static的CubismFramework::GetIdManager函数访问CubismIdManager副本。
// 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函数是相同的字符串,则返回相同的副本,
因此,如果CubismIdHandles相互比较,并且物体相同,则可以保证它们是相同的字符串。
您可以使用static的CubismFramework.getIdManager函数访问CubismIdManager副本。
值操作函数有两种方法:通过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无法定义类型,因此不存在CubismIdHandle类型。
管理类的CubismIdManager.getId函数返回CubismId类型。
CubismId覆盖了equals函数,因此如果将CubismId与equals函数进行比较,可以保证它们是同一个字符串。
您可以使用static的CubismFramework.getIdManager函数访问CubismIdManager副本。
// 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
访问。
设置参数
参数通常从动态中播放,但您也可以直接指定值。
根据应用值的计算方法,有三种类型的参数操作函数,如下所示。
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
SaveParameters和LoadParameters作为API保存和调用值,
但在实际的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函数
参数计算顺序的重要性
参数操作分为三种类型:覆盖、加算和正片叠底。
进行第一次正片叠底计算不会影响以后的加算或覆盖。
如果最后执行覆盖,则到该点为止的所有计算结果都将被忽略。
应用覆盖、加算和正片叠底很常见。
在难以将计算内容作为程序组件等检查的情况下,
您需要知道哪个操作执行哪个计算,并设置按顺序应用组件的顺序。
让我们看看是什么样的代码差异导致了上面Gif所示的眼睛张开差异。
这是将眨眼和表情应用于SDK中包含的范例模型haru的示例。
表情设置处于播放2倍眼睛开合的f06.exp3.json的状态。
只贴出C++代码,因为计算顺序是唯一的问题。
覆盖动作的自动眨眼后,进行正片叠底动作表情设置的A的Update
// C++ // 眨眼 if (!motionUpdated) { if (_eyeBlink != NULL) { // 当主动态没有更新时 _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 眨眼 } } if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 通过表情更新参数(相对变化) }
// TypeScript // 眨眼 if (!motionUpdated) { if (this._eyeBlink != NULL) { // 当主动态没有更新时 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) { // 当主动态没有更新时 _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 眨眼 } }
// TypeScript if (this._expressionManager != NULL) { this._expressionManager.updateMotion(this._model, deltaTimeSeconds); // 通过表情更新参数(相对变化) } // 眨眼 if (!motionUpdated) { if (this._eyeBlink != NULL) { // 当主动态没有更新时 this._eyeBlink.updateParameters(this._model, deltaTimeSeconds); // 眨眼 } }
在A中,您可以看到由于眨眼后表情的效果,眼睛睁大了。
另一方面,在B的计算中,被眨眼覆盖,并被抑制到标准的眼睛开合。
仅计算顺序不同,导致模型所表达的细微差别很大。
请与设计师共享动态、表情、功能的计算顺序。