歡迎來(lái)到這激動(dòng)人心的一課,在這一課里,我們將介紹模型的變形。需要注意的是各個(gè)模型必須要有相同的頂點(diǎn),才能一一對(duì)應(yīng),并應(yīng)用變形。
在這一課里,我們同樣要教會(huì)你如何從一個(gè)文件中讀取模型數(shù)據(jù)。
文件開(kāi)始的部分和前面一樣,沒(méi)有任何變化。
我們結(jié)下來(lái)添加幾個(gè)旋轉(zhuǎn)變量,用來(lái)記錄旋轉(zhuǎn)的信息。并使用cx,cy,cz設(shè)置物體在屏幕上的位置。
變量key用來(lái)記錄當(dāng)前的模型,step用來(lái)設(shè)置相鄰變形之間的中間步驟。如step為200,則需要200次,才能把一個(gè)物體變?yōu)榱硪粋€(gè)物體。
最后我們用一個(gè)變量來(lái)設(shè)置是否使用變形。
GLfloat xrot,yrot,zrot, // X, Y & Z 軸的旋轉(zhuǎn)角度
xspeed,yspeed,zspeed, // X, Y & Z 軸的旋轉(zhuǎn)速度
cx,cy,cz=-15; // 物體的位置
int key=1; // 物體的標(biāo)識(shí)符
int step=0,steps=200; // 變換的步數(shù)
bool morph=FALSE; // 是否使用變形
下面的結(jié)構(gòu)定義一個(gè)三維頂點(diǎn)
typedef struct
{
float x, y, z;
} VERTEX;
下面的結(jié)構(gòu)使用頂點(diǎn)來(lái)描述一個(gè)三維物體
typedef struct // 物體結(jié)構(gòu)
{
int verts; // 物體中頂點(diǎn)的個(gè)數(shù)
VERTEX *points; // 包含頂點(diǎn)數(shù)據(jù)的指針
} OBJECT;
maxver用來(lái)記錄各個(gè)物體中最大的頂點(diǎn)數(shù),如一個(gè)物體使用5個(gè)頂點(diǎn),另一個(gè)物體使用20個(gè)頂點(diǎn),那么物體的頂點(diǎn)個(gè)數(shù)為20。
結(jié)下來(lái)定義了四個(gè)我們使用的模型物體,并把相鄰模型變形的中間狀態(tài)保存在helper中,sour保存原模型物體,dest保存將要變形的模型物體。
int maxver; // 最大的頂點(diǎn)數(shù)
OBJECT morph1,morph2,morph3,morph4, // 我們的四個(gè)物體
helper,*sour,*dest; // 幫助物體,原物體,目標(biāo)物體
WndProc()函數(shù)沒(méi)有變化
下面的函數(shù)用來(lái)為模型分配保存頂點(diǎn)數(shù)據(jù)的內(nèi)存空間
void objallocate(OBJECT *k,int n)
{
k->points=(VERTEX*)malloc(sizeof(VERTEX)*n); // 分配n個(gè)頂點(diǎn)的內(nèi)存空間
}
下面的函數(shù)用來(lái)釋放為模型分配的內(nèi)存空間
void objfree(OBJECT *k)
{
free(k->points);
}
下面的代碼用來(lái)讀取文件中的一行。
我們用一個(gè)循環(huán)來(lái)讀取字符,最多讀取255個(gè)字符,當(dāng)遇到'\n'回車(chē)時(shí),停止讀取并立即返回。
void readstr(FILE *f,char *string) // 讀取一行字符
{
do
{
fgets(string, 255, f); // 最多讀取255個(gè)字符
} while ((string[0] == '/') || (string[0] == '\n')); // 遇到回車(chē)則停止讀取
return; // 返回
}
下面的代碼用來(lái)加載一個(gè)模型文件,并為模型分配內(nèi)存,把數(shù)據(jù)存儲(chǔ)進(jìn)去。
void objload(char *name,OBJECT *k) // 從文件加載一個(gè)模型
{
int ver; // 保存頂點(diǎn)個(gè)數(shù)
float rx,ry,rz; // 保存模型位置
FILE *filein; // 打開(kāi)的文件句柄
char oneline[255]; // 保存255個(gè)字符
filein = fopen(name, "rt"); // 打開(kāi)文本文件,供讀取
readstr(filein,oneline); // 讀入一行文本
sscanf(oneline, "Vertices: %d\n", &ver); // 搜索字符串"Vertices: ",并把其后的頂點(diǎn)數(shù)保存在ver變量中
k->verts=ver; // 設(shè)置模型的頂點(diǎn)個(gè)數(shù)
objallocate(k,ver); // 為模型數(shù)據(jù)分配內(nèi)存
下面的循環(huán),讀取每一行(即每個(gè)頂點(diǎn))的數(shù)據(jù),并把它保存到內(nèi)存中?/td>
for (int i=0;i<ver;i++) // 循環(huán)所有的頂點(diǎn)
{
readstr(filein,oneline); // 讀取一行數(shù)據(jù)
sscanf(oneline, "%f %f %f", &rx, &ry, &rz); // 把頂點(diǎn)數(shù)據(jù)保存在rx,ry,rz中
k->points[i].x = rx; // 保存當(dāng)前頂點(diǎn)的x坐標(biāo)
k->points[i].y = ry; // 保存當(dāng)前頂點(diǎn)的y坐標(biāo)
k->points[i].z = rz; // 保存當(dāng)前頂點(diǎn)的z坐標(biāo)
}
fclose(filein); // 關(guān)閉文件
if(ver>maxver) maxver=ver; // 記錄最大的頂點(diǎn)數(shù)
}
下面的函數(shù)根據(jù)設(shè)定的間隔,計(jì)算第i個(gè)頂點(diǎn)每次變換的位移
VERTEX calculate(int i) // 計(jì)算第i個(gè)頂點(diǎn)每次變換的位移
{
VERTEX a;
a.x=(sour->points[i].x-dest->points[i].x)/steps;
a.y=(sour->points[i].y-dest->points[i].y)/steps;
a.z=(sour->points[i].z-dest->points[i].z)/steps;
return a;
}
ReSizeGLScene()函數(shù)沒(méi)有變化
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
下面的函數(shù)完成初始化功能,它設(shè)置混合模式為半透明
int InitGL(GLvoid)
{
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 設(shè)置半透明混合模式
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 設(shè)置清除色為黑色
glClearDepth(1.0); // 設(shè)置深度緩存中值為1
glDepthFunc(GL_LESS); // 設(shè)置深度測(cè)試函數(shù)
glEnable(GL_DEPTH_TEST); // 啟用深度測(cè)試
glShadeModel(GL_SMOOTH); // 設(shè)置著色模式為光滑著色
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
下面的代碼用來(lái)加載我們的模型物體
maxver=0; // 初始化最大頂點(diǎn)數(shù)為0
objload("data/sphere.txt",&morph1); // 加載球模型
objload("data/torus.txt",&morph2); // 加載圓環(huán)模型
objload("data/tube.txt",&morph3); // 加載立方體模型
第四個(gè)模型不從文件讀取,我們?cè)冢?7,-7,-7)-(7,7,7)之間隨機(jī)生成模型點(diǎn),它和我們載如的模型都一樣具有486個(gè)頂點(diǎn)。
objallocate(&morph4,486); // 為第四個(gè)模型分配內(nèi)存資源
for(int i=0;i<486;i++) // 隨機(jī)設(shè)置486個(gè)頂點(diǎn)
{
morph4.points[i].x=((float)(rand()%14000)/1000)-7;
morph4.points[i].y=((float)(rand()%14000)/1000)-7;
morph4.points[i].z=((float)(rand()%14000)/1000)-7;
}
初始化中間模型為球體,并把原和目標(biāo)模型都設(shè)置為球
objload("data/sphere.txt",&helper);
sour=dest=&morph1;
return TRUE; // 初始化完成,成功返回
}
下面是具體的繪制代碼,向往常一樣我們先設(shè)置模型變化,以便我們更好的觀察。
void DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清空緩存
glLoadIdentity(); // 重置模型變換矩陣
glTranslatef(cx,cy,cz); // 平移和旋轉(zhuǎn)
glRotatef(xrot,1,0,0);
glRotatef(yrot,0,1,0);
glRotatef(zrot,0,0,1);
xrot+=xspeed; yrot+=yspeed; zrot+=zspeed; // 根據(jù)旋轉(zhuǎn)速度,增加旋轉(zhuǎn)角度
GLfloat tx,ty,tz; // 頂點(diǎn)臨時(shí)變量
VERTEX q; // 保存中間計(jì)算的臨時(shí)頂點(diǎn)
接下來(lái)我們來(lái)繪制模型中的點(diǎn),如果啟用了變形,則計(jì)算變形的中間過(guò)程點(diǎn)。
glBegin(GL_POINTS); // 點(diǎn)繪制開(kāi)始
for(int i=0;i<morph1.verts;i++) // 循環(huán)繪制模型1中的每一個(gè)頂點(diǎn)
{
if(morph) q=calculate(i); else q.x=q.y=q.z=0; // 如果啟用變形,則計(jì)算中間模型
helper.points[i].x-=q.x;
helper.points[i].y-=q.y;
helper.points[i].z-=q.z;
tx=helper.points[i].x; // 保存計(jì)算結(jié)果到x,y,z變量中
ty=helper.points[i].y;
tz=helper.points[i].z;
為了讓動(dòng)畫(huà)開(kāi)起來(lái)流暢,我們一共繪制了三個(gè)中間狀態(tài)的點(diǎn)。讓變形過(guò)程從藍(lán)綠色向藍(lán)色下一個(gè)狀態(tài)變化。
glColor3f(0,1,1); // 設(shè)置顏色
glVertex3f(tx,ty,tz); // 繪制頂點(diǎn)
glColor3f(0,0.5f,1); // 把顏色變藍(lán)一些
tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // 如果啟用變形,則繪制2步后的頂點(diǎn)
glVertex3f(tx,ty,tz);
glColor3f(0,0,1); // 把顏色變藍(lán)一些
tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // 如果啟用變形,則繪制2步后的頂點(diǎn)
glVertex3f(tx,ty,tz);
}
glEnd(); // 繪制結(jié)束
最后如果啟用了變形,則增加遞增的步驟參數(shù),然后繪制下一個(gè)點(diǎn)。
// 如果啟用變形則把變形步數(shù)增加
if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
return TRUE; // 一切OK
}
KillGLWindow() 函數(shù)基本沒(méi)有變化,只是添加釋放5個(gè)模型內(nèi)存的代碼
objfree(&morph1); // 釋放模型1內(nèi)存
objfree(&morph2); // 釋放模型2內(nèi)存
objfree(&morph3); // 釋放模型3內(nèi)存
objfree(&morph4); // 釋放模型4內(nèi)存
objfree(&helper); // 釋放模型5內(nèi)存
CreateGLWindow() 函數(shù)沒(méi)有變化
BOOL CreateGLWindow()
LRESULT CALLBACK WndProc()
在WinMain()函數(shù)中,我們添加了一些鍵盤(pán)控制的函數(shù)
if(keys[VK_PRIOR]) // PageUp鍵是否被按下
zspeed+=0.01f; // 按下增加繞z軸旋轉(zhuǎn)的速度
if(keys[VK_NEXT]) // PageDown鍵是否被按下
zspeed-=0.01f; // 按下減少繞z軸旋轉(zhuǎn)的速度
if(keys[VK_DOWN]) // 下方向鍵是否被按下
xspeed+=0.01f; // 按下增加繞x軸旋轉(zhuǎn)的速度
if(keys[VK_UP]) // 上方向鍵是否被按下
xspeed-=0.01f; // 按下減少繞x軸旋轉(zhuǎn)的速度
if(keys[VK_RIGHT]) // 右方向鍵是否被按下
yspeed+=0.01f; // 按下增加沿y軸旋轉(zhuǎn)的速度
if(keys[VK_LEFT]) // 左方向鍵是否被按下
yspeed-=0.01f; // 按下減少沿y軸旋轉(zhuǎn)的速度
if (keys['Q']) // Q鍵是否被按下
cz-=0.01f; // 是則向屏幕里移動(dòng)
if (keys['Z']) // Z鍵是否被按下
cz+=0.01f; // 是則向屏幕外移動(dòng)
if (keys['W']) // W鍵是否被按下
cy+=0.01f; // 是則向上移動(dòng)
if (keys['S']) // S鍵是否被按下
cy-=0.01f; // 是則向下移動(dòng)
if (keys['D']) // D鍵是否被按下
cx+=0.01f; // 是則向右移動(dòng)
if (keys['A']) // A鍵是否被按下
cx-=0.01f; // 是則向左移動(dòng)
1,2,3,4鍵用來(lái)設(shè)置變形的目標(biāo)模型
if (keys['1'] && (key!=1) && !morph) // 如果1被按下,則變形到模型1
{
key=1;
morph=TRUE;
dest=&morph1;
}
if (keys['2'] && (key!=2) && !morph) // 如果2被按下,則變形到模型1
{
key=2;
morph=TRUE;
dest=&morph2;
}
if (keys['3'] && (key!=3) && !morph) // 如果3被按下,則變形到模型1
{
key=3;
morph=TRUE;
dest=&morph3;
}
if (keys['4'] && (key!=4) && !morph) // 如果4被按下,則變形到模型1
{
key=4;
morph=TRUE;
dest=&morph4;
}
我希望你能喜歡這個(gè)教程,相信你已經(jīng)學(xué)會(huì)了變形動(dòng)畫(huà)。
Piotr Cieslak 的代碼非常的新穎,希望通過(guò)這個(gè)教程你能知道如何從文件中加載三維模型。
這份教程化了我三天的時(shí)間,如果有什么錯(cuò)誤請(qǐng)告訴我。