关于姿势
最終更新: 2023年1月26日

什么是姿势功能?
姿势是一种从多个相同类型部件中,根据动态淡化显示单个部件切换的功能。
同类型的多个部件是指同时显示时会引起显示矛盾的部件,例如右手A和右手B等。
也是最终将动态中的部件不透明度操作应用到部件的处理部分。
要了解此页面,建议提前阅读“关于模型”中的“在CubismModel类中使用模型中不存在的参数ID”。
动态中的部件不透明度操作流程
动态播放中的部件不透明度操作

为了保持部件不透明度的一致性,Original WorkFlow的Framework中单独的动态播放并不直接对部件不透明度进行操作。
动态播放的部件不透明度操作被替换为覆盖与部件相同的ID参数。
此时,模型中不存在的参数ID仅作为虚拟参数保留。
不执行与动态切换相关联的渐变处理等,仅执行覆盖。
在OW SDK中,假定针对动态的部件不透明度的插值方法应用了步。
// C++
void CubismMotion::DoUpdateParameters(CubismModel* model, csmFloat32 timeSeconds, csmFloat32 fadeWeight, CubismMotionQueueEntry* motionQueueEntry)
{
/*省略*/
for (; c < _motionData->CurveCount && curves[c].Type == CubismMotionCurveTarget_PartOpacity; ++c)
{
// Find parameter index.
parameterIndex = model->GetParameterIndex(curves[c].Id);
// Skip curve evaluation if no value in sink.
if (parameterIndex == -1)
{
continue;
}
// Evaluate curve and apply value.
value = EvaluateCurve(_motionData, c, time);
model->SetParameterValue(parameterIndex, value);// 导入到虚拟参数而不是部件不透明度。
}
/*省略*/
}
// TypeScript
public doUpdateParameters(model: CubismModel, userTimeSeconds: number, fadeWeight: number, motionQueueEntry: CubismMotionQueueEntry): void
{
/*省略*/
for(; c < this._motionData.curveCount && curves.at(c).type == CubismMotionCurveTarget.CubismMotionCurveTarget_PartOpacity; ++c)
{
// Find parameter index.
parameterIndex = model.getParameterIndex(curves.at(c).id);
// Skip curve evaluation if no value in sink.
if(parameterIndex == -1)
{
continue;
}
// Evaluate curve and apply value.
value = evaluateCurve(this._motionData, c, time);
model.setParameterValueByIndex(parameterIndex, value);
}
/*省略*/
}
// Java
public void doUpdateParameters(CubismModel model, float timeSeconds, float fadeWeight, CubismMotionQueueEntry motionQueueEntry){
/*省略*/
for (CubismMotionCurve curve : curves) {
if (curve.type != CubismMotionCurveTarget.PARAMETER) {
continue;
}
// Findparameter index.
final int parameterIndex = model.getParameterIndex(curve.id);
// Skip curve evaluation if no value.
if (parameterIndex == -1) {
continue;
}
// Evaluate curve and apply value.
value = evaluateCurve(curve, time);
...
model.setParameterValue(parameterIndex, v); // 导入到虚拟参数而不是部件不透明度。
}
/*省略*/
}
应用姿势

通过在模型更新处理的最后阶段应用Pose功能,根据.pose3.json中描述的信息,针对在各组显示哪个部件,参考虚拟参数的值来进行决定。
将Pose应用于模型的API如下所示
// C++ CubismPose::UpdateParameters()
// TypeScript CubismPose.updateParameters()
// Java CubismPose.updateParameters();
部件操作的曲线推荐步,但如果选择直线等,则在它变得大于0.001时,被识别为显示状态。
在决定显示部件时,新的不透明度通过与操作前的部件不透明度的时间差进行比例计算,决定为直线插值。
// C++
void CubismPose::DoFade(CubismModel* model, csmFloat32 deltaTimeSeconds, csmInt32 beginIndex, csmInt32 partGroupCount)
{
csmInt32 visiblePartIndex = -1;
csmFloat32 newOpacity = 1.0f;
const csmFloat32 Phi = 0.5f;
const csmFloat32 BackOpacityThreshold = 0.15f;
// 获取当前显示的部件
for (csmInt32 i = beginIndex; i < beginIndex + partGroupCount; ++i)
{
csmInt32 partIndex = _partGroups[i].PartIndex;
csmInt32 paramIndex = _partGroups[i].ParameterIndex;
if (model->GetParameterValue(paramIndex) > Epsilon) // const csmFloat32 Epsilon= 0.001f;
{
if (visiblePartIndex >= 0)
{
break;
}
visiblePartIndex = i;
newOpacity = model->GetPartOpacity(partIndex);
// 计算新的不透明度
newOpacity += (deltaTimeSeconds / _fadeTimeSeconds);
if (newOpacity > 1.0f)
{
newOpacity = 1.0f;
}
}
}
/*省略*/
}
// TypeScript
public doFade(model: CubismModel, deltaTimeSeconds: number, beginIndex: number, partGroupCount: number): void
{
let visiblePartIndex: number = -1;
let newOpacity: number = 1.0;
const phi: number = 0.5;
const backOpacityThreshold: number = 0.15;
// 获取当前显示的部件
for(let i: number = beginIndex; i < beginIndex + partGroupCount; ++i)
{
const partIndex: number = this._partGroups.at(i).partIndex;
const paramIndex: number = this._partGroups.at(i).parameterIndex;
if(model.getParameterValueByIndex(paramIndex) > Epsilon)
{
if(visiblePartIndex >= 0)
{
break;
}
visiblePartIndex = i;
newOpacity = model.getPartOpacityByIndex(partIndex);
// 计算新的不透明度
newOpacity += (deltaTimeSeconds / this._fadeTimeSeconds);
if(newOpacity > 1.0)
{
newOpacity = 1.0;
}
}
}
/*省略*/
}
// Java
public void doFade(CubismModel model,
float deltaTimeSeconds,
int beginIndex,
int partGroupCount)
{
int visiblePartIndex = -1;
float newOpacity = 1.0f;
// 获取当前显示的部件
for (int i = beginIndex; i < beginIndex + partGroupCount; i++) {
final int paramIndex = partGroups.get(i).parameterIndex;
if (model.getParameterValue(paramIndex) > EPSILON) {
if (visiblePartIndex >= 0) {
break;
}
// 计算新的不透明度
newOpacity = calculateOpacity(model, i, deltaTimeSeconds);
visiblePartIndex = i;
}
}
/*省略*/
}
在决定了显示部件及其新的不透明度后,对全体组进行不透明度覆盖处理。
隐藏部件的新不透明度相对于显示部件的不透明度将不透明度降低到背景不透明的水平。
// C++
void CubismPose::DoFade(CubismModel* model, csmFloat32 deltaTimeSeconds, csmInt32 beginIndex, csmInt32 partGroupCount)
{
csmInt32 visiblePartIndex = -1;
csmFloat32 newOpacity = 1.0f;
const csmFloat32 Phi = 0.5f;
const csmFloat32 BackOpacityThreshold = 0.15f;
/*省略*/
if (visiblePartIndex < 0)
{
visiblePartIndex = 0;
newOpacity = 1.0f;
}
// 设置显示部件和隐藏部件的不透明度
for (csmInt32 i = beginIndex; i < beginIndex + partGroupCount; ++i)
{
csmInt32 partsIndex = _partGroups[i].PartIndex;
// 显示部件的设置
if (visiblePartIndex == i)
{
model->SetPartOpacity(partsIndex, newOpacity); //j/ 先设置
}
// 隐藏部件的设置
else
{
csmFloat32 opacity = model->GetPartOpacity(partsIndex);
csmFloat32 a1; // 通过计算得出的不透明度
if (newOpacity < Phi)
{
a1 = newOpacity * (Phi - 1) / Phi + 1.0f; // 通过(0,1),(phi,phi)的直线方程式
}
else
{
a1 = (1 - newOpacity) * Phi / (1.0f - Phi); // 通过(1,0),(phi,phi)的直线方程式
}
// 限制背景可见比例时
csmFloat32 backOpacity = (1.0f - a1) * (1.0f - newOpacity);
if (backOpacity > BackOpacityThreshold)
{
a1 = 1.0f - BackOpacityThreshold / (1.0f - newOpacity);
}
if (opacity > a1)
{
opacity = a1; // 如果大于计算的不透明度(较暗),则增加不透明度
}
model->SetPartOpacity(partsIndex, opacity);
}
}
}
// TypeScript
public doFade(model: CubismModel, deltaTimeSeconds: number, beginIndex: number, partGroupCount: number): void
{
let visiblePartIndex: number = -1;
let newOpacity: number = 1.0;
const phi: number = 0.5;
const backOpacityThreshold: number = 0.15;
/*省略*/
if(visiblePartIndex < 0)
{
visiblePartIndex = 0;
newOpacity = 1.0;
}
// 设置显示部件和隐藏部件的不透明度
for(let i: number = beginIndex; i < beginIndex + partGroupCount; ++i)
{
const partsIndex: number = this._partGroups.at(i).partIndex;
// 显示部件的设置
if(visiblePartIndex == i)
{
model.setPartOpacityByIndex(partsIndex, newOpacity); // 先设置
}
// 隐藏部件的设置
else
{
let opacity: number = model.getPartOpacityByIndex(partsIndex);
let a1: number; // 通过计算得出的不透明度
if(newOpacity < phi)
{
a1 = newOpacity * (phi - 1) / phi + 1.0; // 通过(0,1),(phi,phi)的直线方程式
}
else
{
a1 = (1 - newOpacity) * phi / (1.0 - phi); // 通过(1,0),(phi,phi)的直线方程式
}
// 限制背景可见比例时
const backOpacity: number = (1.0 - a1) * (1.0 - newOpacity);
if(backOpacity > backOpacityThreshold)
{
a1 = 1.0 - backOpacityThreshold / (1.0 - newOpacity);
}
if(opacity > a1)
{
opacity = a1; // 如果大于计算的不透明度(较暗),则增加不透明度
}
model.setPartOpacityByIndex(partsIndex, opacity);
}
}
}
// Java
public void doFade(CubismModel model,
float deltaTimeSeconds,
int beginIndex,
int partGroupCount)
{
int visiblePartIndex = -1;
float newOpacity = 1.0f;
/*省略*/
if (visiblePartIndex < 0) {
visiblePartIndex = 0;
newOpacity = 1.0f;
}
// 设置显示部件和隐藏部件的不透明度
for (int i = beginIndex; i < beginIndex + partGroupCount; i++) {
final int partsIndex = partGroups.get(i).partIndex;
// 显示部件的设置
if (visiblePartIndex == i) {
model.setPartOpacity(partsIndex, newOpacity);
}
// 隐藏部件的设置
else {
final float opacity = model.getPartOpacity(partsIndex);
final float result = calcNonDisplayedPartsOpacity(opacity, newOpacity);
model.setPartOpacity(partsIndex, result);
}
}
}
Pose数据结构
Pose处理的信息用于加快对参数和部件信息的访问,因此,通过部件ID访问的参数和部件索引被合并并保存在PartData结构中。
此外,联动数据作为Link的PartData的子元素存储。
// C++
/**
* @brief 管理与部件相关的数据
*
*管理与部件相关的各种数据。
*/
struct PartData
{
/*省略*/
CubismIdHandle PartId; ///< 部件ID
csmInt32 ParameterIndex; ///< 参数索引
csmInt32 PartIndex; ///< 部件索引
csmVector<PartData> Link; ///< 联动参数
};
// TypeScript
/**
* 管理与部件相关的数据
*/
export class PartData
{
/*省略*/
partId: CubismIdHandle; // 部件ID
parameterIndex: number; // 参数索引
partIndex: number; // 部件索引
link: csmVector<PartData>; // 联动参数
}
// Java
/**
*管理与部件相关的各种数据。
*/
public class PartData{
/*省略*/
CubismId partId; // 部件ID
int parameterIndex; // 参数索引
int partIndex; // 部件索引
List<PartData> link // 联动参数
}
CubismPose通过一维PartData数组和各组的数量信息数组表达组信息。
// C++
class CubismPose
{
/*省略*/
csmVector<PartData> _partGroups; ///< 部件组
csmVector<csmInt32> _partGroupCounts; ///< 各部件组的数量数
csmFloat32 _fadeTimeSeconds; ///< 渐变时间[秒]
csmFloat32 _lastTimeSeconds; ///< 最后执行的时间[秒]
CubismModel* _lastModel; ///< 上次操作的模型
};
// TypeScript
export class CubismPose
{
/*省略*/
_partGroups: csmVector<PartData>; // 部件组
_partGroupCounts: csmVector<number>; // 各部件组的数量
_fadeTimeSeconds: number; // 渐变时间[秒]
_lastModel: CubismModel; // 上次操作的模型
}
// Java
public class CubismPose{
/*省略*/
List<PartData> partGroups; // 部件组
List<Integer> partGroupCounts; // 各部件组的数量
float fadeTimeSeconds; // 渐变时间[秒]
float lastTimeSeconds; // 最后执行的时间[秒]
CubismModel lastModel; // 上次操作的模型
}
将一维数组的PartData作为一个组,根据各组的数量信息,通过将数组上的起始位置和元素数量传递给DoFade进行处理。
// C++
void CubismPose::UpdateParameters(CubismModel* model, csmFloat32 deltaTimeSeconds)
{
// 和上次的模型不一样时,需要原始化
if (model != _lastModel)
{
// 原始化参数索引
Reset(model);
}
_lastModel = model;
// 如果从设置中变更时间,经过时间可能会变成负数,所以以经过时间0进行处理。
if (deltaTimeSeconds < 0.0f)
{
deltaTimeSeconds = 0.0f;
}
csmInt32 beginIndex = 0;
for (csmUint32 i = 0; i < _partGroupCounts.GetSize(); i++)
{
csmInt32 partGroupCount = _partGroupCounts[i];
DoFade(model, deltaTimeSeconds, beginIndex, partGroupCount);
beginIndex += partGroupCount;
}
CopyPartOpacities(model);
}
// TypeScript
public updateParameters(model: CubismModel, deltaTimeSeconds: number): void
{
// 和上次的模型不一样时,需要原始化
if(model != this._lastModel)
{
// 原始化参数索引
this.reset(model);
}
this._lastModel = model;
// 如果从设置中变更时间,经过时间可能会变成负数,所以以经过时间0进行处理
if(deltaTimeSeconds < 0.0)
{
deltaTimeSeconds = 0.0;
}
let beginIndex: number = 0;
for(let i = 0; i < this._partGroupCounts.getSize(); i++)
{
const partGroupCount: number = this._partGroupCounts.at(i);
this.doFade(model, deltaTimeSeconds, beginIndex, partGroupCount);
beginIndex += partGroupCount;
}
this.copyPartOpacities(model);
}
// Java
public void updateParameters(final CubismModel model, float deltaTimeSeconds) {
// 和上次的模型不一样时,需要原始化
if (model != lastModel) {
reset(model);
}
lastModel = model;
// 如果从设置中变更时间,经过时间可能会变成负数,所以以经过时间0进行处理。
if (deltaTimeSeconds < 0.0f) {
deltaTimeSeconds = 0.0f;
}
int beginIndex = 0;
for (final int partGroupCount : partGroupCounts) {
doFade(model, deltaTimeSeconds, beginIndex, partGroupCount);
beginIndex += partGroupCount;
}
copyPartOpacities(model);
}
Parent ID的不透明度联动
通过Pose应用处理最后调用的以下函数,将值传递到Link中指定的部件。
// C++ CubismPose::CopyPartOpacities()
// TypeScript CubismPose.copyPartOpacities()
// Java CubismPose.copyPartOpacities()
此Link将OWViewer中表述Parent ID的PartID存储为CubismIdHundle数组。
下图中PartArmLB001和PartArmLC001的CubismIdHundle存放在PartManteL001的Link中。

虽然表述为Link,但是是父子结构,如果没有对父部件进行不透明度操作,它将无法联动,敬请注意。
Link中显示的子项未联动。
// C++
void CubismPose::CopyPartOpacities(CubismModel* model)
{
for (csmUint32 groupIndex = 0; groupIndex < _partGroups.GetSize(); ++groupIndex)
{
PartData& partData = _partGroups[groupIndex];
if (partData.Link.GetSize() == 0)
{
continue; // 没有联动的参数
}
csmInt32 partIndex = _partGroups[groupIndex].PartIndex;
csmFloat32 opacity = model->GetPartOpacity(partIndex);
for (csmUint32 linkIndex = 0; linkIndex < partData.Link.GetSize(); ++linkIndex)
{
PartData& linkPart = partData.Link[linkIndex];
csmInt32 linkPartIndex = linkPart.PartIndex;
if (linkPartIndex < 0)
{
continue;
}
model->SetPartOpacity(linkPartIndex, opacity);
}
}
}
// TypeScript
public copyPartOpacities(model: CubismModel): void
{
for(let groupIndex: number = 0; groupIndex < this._partGroups.getSize(); ++groupIndex)
{
let partData: PartData = this._partGroups.at(groupIndex);
if(partData.link.getSize() == 0)
{
continue; // 没有联动的参数
}
const partIndex: number = this._partGroups.at(groupIndex).partIndex;
const opacity: number = model.getPartOpacityByIndex(partIndex);
for(let linkIndex: number = 0; linkIndex < partData.link.getSize(); ++linkIndex)
{
let linkPart: PartData = partData.link.at(linkIndex);
const linkPartIndex: number = linkPart.partIndex;
if(linkPartIndex < 0)
{
continue;
}
model.setPartOpacityByIndex(linkPartIndex, opacity);
}
}
}
// Java
private void copyPartOpacities(CubismModel model) {
for (PartData partData : partGroups) {
if (partData.linkedParameter == null) {
continue;
}
final int partIndex = partData.partIndex;
final float opacity = model.getPartOpacity(partIndex);
for (PartData linkedPart : partData.linkedParameter) {
final int linkedPartIndex = linkedPart.partIndex;
if (linkedPartIndex < 0) {
continue;
}
model.setPartOpacity(linkedPartIndex, opacity);
}
}
}
副本生成(导入.pose3.json文件)
姿势保存在.pose3.json文件中,并使用CubismPose类。
使用以下函数进行生成。
// C++ CubismPose::Create()
// TypeScript CubismPose.create()
// Java CubismPose.create()
实装示例
// C++
csmString path = _modelSetting->GetPoseFileName();
path = _modelHomeDir + path;
buffer = CreateBuffer(path.GetRawString(), &size);
CubismPose* pose = CubismPose::Create(buffer, size);
DeleteBuffer(buffer, path.GetRawString());
// TypeScritp
let path: string = _modelSetting.getPoseFileName();
path = _modelHomeDire + path;
fetch(path).then(
(response) =>
{
return response.arrayBuffer();
}
).then(
(arrayBuffer) =>
{
let buffer: ArrayBuffer = arrayBuffer;
let size: number = buffer.byteLength;
let pose: CubismPose = CubismPose.create(buffer, size);
deleteBuffer(buffer, path);
}
);
// Java String path = modelSetting.getPoseFileName(); path = modelHomeDir + path; buffer = CreateBuffer(path); CubismPose pose = CubismPose.create(buffer);
应用姿势
使用以下函数应用姿势。
// C++ CubismPose::UpdateParameters()
// TypeScript CubismPose.updateParameters()
// Java CubismPose.updateParameters()
需要提前完成应用动态等虚拟参数的计算。
// C++
void LAppModel::Update()
{
const csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
_userTimeSeconds += deltaTimeSeconds;
/*省略*/
//-----------------------------------------------------------------
_model->LoadParameters(); // 读取上次保存的状态
if (_motionManager->IsFinished())
{
// 如果没有动态播放,则从待机动态开始随机播放
StartRandomMotion(MotionGroupIdle, PriorityIdle);
}
else
{
motionUpdated = _motionManager->UpdateMotion(_model, deltaTimeSeconds); // 更新动态
}
_model->SaveParameters(); // 保存状态
//-----------------------------------------------------------------
/*省略*/
// 姿势设置
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._model.loadParameters(); // 读取上次保存的状态
if(this._motionManager.isFinished())
{
// 如果没有动态播放,则从待机动态开始随机播放
this.startRandomMotion(LAppDefine.MotionGroupIdle, LAppDefine.PriorityIdle);
}
else
{
motionUpdated = this._motionManager.updateMotion(this._model, deltaTimeSeconds); // 更新动态
}
this._model.saveParameters(); // 保存状态
//--------------------------------------------------------------------------
/*省略*/
// 姿势设置
if(this._pose != null)
{
this._pose.updateParameters(this._model, deltaTimeSeconds);
}
this._model.update();
}
// Java
public void update() {
final float deltaTimeSeconds = LAppPal.getDeltaTime();
userTimeSeconds += deltaTimeSeconds;
/*省略*/
// -------------------------------------------------
model.loadParameters(); // 读取上次保存的状态
if (motionManager.isFinished()) {
// 如果没有动态播放,则从待机动态开始随机播放
startRandomMotion(LAppDefine.MotionGroup.IDLE.getId(), LAppDefine.Priority.IDLE.getPriority());
} else {
isMotionUpdated = motionManager.updateMotion(model, deltaTimeSeconds);
}
model.saveParameters(); // 保存状态
// -------------------------------------------------
/*省略*/
if (pose != null) {
pose.updateParameters(model, deltaTimeSeconds);
}
model.update();
}
放弃
您还需要在模型释放时放弃CubismPose副本。
// C++ CubismPose::Delete(pose);
// TypeScript CubismPose.delete(pose);
在SDK for Java中,由于垃圾回收负责释放,因此无需放弃。