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