参数操作

最終更新: 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的计算中,被眨眼覆盖,并被抑制到标准的眼睛开合。
仅计算顺序不同,导致模型所表达的细微差别很大。

请与设计师共享动态、表情、功能的计算顺序。

请问这篇文章对您有帮助吗?
关于本报道,敬请提出您的意见及要求。