OpenGL 3ds模型顯示

 

由3DS MAX導出的3ds模型的顯示很簡單。所謂代碼之中,了無祕密。接口很簡單:
存儲:
模型信息和貼圖信息,位於全局變量:
//紋理信息   
UINT g_Texture[10][MAX_TEXTURES] = {0}; 
說明,其中MAX_TEXTURES 是100,表示最大的紋理數目。即一個模型文件,可以對應多個貼圖。
//模型信息
t3DModel g_3DModel[10]; 
函數接口:
加載3D模型:
//入參 模型文件路徑 模型數組g_3DModel的指定下標
void CLoad3DS::Init(char *filename,int j)
顯示3D模型:
//入參 數組g_3DModel下標 顯示座標 縮放倍數
void CLoad3DS::show3ds(int j0,float tx,float ty,float tz,float size)


注意事項:3DS模型文件和對應的貼圖文件要放在同一個文件夾中。3DS MAX系統單位的1毫米對應OPENGL中的一個單位。

附:接口類CLoad3DS的頭文件和源文件
//////////////////////////////////////////////// 3ds.h /////////////////////////////////////////
#ifndef _3DS_H
#define _3DS_H

#include <math.h>
#include <vector>
//  基本塊(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 OBJ_MESH   0x4100  // 新的網格對象
#define MAX_TEXTURES  100   // 最大的紋理數目
//  OBJ_MESH的次級定義
#define OBJ_VERTICES  0x4110  // 對象頂點
#define OBJ_FACES   0x4120  // 對象的面
#define OBJ_MATERIAL  0x4130  // 對象的材質
#define OBJ_UV    0x4140  // 對象的UV紋理座標

using namespace std;
class CVector3  //定義3D點的類,用於保存模型中的頂點
{
 public: float x, y, z;
};

class CVector2  //定義2D點類,用於保存模型的UV紋理座標
{
 public: float x, y;
};

struct tFace  //面的結構定義
{
 int vertIndex[3];   // 頂點索引
 int coordIndex[3];   // 紋理座標索引
};

struct tMatInfo//材質信息結構體
{
 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];   // 對象的名稱
 CVector3  *pVerts;   // 對象的頂點
 CVector3  *pNormals;  // 對象的法向量
 CVector2  *pTexVerts;  // 紋理UV座標
 tFace *pFaces;    // 對象的面信息
};

struct t3DModel //模型信息結構體
{
 int numOfObjects;   // 模型中對象的數目
 int numOfMaterials;   // 模型中材質的數目
 
 vector<tMatInfo> pMaterials; // 材質鏈表信息
 vector<t3DObject> pObject; // 模型中對象鏈表信息
};

struct tChunk //保存塊信息的結構
{
 unsigned short int ID;  // 塊的ID  
 unsigned int length;  // 塊的長度
 unsigned int bytesRead;  // 需要讀的塊數據的字節數
};

//////////////////////////////////////////////////////////////////////////
class CLoad3DS// CLoad3DS類處理所有的裝入代碼
{
public:
 CLoad3DS();        // 初始化數據成員
 virtual ~CLoad3DS();

 void show3ds(int j0,float tx,float ty,float tz,float size);//顯示3ds模型
 void Init(char *filename,int j);
 void CleanUp();          // 關閉文件,釋放內存空間

private:
 bool Import3DS(t3DModel *pModel, char *strFileName);// 裝入3ds文件到模型結構中
 void CreateTexture(UINT textureArray[], LPSTR strFileName, int textureID);//  從文件中創建紋理
 int  GetString(char *);        // 讀一個字符串
 void ReadChunk(tChunk *);       // 讀下一個塊
 void ReadNextChunk(t3DModel *pModel, tChunk *);  // 讀下一個塊
 void ReadNextObjChunk(t3DModel *pModel,t3DObject *pObject,tChunk *);// 讀下一個對象塊
 void ReadNextMatChunk(t3DModel *pModel, tChunk *); // 讀下一個材質塊
 void ReadColor(tMatInfo *pMaterial, tChunk *pChunk);// 讀對象顏色的RGB值
 void ReadVertices(t3DObject *pObject, tChunk *); // 讀對象的頂點
 void ReadVertexIndices(t3DObject *pObject,tChunk *);// 讀對象的面信息
 void ReadUVCoordinates(t3DObject *pObject,tChunk *);// 讀對象的紋理座標
 void ReadObjMat(t3DModel *pModel,t3DObject *pObject,tChunk *pPreChunk);// 讀賦予對象的材質名稱
 void ComputeNormals(t3DModel *pModel);    // 計算對象頂點的法向量

 FILE *m_FilePointer;        // 文件指針
 tChunk *m_CurrentChunk;
 tChunk *m_TempChunk;
};
#endif

//////////////////////////////////////////////// 3ds.cpp /////////////////////////////////////////
#include "stdafx.h"
#include "3ds.h"

//紋理信息   
UINT g_Texture[10][MAX_TEXTURES] = {0}; 

//模型信息
t3DModel g_3DModel[10]; 

//繪製方式        
int   g_ViewMode   = GL_TRIANGLES;
bool  g_bLighting     = true;  

CLoad3DS::CLoad3DS()
{
 m_CurrentChunk = new tChunk;
 m_TempChunk = new tChunk;
}

CLoad3DS::~CLoad3DS()
{
 CleanUp();
 for(int j = 0; j <10;j++)
 {
  for(int i = 0; i < g_3DModel[j].numOfObjects; i++)
  {
   delete [] g_3DModel[j].pObject[i].pFaces;
   delete [] g_3DModel[j].pObject[i].pNormals;
   delete [] g_3DModel[j].pObject[i].pVerts;
   delete [] g_3DModel[j].pObject[i].pTexVerts;
  }
 }
}
////////////////////////////////////////////////////////////////////////
void CLoad3DS::Init(char *filename,int j)//

 Import3DS(&g_3DModel[j], filename);  
 
 for(int i =0; i<g_3DModel[j].numOfMaterials;i++)
 {
  if(strlen(g_3DModel[j].pMaterials[i].strFile)>0)
   CreateTexture(g_Texture[j], g_3DModel[j].pMaterials[i].strFile, i);
  
  g_3DModel[j].pMaterials[i].texureId = i;
 }
}

void CLoad3DS::CreateTexture(UINT textureArray[], LPSTR strFileName, int textureID)

 AUX_RGBImageRec *pBitmap = NULL;
 
 if(!strFileName) 
  return;     
 
 pBitmap = auxDIBImageLoad(strFileName); 
 if(pBitmap == NULL) 
  exit(0); 

 glGenTextures(1, &textureArray[textureID]);

 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
 glBindTexture(GL_TEXTURE_2D, textureArray[textureID]);
 gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pBitmap->sizeX, pBitmap->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pBitmap->data);
 
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR_MIPMAP_LINEAR);
 
 if (pBitmap)     
 {
  if (pBitmap->data) 
   free(pBitmap->data);       
  free(pBitmap);     
 }
}

void CLoad3DS::show3ds(int j0,float tx,float ty,float tz,float size)
{
 glPushAttrib(GL_CURRENT_BIT);
 glPushMatrix();
 glDisable(GL_TEXTURE_2D);
 ::glTranslatef( tx, ty, tz);
 ::glScaled(size,size,size);
 glRotatef(90, 0, 1.0f, 0);

 //繪製模型
 for(int i = 0; i < g_3DModel[j0].numOfObjects; i++)
 {
  if(g_3DModel[j0].pObject.size() <= 0) 
  {
   break;
  }
  
  t3DObject *pObject = &g_3DModel[j0].pObject[i];
  
  if(pObject->bHasTexture)
  {
   //如果這個物體有紋理,綁定紋理
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, g_Texture[j0][pObject->materialID]);
  } 
  else
  {
   //沒有紋理,關閉貼圖
   glDisable(GL_TEXTURE_2D);
  }
  //指定顏色
  glColor3ub(255, 255, 255);

  glBegin(g_ViewMode);    
  //繪製這個物體的所有面片
  for(int j = 0; j < pObject->numOfFaces; j++)
  {
   for(int tex = 0; tex < 3; tex++)    
   {
    int index = pObject->pFaces[j].vertIndex[tex]; 
    //設置法向
    glNormal3f(pObject->pNormals[index].x,
     pObject->pNormals[index].y,  
           pObject->pNormals[index].z); 
   
    if(pObject->bHasTexture)    
    {
     //設置紋理座標
     if(pObject->pTexVerts)    
      glTexCoord2f(pObject->pTexVerts[index].x, pObject->pTexVerts[index].y);
    }
    else
    {
     //沒有紋理,設置顏色
     if(g_3DModel[j0].pMaterials.size() && pObject->materialID>= 0) 
     {
      BYTE *pColor = g_3DModel[j0].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();
 }

 glEnable(GL_TEXTURE_2D);
 glPopMatrix();
 glPopAttrib();
}
//////////////////////////////////////////////////////////////////
//加載模型
bool CLoad3DS::Import3DS(t3DModel *pModel, char *strFileName)
{
 char strMessage[255] = {0};

 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;
 }

 ReadChunk(m_CurrentChunk);

 if (m_CurrentChunk->ID != PRIMARY)
 { 
  sprintf(strMessage, "Unable to load PRIMARY chuck from file: %s!", strFileName);
  MessageBox(NULL, strMessage, "Error", MB_OK);
  return false;
 }

 ReadNextChunk(pModel, m_CurrentChunk);

 ComputeNormals(pModel);

 return true;
}

void CLoad3DS::CleanUp()
{
 fclose(m_FilePointer); 
 delete m_CurrentChunk; 
 delete m_TempChunk;  
}

void CLoad3DS::ReadNextChunk(t3DModel *pModel, tChunk *pPreChunk)
{
 t3DObject newObject = {0};     // 用來添加到對象鏈表
 tMatInfo newTexture = {0};    // 用來添加到材質鏈表
 unsigned int version = 0;     // 保存文件版本
 int buffer[50000] = {0};     // 用來跳過不需要的數據
 
 m_CurrentChunk = new tChunk;    // 爲新的塊分配空間  
 //  下面每讀一個新塊,都要判斷一下塊的ID,如果該塊是需要的讀入的,則繼續進行
 //  如果是不需要讀入的塊,則略過
 // 繼續讀入子塊,直到達到預定的長度
 while (pPreChunk->bytesRead < pPreChunk->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;
   // 進入下一個塊
   ReadNextChunk(pModel, m_CurrentChunk);
   break;
  
  case MATERIAL:       // 材質信息
   // 材質的數目遞增
   pModel->numOfMaterials++;
   // 在紋理鏈表中添加一個空白紋理結構
   pModel->pMaterials.push_back(newTexture);
   // 進入材質裝入函數
   ReadNextMatChunk(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);
   // 進入其餘的對象信息的讀入
   ReadNextObjChunk(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;
  }
  // 增加從最後塊讀入的字節數
  pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
 }
 // 釋放當前塊的內存空間
 delete m_CurrentChunk;
 m_CurrentChunk = pPreChunk;
}
//  下面的函數處理所有的文件中對象的信息
void CLoad3DS::ReadNextObjChunk(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)

 int buffer[50000] = {0};     // 用於讀入不需要的數據
 // 對新的塊分配存儲空間
 m_CurrentChunk = new tChunk;
 
 // 繼續讀入塊的內容直至本子塊結束
 while (pPreChunk->bytesRead < pPreChunk->length)
 { // 讀入下一個塊
  ReadChunk(m_CurrentChunk);
  // 區別讀入是哪種塊
  switch (m_CurrentChunk->ID)
  {
  case OBJ_MESH:     // 正讀入的是一個新塊
   // 使用遞歸函數調用,處理該新塊
   ReadNextObjChunk(pModel, pObject, m_CurrentChunk);
   break;
  case OBJ_VERTICES:    // 讀入是對象頂點
   ReadVertices(pObject, m_CurrentChunk);
   break;
  case OBJ_FACES:     // 讀入的是對象的面
   ReadVertexIndices(pObject, m_CurrentChunk);
   break;
  case OBJ_MATERIAL:    // 讀入的是對象的材質名稱
   // 該塊保存了對象材質的名稱,可能是一個顏色,也可能是一個紋理映射。同時在該塊中也保存了
   // 紋理對象所賦予的面
   // 下面讀入對象的材質名稱
   ReadObjMat(pModel, pObject, m_CurrentChunk);   
   break;
  case OBJ_UV:      // 讀入對象的UV紋理座標
   // 讀入對象的UV紋理座標
   ReadUVCoordinates(pObject, m_CurrentChunk);
   break;
  default:  
   // 略過不需要讀入的塊
   m_CurrentChunk->bytesRead += fread(buffer, 1, m_CurrentChunk->length - m_CurrentChunk->bytesRead, m_FilePointer);
   break;
  }
  // 添加從最後塊中讀入的字節數到前面的讀入的字節中
  pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
 }
 // 釋放當前塊的內存空間,並把當前塊設置爲前面塊
 delete m_CurrentChunk;
 m_CurrentChunk = pPreChunk;
}

//  下面的函數處理所有的材質信息
void CLoad3DS::ReadNextMatChunk(t3DModel *pModel, tChunk *pPreChunk)
{
 int buffer[50000] = {0};     // 用於讀入不需要的數據
 // 給當前塊分配存儲空間
 m_CurrentChunk = new tChunk;
 // 繼續讀入這些塊,知道該子塊結束
 while (pPreChunk->bytesRead < pPreChunk->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顏色
   ReadColor(&(pModel->pMaterials[pModel->numOfMaterials - 1]), m_CurrentChunk);
   break;
  
  case MATMAP:       // 紋理信息的頭部
   // 進入下一個材質塊信息
   ReadNextMatChunk(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;
  }
  // 添加從最後塊中讀入的字節數
  pPreChunk->bytesRead += m_CurrentChunk->bytesRead;
 }
 // 刪除當前塊,並將當前塊設置爲前面的塊
 delete m_CurrentChunk;
 m_CurrentChunk = pPreChunk;
}

//  下面函數讀入塊的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::ReadColor(tMatInfo *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 *pPreChunk)
{
 unsigned short index = 0;     // 用於讀入當前面的索引
 // 讀入該對象中面的數目
 pPreChunk->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++)
  { // 讀入當前面的第一個點 
   pPreChunk->bytesRead += fread(&index, 1, sizeof(index), m_FilePointer);
   if(j < 3)
   { // 將索引保存在面的結構中
    pObject->pFaces[i].vertIndex[j] = index;
   }
  }
 }
}

//  下面的函數讀入對象的UV座標
void CLoad3DS::ReadUVCoordinates(t3DObject *pObject, tChunk *pPreChunk)
{
 // 爲了讀入對象的UV座標,首先需要讀入UV座標的數量,然後纔讀入具體的數據
 // 讀入UV座標的數量
 pPreChunk->bytesRead += fread(&pObject->numTexVertex, 1, 2, m_FilePointer);
 // 分配保存UV座標的內存空間
 pObject->pTexVerts = new CVector2 [pObject->numTexVertex];
 // 讀入紋理座標
 pPreChunk->bytesRead += fread(pObject->pTexVerts, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}

//  讀入對象的頂點
void CLoad3DS::ReadVertices(t3DObject *pObject, tChunk *pPreChunk)
{
 // 在讀入實際的頂點之前,首先必須確定需要讀入多少個頂點。
 // 讀入頂點的數目
 pPreChunk->bytesRead += fread(&(pObject->numOfVerts), 1, 2, m_FilePointer);
 // 分配頂點的存儲空間,然後初始化結構體
 pObject->pVerts = new CVector3 [pObject->numOfVerts];
 memset(pObject->pVerts, 0, sizeof(CVector3) * pObject->numOfVerts);
 // 讀入頂點序列
 pPreChunk->bytesRead += fread(pObject->pVerts, 1, pPreChunk->length - pPreChunk->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::ReadObjMat(t3DModel *pModel, t3DObject *pObject, tChunk *pPreChunk)
{
 char strMaterial[255] = {0};   // 用來保存對象的材質名稱
 int buffer[50000] = {0};    // 用來讀入不需要的數據
 // 材質或者是顏色,或者是對象的紋理,也可能保存了象明亮度、發光度等信息。
 // 下面讀入賦予當前對象的材質名稱
 pPreChunk->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) {
    // 設置對象的紋理映射標誌
    pObject->bHasTexture = true;
   } 
   break;
  }
  else
  { // 如果該對象沒有材質,則設置ID爲-1
   pObject->materialID = -1;
  }
 }
 pPreChunk->bytesRead += fread(buffer, 1, pPreChunk->length - pPreChunk->bytesRead, m_FilePointer);
}   
//  下面的這些函數主要用來計算頂點的法向量,頂點的法向量主要用來計算光照
// 下面的宏定義計算一個矢量的長度
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))
// 下面的函數求兩點決定的矢量
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)

 CVector3 vVector;       
 vVector.x = vPoint1.x - vPoint2.x;   
 vVector.y = vPoint1.y - vPoint2.y;   
 vVector.z = vPoint1.z - vPoint2.z;   
 return vVector;        
}
// 下面的函數兩個矢量相加
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)

 CVector3 vResult;       
 vResult.x = vVector2.x + vVector1.x;  
 vResult.y = vVector2.y + vVector1.y;  
 vResult.z = vVector2.z + vVector1.z;  
 return vResult;        
}
// 下面的函數處理矢量的縮放
CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler)

 CVector3 vResult;       
 vResult.x = vVector1.x / Scaler;   
 vResult.y = vVector1.y / Scaler;   
 vResult.z = vVector1.z / Scaler;   
 return vResult;        
}
// 下面的函數返回兩個矢量的叉積
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)

 CVector3 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;        
}
// 下面的函數規範化矢量
CVector3 Normalize(CVector3 vNormal)

 double Magnitude;       
 Magnitude = Mag(vNormal);     // 獲得矢量的長度
 vNormal.x /= (float)Magnitude;    
 vNormal.y /= (float)Magnitude;    
 vNormal.z /= (float)Magnitude;    
 return vNormal;        
}
//  下面的函數用於計算對象的法向量
void CLoad3DS::ComputeNormals(t3DModel *pModel)

 CVector3 vVector1, vVector2, vNormal, vPoly[3];
 // 如果模型中沒有對象,則返回
 if(pModel->numOfObjects <= 0)
  return;
 // 遍歷模型中所有的對象
 for(int index = 0; index < pModel->numOfObjects; index++)
 {
  // 獲得當前的對象
  t3DObject *pObject = &(pModel->pObject[index]);
  // 分配需要的存儲空間
  CVector3 *pNormals  = new CVector3 [pObject->numOfFaces];
  CVector3 *pTempNormals = new CVector3 [pObject->numOfFaces];
  pObject->pNormals  = new CVector3 [pObject->numOfVerts];
  // 遍歷對象的所有面
  for(int 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;      // 將法向量添加到法向量列表中
  }
  //  下面求頂點法向量
  CVector3 vSum = {0.0, 0.0, 0.0};
  CVector3 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;
 }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章