Windows下Qt開發環境:OpenGL導入3DMax模型(.3DS)


參考: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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM