一、3ds导入工具

CLoad3DS.h和CLoad3DS.cpp (见文末)

二、使用方法

1、在你工程中加入头文件

#include "CLoad3DS.h"

2、在定义全局变量的地方加入以下代码

CLoad3DS *gothicLoader=new(CLoad3DS);  
t3DModel gothicModel;

float gothicTrans[10] = {   
    0, 0 , -30 ,     //表示在世界矩阵的位置  
       0.2 , 0.2 , 0.2 ,      //表示xyz放大倍数  
        0 , 0 , 0 , 0  //表示旋转  
};

这里要注意一个问题,若你需要封装代码,想在内里面定义t3DModel gothicModel;  一定要记得初始化其gothicModel.numOfMaterials =0和gothicModel.numOfObject=0。否则程序会报错。上面没有初始化,是因为是全局变量,C++里,全局变量是默认初始化的,也就是其成员默认初始化为0.

3、在opengl初始化的地方加入以下代码

gothicLoader->Import3DS(&gothicModel, "Data/3ds/GUTEMB_L.3DS");//导入模型,第二个参数是3ds文件的路径,

这里使用的是相对路径,你也可以使用绝对路径

4、在opengl绘制函数里加入如下代码

changeObject( gothicTrans );  
drawModel(gothicModel,true,false);

三、3ds模型制作注意事项

其实没什么注意事项,唯一要注意的是,一定要有贴图的原素材,如果用的3dmax的系统贴图素材,需要去安装目录

找出原始图片,共后面opengl导入时使用。

四、3ds导入工具解析

在CLoad3DS.h中存在一个宏    #define PICPATH "\\Data\\pic\\"     //定义了纹理资源的相对地址,即相对工程目录

的。结合下面三句代码,共同决定了从何处加载到纹理资源。

GetCurrentDirectory(MAX_PATH, szPath);              // Get Our Working Directory  获得工程的目录
strcat(szPath, PICPATH);                                        // Append "\" After The Working Directory
strcat(szPath, szPathName);                                  // Append The PathName

因此,若你使用的纹理资源不放在当前工程目录下的Data\pic文件夹下,会导致最终显示模型丢失纹理。此外,你也

可以根据你纹理资源位置,调整这两处的代码,也可以实现纹理资源的加载。

3ds导入工具模板

#ifndef _CLoad3DS_h_
#define _CLoad3DS_h_
#include #include #include #include #include #include #include #include #include #include #include #include #include //初始化OpenGL环境 #include #include //#include #include #pragma comment(lib,"opengl32.lib") #pragma comment(lib,"glu32.lib") //#pragma comment(lib,"glaux.lib") #define PICPATH "\\Data\\pic\\" //纹理资源的地址 // 基本块(Primary Chunk),位于文件的开始 #define PRIMARY 0x4D4D // 主块(Main Chunks) #define OBJECTINFO 0x3D3D // 网格对象的版本号 #define VERSION 0x0002 // .3ds文件的版本 #define EDITKEYFRAME 0xB000 // 所有关键帧信息的头部 // 对象的次级定义(包括对象的材质和对象) #define MATERIAL 0xAFFF // 保存纹理信息 #define OBJECT 0x4000 // 保存对象的面、顶点等信息 // 材质的次级定义 #define MATNAME 0xA000 // 保存材质名称 #define MATDIFFUSE 0xA020 // 对象/材质的颜色 #define MATMAP 0xA200 // 新材质的头部 #define MATMAPFILE 0xA300 // 保存纹理的文件名 #define OBJECT_MESH 0x4100 // 新的网格对象 // OBJECT_MESH的次级定义 #define OBJECT_VERTICES 0x4110 // 对象顶点 #define OBJECT_FACES 0x4120 // 对象的面 #define OBJECT_MATERIAL 0x4130 // 对象的材质 #define OBJECT_UV 0x4140 // 对象的UV纹理坐标 // 下面的宏定义计算一个矢量的长度 #define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z)) #define MAX_TEXTURES 100 // 最大的纹理数目 using namespace std; class NBVector3 { public: NBVector3() {} NBVector3(float X, float Y, float Z) { x = X; y = Y; z = Z; } inline NBVector3 operator+(NBVector3 vVector) { return NBVector3(vVector.x + x, vVector.y + y, vVector.z + z); } inline NBVector3 operator-(NBVector3 vVector) { return NBVector3(x - vVector.x, y - vVector.y, z - vVector.z); } inline NBVector3 operator-() { return NBVector3(-x, -y, -z); } inline NBVector3 operator*(float num) { return NBVector3(x * num, y * num, z * num); } inline NBVector3 operator/(float num) { return NBVector3(x / num, y / num, z / num); } inline NBVector3 operator^(const NBVector3 &rhs) const { return NBVector3(y * rhs.z - rhs.y * z, rhs.x * z - x * rhs.z, x * rhs.y - rhs.x * y); } union { struct { float x; float y; float z; }; float v[3]; }; }; // 定义2D点类,用于保存模型的UV纹理坐标 class CVector2 { public: float x, y; }; // 面的结构定义 struct tFace { int vertIndex[3]; // 顶点索引 int coordIndex[3]; // 纹理坐标索引 }; // 材质信息结构体 struct tMaterialInfo { char strName[255]; // 纹理名称 char strFile[255]; // 如果存在纹理映射,则表示纹理文件名称 BYTE color[3]; // 对象的RGB颜色 int texureId; // 纹理ID float uTile; // u 重复 float vTile; // v 重复 float uOffset; // u 纹理偏移 float vOffset; // v 纹理偏移 } ; // 对象信息结构体 struct t3DObject { int numOfVerts; // 模型中顶点的数目 int numOfFaces; // 模型中面的数目 int numTexVertex; // 模型中纹理坐标的数目 int materialID; // 纹理ID bool bHasTexture; // 是否具有纹理映射 char strName[255]; // 对象的名称 NBVector3 *pVerts; // 对象的顶点 NBVector3 *pNormals; // 对象的法向量 CVector2 *pTexVerts; // 纹理UV坐标 tFace *pFaces; // 对象的面信息 }; // 模型信息结构体 struct t3DModel { UINT texture[MAX_TEXTURES]; int numOfObjects; // 模型中对象的数目 int numOfMaterials; // 模型中材质的数目 vectorpMaterials; // 材质链表信息 vectorpObject; // 模型中对象链表信息 }; struct tIndices { unsigned short a, b, c, bVisible; }; // 保存块信息的结构 struct tChunk { unsigned short int ID; // 块的ID unsigned int length; // 块的长度 unsigned int bytesRead; // 需要读的块数据的字节数 }; typedef struct tagBoundingBoxStruct { NBVector3 BoxPosMaxVertex; NBVector3 BoxNegMaxVertex; } BoundingBoxVertex2; // 下面的函数求两点决定的矢量 NBVector3 Vector(NBVector3 vPoint1, NBVector3 vPoint2); // 下面的函数两个矢量相加 NBVector3 AddVector(NBVector3 vVector1, NBVector3 vVector2); // 下面的函数处理矢量的缩放 NBVector3 DivideVectorByScaler(NBVector3 vVector1, float Scaler); // 下面的函数返回两个矢量的叉积 NBVector3 Cross(NBVector3 vVector1, NBVector3 vVector2); // 下面的函数规范化矢量 NBVector3 Normalize(NBVector3 vNormal); void DrawModel(t3DModel& Model,bool touming=false); // #define FRAND (((float)rand()-(float)rand())/RAND_MAX) #define Clamp(x, min, max) x = (xRelease(); // Decrements IPicture Reference Count return FALSE; // Return False (Failure) } glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim); // Get Maximum Texture Size Supported pPicture->get_Width(&lWidth); // Get IPicture Width (Convert To Pixels) lWidthPixels = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540); pPicture->get_Height(&lHeight); // Get IPicture Height (Convert To Pixels) lHeightPixels = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540); // Resize Image To Closest Power Of Two if (lWidthPixels <= glMaxTexDim) // Is Image Width Less Than Or Equal To Cards Limit lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f); else // Otherwise Set Width To "Max Power Of Two" That The Card Can Handle lWidthPixels = glMaxTexDim; if (lHeightPixels <= glMaxTexDim) // Is Image Height Greater Than Cards Limit lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f); else // Otherwise Set Height To "Max Power Of Two" That The Card Can Handle lHeightPixels = glMaxTexDim; // Create A Temporary Bitmap BITMAPINFO bi = {0}; // The Type Of Bitmap We Request DWORD *pBits = 0; // Pointer To The Bitmap Bits bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); // Set Structure Size bi.bmiHeader.biBitCount = 32; // 32 Bit bi.bmiHeader.biWidth = lWidthPixels; // Power Of Two Width bi.bmiHeader.biHeight = lHeightPixels; // Make Image Top Up (Positive Y-Axis) bi.bmiHeader.biCompression = BI_RGB; // RGB Encoding bi.bmiHeader.biPlanes = 1; // 1 Bitplane // Creating A Bitmap This Way Allows Us To Specify Color Depth And Gives Us Imediate Access To The Bits hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0); if(!hbmpTemp) // Did Creation Fail? { DeleteDC(hdcTemp); // Delete The Device Context pPicture->Release(); // Decrements IPicture Reference Count return FALSE; // Return False (Failure) } SelectObject(hdcTemp, hbmpTemp); // Select Handle To Our Temp DC And Our Temp Bitmap Object // Render The IPicture On To The Bitmap pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0); // Convert From BGR To RGB Format And Add An Alpha Value Of 255 for(long i = 0; i < lWidthPixels * lHeightPixels; i++) // Loop Through All Of The Pixels { BYTE* pPixel = (BYTE*)(&pBits[i]); // Grab The Current Pixel BYTE temp = pPixel[0]; // Store 1st Color In Temp Variable (Blue) pPixel[0] = pPixel[2]; // Move Red Value To Correct Position (1st) pPixel[2] = temp; // Move Temp Value To Correct Blue Position (3rd) // This Will Make Any Black Pixels, Completely Transparent (You Can Hardcode The Value If You Wish) if ((pPixel[0]==0) && (pPixel[1]==0) && (pPixel[2]==0)) // Is Pixel Completely Black pPixel[3] = 0; // Set The Alpha Value To 0 else // Otherwise pPixel[3] = 255; // Set The Alpha Value To 255 } glGenTextures(1, &texid); // Create The Texture // Typical Texture Generation Using Data From The Bitmap glBindTexture(GL_TEXTURE_2D, texid); // Bind To The Texture ID glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want) glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // (Modify This For The Type Of Filtering You Want) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits); // (Modify This If You Want Mipmaps) DeleteObject(hbmpTemp); // Delete The Object DeleteDC(hdcTemp); // Delete The Device Context pPicture->Release(); // Decrements IPicture Reference Count printf( "load %s!" , szPath ); return TRUE; // Return True (All Good) } // 构造函数的功能是初始化tChunk数据 CLoad3DS::CLoad3DS() { m_CurrentChunk = new tChunk; // 初始化并为当前的块分配空间 m_TempChunk = new tChunk; // 初始化一个临时块并分配空间 } // 打开一个3ds文件,读出其中的内容,并释放内存 bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName) { char strMessage[255] = {0}; // 打开一个3ds文件 m_FilePointer = fopen(strFileName, "rb"); // 确保所获得的文件指针合法 if(!m_FilePointer) { sprintf(strMessage, "Unable to find the file: %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // 当文件打开之后,首先应该将文件最开始的数据块读出以判断是否是一个3ds文件 // 如果是3ds文件的话,第一个块ID应该是PRIMARY // 将文件的第一块读出并判断是否是3ds文件 ReadChunk(m_CurrentChunk); // 确保是3ds文件 if (m_CurrentChunk->ID != PRIMARY) { sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName); MessageBox(NULL, strMessage, "Error", MB_OK); return false; } // 现在开始读入数据,ProcessNextChunk()是一个递归函数 // 通过调用下面的递归函数,将对象读出 ProcessNextChunk(pModel, m_CurrentChunk); // 在读完整个3ds文件之后,计算顶点的法线 ComputeNormals(pModel); // 释放内存空间 CleanUp(); return true; } // 下面的函数释放所有的内存空间,并关闭文件 void CLoad3DS::CleanUp() { fclose(m_FilePointer); // 关闭当前的文件指针 delete m_CurrentChunk; // 释放当前块 delete m_TempChunk; // 释放临时块 } // 下面的函数读出3ds文件的主要部分 void CLoad3DS::ProcessNextChunk(t3DModel *pModel, tChunk *pPreviousChunk) { t3DObject newObject = {0}; // 用来添加到对象链表 tMaterialInfo newTexture = {0}; // 用来添加到材质链表 unsigned int version = 0; // 保存文件版本 int buffer[50000] = {0}; // 用来跳过不需要的数据 m_CurrentChunk = new tChunk; // 为新的块分配空间 // 下面每读一个新块,都要判断一下块的ID,如果该块是需要的读入的,则继续进行 // 如果是不需要读入的块,则略过 // 继续读入子块,直到达到预定的长度 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 读入下一个块 ReadChunk(m_CurrentChunk); // 判断块的ID号 switch (m_CurrentChunk->ID) { case VERSION: // 文件版本号 // 在该块中有一个无符号短整型数保存了文件的版本 // 读入文件的版本号,并将字节数添加到bytesRead变量中 m_CurrentChunk->bytesRead += fread(&version, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); // 如果文件版本号大于3,给出一个警告信息 if (version > 0x03) MessageBox(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK); break; case OBJECTINFO: // 网格版本信息 // 读入下一个块 ReadChunk(m_TempChunk); // 获得网格的版本号 m_TempChunk->bytesRead += fread(&version, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); // 增加读入的字节数 m_CurrentChunk->bytesRead += m_TempChunk->bytesRead; // 进入下一个块 ProcessNextChunk(pModel, m_CurrentChunk); break; case MATERIAL: // 材质信息 // 材质的数目递增 pModel->numOfMaterials++; // 在纹理链表中添加一个空白纹理结构 pModel->pMaterials.push_back(newTexture); // 进入材质装入函数 ProcessNextMaterialChunk(pModel, m_CurrentChunk); break; case OBJECT: // 对象的名称 // 该块是对象信息块的头部,保存了对象了名称 // 对象数递增 pModel->numOfObjects++; // 添加一个新的tObject节点到对象链表中 pModel->pObject.push_back(newObject); // 初始化对象和它的所有数据成员 memset(&(pModel->pObject[pModel->numOfObjects - 1]), 0, sizeof(t3DObject)); // 获得并保存对象的名称,然后增加读入的字节数 m_CurrentChunk->bytesRead += GetString(pModel->pObject[pModel->numOfObjects - 1].strName); // 进入其余的对象信息的读入 ProcessNextObjectChunk(pModel, &(pModel->pObject[pModel->numOfObjects - 1]), m_CurrentChunk); break; case EDITKEYFRAME: // 跳过关键帧块的读入,增加需要读入的字节数 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; default: // 跳过所有忽略的块的内容的读入,增加需要读入的字节数 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 增加从最后块读入的字节数 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 释放当前块的内存空间 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面的函数处理所有的文件中对象的信息 void CLoad3DS::ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk) { int buffer[50000] = {0}; // 用于读入不需要的数据 // 对新的块分配存储空间 m_CurrentChunk = new tChunk; // 继续读入块的内容直至本子块结束 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 读入下一个块 ReadChunk(m_CurrentChunk); // 区别读入是哪种块 switch (m_CurrentChunk->ID) { case OBJECT_MESH: // 正读入的是一个新块 // 使用递归函数调用,处理该新块 ProcessNextObjectChunk(pModel, pObject, m_CurrentChunk); break; case OBJECT_VERTICES: // 读入是对象顶点 ReadVertices(pObject, m_CurrentChunk); break; case OBJECT_FACES: // 读入的是对象的面 ReadVertexIndices(pObject, m_CurrentChunk); break; case OBJECT_MATERIAL: // 读入的是对象的材质名称 // 该块保存了对象材质的名称,可能是一个颜色,也可能是一个纹理映射。同时在该块中也保存了 // 纹理对象所赋予的面 // 下面读入对象的材质名称 ReadObjectMaterial(pModel, pObject, m_CurrentChunk); break; case OBJECT_UV: // 读入对象的UV纹理坐标 // 读入对象的UV纹理坐标 ReadUVCoordinates(pObject, m_CurrentChunk); break; default: // 略过不需要读入的块 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 添加从最后块中读入的字节数到前面的读入的字节中 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 释放当前块的内存空间,并把当前块设置为前面块 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面的函数处理所有的材质信息 void CLoad3DS::ProcessNextMaterialChunk(t3DModel *pModel, tChunk *pPreviousChunk) { int buffer[50000] = {0}; // 用于读入不需要的数据 // 给当前块分配存储空间 m_CurrentChunk = new tChunk; // 继续读入这些块,知道该子块结束 while (pPreviousChunk->bytesRead < pPreviousChunk->length) { // 读入下一块 ReadChunk(m_CurrentChunk); // 判断读入的是什么块 switch (m_CurrentChunk->ID) { case MATNAME: // 材质的名称 // 读入材质的名称 m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strName, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; case MATDIFFUSE: // 对象的R G B颜色 ReadColorChunk(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk); break; case MATMAP: // 纹理信息的头部 // 进入下一个材质块信息 ProcessNextMaterialChunk(pModel, m_CurrentChunk); break; case MATMAPFILE: // 材质文件的名称 // 读入材质的文件名称 m_CurrentChunk->bytesRead += fread(pModel->pMaterials[pModel->numOfMaterials - 1].strFile, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; default: // 掠过不需要读入的块 m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer); break; } // 添加从最后块中读入的字节数 pPreviousChunk->bytesRead += m_CurrentChunk->bytesRead; } // 删除当前块,并将当前块设置为前面的块 delete m_CurrentChunk; m_CurrentChunk = pPreviousChunk; } // 下面函数读入块的ID号和它的字节长度 void CLoad3DS::ReadChunk(tChunk *pChunk) { // 读入块的ID号,占用了2个字节。块的ID号象OBJECT或MATERIAL一样,说明了在块中所包含的内容 pChunk->bytesRead = fread(&pChunk->ID, 1, 2, m_FilePointer); // 然后读入块占用的长度,包含了四个字节 pChunk->bytesRead += fread(&pChunk->length, 1, 4, m_FilePointer); } // 下面的函数读入一个字符串 int CLoad3DS::GetString(char *pBuffer) { int index = 0; // 读入一个字节的数据 fread(pBuffer, 1, 1, m_FilePointer); // 直到结束 while (*(pBuffer + index++) != 0) { // 读入一个字符直到NULL fread(pBuffer + index, 1, 1, m_FilePointer); } // 返回字符串的长度 return strlen(pBuffer) + 1; } // 下面的函数读入RGB颜色 void CLoad3DS::ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk) { // 读入颜色块信息 ReadChunk(m_TempChunk); // 读入RGB颜色 m_TempChunk->bytesRead += fread(pMaterial->color, 1, m_TempChunk->length - m_TempChunk->bytesRead, m_FilePointer); // 增加读入的字节数 pChunk->bytesRead += m_TempChunk->bytesRead; } // 下面的函数读入顶点索引 void CLoad3DS::ReadVertexIndices(t3DObject *pObject, tChunk *pPreviousChunk) { unsigned short index = 0; // 用于读入当前面的索引 // 读入该对象中面的数目 pPreviousChunk->bytesRead += fread(&pObject->numOfFaces, 1, 2, m_FilePointer); // 分配所有面的存储空间,并初始化结构 pObject->pFaces = new tFace [pObject->numOfFaces]; memset(pObject->pFaces, 0, sizeof(tFace) * pObject->numOfFaces); // 遍历对象中所有的面 for(int i = 0; i < pObject->numOfFaces; i++) { for(int j = 0; j < 4; j++) { // 读入当前面的第一个点 pPreviousChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer); if(j < 3) { // 将索引保存在面的结构中 pObject->pFaces[i].vertIndex[j] = index; } } } } // 下面的函数读入对象的UV坐标 void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreviousChunk) { // 为了读入对象的UV坐标,首先需要读入UV坐标的数量,然后才读入具体的数据 // 读入UV坐标的数量 pPreviousChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer); // 分配保存UV坐标的内存空间 pObject->pTexVerts = new CVector2 [pObject->numTexVertex]; // 读入纹理坐标 pPreviousChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); } // 读入对象的顶点 void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreviousChunk) { // 在读入实际的顶点之前,首先必须确定需要读入多少个顶点。 // 读入顶点的数目 pPreviousChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer); // 分配顶点的存储空间,然后初始化结构体 pObject->pVerts = new NBVector3 [pObject->numOfVerts]; memset(pObject->pVerts, 0, sizeof(NBVector3) * pObject->numOfVerts); // 读入顶点序列 pPreviousChunk->bytesRead += fread(pObject->pVerts, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); // 现在已经读入了所有的顶点。 // 因为3D Studio Max的模型的Z轴是指向上的,因此需要将y轴和z轴翻转过来。 // 具体的做法是将Y轴和Z轴交换,然后将Z轴反向。 // 遍历所有的顶点 for(int i = 0; i < pObject->numOfVerts; i++) { // 保存Y轴的值 float fTempY = pObject->pVerts[i].y; // 设置Y轴的值等于Z轴的值 pObject->pVerts[i].y = pObject->pVerts[i].z; // 设置Z轴的值等于-Y轴的值 pObject->pVerts[i].z = -fTempY; } } // 下面的函数读入对象的材质名称 void CLoad3DS::ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk) { char strMaterial[255] = {0}; // 用来保存对象的材质名称 int buffer[50000] = {0}; // 用来读入不需要的数据 // 材质或者是颜色,或者是对象的纹理,也可能保存了象明亮度、发光度等信息。 // 下面读入赋予当前对象的材质名称 pPreviousChunk->bytesRead += GetString(strMaterial); // 遍历所有的纹理 for(int i = 0; i < pModel->numOfMaterials; i++) { //如果读入的纹理与当前的纹理名称匹配 if(strcmp(strMaterial, pModel->pMaterials[i].strName) == 0) { // 设置材质ID pObject->materialID = i; // 判断是否是纹理映射,如果strFile是一个长度大于1的字符串,则是纹理 if(strlen(pModel->pMaterials[i].strFile) > 0) { //载入纹理 BuildTexture(pModel->pMaterials[i].strFile, pModel->texture[pObject->materialID]); // 设置对象的纹理映射标志 pObject->bHasTexture = true; char strMessage[100]; sprintf(strMessage, "file name : %s!", pModel->pMaterials[i].strFile); printf( "%s\n" , strMessage ); // MessageBox(NULL, strMessage, "Error", MB_OK); } break; } else { // 如果该对象没有材质,则设置ID为-1 pObject->materialID = -1; } } pPreviousChunk->bytesRead += fread(buffer, 1, pPreviousChunk->length - pPreviousChunk->bytesRead, m_FilePointer); } // 下面的这些函数主要用来计算顶点的法向量,顶点的法向量主要用来计算光照 // 下面的函数用于计算对象的法向量 void CLoad3DS::ComputeNormals(t3DModel *pModel) { NBVector3 vVector1, vVector2, vNormal, vPoly[3]; // 如果模型中没有对象,则返回 if(pModel->numOfObjects <= 0) return; // 遍历模型中所有的对象 for(int index = 0; index < pModel->numOfObjects; index++) { // 获得当前的对象 t3DObject *pObject = &(pModel->pObject[index]); // 分配需要的存储空间 NBVector3 *pNormals = new NBVector3 [pObject->numOfFaces]; NBVector3 *pTempNormals = new NBVector3 [pObject->numOfFaces]; pObject->pNormals = new NBVector3 [pObject->numOfVerts]; int i=0; // 遍历对象的所有面 for(i=0; i < pObject->numOfFaces; i++) { vPoly[0] = pObject->pVerts[pObject->pFaces[i].vertIndex[0]]; vPoly[1] = pObject->pVerts[pObject->pFaces[i].vertIndex[1]]; vPoly[2] = pObject->pVerts[pObject->pFaces[i].vertIndex[2]]; // 计算面的法向量 vVector1 = Vector(vPoly[0], vPoly[2]); // 获得多边形的矢量 vVector2 = Vector(vPoly[2], vPoly[1]); // 获得多边形的第二个矢量 vNormal = Cross(vVector1, vVector2); // 获得两个矢量的叉积 pTempNormals[i] = vNormal; // 保存非规范化法向量 vNormal = Normalize(vNormal); // 规范化获得的叉积 pNormals[i] = vNormal; // 将法向量添加到法向量列表中 } // 下面求顶点法向量 NBVector3 vSum (0.0, 0.0, 0.0); NBVector3 vZero = vSum; int shared=0; // 遍历所有的顶点 for (i = 0; i < pObject->numOfVerts; i++) { for (int j = 0; j < pObject->numOfFaces; j++) // 遍历所有的三角形面 { // 判断该点是否与其它的面共享 if (pObject->pFaces[j].vertIndex[0] == i || pObject->pFaces[j].vertIndex[1] == i || pObject->pFaces[j].vertIndex[2] == i) { vSum = AddVector(vSum, pTempNormals[j]); shared++; } } pObject->pNormals[i] = DivideVectorByScaler(vSum, float(-shared)); // 规范化最后的顶点法向 pObject->pNormals[i] = Normalize(pObject->pNormals[i]); vSum = vZero; shared = 0; } // 释放存储空间,开始下一个对象 delete [] pTempNormals; delete [] pNormals; } } void changeObject(float trans[10]) { glTranslatef(trans[0],trans[1],trans[2]); glScalef(trans[3],trans[4],trans[5]); glRotatef(trans[6],trans[7],trans[8],trans[9]); } void drawModel(t3DModel Model,bool touming,bool outTex) { if( touming ){ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1,1,1,0.5); } // ±éàú?£Dí?D?ùóDμ????ó for(int i = 0; i < Model.numOfObjects; i++) { // ??μ?μ±?°??ê?μ????ó t3DObject *pObject = &Model.pObject[i]; // ?D???????óê?·?óD??àíó3é? if(!outTex) { if(pObject->bHasTexture) { // ′ò?a??àíó3é? glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, Model.texture[pObject->materialID]); } else { // 1?±???àíó3é? glDisable(GL_TEXTURE_2D); glColor3ub(255, 255, 255); } } // ?aê?ò?g_ViewMode?£ê????? glBegin(GL_TRIANGLES); // ±éàú?ùóDμ??? for(int j = 0; j < pObject->numOfFaces; j++) { // ±éàúèy??D?μ??ùóDμ? for(int whichVertex = 0; whichVertex < 3; whichVertex++) { // ??μ?????????μ?μ??÷òy int index = pObject->pFaces[j].vertIndex[whichVertex]; // ??3?·¨?òá? glNormal3f(pObject->pNormals[ index ].x, pObject->pNormals[ index ].y, pObject->pNormals[ index ].z); //è?1????ó??óD??àí if(pObject->bHasTexture) { // è·?¨ê?·?óDUVW??àí×?±ê if(pObject->pTexVerts) { glColor3f(1.0,1.0,1.0); glTexCoord2f(pObject->pTexVerts[ index ].x, pObject->pTexVerts[ index ].y); } } else{ if(Model.pMaterials.size() && pObject->materialID >= 0) { BYTE *pColor = Model.pMaterials[pObject->materialID].color; glColor3ub(pColor[0], pColor[1], pColor[2]); } } glVertex3f(pObject->pVerts[ index ].x, pObject->pVerts[ index ].y, pObject->pVerts[ index ].z); } } glEnd(); // ?????áê? } if( touming ) glDisable(GL_BLEND); }

opengl导入3DS文件(带纹理)之填坑相关推荐

  1. OpenGL导入3DS专题

    最近一直在研究如何将3D模型导入OpenGL,略微有些进展,在附录部分把它记录下,希望能起到抛砖引玉的作用,有不足的地方,请大家指出. 1. 简述 如果想把做场景做的复杂,导入3D模型是一个必不可少的 ...

  2. SketchUp导入3ds文件单位错位的问题

    在导入3ds文件时,选择文件对话框右侧点击选项,将比例选择与原三维场景中单位一致.

  3. [OpenGL]导入obj文件

    通常来说我们构建一个模型是比较复杂的工作,那么我们还有什么途径获得模型呢.其中一种方法就是导入obj模型,不要被这个名词给吓到了,其实就是把一个制作好的模型保存到一个文件中,我们称为obj文件. 先上 ...

  4. QT+OpenGL导入STL文件(二进制/ascll码格式),鼠标交互实现缩放旋转

    碎碎念 由于课设和大创涉及到了模型的旋转,因此专门去学习了模型的导入,也是废了不少心思,现在总结一下两种格式的简单导入,以及对stl模型两种格式的简单介绍.网上有很多大佬都有详细的解答,结尾附上链接: ...

  5. java导入csv文件时利弊,关于在自己写的应用上导入csv文件时踩过的坑

    1.因为字符串分割问题,一个疏漏导致Java java.lang.NumberFormatException: empty String: 2.涉及到文件编码问题而导致的乱码. image.png 首 ...

  6. VTK中导入并显示STL、3DS文件

    VTK(visualization toolkit)是一个开源的免费软件系统,主要用于三维计算机图形学.图像处理和科学计算可视化.VTK是在三维函数库OpenGL 的基础上采用面向对象的设计方法发展起 ...

  7. 关于.rdc文件导入Blender软件时失败报错的解决办法(从谷歌地图中获取带纹理的倾斜模型数据)

    文章目录 写在前面 一.如何获取谷歌地图中带纹理的倾斜模型数据 1.传送门 2.注意事项 二.遇到问题:导入.rdc文件后报错 三.解决办法:更换软件版本 1.版本选择 2.注意事项 四.写在最后 写 ...

  8. Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)

    参考:http://blog.csdn.net/cq361106306/article/details/41876541 效果: 源代码: 解释: CLoad3DS.h为加载3DMax模型的头文件,C ...

  9. openGL绘制带纹理地球,并实现鼠标键盘控制

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.绘制球体 二.关键代码 球体类(Sphere) 主函数 顶点着色器 片元着色器 显示效果 源码下载 参考 前言 openGL绘制 ...

  10. Ab3d.Reader3ds - 3ds 文件导入器 --FIX-Crack

    Ab3d.Reader3ds - 3ds 文件导入器 在几乎任何 3D 建模 应用程序中创建 3D 模型并在您的 WPF 应用程序中使用它们. Ab3d.Reader3ds - 3ds 文件导入器 A ...

最新文章

  1. OPM攻击事件后:我们从中学到了什么?
  2. fuel6.0安装部署
  3. DDD领域驱动设计理论篇 - 学习笔记
  4. CentOS 7.0安装配置Vsftp服务器
  5. 库克:到2030年苹果要实现整个供应链的碳中和
  6. 如何寻找灵感?让那些小众但富有创意的页面告诉你
  7. Android 反编译快手APP,gksvideourla
  8. Pwn2Own 2022迈阿密大赛落幕 去年春季赛冠军蝉联Master of Pwn
  9. Vue-cli使用prerender-spa-plugin插件预渲染和配置cdn
  10. 基于麻雀算法优化的核极限学习机(KELM)回归预测 -附代码
  11. 浙大计算机就业方向,浙大四大 王牌专业,毕业后工作待遇高,发展前景非常广阔...
  12. 解决警告Presenting view controllers on detached view controllers is discouraged 以及引申
  13. 手机Web前端调试页面之——Chrome DevTools(谷歌浏览器)的模拟手机调试
  14. 代理后域名及Https协议向后传递,后端Spring获取不到问题记录及分析
  15. python字符串操作符-python字符串操作
  16. 玩转Jetson Nano(一)烧写系统
  17. RMAN backup
  18. 1230v3配服务器内存性能提升,有点小失望 E3-1280 v3性能实测
  19. linux下使用百度网盘不限速下载文件超简超好用
  20. 顺丰丰桥接口之下订单接口,适用于第三方系统对接

热门文章

  1. 【我们一起写框架】MVVM的WPF框架(一)—序篇
  2. 用友T+改成IIS-网站报500.19错误代码0x8007000d问题解决
  3. 【Javascript】length属性
  4. Kotlin中三元运算符
  5. uniapp的苹果全屏播放再退出会导致页面字体变大解决方法
  6. 防止backspace键后退网页
  7. [PAT] PAT注意点
  8. 阴阳是世界的生成之理,亦是其发展之理。道生一,一生二,二生三,三生万物。...
  9. GCD中dispatch_group的使用方法
  10. MineCraft mod开发环境搭建