前回
モーフ
モーフィングは3Dモデルの形状を別の形状に変形させることを指します。
代表的な例として、顔の表情の変化に使用されます。
ブレンドシェイプとも呼ばれています。
PMXモデルは様々な方法でモーフィングを使用します。
- 頂点位置の移動
- UV値の変更
- マテリアルパラメータ値の変更
- ボーンの位置や回転の変更
モーフィングを実装してミクさんに表情のアニメーションも適用させてみましょう。
モーフクラス
モーフクラスは、モーフィング自体をクラスにしたものです。
enum class MorphType
{
None,
Position,
UV,
Material,
Bone,
Group
};
struct PositionMorphData
{
unsigned int vertexIndex;
XMFLOAT3 position;
};
struct UVMorphData
{
unsigned int vertexIndex;
XMFLOAT4 uv;
};
class Morph
{
public:
Morph();
void SetName(const std::wstring& name) { _name = name; }
const std::wstring& GetName() const { return _name; }
void SetWeight(float weight) { _weight = weight; }
float GetWeight() const { return _weight; }
void SetMorphType(MorphType morphType) { _morphType = morphType; }
const MorphType& GetMorphType() const { return _morphType; }
void SetPositionMorph(std::vector<PMXMorph::PositionMorph> pmxPositionMorphs);
void SetUVMorph(std::vector<PMXMorph::UVMorph> pmxUVMorphs);
void SetMaterialMorph(std::vector<PMXMorph::MaterialMorph> pmxMaterialMorphs);
void SetBoneMorph(std::vector<PMXMorph::BoneMorph> pmxBoneMorphs);
void SetGroupMorph(std::vector<PMXMorph::GroupMorph> pmxGroupMorphs);
const std::vector<PMXMorph::PositionMorph>& GetPositionMorphData() const { return _positionMorphData; }
const std::vector<PMXMorph::UVMorph>& GetUVMorphData() const { return _uvMorphData; }
const std::vector<PMXMorph::MaterialMorph>& GetMaterialMorphData() const { return _materialMorphData; }
const std::vector<PMXMorph::BoneMorph>& GetBoneMorphData() const { return _boneMorphData; }
const std::vector<PMXMorph::GroupMorph>& GetGroupMorphData() const { return _groupMorphData; }
void SaveBaseAnimation() { _saveAnimWeight = _weight; }
void LoadBaseAnimation() { _weight = _saveAnimWeight; }
void ClearBaseAnimation() { _saveAnimWeight = 0; }
float GetBaseAnimationWeight() const { return _saveAnimWeight; }
private:
std::wstring _name;
float _weight = 0.f;
float _saveAnimWeight = 0.f;
MorphType _morphType;
std::vector<PMXMorph::PositionMorph> _positionMorphData;
std::vector<PMXMorph::UVMorph> _uvMorphData;
std::vector<PMXMorph::MaterialMorph> _materialMorphData;
std::vector<PMXMorph::BoneMorph> _boneMorphData;
std::vector<PMXMorph::GroupMorph> _groupMorphData;
};
宣言部です。
void Morph::SetPositionMorph(std::vector<PMXMorph::PositionMorph> pmxPositionMorphs)
{
_positionMorphData.resize(pmxPositionMorphs.size());
for (int i = 0; i < pmxPositionMorphs.size(); i++)
{
_positionMorphData[i].position = pmxPositionMorphs[i].position;
_positionMorphData[i].vertexIndex = pmxPositionMorphs[i].vertexIndex;
}
}
void Morph::SetUVMorph(std::vector<PMXMorph::UVMorph> pmxUVMorphs)
{
_uvMorphData.resize(pmxUVMorphs.size());
for (int i = 0; i < pmxUVMorphs.size(); i++)
{
_uvMorphData[i].vertexIndex = pmxUVMorphs[i].vertexIndex;
_uvMorphData[i].uv = pmxUVMorphs[i].uv;
}
}
void Morph::SetMaterialMorph(std::vector<PMXMorph::MaterialMorph> pmxMaterialMorphs)
{
_materialMorphData.resize(pmxMaterialMorphs.size());
for (int i = 0; i < pmxMaterialMorphs.size(); i++)
{
_materialMorphData[i] = pmxMaterialMorphs[i];
}
}
void Morph::SetBoneMorph(std::vector<PMXMorph::BoneMorph> pmxBoneMorphs)
{
_boneMorphData.resize(pmxBoneMorphs.size());
for (int i = 0; i < pmxBoneMorphs.size(); i++)
{
_boneMorphData[i] = pmxBoneMorphs[i];
}
}
void Morph::SetGroupMorph(std::vector<PMXMorph::GroupMorph> pmxGroupMorphs)
{
_groupMorphData.resize(pmxGroupMorphs.size());
for (int i = 0; i < pmxGroupMorphs.size(); i++)
{
_groupMorphData[i] = pmxGroupMorphs[i];
}
}
これらのメソッドは、主にMorphオブジェクトにPMXから読み取った情報を設定するためのものです。
設定されたデータを使用して、後でMorphManagerでアニメーションを再生します。
MorphManager
これはモーフィングデータを使用してアニメーションを実行するクラスです。
struct MaterialMorphData
{
float weight;
PMXMorph::MaterialMorph::OpType opType;
XMFLOAT4 diffuse;
XMFLOAT3 specular;
float specularPower;
XMFLOAT3 ambient;
XMFLOAT4 edgeColor;
float edgeSize;
XMFLOAT4 textureFactor;
XMFLOAT4 sphereTextureFactor;
XMFLOAT4 toonTextureFactor;
};
struct BoneMorphData
{
float weight;
XMFLOAT3 position;
XMFLOAT4 quaternion;
};
class MorphManager
{
public:
MorphManager();
void Init(const std::vector<PMXMorph>& pmxMorphs, const std::vector<VMDMorph>& vmdMorphs, unsigned int vertexCount, unsigned int materialCount, unsigned int boneCount);
void Animate(unsigned int frame);
const XMFLOAT3& GetMorphVertexPosition(unsigned int index) const;
const XMFLOAT4& GetMorphUV(unsigned int index) const;
const MaterialMorphData& GetMorphMaterial(unsigned int index) const;
const BoneMorphData& GetMorphBone(unsigned int index) const;
private:
void AnimateMorph(Morph& morph, float weight = 1.f);
void AnimatePositionMorph(Morph& morph, float weight);
void AnimateUVMorph(Morph& morph, float weight);
void AnimateMaterialMorph(Morph& morph, float weight);
void AnimateBoneMorph(Morph& morph, float weight);
void AnimateGroupMorph(Morph& morph, float weight);
void ResetMorphData();
private:
std::vector<Morph> _morphs;
std::unordered_map<std::wstring, Morph*> _morphByName;
std::vector<VMDMorph> _morphKeys;
std::unordered_map<std::wstring, std::vector<VMDMorph*>> _morphKeyByName;
std::vector<XMFLOAT3> _morphVertexPosition;
std::vector<XMFLOAT4> _morphUV;
std::vector<MaterialMorphData> _morphMaterial;
std::vector<BoneMorphData> _morphBone;
};
宣言部です。
void MorphManager::Init(const std::vector<PMXMorph>& pmxMorphs, const std::vector<VMDMorph>& vmdMorphs, unsigned int vertexCount, unsigned int materialCount, unsigned int boneCount)
{
_morphs.resize(pmxMorphs.size());
for (unsigned int index = 0; index < pmxMorphs.size(); index++)
{
Morph& currentMorph = _morphs[index];
const PMXMorph& currentPMXMorph = pmxMorphs[index];
currentMorph.SetName(currentPMXMorph.name);
currentMorph.SetWeight(0.0f);
MorphType morphType = MorphType::None;
switch (currentPMXMorph.morphType)
{
case PMXMorphType::Position:
{
morphType = MorphType::Position;
currentMorph.SetMorphType(morphType);
currentMorph.SetPositionMorph(currentPMXMorph.positionMorph);
}
break;
case PMXMorphType::UV:
{
morphType = MorphType::UV;
currentMorph.SetMorphType(morphType);
currentMorph.SetUVMorph(currentPMXMorph.uvMorph);
}
break;
case PMXMorphType::Material:
{
morphType = MorphType::Material;
currentMorph.SetMorphType(morphType);
currentMorph.SetMaterialMorph(currentPMXMorph.materialMorph);
}
break;
case PMXMorphType::Bone:
{
morphType = MorphType::Bone;
currentMorph.SetMorphType(morphType);
currentMorph.SetBoneMorph(currentPMXMorph.boneMorph);
}
break;
case PMXMorphType::Group:
{
morphType = MorphType::Group;
currentMorph.SetMorphType(morphType);
currentMorph.SetGroupMorph(currentPMXMorph.groupMorph);
}
break;
default:
{
morphType = MorphType::None;
currentMorph.SetMorphType(morphType);
}
break;
}
_morphByName[currentMorph.GetName()] = ¤tMorph;
}
_morphKeys.resize(vmdMorphs.size());
for (int i =0; i < vmdMorphs.size(); i++)
{
_morphKeys[i] = vmdMorphs[i];
if (_morphByName.find(_morphKeys[i].blendShapeName) == _morphByName.end())
{
continue;
}
_morphKeyByName[_morphKeys[i].blendShapeName].push_back(&_morphKeys[i]);
}
for (auto& morphKey : _morphKeyByName)
{
std::vector<VMDMorph*>& morphKeys = morphKey.second;
if (morphKeys.size() <= 1)
{
continue;
}
std::sort(morphKeys.begin(), morphKeys.end(),
[](const VMDMorph* left, const VMDMorph* right)
{
if (left->frame == right->frame)
{
return false;
}
return left->frame < right->frame;
});
}
_morphVertexPosition.resize(vertexCount);
_morphUV.resize(vertexCount);
_morphMaterial.resize(materialCount);
_morphBone.resize(boneCount);
}
これは初期化メソッドです。
モーフィングデータをタイプごとに設定し、モーフィングの名前を基準としてキーとしてunordered_mapに保存します。
名前に合うMorphオブジェクトを見つけて、モーフィングアニメーションキーを追加します。
そして、アニメーションの時と同様に、frameNoを基準に並び替えを行います。
void MorphManager::ResetMorphData()
{
for (XMFLOAT3& position : _morphVertexPosition)
{
position = XMFLOAT3(0.f, 0.f, 0.f);
}
for (XMFLOAT4& uv : _morphUV)
{
uv = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
}
for (MaterialMorphData material : _morphMaterial)
{
material.weight = 0.f;
material.diffuse = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
material.specular = XMFLOAT3(0.f, 0.f, 0.f);
material.specularPower = 0.f;
material.ambient = XMFLOAT3(0.f, 0.f, 0.f);
material.edgeColor = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
material.edgeSize = 0.f;
material.textureFactor = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
material.sphereTextureFactor = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
material.toonTextureFactor = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
}
for (BoneMorphData bone : _morphBone)
{
bone.weight = 0.f;
bone.position = XMFLOAT3(0.f, 0.f, 0.f);
bone.quaternion = XMFLOAT4(0.f, 0.f, 0.f, 0.f);
}
}
モーフィングアニメーションを実行する前に、モーフィングに関連する値を初期値に初期化するメソッドを作成します。
void MorphManager::Animate(unsigned frame)
{
ResetMorphData();
for (auto& morphKey : _morphKeyByName)
{
auto morphIt = _morphByName.find(morphKey.first);
if (morphIt == _morphByName.end())
{
continue;
}
auto rit = std::find_if(morphKey.second.rbegin(), morphKey.second.rend(),
[frame](const VMDMorph* morph)
{
return morph->frame <= frame;
});
auto iterator = rit.base();
if (iterator == morphKey.second.end())
{
morphIt->second->SetWeight(0.0f);
}
else
{
float t = static_cast<float>(frame - (*rit)->frame) / static_cast<float>((*iterator)->frame - (*rit)->frame);
morphIt->second->SetWeight(MathUtil::Lerp((*rit)->weight, (*iterator)->weight, t));
}
}
for (Morph& morph : _morphs)
{
AnimateMorph(morph);
}
}
これはモーフィングアニメーションを実行するメソッドです。
通常のアニメーションと似ています。
モーフィングアニメーションはウェイト値を使って実行されるため、ウェイト値で線形補間を行います。
void MorphManager::AnimateMorph(Morph& morph, float weight)
{
switch (morph.GetMorphType())
{
case MorphType::Position:
{
AnimatePositionMorph(morph, weight);
}
break;
case MorphType::UV:
{
AnimateUVMorph(morph, weight);
}
break;
case MorphType::Material:
{
AnimateMaterialMorph(morph, weight);
}
break;
case MorphType::Bone:
{
AnimateBoneMorph(morph, weight);
}
break;
case MorphType::Group:
{
AnimateGroupMorph(morph, weight);
}
break;
default:
break;
}
}
モーフィングタイプに応じてアニメーションが実行されるようにします。
void MorphManager::AnimatePositionMorph(Morph& morph, float weight)
{
const auto& vertexPositionMorph = morph.GetPositionMorphData();
for (const PMXMorph::PositionMorph& data : vertexPositionMorph)
{
if (data.vertexIndex >= _morphVertexPosition.size())
{
continue;
}
XMVECTOR originPosition = XMLoadFloat3(&_morphVertexPosition[data.vertexIndex]);
XMVECTOR morphPosition = XMLoadFloat3(&data.position) * morph.GetWeight() * weight;
XMFLOAT3 storePosition;
XMStoreFloat3(&storePosition, originPosition + morphPosition);
_morphVertexPosition[data.vertexIndex] = storePosition;
}
}
頂点モーフィングが一番多いモーフィング方法です。
顔の口の形が変わったり、目を閉じたりするような部分に使用されることになるでしょう。
モーフィングデータの位置値にウェイト値を掛けて保存します。
保存しておいた移動値は頂点スキニングを行う際に加算されることになります。
void MorphManager::AnimateUVMorph(Morph& morph, float weight)
{
const auto& uvMorph = morph.GetUVMorphData();
for (const PMXMorph::UVMorph& data : uvMorph)
{
if (data.vertexIndex >= _morphUV.size())
{
continue;
}
XMVECTOR morphUV = XMLoadFloat4(&data.uv);
XMVECTOR originUV = XMLoadFloat4(&_morphUV[data.vertexIndex]);
XMStoreFloat4(&_morphUV[data.vertexIndex], originUV + morphUV * morph.GetWeight() * weight);
}
これは元のUV座標とは異なるUV座標をサンプリングさせるためにUV値を変更するモーフィングです。
モーフィングデータのUV変化値にウェイト値を掛けて保存しておきます。
UVモーフィングも頂点スキニングの際に一緒に適用する予定です。
void MorphManager::AnimateMaterialMorph(Morph& morph, float weight)
{
const auto& materialMorph = morph.GetMaterialMorphData();
for (const PMXMorph::MaterialMorph& data : materialMorph)
{
if (data.materialIndex >= _morphMaterial.size())
{
continue;
}
MaterialMorphData& cur = _morphMaterial[data.materialIndex];
cur.weight = morph.GetWeight() * weight;
cur.opType = data.opType;
cur.diffuse = data.diffuse;
cur.specular = data.specular;
cur.specularPower = data.specularPower;
cur.ambient = data.ambient;
cur.edgeColor = data.edgeColor;
cur.edgeSize = data.edgeSize;
cur.textureFactor = data.textureFactor;
cur.sphereTextureFactor = data.sphereTextureFactor;
cur.toonTextureFactor = data.toonTextureFactor;
}
}
マテリアルのパラメータの値を変化させるモーフィングです。
後でPMXActorでこの値がマテリアルに適用されるように修正します。
void MorphManager::AnimateBoneMorph(Morph& morph, float weight)
{
const auto& bornMorph = morph.GetBoneMorphData();
for (const PMXMorph::BoneMorph& data : bornMorph)
{
if (data.boneIndex >= _morphBone.size())
{
continue;
}
_morphBone[data.boneIndex].weight = morph.GetWeight() * weight;
_morphBone[data.boneIndex].position = data.position;
_morphBone[data.boneIndex].quaternion = data.quaternion;
}
}
ボーンの位置や回転を変化させるモーフィングです。
ここで保存された値をBoneNodeに設定する処理が必要になります。
void MorphManager::AnimateGroupMorph(Morph& morph, float weight)
{
const auto& groupMorph = morph.GetGroupMorphData();
for (const PMXMorph::GroupMorph& data : groupMorph)
{
if (data.morphIndex >= _morphs.size())
{
continue;
}
AnimateMorph(_morphs[data.morphIndex], morph.GetWeight() * weight);
}
}
グループは、上記で見たモーフィングを複数のタイプで使用する際に使用されます。
PMXActorの修正
MorphManager _morphManager;
MorphManagerをメンバーとして追加します。
_morphManager.Init(_pmxFileData.morphs, _vmdFileData.morphs, _pmxFileData.vertices.size(), _pmxFileData.materials.size(), _pmxFileData.bones.size());
PMXのモーフィングデータ、VMDのモーフィングデータ、頂点数、マテリアル数、ボーン数でMorphManagerを初期化します。
void NodeManager::BeforeUpdateAnimation()
{
for (BoneNode* curNode : _boneNodeByIdx)
{
curNode->SetMorphPosition(XMFLOAT3(0.f, 0.f, 0.f));
curNode->SetMorphRotation(XMMatrixIdentity());
}
}
アニメーションを実行する前に、BoneNodeのモーフィングに関連するパラメータを初期値に初期化する関数を追加します。
void PMXActor::UpdateAnimation()
{
if (_startTime <= 0)
{
_startTime = timeGetTime();
}
DWORD elapsedTime = timeGetTime() - _startTime;
unsigned int frameNo = 30 * (elapsedTime / 1000.0f);
if (frameNo > _duration)
{
_startTime = timeGetTime();
frameNo = 0;
}
_nodeManager.BeforeUpdateAnimation();
_morphManager.Animate(frameNo);
_nodeManager.UpdateAnimation(frameNo);
MorphMaterial();
MorphBone();
VertexSkinning();
std::copy(_uploadVertices.begin(), _uploadVertices.end(), _mappedVertex);
}
アニメーションを実行する前にBeforeUpdateAnimationを呼び出し、
MorphManagerでモーフィングアニメーションを実行します。
アニメーションの実行が完了したら、結果値が適用されるようにMorphMaterialとMorphBoneを呼び出すようにします。
これら2つのメソッドは後で作成します。
void PMXActor::VertexSkinningByRange(const SkinningRange& range)
{
for (unsigned int i = 0; i < _pmxFileData.vertices.size(); ++i)
{
const PMXVertex& currentVertexData = _pmxFileData.vertices[i];
XMVECTOR position = XMLoadFloat3(¤tVertexData.position);
XMVECTOR morphPosition = XMLoadFloat3(&_morphManager.GetMorphVertexPosition(i));
switch (currentVertexData.weightType)
{
case PMXVertexWeight::BDEF1:
{
BoneNode* bone0 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[0]);
XMMATRIX m0 = XMMatrixMultiply(bone0->GetInitInverseTransform(), bone0->GetGlobalTransform());
position += morphPosition;
position = XMVector3Transform(position, m0);
break;
}
case PMXVertexWeight::BDEF2:
{
float weight0 = currentVertexData.boneWeights[0];
float weight1 = 1.0f - weight0;
BoneNode* bone0 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[0]);
BoneNode* bone1 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[1]);
XMMATRIX m0 = XMMatrixMultiply(bone0->GetInitInverseTransform(), bone0->GetGlobalTransform());
XMMATRIX m1 = XMMatrixMultiply(bone1->GetInitInverseTransform(), bone1->GetGlobalTransform());
XMMATRIX mat = m0 * weight0 + m1 * weight1;
position += morphPosition;
position = XMVector3Transform(position, mat);
break;
}
case PMXVertexWeight::BDEF4:
{
float weight0 = currentVertexData.boneWeights[0];
float weight1 = currentVertexData.boneWeights[1];
float weight2 = currentVertexData.boneWeights[2];
float weight3 = currentVertexData.boneWeights[3];
BoneNode* bone0 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[0]);
BoneNode* bone1 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[1]);
BoneNode* bone2 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[2]);
BoneNode* bone3 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[3]);
XMMATRIX m0 = XMMatrixMultiply(bone0->GetInitInverseTransform(), bone0->GetGlobalTransform());
XMMATRIX m1 = XMMatrixMultiply(bone1->GetInitInverseTransform(), bone1->GetGlobalTransform());
XMMATRIX m2 = XMMatrixMultiply(bone2->GetInitInverseTransform(), bone2->GetGlobalTransform());
XMMATRIX m3 = XMMatrixMultiply(bone3->GetInitInverseTransform(), bone3->GetGlobalTransform());
XMMATRIX mat = m0 * weight0 + m1 * weight1 + m2 * weight2 + m3 * weight3;
position += morphPosition;
position = XMVector3Transform(position, mat);
break;
}
case PMXVertexWeight::SDEF:
{
float w0 = currentVertexData.boneWeights[0];
float w1 = 1.0f - w0;
XMVECTOR sdefc = XMLoadFloat3(¤tVertexData.sdefC);
XMVECTOR sdefr0 = XMLoadFloat3(¤tVertexData.sdefR0);
XMVECTOR sdefr1 = XMLoadFloat3(¤tVertexData.sdefR1);
XMVECTOR rw = sdefr0 * w0 + sdefr1 * w1;
XMVECTOR r0 = sdefc + sdefr0 - rw;
XMVECTOR r1 = sdefc + sdefr1 - rw;
XMVECTOR cr0 = (sdefc + r0) * 0.5f;
XMVECTOR cr1 = (sdefc + r1) * 0.5f;
BoneNode* bone0 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[0]);
BoneNode* bone1 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[1]);
XMVECTOR q0 = XMQuaternionRotationMatrix(bone0->GetGlobalTransform());
XMVECTOR q1 = XMQuaternionRotationMatrix(bone1->GetGlobalTransform());
XMMATRIX m0 = XMMatrixMultiply(bone0->GetInitInverseTransform(), bone0->GetGlobalTransform());
XMMATRIX m1 = XMMatrixMultiply(bone1->GetInitInverseTransform(), bone1->GetGlobalTransform());
XMMATRIX rotation = XMMatrixRotationQuaternion(XMQuaternionSlerp(q0, q1, w1));
position += morphPosition;
position = XMVector3Transform(position - sdefc, rotation) + XMVector3Transform(cr0, m0) * w0 + XMVector3Transform(cr1, m1) * w1;
XMVECTOR normal = XMLoadFloat3(¤tVertexData.normal);
normal = XMVector3Transform(normal, rotation);
XMStoreFloat3(&_uploadVertices[i].normal, normal);
break;
}
case PMXVertexWeight::QDEF:
{
BoneNode* bone0 = _nodeManager.GetBoneNodeByIndex(currentVertexData.boneIndices[0]);
XMMATRIX m0 = XMMatrixMultiply(bone0->GetInitInverseTransform(), bone0->GetGlobalTransform());
position += morphPosition;
position = XMVector3Transform(position, m0);
break;
}
default:
break;
}
XMStoreFloat3(&_uploadVertices[i].position, position);
const XMFLOAT4& morphUV = _morphManager.GetMorphUV(i);
const XMFLOAT2& originalUV = _uploadVertices[i].uv;
_uploadVertices[i].uv = XMFLOAT2(originalUV.x + morphUV.x, originalUV.y + morphUV.y);
}
}
頂点移動とUVモーフィングを頂点スキニング処理中に適用するように修正します。
頂点のインデックスがあれば、MorphManagerからモーフィング値を取得できます。
頂点移動値(morphPosition)に保存し、スキニング計算が完了した後、最終位置値に加算するだけで済みます。
UV値も同様に、元のUV値に加算するだけです。
void PMXActor::MorphMaterial()
{
size_t bufferSize = sizeof(MaterialForShader);
bufferSize = (bufferSize + 0xff) & ~0xff;
char* mappedMaterialPtr = _mappedMaterial;
for (int i = 0; i < _pmxFileData.materials.size(); i++)
{
PMXMaterial& material = _pmxFileData.materials[i];
MaterialForShader* uploadMat = reinterpret_cast<MaterialForShader*>(mappedMaterialPtr);
XMVECTOR diffuse = XMLoadFloat4(&material.diffuse);
XMVECTOR specular = XMLoadFloat3(&material.specular);
XMVECTOR ambient = XMLoadFloat3(&material.ambient);
const MaterialMorphData& morphMaterial = _morphManager.GetMorphMaterial(i);
float weight = morphMaterial.weight;
XMFLOAT4 resultDiffuse;
XMFLOAT3 resultSpecular;
XMFLOAT3 resultAmbient;
if (morphMaterial.opType == PMXMorph::MaterialMorph::OpType::Add)
{
XMStoreFloat4(&resultDiffuse, diffuse + XMLoadFloat4(&morphMaterial.diffuse) * weight);
XMStoreFloat3(&resultSpecular, specular + XMLoadFloat3(&morphMaterial.specular) * weight);
XMStoreFloat3(&resultAmbient, ambient + XMLoadFloat3(&morphMaterial.ambient) * weight);
uploadMat->specularPower += morphMaterial.specularPower * weight;
}
else
{
XMVECTOR morphDiffuse = diffuse * XMLoadFloat4(&morphMaterial.diffuse);
XMVECTOR morphSpecular = specular * XMLoadFloat3(&morphMaterial.specular);
XMVECTOR morphAmbient = ambient * XMLoadFloat3(&morphMaterial.ambient);
XMStoreFloat4(&resultDiffuse, XMVectorLerp(diffuse, morphDiffuse, weight));
XMStoreFloat3(&resultSpecular, XMVectorLerp(specular, morphSpecular, weight));
XMStoreFloat3(&resultAmbient, XMVectorLerp(ambient, morphAmbient, weight));
uploadMat->specularPower = MathUtil::Lerp(material.specularPower, material.specularPower * morphMaterial.specularPower, weight);
}
uploadMat->diffuse = resultDiffuse;
uploadMat->specular = resultSpecular;
uploadMat->ambient = resultAmbient;
mappedMaterialPtr += bufferSize;
}
}
これはGPUにアップロードするマテリアルデータにマテリアルモーフィングの結果を適用するメソッドです。
マテリアルには値を加算するか乗算するかを示すフラグがあります。
このフラグの値に応じて、結果を加算または乗算します。
void PMXActor::MorphBone()
{
const std::vector<BoneNode*>& allNodes = _nodeManager.GetAllNodes();
for (BoneNode* boneNode : allNodes)
{
BoneMorphData morph = _morphManager.GetMorphBone(boneNode->GetBoneIndex());
boneNode->SetMorphPosition(MathUtil::Lerp(XMFLOAT3(0.f, 0.f, 0.f), morph.position, morph.weight));
XMVECTOR animateRotation = XMQuaternionRotationMatrix(boneNode->GetAnimateRotation());
XMVECTOR morphRotation = XMLoadFloat4(&morph.quaternion);
animateRotation = XMQuaternionSlerp(animateRotation, morphRotation, morph.weight);
boneNode->SetAnimateRotation(XMMatrixRotationQuaternion(animateRotation));
}
}
これはボーンモーフィングを適用するメソッドです。
移動値はBoneNodeのSetMorphPositionで設定します。
回転はアニメーションが完了した回転行列に直接適用します。
ウエート値があるため、球面線形補間を行います。
void BoneNode::UpdateLocalTransform()
{
XMMATRIX scale = XMMatrixIdentity();
XMMATRIX rotation = _animateRotation;
if (_enableIK == true)
{
rotation = rotation * _ikRotation;
}
if (_isAppendRotate == true)
{
rotation = rotation * _appendRotation;
}
XMVECTOR t = XMLoadFloat3(&_animatePosition) + XMLoadFloat3(&_position) + XMLoadFloat3(&_morphPosition);
if (_isAppendTranslate == true)
{
t += XMLoadFloat3(&_appendTranslate);
}
XMMATRIX translate = XMMatrixTranslationFromVector(t);
_localTransform = scale * rotation * translate;
}
BoneNodeのUpdateLocalTransformメソッドも修正して、先ほどSetMorphPositionで設定したボーンの移動値が適用されるようにします。
_morphPositionが最終位置に加算されるようにします。
ビルドして確認してみましょう。
ミクさんの表情が変わります!
まとめ
この記事ではモーフィングを実装しました。
IKと比較すると、構造はずっと単純でした。
ここまで来れば、十分に自然なアニメーションを見ることができるようになったようです。
しかし、まだ物足りない部分がありますね。
髪の毛や服が動きません。
次の記事では物理エンジンをプロジェクトに組み込んで、髪の毛や服が物理シミュレーションされるようにしてみましょう。
次回