4.从milkshape3D导入场景
前两天突然就生病了。打完针输完液庆幸只是国产的感冒。看来要加强运动啦。这一节我们将会在上一节
的基础上,研究构建我们三维场景的更高效途径。首先,让我们回归一下之前我们都完成了哪些:
1.建立了一个三维场景
2.用自定义文件及数据格式的方式构建了我们需要的简单对象(立方体,贝塞尔曲面)
3.在以上的建模及编程中,我们用到了诸如显示列表的一些东西
这一节中,我们将研究怎样将milkshape3D的模型场景加载到我们的程序中,这样,我们就可以在程序外部
编辑我们的场景,然后把它们加载进来。不过,如果是一些需要时刻改变的对象,比如我们的汽车,最好还是
单独加载并加以控制的代码。以下的代码都是基于QT+openGL实现的,有任何疑问可以给我留言,我会尽快回
复。
对于milkshape3D模型场景的导入,我们首先需要明白几个概念。
1.milkshape3D是什么。简单的说,它就是和大家熟知的3DMAX相似的一种3D建模软件,存储的文件格式为
xx.ms3d(xx代表文件名)。我们在实际的文件导入中,需要将它以2进制的形式打开,并将里面的信息存放进
我们事先定义好的结构体中
2.需要哪些结构体。我们所用到的结构体都是用来存储ms3d文件信息的,它们包括下面列出的元素
MS3DHeader /*包含ms3d文件的版本信息*、
MS3DVertex /*顶点信息*/
MS3DMaterial /*材质(纹理贴图等)信息*/
MS3DTriangle /*绘制三角形信息*/
MS3DJoint /*节点(骨骼)信息*/
MS3DKeyframe /*关键窗口*/
其中,MS3DJoint和MS3DKeyframe我们这一节中用不到,不过我们还是给出他们的定义。
下面的结构体是存放上面结构体解析后的信息
Mesh /*网格*/
Material /*材质*/
Triangle /*三角形*/
Vertex /*定点*/
关于3D建模的知识,很抱歉,我只是一个门外汉,所以无法跟大家解释我们的三维图形是如何一步一步画出来
的,不过我知道他们的步骤,并按部就班的把信息解析出来,然后再画到我们的openGL中。
3.特别强调。关于以上结构体的定义和我们的实际编程,我们在解析2进制文件的时候需要用到字节对齐。不
清楚的朋友请自行补上,我们这个用的是1字节对齐,即有如下形式:
# pragma pack( 1 )
# define PACK_STRUCT
PACK_STRUCT为我们结构体的别名
对于QT+linux的环境,我们需要作两个变量定义
typedef unsigned char byte;
typedef unsigned short word;
明白了这些概念后,就是按部就班的解析并存储信息了
1.loadModelData
bool scene::loadModelData( const char *filename )
{
ifstream inputFile( filename, ios::in | ios::binary);
if(inputFile.fail())
return false;
//以二进制的方式打开文件,如果失败则返回
inputFile.seekg( 0, ios::end );
long fileSize = inputFile.tellg();
inputFile.seekg( 0, ios::beg );
byte *pBuffer = new byte[fileSize];
inputFile.read( (char*)pBuffer, fileSize );
inputFile.close();
//分配一个内存,载入文件,并关闭文件
const byte *pPtr = pBuffer;
MS3DHeader *pHeader = ( MS3DHeader* )pPtr;
pPtr += sizeof( MS3DHeader );
if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 )
{ printf("不是一个有效的MS3D文件/n");
return false; // 如果不是一个有效的MS3D文件则返回
}
if ( (pHeader->m_version < 3 )|| (pHeader->m_version > 4 ))
{
printf("不能支持这种版本的文件,%d/n",pHeader->m_version);
return false; // 如果不能支持这种版本的文件,则返回失败
}
printf("支持这种版本的文件,Version=%d/n",pHeader->m_version);
//上面的文件读取文件头
int nVertices = *( word* )pPtr;
m_numVertices = nVertices;
m_pVertices = new Vertex[nVertices];
pPtr += sizeof( word );
int i;
for ( i = 0; i < nVertices; i++ )
{
MS3DVertex *pVertex = ( MS3DVertex* )pPtr;
m_pVertices[i].m_boneID = pVertex->m_boneID;
memcpy( m_pVertices[i].m_location, pVertex->m_vertex, sizeof( float )*3 );
pPtr += sizeof( MS3DVertex );
}
//上面的代码读取顶点数据
int nTriangles = *( word* )pPtr;
m_numTriangles = nTriangles;
m_pTriangles = new Triangle[nTriangles];
pPtr += sizeof( word );
for ( i = 0; i < nTriangles; i++ )
{
MS3DTriangle *pTriangle = ( MS3DTriangle* )pPtr;
int vertexIndices[3] = { pTriangle->m_vertexIndices[0], pTriangle-
>m_vertexIndices[1], pTriangle->m_vertexIndices[2] };
float t[3] = { 1.0f-pTriangle->m_t[0], 1.0f-pTriangle->m_t[1], 1.0f-pTriangle-
>m_t[2] };
memcpy( m_pTriangles[i].m_vertexNormals, pTriangle->m_vertexNormals, sizeof(
float )*3*3 );
memcpy( m_pTriangles[i].m_s, pTriangle->m_s, sizeof( float )*3 );
memcpy( m_pTriangles[i].m_t, t, sizeof( float )*3 );
memcpy( m_pTriangles[i].m_vertexIndices, vertexIndices, sizeof( int )*3 );
pPtr += sizeof( MS3DTriangle );
}
//上面的代码用来读取三角形信息,因为MS3D使用窗口坐标系而OpenGL使用笛卡儿坐标系,所以需要反转每
个顶点Y方向的纹理坐标
int nGroups = *( word* )pPtr;
m_numMeshes = nGroups;
m_pMeshes = new Mesh[nGroups];
pPtr += sizeof( word );
for ( i = 0; i < nGroups; i++ )
{
pPtr += sizeof( byte );
pPtr += 32;
word nTriangles = *( word* )pPtr;
pPtr += sizeof( word );
int *pTriangleIndices = new int[nTriangles];
for ( int j = 0; j < nTriangles; j++ )
{
pTriangleIndices[j] = *( word* )pPtr;
pPtr += sizeof( word );
}
char materialIndex = *( char* )pPtr;
pPtr += sizeof( char );
m_pMeshes[i].m_materialIndex = materialIndex;
m_pMeshes[i].m_numTriangles = nTriangles;
m_pMeshes[i].m_pTriangleIndices = pTriangleIndices;
}
//上面的代码填充网格结构
int nMaterials = *( word* )pPtr;
m_numMaterials = nMaterials;
m_pMaterials = new Material[nMaterials];
pPtr += sizeof( word );
for ( i = 0; i < nMaterials; i++ )
{
MS3DMaterial *pMaterial = ( MS3DMaterial* )pPtr;
memcpy( m_pMaterials[i].m_ambient, pMaterial->m_ambient, sizeof( float )*4 );
memcpy( m_pMaterials[i].m_diffuse, pMaterial->m_diffuse, sizeof( float )*4 );
memcpy( m_pMaterials[i].m_specular, pMaterial->m_specular, sizeof( float )*4 );
memcpy( m_pMaterials[i].m_emissive, pMaterial->m_emissive, sizeof( float )*4 );
m_pMaterials[i].m_shininess = pMaterial->m_shininess;
m_pMaterials[i].m_pTextureFilename = new char[strlen( pMaterial->m_texture )+1];
strcpy( m_pMaterials[i].m_pTextureFilename, pMaterial->m_texture );
pPtr += sizeof( MS3DMaterial );
}
reloadTextures();
// 上面的代码加载纹理数据
delete[] pBuffer;
return true;
}
2.drawModel
void scene::drawModel()
{
glLoadIdentity();
glTranslatef( 0.0, 0.0, -4.0 );
//gluLookAt( 75, 75, 75, 0, 0, 0, 0, 1, 0 ); // (3) Eye Postion (3) Center Point (3) Y-Axis
Up Vector
// 按网格分组绘制
for ( int i = 0; i < m_numMeshes; i++ )
{
int materialIndex = m_pMeshes[i].m_materialIndex;
if ( materialIndex >= 0 )
{
printf("materialIndex > 0/n");
glMaterialfv( GL_FRONT, GL_AMBIENT, m_pMaterials
[materialIndex].m_ambient );
glMaterialfv( GL_FRONT, GL_DIFFUSE, m_pMaterials
[materialIndex].m_diffuse );
glMaterialfv( GL_FRONT, GL_SPECULAR, m_pMaterials
[materialIndex].m_specular );
glMaterialfv( GL_FRONT, GL_EMISSION, m_pMaterials
[materialIndex].m_emissive );
glMaterialf( GL_FRONT, GL_SHININESS, m_pMaterials
[materialIndex].m_shininess );
if ( m_pMaterials[materialIndex].m_texture > 0 )
{
printf("m_pMaterials[materialIndex].m_texture > 0/n");
glBindTexture( GL_TEXTURE_2D, m_pMaterials
[materialIndex].m_texture );
glEnable( GL_TEXTURE_2D );
}
else
glEnable( GL_TEXTURE_2D );//disable instead
}
else
{
glEnable( GL_TEXTURE_2D );//disable instead
}
glBegin( GL_TRIANGLES );
{
for ( int j = 0; j < m_pMeshes[i].m_numTriangles; j++ )
{
int triangleIndex = m_pMeshes[i].m_pTriangleIndices[j];
const Triangle* pTri = &m_pTriangles[triangleIndex];
for ( int k = 0; k < 3; k++ )
{
int index = pTri->m_vertexIndices[k];
glNormal3fv( pTri->m_vertexNormals[k] );
glTexCoord2f( pTri->m_s[k], pTri->m_t[k] );
glVertex3fv( m_pVertices[index].m_location );
}
}
}
glEnd();
}
glEnable( GL_TEXTURE_2D );
}
上面的代码我并没有将纹理贴图部分加上,添加的方法如果你阅读过前几节应该不难做到。整个过程中最容易
出问题的地方可能在模型加载成功后的不可见上。这里特别强调,milkshape3D中的坐标系统与QT+openGL里的
并不那么一致,所以可能你加载的模型场景太大所以显示不出来,或者整个屏幕都显示一种颜色。这里给出一
种简单的解决办法(P.S.很抱歉初学的我还没找到正规的解决办法):大家翻回到前几节可能会注意到,我们绘
制的图形的大小,即我们可是屏幕的大小在-2到+2之间,所以我们在加载milkshape3D模型前需要对其进行缩
放以适应我们的QT。当然,也希望对此有研究成果的朋友不吝赐教,告诉我该怎么办。我的邮箱地址是
chuckgao.cg@gmail.com ,欢迎大家跟我联系。