参数操作
[最后更新时间:2022/12/08]
CubismIdHandle
为了最终识别Cubism中的一个参数,我们需要得到一个Index,它是一个参数列表。
用ID指定时,作为参数的字符串的ID将被核对。
在 Native 和 Web 框架中,我们准备了一个名为 CubismIdHandle 的类型来降低匹配的计算成本。
由于无法在 Java 中定义新类型,我们采用了另一种方法。
Native
CubismIdHandle类型的实际状态是CubismId类的指针类型,可以通过管理类的函数CubismIdManager::GetId函数获取。
由于 CubismIdManager :: GetId 函数如果是相同的字符串则返回相同的指针,
如果 CubismIdHandles 相互比较,并且指针地址相同,则可以保证它们是相同的字符串。
您可以使用静态 CubismFramework :: GetIdManager 函数访问 CubismIdManager 实例。
1 2 3 4 5 6 7 8 9 |
// 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 相互比较,它们是同一个对象,则可以保证它们是同一个字符串。
您可以使用静态 CubismFramework.getIdManager 函数访问 CubismIdManager 实例。
1 2 3 4 5 6 7 8 9 |
// 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 |
值操作函数的访问方式有两种:索引访问和CubismIdHandle访问。
Java
与上述两种不同的是,SDK for Java 没有 CubismIdHandle 类型,因为它无法定义。
管理类的 CubismIdManager.getId 函数返回 CubismId 类型。
CubismId 覆盖了 equals 函数,因此如果将 CubismIds 与 equals 函数进行比较,可以保证它们是同一个字符串。
您可以使用静态 CubismFramework.getIdManager 函数访问 CubismIdManager 实例。
1 2 3 4 5 6 7 8 9 |
// 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 |
值操作函数的访问方式有两种:索引访问和CubismId访问。
设定参数
参数通常从运动中播放,但您也可以直接指定值。
根据应用值的计算方法,有三种类型的参数操作函数,如下所示。
1.覆盖值
使用以下功能之一。
- 本机 (C++) CubismModel::SetParameterValue 函数
- Web (TypeScript) CubismModel.setParameterValueById 函数
- Java 中的 CubismModel.setParameterValue 函数
在第一个参数中设定参数 ID 或索引,在第二个参数中设定值,在第三个参数中设定影响程度。
影响程度是可选的,在这种情况下它将是1。
例如,如果将其设定为 0.5,则会在保留前一个值的 50% 影响的同时进行设定。
例子
1 2 |
// C++ _model->SetParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 30.0f, 1.0f); |
1 2 |
// TypeScript _model.setParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 30.0, 1.0); |
1 2 |
// Java _model.setParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 30.0f, 1.0f); |
2.添加到当前值
使用以下功能之一。
- 本机 (C++) CubismModel::AddParameterValue 函数
- Web (TypeScript) CubismModel.addParameterValueById 函数
- Java 中的 CubismModel.addParameterValue 函数
参数与以下函数之一相同。
- 本机 (C++) CubismModel::SetParameterValue 函数
- Web (TypeScript) CubismModel.setParameterValueById 函数
- Java 中的 CubismModel.setParameterValue 函数
此函数设定的值将添加到当前为该参数设定的值。
例子
1 2 |
// C++ _model->AddParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 1.0f, 1.0f); |
1 2 |
// TypeScript _model.addParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 1.0, 1.0); |
1 2 |
// Java _model.addParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 1.0f, 1.0f); |
3.乘以当前值
使用以下功能之一。
- 本机 (C++) CubismModel::MultiplyParameterValue 函数
- Web (TypeScript) CubismModel.multiplyParameterValueById 函数
- Java 中的 CubismModel.multiplyParameterValue 函数
参数与以下函数之一相同。
- CubismModel::MultiplyParameterValue 函数
- Web (TypeScript) CubismModel.multiplyParameterValueById 函数
- Java 中的 CubismModel.multiplyParameterValue 函数
此函数设定的值乘以当前为该参数设定的值。
例子
1 2 |
// C++ _model->MultiplyParameterValue(CubismFramework::GetIdManager()->GetId("ParamAngleX"), 2.0f, 1.0f); |
1 2 |
// TypeScript _model.multiplyParameterValueById(CubismFramework.getIdManager().getId("ParamAngleX"), 2.0, 1.0); |
1 2 |
// Java _model.multiplyParameterValue(CubismFramework.getIdManager().getId("ParamAngleX"), 2.0f, 1.0f); |
此外,使用以下函数之一来获取当前参数值。
- 本机 (C++) CubismModel::GetParameterValue 函数
- Web (TypeScript) CubismModel.getParameterValueById 函数
- Java 中的 CubismModel.getParameterValue 函数
参数指标规范
参数ID的指定方式有两种,一种是指定由字符串生成的ID类型,另一种是通过索引指定。
缓存后使用索引会更快,比如提前获取索引,所以建议在调用频率高的时候使用索引。
您可以使用以下函数之一获取参数索引:
- 本机 (C ++) CubismModel::GetParameterIndex 函数
- Web (TypeScript) CubismModel.getParameterIndex 函数
- Java 中的 CubismModel.getParameterIndex 函数
例子
1 2 3 4 5 6 7 |
// C++ // 初始化时 csmInt32 paramAngleX; paramAngleX = _model->GetParameterIndex(CubismFramework::GetIdManager()->GetId("ParamAngleX")); // 设定参数时 _model->SetParameterValue( paramAngleX, 30.0f , 1.0f); |
1 2 3 4 5 6 7 |
// TypeScript // 初始化时 let paramAngleX: number; paramAngleX = _model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // 设定参数时 _model.setParameterValueByIndex(paramAngleX, 30.0, 1.0); |
1 2 3 4 5 6 |
// Java // 初始化时 int paramAngleXIndex; paramAngleXIndex = _model.getParameterIndex(CubismFramework.getIdManager().getId("ParamAngleX")); // 设定参数时 _model.setParameterValue(paramAngleXIndex, 30.0f, 1.0f); |
保存和恢复参数值
使用以下功能之一临时存储模型的当前参数值。
- 本机 (C++) CubismModel::SaveParameters 函数
- Web (TypeScript) CubismModel.saveParameters 函数
- Java 中的 CubismModel.saveParameters 函数
使用以下功能之一恢复临时保存的模型参数值。
- 本机 (C++) CubismModel::LoadParameters 函数
- Web (TypeScript) CubismModel.loadParameters 函数
- Java 中的 CubismModel.loadParameters 函数
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 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 ) ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 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)); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 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 中使用 SaveParameters 和 LoadParameters :: 更新
SaveParameters 和 LoadParameters 作为 API 保存和调用值,
在实际的 LAppModel::Update 函数中,调用 LoadParameters 重置为之前的状态,并且
应用运动播放后,我正在运行保存参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
// 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(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
// 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(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
// 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(); } |
此方法的目的是对不播放运动或运动播放未指定的参数。
通过在进入更新的其他操作之前的值覆盖,它是创建加法/正片叠底计算的基础。
如果此功能不可用,如果添加到运动中未指定的参数,则每次更新都会添加该值,并且该值将超出范围。
即使仅在动作播放前加载,您也可以为加法/正片叠底创建基础,
运动播放后进入保存,可以保留运动的最终状态。
获取和设定部件的不透明度
使用以下功能之一设置零件的不透明度。
- 本机 (C++) CubismModel::SetPartOpacity 函数
- Web (TypeScript) CubismModel.setPartOpacityById 函数
- Java 中的 CubismModel.setPartOpactiy 函数
使用以下函数之一来获取零件的不透明度。
- 本机 (C++) CubismModel::GetPartOpacity 函数
- Web (TypeScript) CubismModel.getPartOpacityById 函数
- Java 中的 CubismModel.getPartOpacity 函数
例子
1 2 3 |
// C++ // 设定人脸部件的不透明度为0.5 _model->SetPartOpacity( CubismFramework::GetIdManager()->GetId("PartArmLB001"),0.5f ); |
1 2 3 |
// TypeScript // 设定人脸部件的不透明度为0.5 _model.setPartOpacityById(CubismFramework.getIdManager().getId("PartArmLB001"), 0.5); |
1 2 3 |
// Java // 设定人脸部件的不透明度为0.5 _model.setPartOpacity(CubismFramework.getIdManager().getId("PartArmLB001"), 0.5f); |
参数应用时间和成本
设定参数时,只重写参数值,不计算顶点。
更改参数后,使用以下函数之一计算顶点。
- 本机 (C++) CubismModel::Updatel 函数
- Web (TypeScript) CubismModel.updatel 函数
- Java 中的 CubismModel.updatel 函数
之后,使用以下功能之一绘制应用参数后的模型。
- 本机 (C++) CubismRenderer::DrawModel 函数
- Web (TypeScript) CubismRenderer.drawModel 函数
- Java 中的 CubismRenderer.drawModel 函数
参数计算顺序的重要性
参数操作分为三种类型:覆盖、加法和正片叠底。
第一次正片叠底计算不会影响以后的加法或覆盖。
如果最后执行覆盖,则到该点为止的所有计算结果都将被忽略。
应用覆盖、加法和正片叠底是很常见的。
在难以将计算内容作为程序组件等检查的情况下。
您需要知道哪个操作执行哪个计算并设定按顺序应用组件的顺序。
让我们看看是什么样的代码差异导致了上面 Gif 所示的眼睛张开差异。
这是将眨眼和面部表情应用于 SDK 中包含的模型范例 haru 的示例。
关于面部表情设定,是在播放f06.exp3.json的状态下睁眼闭眼两次。
只贴出 C++ 代码,因为计算顺序是唯一的问题。
覆盖操作自动眨眼后,设定正片叠底操作的面部表情A更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// C++ // 眨眼 if (!motionUpdated) { if (_eyeBlink != NULL) { //j/ 没有主动作更新时 _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 眼裂 } } if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 用面部表情更新参数(相对变化) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// C++ if (_expressionManager != NULL) { _expressionManager->UpdateMotion(_model, deltaTimeSeconds); // 用面部表情更新参数(相对变化) } // 眨眼 if (!motionUpdated) { if (_eyeBlink != NULL) { //j/ 没有主动作更新时 _eyeBlink->UpdateParameters(_model, deltaTimeSeconds); // 眼裂 } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 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的计算中,被眨眼覆盖,被抑制到标准的开闭眼。
由于计算顺序不同,模型所表达的细微差别很大。
与设计师分享动作、面部表情、功能的计算顺序。