directx加载ms3d动画模型
最近刚完成了ms3d模型的加载及动画显示,为了让更多的人容易学会和使用该模型,本人就自己所掌握的内容稍微谈谈。
说起骨骼动画,大家一定不会陌生,这里本人假定大家都了解骨骼动画的基本原理。如果不熟悉的话可参考《Advanced Animation with DirectX》和《Focus.On.3D.Models》。其中《Advanced Animation with DirectX》讲了基本的骨骼原理和.x文件动画的显示,《Focus.On.3D.Models》讲了骨骼原理、ms3d格式(还有几种)及动画的显示。
在DirectX下实现ms3d模型与在opengl下有着微妙的不同。本人就是搜索了很多英文网站也只找到了两份有用的源代码,一份是nehe的opengl代码,一份是milkshape写的directx代码。前一份代码阅读性很强,很容易看懂,本人的代码也是参考自它。后一份代码功能强大,但就我来说它不适于开始学习,太复杂了点,而且尽管是用direcxt,里面的坐标也全是基于右手的。
下面开始实现:由于模型空间为右手系,dx为左手系,因此代码中还得对坐标进行转换
#pragma pack(push) //保存对齐状态
#pragma pack(1)//设定为4字节对齐
//-----------------------ms3d数据-------------------------------
typedef struct MS3DHeader_TYP
{
char m_ID[10]; // Must be 'MS3D000000'
int m_version; // File format version (3 or 4)
} MS3DHeader;
// Vertex information
typedef struct MS3DVertex_TYP
{
unsigned char m_flags; // Editor flags
float m_vertex[3]; // World coordinates
char m_boneID; // -1 = no bone
unsigned char m_refCount; // Reference count
} MS3DVertex;
// Triangle information
typedef struct MS3DTriangle_TYP
{
unsigned short m_flags; // Editor flags
unsigned short m_vertexIndices[3]; // Vertex indices
float m_vertexNormals[3][3]; // Normals (each vertex, x/y/z)
float m_u[3]; // Texture u coordinate
float m_v[3]; // Texture v coordinate
unsigned char m_smoothingGroup; // Smoothing group
unsigned char m_groupIndex; // Material group
} MS3DTriangle;
///Mesh information
typedef struct MS3DMesh_TYP
{
unsigned char m_flags; // Editor flags
char m_name[32]; // Name of group
unsigned short m_numTriangles; // # faces in group
unsigned short *m_TriangleIndices; // Face indices
char m_MaterialIndex; // -1 = no material
} MS3DMesh;
// Material information
typedef struct MS3DMaterial_TYP
{
char m_name[32]; // Material name
float m_ambient[4]; // Ambient colors
float m_diffuse[4]; // Diffuse colors
float m_specular[4]; // Specular colors
float m_emissive[4]; // Emmisive colors
float m_shininess; // Shininess strength
float m_transparency; // Transparency amount
char m_mode; // Mode 0-3 0, 1, 2 is unused now
char m_texture[128]; // Texture map .bmp filename
char m_alphamap[128]; // Alpha map .bmp filename
} MS3DMaterial;
//-------------------------骨骼部分----------------------------
// Joint information
struct MS3DJoint
{
char m_flags;
char m_name[32];
char m_parentName[32];
float m_rotation[3];
float m_translation[3];
unsigned short m_numRotationKeyframes;
unsigned short m_numTranslationKeyframes;
};
// Keyframe data
struct MS3DKeyframe
{
float m_time;
float m_parameter[3];
};
#pragma pack(pop)
以上是对整个ms3d文件内部组成的说明,我们使用c++的文件输入来读取其中内容,当然也可以使用其它io方法,只要效果一样即可。
bool CMs3dMesh:oad(IDirect3DDevice9 *pDevice, const char *filename, float scale, char *TexturePath)
{
if ((m_pDevice = pDevice) == NULL)
return false;
std::ifstream file(filename, std::ios::in | std::ios::binary);
//Open the .ms3d model file
if (file == NULL)
return false;
file.seekg(0, std::ios::end);
long fileSize = file.tellg();
file.seekg(0, std::ios::beg);
char *pBuffer = new char[fileSize];
file.read(pBuffer, fileSize);
file.close();
const unsigned char *pPtr = (const unsigned char *)pBuffer;
MS3DHeader *pHeader = (MS3DHeader *)pPtr;
pPtr += sizeof(MS3DHeader);
if (::strncmp(pHeader->m_ID, "MS3D000000", 10) != 0)
{
MessageBox(NULL,"id error","ERROR",MB_OK|MB_ICONEXCLAMATION);
return false;
}
if (pHeader->m_version < 3)// ||Header.m_version > 4)//支持1.5
{
MessageBox(NULL,"version error","ERROR",MB_OK|MB_ICONEXCLAMATION);
return false; // "Unhandled file version. Only Milkshape3D Version 1.3 and 1.4 or above is supported." );
}
m_numVertices = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
m_pVertices = new Vertex[m_numVertices];
for (unsigned short i = 0; i < m_numVertices; i++)
{
MS3DVertex *pVertex = (MS3DVertex *)pPtr;
/////////////data copy
m_pVertices.m_location[0] = pVertex->m_vertex[0];
m_pVertices.m_location[1] = pVertex->m_vertex[1];
//右手到左手
m_pVertices.m_location[2] = -pVertex->m_vertex[2];
///////////////zoom
m_pVertices.m_location[0] *= scale;
m_pVertices.m_location[1] *= scale;
m_pVertices.m_location[2] *= scale;
//骨骼
m_pVertices.m_boneID = pVertex->m_boneID;
pPtr += sizeof(MS3DVertex);
}
///////////read Triangles///////////////////////////
m_numTriangles = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
m_pTriangles = new Triangle[m_numTriangles];
for (i = 0; i < m_numTriangles; i++)
{
MS3DTriangle *pTriangle = (MS3DTriangle *)pPtr;
//////////////////////////////////////////////////////////////////////
::memcpy(m_pTriangles.m_normal, pTriangle->m_vertexNormals, sizeof(float)*9);
//右手到左手
m_pTriangles.m_normal[0][2] = -m_pTriangles.m_normal[0][2];
m_pTriangles.m_normal[1][2] = -m_pTriangles.m_normal[1][2];
m_pTriangles.m_normal[2][2] = -m_pTriangles.m_normal[2][2];
::memcpy(m_pTriangles.m_u, pTriangle->m_u, sizeof( float )*3);
::memcpy(m_pTriangles.m_v, pTriangle->m_v, sizeof( float )*3);
::memcpy(m_pTriangles.m_vertexIndices, pTriangle->m_vertexIndices, sizeof(unsigned short )*3);
m_pTriangles.m_groupID = pTriangle->m_groupIndex;
pPtr += sizeof(MS3DTriangle);
}
//////////////read Meshes/////////////
m_numMeshes = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
m_pMeshes = new Mesh[m_numMeshes];
for (i = 0; i < m_numMeshes; i++)
{
pPtr += sizeof(unsigned char); // flags
pPtr += 32; // name
unsigned short nTriangles = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
unsigned short *pTriangleIndices = new unsigned short[nTriangles];
for (int j = 0; j < nTriangles; j++)
{
pTriangleIndices[j] = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
}
char materialIndex = *(char *)pPtr;
pPtr += sizeof(char);
m_pMeshes.m_textureIndex = materialIndex;
m_pMeshes.m_numTriangles = nTriangles;
m_pMeshes.m_pTriangleIndices = pTriangleIndices;
}
////////////////read texture/////////////
// Read materials, creating a default one if none in file
m_numMaterials = *(unsigned short *)pPtr;
pPtr += sizeof(short);
if (!m_numMaterials)
{
// Create a single material and color it white
m_numMaterials = 1;
m_pMaterial = new MATERIAL[1];
ZeroMemory(&m_pMaterial[0], sizeof(MATERIAL));
m_pMaterial[0].MatD3D.Diffuse.a =
m_pMaterial[0].MatD3D.Diffuse.r =
m_pMaterial[0].MatD3D.Diffuse.g =
m_pMaterial[0].MatD3D.Diffuse.b = 1.0f;
// Set all groups to use material #0
// If there are no materials, set all groups to
// use material #0
if (m_numMeshes)
{
for (i=0; i<m_numMeshes; i++)
m_pMeshes.m_textureIndex = 0;
}
}
else
{
m_pMaterial = new MATERIAL[m_numMaterials];
ZeroMemory(m_pMaterial, sizeof(MATERIAL)*m_numMaterials);
// Read in all materials from file
for (i=0; i<m_numMaterials; i++)
{
MS3DMaterial *pMaterial = (MS3DMaterial *)pPtr;
/////////////////拷贝
m_pMaterial.MatD3D.Diffuse.a = pMaterial->m_diffuse[3];
m_pMaterial.MatD3D.Diffuse.r = pMaterial->m_diffuse[2];
m_pMaterial.MatD3D.Diffuse.g = pMaterial->m_diffuse[1];
m_pMaterial.MatD3D.Diffuse.b = pMaterial->m_diffuse[0];
m_pMaterial.MatD3D.Ambient.a = pMaterial->m_ambient[3];
m_pMaterial.MatD3D.Ambient.r = pMaterial->m_ambient[2];
m_pMaterial.MatD3D.Ambient.g = pMaterial->m_ambient[1];
m_pMaterial.MatD3D.Ambient.b = pMaterial->m_ambient[0];
m_pMaterial.MatD3D.Specular.a = pMaterial->m_specular[3];
m_pMaterial.MatD3D.Specular.r = pMaterial->m_specular[2];
m_pMaterial.MatD3D.Specular.g = pMaterial->m_specular[1];
m_pMaterial.MatD3D.Specular.b = pMaterial->m_specular[0];
m_pMaterial.MatD3D.Emissive.a = pMaterial->m_emissive[3];
m_pMaterial.MatD3D.Emissive.r = pMaterial->m_emissive[2];
m_pMaterial.MatD3D.Emissive.g = pMaterial->m_emissive[1];
m_pMaterial.MatD3D.Emissive.b = pMaterial->m_emissive[0];
m_pMaterial.MatD3D.Power = pMaterial->m_shininess;
if (pMaterial->m_texture)
{
char TextureFile[250] = {0};
// MS3D 1.5.x 相对路径
if (::strncmp(pMaterial->m_texture, ".\", 2 ) == 0 )
ParseTextureFileName(pMaterial->m_texture);
sprintf(TextureFile, "%s%s", TexturePath, pMaterial->m_texture);
if (FAILED(D3DXCreateTextureFromFile(m_pDevice, TextureFile, &m_pMaterial.pTexture)))
{
D3DXCreateTextureFromFile(m_pDevice, pMaterial->m_texture, &m_pMaterial.pTexture);
}
}
pPtr += sizeof(MS3DMaterial);
}
}
//骨骼动画部分
m_fFps = *(float *)pPtr;
pPtr += sizeof(float);
// skip currentTime
pPtr += sizeof(float);
int totalFrames = *(int *)pPtr;
pPtr += sizeof(int);
m_totalTime = totalFrames*1000.0/m_fFps;
m_numJoints = *(unsigned short *)pPtr;
pPtr += sizeof(unsigned short);
//有骨骼信息
if (m_numJoints > 0)
{
m_pJoints = new Joint[m_numJoints];
const unsigned char *pTempPtr = pPtr;
JointNameListRec *pNameList = new JointNameListRec[m_numJoints];
unsigned short i = 0;
for (i = 0; i < m_numJoints; i++)
{
MS3DJoint *pJoint = (MS3DJoint *)pTempPtr;
pTempPtr += sizeof(MS3DJoint);
pTempPtr += sizeof(MS3DKeyframe)*(pJoint->m_numRotationKeyframes+pJoint->m_numTranslationKeyframes);
pNameList.m_jointIndex = i;
pNameList.m_pName = pJoint->m_name;
}
for (i = 0; i < m_numJoints; i++)
{
MS3DJoint *pJoint = (MS3DJoint *)pPtr;
pPtr += sizeof(MS3DJoint);
int j, parentIndex = -1;
if (::strlen(pJoint->m_parentName) > 0)
{
for (j = 0; j < m_numJoints; j++)
{
if (::stricmp(pNameList[j].m_pName, pJoint->m_parentName) == 0)
{
parentIndex = pNameList[j].m_jointIndex;
break;
}
}
if (parentIndex == -1)
{
MessageBox(NULL, "55", 0, 0);
return false;
}
}
::memcpy(m_pJoints.m_localRotation, pJoint->m_rotation, sizeof(float)*3);
::memcpy(m_pJoints.m_localTranslation, pJoint->m_translation, sizeof(float)*3);
//右手到左手
m_pJoints.m_localRotation[0] = -m_pJoints.m_localRotation[0];
m_pJoints.m_localRotation[1] = -m_pJoints.m_localRotation[1];
m_pJoints.m_localTranslation[2] = -m_pJoints.m_localTranslation[2];
m_pJoints.m_localTranslation[0] *= scale;
m_pJoints.m_localTranslation[1] *= scale;
m_pJoints.m_localTranslation[2] *= scale;
m_pJoints.m_parent = parentIndex;
m_pJoints.m_numRotationKeyframes = pJoint->m_numRotationKeyframes;
m_pJoints.m_pRotationKeyframes = new Keyframe[pJoint->m_numRotationKeyframes];
m_pJoints.m_numTranslationKeyframes = pJoint->m_numTranslationKeyframes;
m_pJoints.m_pTranslationKeyframes = new Keyframe[pJoint->m_numTranslationKeyframes];
for (j = 0; j < pJoint->m_numRotationKeyframes; j++)
{
MS3DKeyframe *pKeyframe = (MS3DKeyframe *)pPtr;
pPtr += sizeof(MS3DKeyframe);
SetJointKeyframe(i, j, pKeyframe->m_time*1000.0f, pKeyframe->m_parameter, true);
}
for (j = 0; j < pJoint->m_numTranslationKeyframes; j++)
{
MS3DKeyframe *pKeyframe = (MS3DKeyframe *)pPtr;
pPtr += sizeof(MS3DKeyframe);
SetJointKeyframe(i, j, pKeyframe->m_time*1000.0f, pKeyframe->m_parameter, false, scale);
}
}
delete[] pNameList;
}
delete []pBuffer;
GenerateVB();
ComputBoxAndSphere();
if (m_numJoints > 0)
{
SetupJoints();
Restart();
}
return true;
}
大家主意到上面代码里本人使用反转z坐标来实现右手到左手的转换,同时由于右手空间中旋转方向为逆时针方向,而dx空间为顺时针方向,旋转时也得反转。读者可以自己在草稿上笔画,一边是左手坐标变换,一边是右手坐标变换,左手中该怎么变换才能同右手效果一样。我讲的方法只是其中一种,只要是最终显示效果跟右手空间中显示效果一样,此方法即可。
void CMs3dMesh::SetJointKeyframe(int jointIndex, int keyframeIndex, float time, float *parameter, bool isRotation, float scale)
{
Keyframe& keyframe = isRotation ? m_pJoints[jointIndex].m_pRotationKeyframes[keyframeIndex] :
m_pJoints[jointIndex].m_pTranslationKeyframes[keyframeIndex];
keyframe.m_jointIndex = jointIndex;
keyframe.m_time = time;
::memcpy(keyframe.m_parameter, parameter, sizeof(float)*3);
//右手到左手
if (isRotation)
{
keyframe.m_parameter[0] = -keyframe.m_parameter[0];
keyframe.m_parameter[1] = -keyframe.m_parameter[1];
//
}
else
{
keyframe.m_parameter[2] = -keyframe.m_parameter[2];
keyframe.m_parameter[0] *= scale;
keyframe.m_parameter[1] *= scale;
keyframe.m_parameter[2] *= scale;
}
}
这里旋转的z值不要反转了,大家画图就可知道。^_^
缩放系数是我代码里的功能,读者都把其看作1即可。
void CMs3dMesh::SetupJoints()
{
unsigned short i;
for (i = 0; i < m_numJoints; i++)
{
Joint &joint = m_pJoints;
D3DXVECTOR3 vecRol(joint.m_localRotation[0], joint.m_localRotation[1], joint.m_localRotation[2]);
D3DXVECTOR3 vecPos(joint.m_localTranslation[0], joint.m_localTranslation[1], joint.m_localTranslation[2]);
D3DXMatrixFromRotXYZPos(&joint.m_relative, vecRol, vecPos);
if (joint.m_parent != -1)
{
joint.m_absolute = joint.m_relative*m_pJoints[joint.m_parent].m_absolute;
}
else
joint.m_absolute = joint.m_relative;
D3DXMatrixInverse(&m_pJoints.m_offset, NULL, &m_pJoints.m_absolute);
}
for (i = 0; i < m_numVertices; i++)
{
Vertex &vertex = m_pVertices;
if (vertex.m_boneID != -1)
{
const D3DXMATRIX &matrix = m_pJoints[vertex.m_boneID].m_offset;
D3DXVECTOR3 vec(m_pVertices.m_location[0], m_pVertices.m_location[1], m_pVertices.m_location[2]);
D3DXVec3TransformCoord(&vec, &vec, &matrix);
m_pVertices.m_location[0] = vec.x;
m_pVertices.m_location[1] = vec.y;
m_pVertices.m_location[2] = vec.z;
}
}
for (i = 0; i < m_numTriangles; i++)
{
Triangle &triangle = m_pTriangles;
for (int j = 0; j < 3; j++)
{
const Vertex &vertex = m_pVertices[triangle.m_vertexIndices[j]];
if (vertex.m_boneID != -1)
{
const D3DXMATRIX &matrix = m_pJoints[vertex.m_boneID].m_offset;
D3DXVECTOR3 vec(triangle.m_normal[j][0], triangle.m_normal[j][1], triangle.m_normal[j][2]);
D3DXVec3TransformNormal(&vec, &vec, &matrix);
D3DXVec3Normalize(&vec, &vec);
triangle.m_normal[j][0] = vec.x;
triangle.m_normal[j][1] = vec.y;
triangle.m_normal[j][2] = vec.z;
}
}
}
}
看到没,所有代码基本上都是参考自nehe的代码。^_^,感谢网络!
以下得坐标原点为整个模型得坐标原点。
这里右手坐标为数学坐标,dx与其相反。在数学坐标下矩阵相乘由右到左,dx下由左到右。
代码中的relative(相对)矩阵是每个joint自己的空间矩阵,你把每个joint看成是个模型。而absolute(绝对)矩阵则是每个joint到坐标原点的变换矩阵。对此矩阵进行求逆,得offset矩阵,对每个joint实行offset矩阵变换,每个节点就会回到坐标原点。这里上面方法调用完每个顶点都可以进行动画矩阵变换了。
void CMs3dMesh::UpdateAnimation(DWORD time, bool bLoop)
{
if (time > m_totalTime)
{
if (bLoop)
{
time %= m_totalTime;
if (time <= m_dwSpeed)
Restart();
}
else
time = m_totalTime;
}
for (int i = 0; i < m_numJoints; i++)
{
float transVec[3] = {0.0f, 0.0f, 0.0f};
D3DXMATRIX transform;
int frame;
Joint *pJoint = &m_pJoints;
if (pJoint->m_numRotationKeyframes == 0 && pJoint->m_numTranslationKeyframes == 0)
{
pJoint->m_final = pJoint->m_absolute;
continue;
}
frame = pJoint->m_currentTranslationKeyframe;
while (frame < pJoint->m_numTranslationKeyframes && pJoint->m_pTranslationKeyframes[frame].m_time < time)
{
frame++;
}
pJoint->m_currentTranslationKeyframe = frame;
if (frame == 0)
::memcpy(transVec, pJoint->m_pTranslationKeyframes[0].m_parameter, sizeof(float)*3);
else if (frame == pJoint->m_numTranslationKeyframes)
::memcpy(transVec, pJoint->m_pTranslationKeyframes[frame-1].m_parameter, sizeof(float)*3);
else
{
const Keyframe &curFrame = pJoint->m_pTranslationKeyframes[frame];
const Keyframe &prevFrame = pJoint->m_pTranslationKeyframes[frame-1];
float timeDelta = curFrame.m_time-prevFrame.m_time;
float interpValue = (float)((time-prevFrame.m_time)/timeDelta);
transVec[0] = prevFrame.m_parameter[0]+(curFrame.m_parameter[0]-prevFrame.m_parameter[0])*interpValue;
transVec[1] = prevFrame.m_parameter[1]+(curFrame.m_parameter[1]-prevFrame.m_parameter[1])*interpValue;
transVec[2] = prevFrame.m_parameter[2]+(curFrame.m_parameter[2]-prevFrame.m_parameter[2])*interpValue;
}
frame = pJoint->m_currentRotationKeyframe;
while (frame < pJoint->m_numRotationKeyframes && pJoint->m_pRotationKeyframes[frame].m_time < time)
{
frame++;
}
pJoint->m_currentRotationKeyframe = frame;
float rotVec[3] = {0.0f, 0.0f, 0.0f};
if (frame == 0)
::memcpy(rotVec, pJoint->m_pRotationKeyframes[0].m_parameter, sizeof(float)*3);
else if (frame == pJoint->m_numRotationKeyframes)
::memcpy(rotVec, pJoint->m_pRotationKeyframes[frame-1].m_parameter, sizeof(float)*3);
else
{
const Keyframe& curFrame = pJoint->m_pRotationKeyframes[frame];
const Keyframe& prevFrame = pJoint->m_pRotationKeyframes[frame-1];
float timeDelta = curFrame.m_time-prevFrame.m_time;
float interpValue = (float)((time-prevFrame.m_time)/timeDelta);
rotVec[0] = prevFrame.m_parameter[0]+(curFrame.m_parameter[0]-prevFrame.m_parameter[0])*interpValue;
rotVec[1] = prevFrame.m_parameter[1]+(curFrame.m_parameter[1]-prevFrame.m_parameter[1])*interpValue;
rotVec[2] = prevFrame.m_parameter[2]+(curFrame.m_parameter[2]-prevFrame.m_parameter[2])*interpValue;
}
D3DXVECTOR3 vecRol(rotVec[0], rotVec[1], rotVec[2]);
D3DXVECTOR3 vecPos(transVec[0], transVec[1], transVec[2]);
D3DXMatrixFromRotXYZPos(&transform, vecRol, vecPos);
D3DXMATRIX relativeFinal = transform*pJoint->m_relative;
if (pJoint->m_parent == -1)
pJoint->m_final = relativeFinal;
else
{
pJoint->m_final = relativeFinal*m_pJoints[pJoint->m_parent].m_final;
}
}
}
每个节点变换完自己得动画后才再实现父节点得变换,pJoint->m_final = relativeFinal*m_pJoints[pJoint->m_parent].m_final。而自己得动画变化是在自己节点得空间矩阵下进行的,所以:relativeFinal = transform*pJoint->m_relative;
VOID D3DXMatrixFromRotXYZPos(
D3DXMATRIX *pOut,
D3DXVECTOR3 rot,
D3DXVECTOR3 pos)
{
D3DXMATRIX matRotX, matRotY, matRotZ;
D3DXMatrixRotationX(&matRotX, rot.x);
D3DXMatrixRotationY(&matRotY, rot.y);
D3DXMatrixRotationZ(&matRotZ, rot.z);
*pOut = matRotX;
*pOut *= matRotY;
*pOut *= matRotZ;
pOut->_41 = pos.x;
pOut->_42 = pos.y;
pOut->_43 = pos.z;
}
最后是更新:
void CMs3dMesh::Update(DWORD time, bool bLoop)
{
if (m_numJoints == 0)
return;
UpdateAnimation(time, bLoop);
UpdateVB();//更新vertex buffer
}
代码还存在许多可以优化得地方,如可用shader等,这方面网上有相关资料。矩阵旋转我也没用四元数,这个我也不怎么熟也不想用的复杂,简单得东西就行!
所有得代码本人还没整理好,但已经能很好得显示ms3d动画了。这点请大家放心!
由于时间仓促,有说错或改进得地方请大家多多指教,再次声明:这只是本人兴致所致,随便谈谈,只要对大家有点帮助得话本人就足矣!
以下图片为显示ms3d模型动画得效果