參考:http://blog.csdn.net/cq361106306/article/details/41876541
效果:




源代碼:
解釋:
CLoad3DS.h為加載3DMax模型的頭文件,CLoad3DS.cpp為加載3DMax模型的實現文件,
nehewidget.h為Qt下使用OpenGL頭文件,nehewidget.cpp為Qt下使用OpenGL實現文件。
注意:
1.3D模型和紋理圖片資源需要放在源代碼同一目錄下的Data目錄中,即/Data/3DS和/Data/pic下。
2.圖標和其他紋理圖片存放在Resources文件夾下。
CLoad3DS.h:
#ifndef _CLoad3DS_h_
#define _CLoad3DS_h_
#include <windows.h>
#include <cassert>
#include <cmath>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <vector>
#include <olectl.h>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <direct.h>
//初始化OpenGL環境
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glut.h>
#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.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; // 模型中材質的數目
vector<tMaterialInfo> pMaterials; // 材質鏈表信息
vector<t3DObject> pObject; // 模型中對象鏈表信息
};
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);
//////////////////////////////////////////////////////////////////////////
#define FRAND (((float)rand()-(float)rand())/RAND_MAX)
#define Clamp(x, min, max) x = (x<min ? min : x<max ? x : max);
#define SQUARE(x) (x)*(x)
struct vector3_t
{
vector3_t(float x, float y, float z) : x(x), y(y), z(z) {}
vector3_t(const vector3_t &v) : x(v.x), y(v.y), z(v.z) {}
vector3_t() : x(0.0f), y(0.0f), z(0.0f) {}
vector3_t& operator=(const vector3_t &rhs)
{
x = rhs.x;
y = rhs.y;
z = rhs.z;
return *this;
}
// vector add
vector3_t operator+(const vector3_t &rhs) const
{
return vector3_t(x + rhs.x, y + rhs.y, z + rhs.z);
}
// vector subtract
vector3_t operator-(const vector3_t &rhs) const
{
return vector3_t(x - rhs.x, y - rhs.y, z - rhs.z);
}
// scalar multiplication
vector3_t operator*(const float scalar) const
{
return vector3_t(x * scalar, y * scalar, z * scalar);
}
// dot product
float operator*(const vector3_t &rhs) const
{
return x * rhs.x + y * rhs.y + z * rhs.z;
}
// cross product
vector3_t operator^(const vector3_t &rhs) const
{
return vector3_t(y * rhs.z - rhs.y * z, rhs.x * z - x * rhs.z, x * rhs.y - rhs.x * y);
}
float& operator[](int index)
{
return v[index];
}
float Length()
{
float length = (float)sqrt(SQUARE(x) + SQUARE(y) + SQUARE(z));
return (length != 0.0f) ? length : 1.0f;
}
/*****************************************************************************
Normalize()
Helper function to normalize vectors
*****************************************************************************/
vector3_t Normalize()
{
*this = *this * (1.0f/Length());
return *this;
}
union
{
struct
{
float x;
float y;
float z;
};
float v[3];
};
};
// CLoad3DS類處理所有的裝入代碼
class CLoad3DS
{
public:
CLoad3DS(); // 初始化數據成員
// 裝入3ds文件到模型結構中
bool Import3DS(t3DModel *pModel, char *strFileName);
private:
// 讀入一個紋理
int BuildTexture(char *szPathName, GLuint &texid);
// 讀一個字符串
int GetString(char *);
// 讀下一個塊
void ReadChunk(tChunk *);
// 讀下一個塊
void ProcessNextChunk(t3DModel *pModel, tChunk *);
// 讀下一個對象塊
void ProcessNextObjectChunk(t3DModel *pModel, t3DObject *pObject, tChunk *);
// 讀下一個材質塊
void ProcessNextMaterialChunk(t3DModel *pModel, tChunk *);
// 讀對象顏色的RGB值
void ReadColorChunk(tMaterialInfo *pMaterial, tChunk *pChunk);
// 讀對象的頂點
void ReadVertices(t3DObject *pObject, tChunk *);
// 讀對象的面信息
void ReadVertexIndices(t3DObject *pObject, tChunk *);
// 讀對象的紋理坐標
void ReadUVCoordinates(t3DObject *pObject, tChunk *);
// 讀賦予對象的材質名稱
void ReadObjectMaterial(t3DModel *pModel, t3DObject *pObject, tChunk *pPreviousChunk);
// 計算對象頂點的法向量
void ComputeNormals(t3DModel *pModel);
// 關閉文件,釋放內存空間
void CleanUp();
// 文件指針
FILE *m_FilePointer;
tChunk *m_CurrentChunk;
tChunk *m_TempChunk;
};
void changeObject(float trans[10]);
void drawModel(t3DModel Model,bool touming,bool outTex);
#endif
CLoad3DS.cpp:
#include "CLoad3DS.h"
#pragma warning (disable: 4996)
// 下面的函數求兩點決定的矢量
NBVector3 Vector(NBVector3 vPoint1, NBVector3 vPoint2)
{
NBVector3 vVector;
vVector.x = vPoint1.x - vPoint2.x;
vVector.y = vPoint1.y - vPoint2.y;
vVector.z = vPoint1.z - vPoint2.z;
return vVector;
}
// 下面的函數兩個矢量相加
NBVector3 AddVector(NBVector3 vVector1, NBVector3 vVector2)
{
NBVector3 vResult;
vResult.x = vVector2.x + vVector1.x;
vResult.y = vVector2.y + vVector1.y;
vResult.z = vVector2.z + vVector1.z;
return vResult;
}
// 下面的函數處理矢量的縮放
NBVector3 DivideVectorByScaler(NBVector3 vVector1, float Scaler)
{
NBVector3 vResult;
vResult.x = vVector1.x / Scaler;
vResult.y = vVector1.y / Scaler;
vResult.z = vVector1.z / Scaler;
return vResult;
}
// 下面的函數返回兩個矢量的叉積
NBVector3 Cross(NBVector3 vVector1, NBVector3 vVector2)
{
NBVector3 vCross;
vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));
return vCross;
}
// 下面的函數歸一化矢量
NBVector3 Normalize(NBVector3 vNormal)
{
double Magnitude;
Magnitude = Mag(vNormal); // 獲得矢量的長度
vNormal.x /= (float)Magnitude;
vNormal.y /= (float)Magnitude;
vNormal.z /= (float)Magnitude;
return vNormal;
}
// 讀入一個紋理
int CLoad3DS::BuildTexture(char *szPathName, GLuint &texid)
{
HDC hdcTemp; // The DC To Hold Our Bitmap
HBITMAP hbmpTemp; // Holds The Bitmap Temporarily
LPPICTURE pPicture; // IPicture Interface,此處較參考網址的博主的代碼做了修改
OLECHAR wszPath[MAX_PATH+1]; // Full Path To Picture (WCHAR)
char szPath[MAX_PATH+1]; // Full Path To Picture
long lWidth; // Width In Logical Units
long lHeight; // Height In Logical Units
long lWidthPixels; // Width In Pixels
long lHeightPixels; // Height In Pixels
GLint glMaxTexDim ; // Holds Maximum Texture Size
if (strstr(szPathName, "http://")) // If PathName Contains http:// Then...
{
strcpy(szPath, szPathName); // Append The PathName To szPath
}
else // Otherwise... We Are Loading From A File
{
getcwd(szPath,MAX_PATH); // Get Our Working Directory,此處較參考網址的博主的代碼做了修改
strcat(szPath, PICPATH); // Append "\" After The Working Directory
strcat(szPath, szPathName); // Append The PathName
}
MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH); // Convert From ASCII To Unicode
HRESULT hr = OleLoadPicturePath(wszPath, 0, 0, 0, IID_IPicture, reinterpret_cast<LPVOID *>(&pPicture));//此處較參考網址的博主的代碼做了修改
if(FAILED(hr)) // If Loading Failed
return FALSE; // Return False
hdcTemp = CreateCompatibleDC(GetDC(0)); // Create The Windows Compatible Device Context
if(!hdcTemp) // Did Creation Fail?
{
pPicture->Release(); // 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 (All is okay)
return TRUE;
}
// 構造函數的功能是初始化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, (LPWSTR)strMessage, (LPWSTR)"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, (LPWSTR)strMessage, (LPWSTR)"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, (LPWSTR)"This 3DS file is over version 3 so it may load incorrectly", (LPWSTR)"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);
}
for(int i = 0; i < Model.numOfObjects; i++)
{
t3DObject *pObject = &Model.pObject[i];
if(!outTex)
{
if(pObject->bHasTexture)
{
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, Model.texture[pObject->materialID]);
}
else
{
glDisable(GL_TEXTURE_2D);
glColor3ub(255, 255, 255);
}
}
glBegin(GL_TRIANGLES);
for(int j = 0; j < pObject->numOfFaces; j++)
{
for(int whichVertex = 0; whichVertex < 3; whichVertex++)
{
int index = pObject->pFaces[j].vertIndex[whichVertex];
glNormal3f(pObject->pNormals[ index ].x, pObject->pNormals[ index ].y, pObject->pNormals[ index ].z);
if(pObject->bHasTexture)
{
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);
}
nehewidget.h:
#ifndef NEHEWIDGET_H
#define NEHEWIDGET_H
#include <QGLWidget>
#include <QKeyEvent>
#include <QTimer>
#include <QImage>
#include <QMessageBox>
#include <QIcon>
#include <QDebug>
#include <GL/glu.h>
#include <CLoad3DS.h>
class NeHeWidget : public QGLWidget
{
Q_OBJECT
public:
explicit NeHeWidget(QWidget *parent = 0);
~NeHeWidget();
protected:
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
void keyPressEvent(QKeyEvent *);
private:
bool fullscreen;
float xRot,yRot,zRot;
GLuint texture[3];
void initLight();
void drawObject();
void init3DMAX();
signals:
public slots:
};
#endif // NEHEWIDGET_H
nehewidget.cpp:
#include "nehewidget.h"
GLfloat lightAmbient[4] = { 0.5, 0.5, 0.5, 1.0 };
GLfloat lightDiffuse[4] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat lightPosition[4] = { 0.0, 0.0, 2.0, 1.0 };
CLoad3DS *gothicLoader=new CLoad3DS; t3DModel gothicModel;
NeHeWidget::NeHeWidget(QWidget *parent) :
QGLWidget(parent)
{
this->setWindowTitle(tr("OpenGL加載3DS模型"));
this->setWindowIcon(QIcon(":/Textures/Resources/Desert.jpg"));
resize(640,480);
xRot = 0.0;
zRot = 0.0;
yRot = 0.0;
}
NeHeWidget::~NeHeWidget()
{
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:08
// 權 限: protected
// 返 回: void
// 方法說明: OpenGL初始化
//*************************************
void NeHeWidget::initializeGL()
{
//使用陰影平滑
glShadeModel(GL_SMOOTH);
//黑色清屏r,g,b,alpha
glClearColor(0.0,0.0,0.0,0.0);
//設置深度緩存
glClearDepth(1.0);
//啟用深度緩存
glEnable(GL_DEPTH_TEST);
//啟用深度測試的類型
glDepthFunc(GL_LEQUAL);
//使用透視
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
//初始化燈光
initLight();
//初始化3D模型 init3DMAX();
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:07
// 權 限: protected
// 返 回: void
// 方法說明: 窗口大小變化
//*************************************
void NeHeWidget::resizeGL(int w, int h)
{
if(h == 0)
{
h = 1;
} // Far
//重置當前視口
glViewport(0,0,(GLint)w,(GLint)h);
//選擇投影矩陣
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 100.0);//建立透視投影矩陣
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:17
// 權 限: protected
// 返 回: void
// 方法說明: 繪制
//*************************************
void NeHeWidget::paintGL()
{
//清楚屏幕顏色和深度
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawObject();
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:06
// 權 限: private
// 返 回: void
// 方法說明: 繪制物體
//*************************************
void NeHeWidget::drawObject()
{
glLoadIdentity();
glTranslatef(0.0,0.0,-30.0);
glRotatef(xRot,1.0,0.0,0.0);
glRotatef(yRot,0.0,1.0,0.0);
glTranslatef(0.0,-3.0,0.0);
glScaled(0.25,0.25,0.25);
drawModel(gothicModel,true,false);
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:10
// 權 限: protected
// 返 回: void
// 方法說明: 鼠標響應事件
//*************************************
void NeHeWidget::keyPressEvent(QKeyEvent * e)
{
switch(e->key())
{
case Qt::Key_F2:
fullscreen = !fullscreen;
if(fullscreen)
{
showFullScreen();
}
else
{
showNormal();
resize(640,480);
}
updateGL();
break;
case Qt::Key_Escape:
close();
break;
case Qt::Key_Up:
xRot+= 5.0;
if (xRot>=360)
{
xRot=0;
}
updateGL();
break;
case Qt::Key_Down:
xRot -= 5.0;
if (xRot<=0)
{
xRot=360;
}
updateGL();
break;
case Qt::Key_Right:
yRot += 5.0;
if (yRot>=360)
{
yRot=0;
}
updateGL();
break;
case Qt::Key_Left:
yRot -= 5.0;
if (yRot<=0)
{
yRot=360;
}
updateGL();
break;
//啟用燈光
case Qt::Key_L:
{
static bool choose = false;
if(choose)
{
glDisable(GL_LIGHTING);
choose = false;
}
else
{
glEnable(GL_LIGHTING);
choose = true;
}
updateGL();
}
break;
//啟用混合
case Qt::Key_B:
{
static bool blend = !blend;
if ( blend )
{
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
blend = false;
}
else
{
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
blend = true;
}
updateGL();
}
break;
}
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 15:07
// 權 限: private
// 返 回: void
// 方法說明: 初始化燈光
//*************************************
void NeHeWidget::initLight()
{
glLightfv( GL_LIGHT1, GL_AMBIENT, lightAmbient );
glLightfv( GL_LIGHT1, GL_DIFFUSE, lightDiffuse );
glLightfv( GL_LIGHT1, GL_POSITION, lightPosition );
glEnable( GL_LIGHT1 );
}
//*************************************
// 作 者: 朱興宇
// 時 間: 2015/4/16 22:00
// 權 限: private
// 返 回: void
// 方法說明: 初始化3DMAX導入模型
//*************************************
void NeHeWidget::init3DMAX() { //導入模型 模型的文件夾盡量這樣設置 //然后模型貼圖 裝在Data/3DS里面,一定要跟前面截圖的文件夾名字一樣,想改得去CLoad3DS文件里面改 gothicLoader->Import3DS(&gothicModel, "Data/3DS/GUTEMB_L.3DS"); }
main.cpp:
#include "nehewidget.h"
#include <QTextCodec>
#include <QtGui/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
{
QTextCodec *codec = QTextCodec::codecForName("utf8");
QTextCodec::setCodecForLocale(codec);
QTextCodec::setCodecForCStrings(codec);
QTextCodec::setCodecForTr(codec);
}
NeHeWidget* w=new NeHeWidget;
w->show();
return a.exec();
}
dinlou.qrc:
<RCC>
<qresource prefix="/Textures">
<file>Resources/text.bmp</file>
<file>Resources/Desert.jpg</file>
</qresource>
</RCC>
