参数操作
最終更新: 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的计算中,被眨眼覆盖,并被抑制到标准的眼睛开合。
仅计算顺序不同,导致模型所表达的细微差别很大。
请与设计师共享动态、表情、功能的计算顺序。