小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

NeHe OpenGL第十課:3D世界

 小乙vk 2014-10-20

NeHe OpenGL第十課:3D世界

加載3D世界,并在其中漫游:

在這一課中,你將學會如何加載3D世界,并在3D世界中漫游。這一課使用第一課的代碼,當然在課程說明中我只介紹改變了代碼。

這一課是由Lionel Brits (βtelgeuse)所寫的。在本課中我們只對增加的代碼做解釋。當然只添加課程中所寫的代碼,程序是不會運行的。如果您有興趣知道下面的每一行代碼是如何運行的話,請下載完整的源碼

,并在瀏覽這一課的同時,對源碼進行跟蹤。
好了現(xiàn)在歡迎來到名不見經(jīng)傳的第十課。到現(xiàn)在為止,您應該有能力創(chuàng)建一個旋轉的立方體或一群星星了,對3D編程也應該有些感覺了吧?但還是請等一下!不要立馬沖動地要開始寫個Quake
IV,好不好...:)。只靠旋轉的立方體還很難來創(chuàng)造一個可以決一死戰(zhàn)的酷斃了的對手....:)?,F(xiàn)在這些日子您所需要的是一個大一點的、更復雜些的、動態(tài)3D世界,它帶有空間的六自由度和花哨的效果如鏡像、入口

、扭曲等等,當然還要有更快的幀顯示速度。這一課就要解釋一個基本的3D世界"結構",以及如何在這個世界里游走。
數(shù)據(jù)結構
當您想要使用一系列的數(shù)字來完美的表達3D環(huán)境時,隨著環(huán)境復雜度的上升,這個工作的難度也會隨之上升。出于這個原因,我們必須將數(shù)據(jù)歸類,使其具有更多的可操作性風格。在程序清單頭部出現(xiàn)了sector(區(qū)段)

的定義。每個3D世界基本上可以看作是sector(區(qū)段)的集合。一個sector(區(qū)段)可以是一個房間、一個立方體、或者任意一個閉合的區(qū)間。 

typedef struct tagSECTOR      // 創(chuàng)建Sector區(qū)段結構
{
 int numtriangles;      // Sector中的三角形個數(shù)
 TRIANGLE* triangle;      // 指向三角數(shù)組的指針
} SECTOR;        // 命名為SECTOR

一個sector(區(qū)段)包含了一系列的多邊形,所以下一個目標就是triangle(我們將只用三角形,這樣寫代碼更容易些)。 
  
typedef struct tagTRIANGLE      // 創(chuàng)建Triangle三角形結構
{
 VERTEX vertex[3];      // VERTEX矢量數(shù)組,大小為3
} TRIANGLE;        // 命名為 TRIANGLE

三角形本質上是由一些(兩個以上)頂點組成的多邊形,頂點同時也是我們的最基本的分類單位。頂點包含了OpenGL真正感興趣的數(shù)據(jù)。我們用3D空間中的坐標值(x,y,z)以及它們的紋理坐標(u,v)來定義三角形的每

個頂點。 
  
typedef struct tagVERTEX      // 創(chuàng)建Vertex頂點結構
{
 float x, y, z;       // 3D 坐標
 float u, v;       // 紋理坐標
} VERTEX;        // 命名為VERTEX

載入文件
在程序內(nèi)部直接存儲數(shù)據(jù)會讓程序顯得太過死板和無趣。從磁盤上載入世界資料,會給我們帶來更多的彈性,可以讓我們體驗不同的世界,而不用被迫重新編譯程序。另一個好處就是用戶可以切換世界資料并修改它們而

無需知道程序如何讀入輸出這些資料的。數(shù)據(jù)文件的類型我們準備使用文本格式。這樣編輯起來更容易,寫的代碼也更少。等將來我們也許會使用二進制文件。

問題是,怎樣才能從文件中取得數(shù)據(jù)資料呢?首先,創(chuàng)建一個叫做SetupWorld()的新函數(shù)。把這個文件定義為filein,并且使用只讀方式打開文件。我們必須在使用完畢之后關閉文件。大家一起來看看現(xiàn)在的代碼:

// 先前的定義: char* worldfile = "data\\world.txt";
void SetupWorld()       // 設置我們的世界
{
 FILE *filein;       // 工作文件
 filein = fopen(worldfile, "rt");    // 打開文件

 ...
 (讀入數(shù)據(jù)資料))
 ...

 fclose(filein);       // 關閉文件
 return;        // 返回
}

下一個挑戰(zhàn)是將每個單獨的文本行讀入變量。這有很多辦法可以做到。一個問題是文件中并不是所有的行都包含有意義的信息??招泻妥⑨尣粦摫蛔x入。我們創(chuàng)建了一個叫做readstr()的函數(shù)。這個函數(shù)會從數(shù)據(jù)文

件中讀入一個有意義的行至一個已經(jīng)初始化過的字符串。下面就是代碼:
 
void readstr(FILE *f,char *string)     //  讀入一個字符串

{
 do        // 循環(huán)開始
 {
  fgets(string, 255, f);     // 讀入一行
 } while ((string[0] == '/') || (string[0] == '\n'));  // 考察是否有必要進行處理
 return;        // 返回
}

下一步我們讀入?yún)^(qū)段數(shù)據(jù)。這一課將只處理一個區(qū)段,不過實現(xiàn)一個多區(qū)段引擎也很容易。讓我們將注意力轉回SetupWorld()。程序必須知道區(qū)段內(nèi)包含了多少個三角形。我們在數(shù)據(jù)文件中以下面這種形式定義三角形

數(shù)量:
接下來是讀取三角形數(shù)量的代碼: 
  
int numtriangles;       // 區(qū)段中的三角形數(shù)量
char oneline[255];       // 存儲數(shù)據(jù)的字符串
...
readstr(filein,oneline);      // 讀入一行數(shù)據(jù)
sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);   // 讀入三角形數(shù)量

余下的世界載入過程采用了相似的方法。接著,我們對區(qū)段進行初始化,并讀入部分數(shù)據(jù): 
  
// 先前的定義: SECTOR sector1;
char oneline[255];       // 存儲數(shù)據(jù)的字符串
int numtriangles;       // 區(qū)段的三角形數(shù)量
float x, y, z, u, v;       // 3D 和 紋理坐標
...
sector1.triangle = new TRIANGLE[numtriangles];    // 為numtriangles個三角形分配內(nèi)存并設定指針
sector1.numtriangles = numtriangles;     // 定義區(qū)段1中的三角形數(shù)量
// 遍歷區(qū)段中的每個三角形
for (int triloop = 0; triloop < numtriangles; triloop++)  // 遍歷所有的三角形
{
 // 遍歷三角形的每個頂點
 for (int vertloop = 0; vertloop < 3; vertloop++)  // 遍歷所有的頂點
 {
  readstr(filein,oneline);    // 讀入一行數(shù)據(jù)
  // 讀入各自的頂點數(shù)據(jù)
  sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
  // 將頂點數(shù)據(jù)存入各自的頂點
  sector1.triangle[triloop].vertex[vertloop].x = x; // 區(qū)段 1,  第 triloop 個三角形, 第  vertloop 個頂點, 值 x =x
  sector1.triangle[triloop].vertex[vertloop].y = y; // 區(qū)段 1,  第 triloop 個三角形, 第  vertloop 個頂點, 值 y =y
  sector1.triangle[triloop].vertex[vertloop].z = z; // 區(qū)段 1,  第 triloop 個三角形, 第  vertloop 個頂點, 值  z =z
  sector1.triangle[triloop].vertex[vertloop].u = u; // 區(qū)段 1,  第 triloop 個三角形, 第  vertloop 個頂點, 值  u =u
  sector1.triangle[triloop].vertex[vertloop].v = v; // 區(qū)段 1,  第 triloop 個三角形, 第  vertloop 個頂點, 值  e=v
 }
}

數(shù)據(jù)文件中每個三角形都以如下形式聲明:
X1 Y1 Z1 U1 V1
X2 Y2 Z2 U2 V2
X3 Y3 Z3 U3 V3
顯示世界
現(xiàn)在區(qū)段已經(jīng)載入內(nèi)存,我們下一步要在屏幕上顯示它。到目前為止,我們所作過的都是些簡單的旋轉和平移。但我們的鏡頭始終位于原點(0,0,0)處。任何一個不錯的3D引擎都會允許用戶在這個世界中游走和遍歷,

我們的這個也一樣。實現(xiàn)這個功能的一種途徑是直接移動鏡頭并繪制以鏡頭為中心的3D環(huán)境。這樣做會很慢并且不易用代碼實現(xiàn)。我們的解決方法如下:
根據(jù)用戶的指令旋轉并變換鏡頭位置。
圍繞原點,以與鏡頭相反的旋轉方向來旋轉世界。(讓人產(chǎn)生鏡頭旋轉的錯覺)
以與鏡頭平移方式相反的方式來平移世界(讓人產(chǎn)生鏡頭移動的錯覺)。
這樣實現(xiàn)起來就很簡單.
下面從第一步開始吧(平移并旋轉鏡頭)。

if (keys[VK_RIGHT])       // 右方向鍵按下了么?
{
 yrot -= 1.5f;       // 向左旋轉場景
}

if (keys[VK_LEFT])       // 左方向鍵按下了么?
{
 yrot += 1.5f;       // 向右側旋轉場景
}

if (keys[VK_UP])       // 向上方向鍵按下了么?
{
 xpos -= (float)sin(heading*piover180) * 0.05f;   // 沿游戲者所在的X平面移動
 zpos -= (float)cos(heading*piover180) * 0.05f;   // 沿游戲者所在的Z平面移動
 if (walkbiasangle >= 359.0f)     // 如果walkbiasangle大于359度
 {
  walkbiasangle = 0.0f;     // 將 walkbiasangle 設為0
 }
 else        // 否則
 {
   walkbiasangle+= 10;     // 如果 walkbiasangle < 359 ,則增加 10
 }
 walkbias = (float)sin(walkbiasangle * piover180)/20.0f;  // 使游戲者產(chǎn)生跳躍感
}

if (keys[VK_DOWN])       // 向下方向鍵按下了么?
{
 xpos += (float)sin(heading*piover180) * 0.05f;   // 沿游戲者所在的X平面移動
 zpos += (float)cos(heading*piover180) * 0.05f;   // 沿游戲者所在的Z平面移動
 if (walkbiasangle <= 1.0f)     // 如果walkbiasangle小于1度
 {
  walkbiasangle = 359.0f;     // 使 walkbiasangle 等于 359
 }
 else        // 否則
 {
  walkbiasangle-= 10;     // 如果 walkbiasangle > 1 減去 10
 }
 walkbias = (float)sin(walkbiasangle * piover180)/20.0f;  // 使游戲者產(chǎn)生跳躍感
}

這個實現(xiàn)很簡單。當左右方向鍵按下后,旋轉變量yrot
相應增加或減少。當前后方向鍵按下后,我們使用sine和cosine函數(shù)重新生成鏡頭位置(您需要些許三角函數(shù)學的知識:-)。Piover180
是一個很簡單的折算因子用來折算度和弧度。
接著您可能會問:walkbias是什么意思?這是NeHe的發(fā)明的單詞:-)。基本上就是當人行走時頭部產(chǎn)生上下擺動的幅度。我們使用簡單的sine正弦波來調(diào)節(jié)鏡頭的Y軸位置。如果不添加這個而只是前后移動的話,程序

看起來就沒這么棒了。
現(xiàn)在,我們已經(jīng)有了下面這些變量??梢蚤_始進行步驟2和3了。由于我們的程序還不太復雜,我們無需新建一個函數(shù),而是直接在顯示循環(huán)中完成這些步驟。 
  
int DrawGLScene(GLvoid)       // 繪制 OpenGL 場景
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // 清除 場景 和 深度緩沖
 glLoadIdentity();      // 重置當前矩陣


 GLfloat x_m, y_m, z_m, u_m, v_m;    // 頂點的臨時 X, Y, Z, U 和 V 的數(shù)值
 GLfloat xtrans = -xpos;      // 用于游戲者沿X軸平移時的大小
 GLfloat ztrans = -zpos;      // 用于游戲者沿Z軸平移時的大小
 GLfloat ytrans = -walkbias-0.25f;    // 用于頭部的上下擺動
 GLfloat sceneroty = 360.0f - yrot;    // 位于游戲者方向的360度角

 int numtriangles;      // 保有三角形數(shù)量的整數(shù)

 glRotatef(lookupdown,1.0f,0,0);     // 上下旋轉
 glRotatef(sceneroty,0,1.0f,0);     // 根據(jù)游戲者正面所對方向所作的旋轉

 glTranslatef(xtrans, ytrans, ztrans);    // 以游戲者為中心的平移場景
 glBindTexture(GL_TEXTURE_2D, texture[filter]);   // 根據(jù) filter 選擇的紋理

 numtriangles = sector1.numtriangles;    // 取得Sector1的三角形數(shù)量

 // 逐個處理三角形
 for (int loop_m = 0; loop_m < numtriangles; loop_m++)  // 遍歷所有的三角形
 {
  glBegin(GL_TRIANGLES);     // 開始繪制三角形
   glNormal3f( 0.0f, 0.0f, 1.0f);   // 指向前面的法線
   x_m = sector1.triangle[loop_m].vertex[0].x; // 第一點的 X 分量
   y_m = sector1.triangle[loop_m].vertex[0].y; // 第一點的 Y 分量
   z_m = sector1.triangle[loop_m].vertex[0].z; // 第一點的 Z 分量
   u_m = sector1.triangle[loop_m].vertex[0].u; // 第一點的 U  紋理坐標
   v_m = sector1.triangle[loop_m].vertex[0].v; // 第一點的 V  紋理坐標
   glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設置紋理坐標和頂點

   x_m = sector1.triangle[loop_m].vertex[1].x; // 第二點的 X 分量
   y_m = sector1.triangle[loop_m].vertex[1].y; // 第二點的 Y 分量
   z_m = sector1.triangle[loop_m].vertex[1].z; // 第二點的 Z 分量
   u_m = sector1.triangle[loop_m].vertex[1].u; // 第二點的 U  紋理坐標
   v_m = sector1.triangle[loop_m].vertex[1].v; // 第二點的 V  紋理坐標
   glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設置紋理坐標和頂點

   x_m = sector1.triangle[loop_m].vertex[2].x; // 第三點的 X 分量
   y_m = sector1.triangle[loop_m].vertex[2].y; // 第三點的 Y 分量
   z_m = sector1.triangle[loop_m].vertex[2].z; // 第三點的 Z 分量
   u_m = sector1.triangle[loop_m].vertex[2].u; // 第二點的 U  紋理坐標
   v_m = sector1.triangle[loop_m].vertex[2].v; // 第二點的 V  紋理坐標
   glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m); // 設置紋理坐標和頂點
  glEnd();      // 三角形繪制結束
 }
 return TRUE;       // 返回
}
原文及其個版本源代碼下載:

http://nehe./data/lessons/lesson.asp?lesson=10

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多