最近一直在試圖把魔獸3的mdx文件轉(zhuǎn)為Ogre Mesh,學(xué)習(xí)一下基礎(chǔ)的3D編程。Ogre Mesh的導(dǎo)出在很久之前也曾試圖做過,并且還把WOW的m2模型以及WMO模型導(dǎo)入到了Max中,但是只做到了骨架的導(dǎo)入,動畫數(shù)據(jù)始終出不來,于是放棄。
這次依然是碰到這里的問題,導(dǎo)出靜態(tài)的Mesh很快就完成,包括模型與材質(zhì),代碼也比較簡單。
// 模型數(shù)據(jù)
bool ModelLoaderMdx::loadGeosets(Ogre::MeshPtr model, MdxDataStreamPtr dataStream, int size)
{
unsigned int index = 0;
while(size > 0)
{
int geosetSize = dataStream->read<int>();
size -= geosetSize;
Ogre::String meshName = m_modelName + Ogre::String("_sub_") + Ogre::StringConverter::toString(index++);
Ogre::SubMesh* subMesh = model->createSubMesh(meshName);
// 硬件緩沖編號
// 分別為頂點坐標(biāo) 法線 貼圖坐標(biāo)
#define HARDWARE_BUFFER_SOURCE_VERTEX 0
#define HARDWARE_BUFFER_SOURCE_NORMAL 1
#define HARDWARE_BUFFER_SOURCE_TEXPOS 2
//
// 頂點數(shù)據(jù)
//
if(!expectTag(dataStream, 'VRTX'))
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Expect VRTX data for geoset",
"ModelLoaderMdx::loadGeosets");
}
unsigned int vertexCount = dataStream->read<unsigned int>();
// 不使用共享頂點數(shù)據(jù)
// 每個SubMesh都創(chuàng)建自己的VertexData
subMesh->useSharedVertices = false;
subMesh->vertexData = OGRE_NEW Ogre::VertexData();
subMesh->vertexData->vertexStart = 0;
subMesh->vertexData->vertexCount = vertexCount;
subMesh->vertexData->vertexDeclaration->addElement(
HARDWARE_BUFFER_SOURCE_VERTEX, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
size_t vertexSize = sizeof(float) * 3;
assert(subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_VERTEX) == vertexSize);
if (subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_VERTEX) != vertexSize)
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "VertexSize error",
"ModelLoaderMdx::loadGeoset");
}
Ogre::HardwareVertexBufferSharedPtr vertexBuffer;
vertexBuffer = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
vertexSize,
vertexCount,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
void* vertexBufferData = vertexBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
unsigned int vertexBufferDataPos = 0;
for (unsigned int i = 0; i < vertexCount; ++i)
{
Ogre::Vector3 data(
dataStream->read<float>(),
dataStream->read<float>(),
dataStream->read<float>()
);
transformCoord(data);
memcpy((char*)vertexBufferData + vertexBufferDataPos, &data, sizeof(Ogre::Vector3));
vertexBufferDataPos += sizeof(Ogre::Vector3);
}
vertexBuffer->unlock();
subMesh->vertexData->vertexBufferBinding->setBinding(HARDWARE_BUFFER_SOURCE_VERTEX, vertexBuffer);
//
// 法線數(shù)據(jù)
//
if(!expectTag(dataStream, 'NRMS'))
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Expect NRMS data for material",
"ModelLoaderMdx::loadGeosets");
}
unsigned int normalCount = dataStream->read<unsigned int>();
if(normalCount != vertexCount)
{
std::stringstream stream;
stream << "Normal count mismatch, " << normalCount << " normals for " << vertexCount << " vertices)!";
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, stream.str(),
"ModelLoaderMdx::loadGeoset");
}
subMesh->vertexData->vertexDeclaration->addElement(
HARDWARE_BUFFER_SOURCE_NORMAL, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL);
size_t normalSize = sizeof(float) * 3;
assert(subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_NORMAL) == normalSize);
if (subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_NORMAL) != normalSize)
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "NormalSize error",
"ModelLoaderMdx::loadGeoset");
}
Ogre::HardwareVertexBufferSharedPtr normalBuffer;
normalBuffer = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
normalSize,
normalCount,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
void* normalBufferData = normalBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
unsigned int normalBufferDataPos = 0;
for (unsigned int i = 0; i < normalCount; ++i)
{
Ogre::Vector3 data(
dataStream->read<float>(),
dataStream->read<float>(),
dataStream->read<float>()
);
transformCoord(data);
memcpy((char*)normalBufferData + normalBufferDataPos, &data, sizeof(Ogre::Vector3));
normalBufferDataPos += sizeof(Ogre::Vector3);
}
normalBuffer->unlock();
subMesh->vertexData->vertexBufferBinding->setBinding(HARDWARE_BUFFER_SOURCE_NORMAL, normalBuffer);
…………
//
// 頂點索引
//
if(!expectTag(dataStream, 'PVTX'))
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Expect PVTX data for geoset",
"ModelLoaderMdx::loadGeosets");
}
unsigned int indexCount = dataStream->read<unsigned int>();
assert(totalIndexCount == indexCount);
if (totalIndexCount != indexCount)
{
std::stringstream stream;
stream << "indexCount is " << indexCount << ", but totalIndexCount for all faces is " << totalIndexCount;
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, stream.str(),
"ModelLoaderMdx::loadGeoset");
}
subMesh->indexData->indexStart = 0;
subMesh->indexData->indexCount = indexCount;
Ogre::HardwareIndexBufferSharedPtr indexBuffer;
indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
Ogre::HardwareIndexBuffer::IT_16BIT,
indexCount,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
void* indexBufferData = indexBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
dataStream->read(indexBufferData, indexCount * sizeof(unsigned short));
// 三角形反轉(zhuǎn), 將原來的反面朝外
unsigned short* tmpData = (unsigned short*)indexBufferData;
for (unsigned int i = 0; i < indexCount; i += 3)
{
unsigned short tmp = tmpData[i + 1];
tmpData[i + 1] = tmpData[i + 2];
tmpData[i + 2] = tmp;
}
indexBuffer->unlock();
subMesh->indexData->indexBuffer = indexBuffer;
subMesh->operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST;
…………
// 材質(zhì)ID
unsigned int materialID = dataStream->read<unsigned int>();
Ogre::String materialName = m_modelName + Ogre::String("_") + boost::lexical_cast<Ogre::String>(materialID);
subMesh->setMaterialName(materialName);
…………
//
// 貼圖坐標(biāo)
//
if(!expectTag(dataStream, 'UVBS'))
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "Expect UVBS data for geoset",
"ModelLoaderMdx::loadGeosets");
}
unsigned int texturePositionCount = dataStream->read<unsigned int>();
if(texturePositionCount != vertexCount)
{
std::stringstream stream;
stream << "Texture position count mismatch, " << texturePositionCount << " texture positions for " << vertexCount << " vertices)!";
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, stream.str(),
"ModelLoaderMdx::loadGeoset");
}
// TextureCoord Data
subMesh->vertexData->vertexDeclaration->addElement(
HARDWARE_BUFFER_SOURCE_TEXPOS, 0, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
size_t texPosSize = sizeof(float) * 2;
assert(subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_TEXPOS) == texPosSize);
if (subMesh->vertexData->vertexDeclaration->getVertexSize(HARDWARE_BUFFER_SOURCE_TEXPOS) != texPosSize)
{
OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "TexturePositionSize error",
"ModelLoaderMdx::loadGeoset");
}
Ogre::HardwareVertexBufferSharedPtr texPosBuffer;
texPosBuffer = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
texPosSize,
texturePositionCount,
Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);
void* texPosBufferData = texPosBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);
dataStream->read(texPosBufferData, texPosSize * texturePositionCount);
texPosBuffer->unlock();
subMesh->vertexData->vertexBufferBinding->setBinding(HARDWARE_BUFFER_SOURCE_TEXPOS, texPosBuffer);
}
return true;
}
但是到了動畫數(shù)據(jù)這里問題又出來了,而且mdx模型與m2模型還有些差別,mdx模型中沒有骨骼數(shù)據(jù),只有max中最簡單的三種變換數(shù)據(jù),Ogre中只有MorphAnimation能夠?qū)崿F(xiàn)此種動畫。
Ogre的MorphAnimation的第一個KeyFrame必須帶有完整的頂點信息,而mdx模型中旋轉(zhuǎn)、縮放與位移是分開的,也就是一個KeyFrame上可能只有一種變換,或者多種。而實際上在max中制作動畫的時候這三種變換也是獨立開的,Ogre的論壇上找到一篇討論有人提到了這個問題,可惜制作者的回復(fù)是MorphAnimation只實現(xiàn)到這樣……
也確實,現(xiàn)在除了一些小物件的動畫外,主角,怪物的動畫都用skeleton了,也許MorphAnimation就快退出歷史的舞臺,在Ogre中看不到了,也不能指望會有什么改進。
繼續(xù)實現(xiàn)之,那就只能在有KeyFrame的地方把三種變換都計算一次,然后取得最終變換后的位置數(shù)據(jù),也就是做人工的動畫幀采樣。在War3EditorSource的基礎(chǔ)上做了些修改,終于,一幀幀的動畫計算出來了。
不過問題依然還有很多,比如mdx模型,尤其是怪物和角色模型中大量用到了ReplacableTexture,這些需要通過讀配置文件來獲取可用的貼圖,另外模型上附帶的粒子特效、紋理動畫等都還沒有導(dǎo)出,看看這個沒有貼圖的攻擊中的蝎子,前面的路仍然很遠。
