/LGC圖形渲染/基於 OpenGL 進行 3D 圖形開發

來源:互聯網
上載者:User
基於 OpenGL 進行 3D 圖形開發 作者:
劉鵬 日期:

2010-05-10

本文介紹了基於 OpenGL 進行 3D 開發的基本思路,協助初學者快速理清思路。 3D 開發的一般思路

3D 開發主要分為三個步驟:建模、渲染、邏輯控制。

建模,指通過一些基本圖元如點、線、三角形、多邊形將物體畫出來,一般使用
3DMax、MilkShape 等建模工具來完成。具體來說,建模涉及模型的構建、貼紋
理、製作動畫等。

渲染,即使用 OpenGL 圖形介面將模型在電腦上畫出來。

邏輯控制,若要模型動起來,需要根據時間計算模型各個頂點的座標,這個通過
程式邏輯來控制。

在 3D 開發中有一個常用的速度指標,即幀每秒(f/s),其計算方法如下:

fps = numFrame / Interval
  • numFrame 是當前的 frame 總和,從程式運行起,每運行一個 frame 這個就加1;
  • Interval 指從程式運行起的時間總和。
建模

用 3dMax 等工具建模的一般思路是:

  1. 用基本形狀,如三角形、多邊形、曲面、球體、立方體等,把物體輪廓構件出來
  2. 通過展開、調整頂點等方法對細節進行處理

建模工具有很多,如 3DMax、Maya、Blender、MilkShape、AC3D 等,其中
MilkShape 短小精悍,容易上手比較推薦新手使用。

Milkshape :建模、貼紋理、加骨骼、做動畫

  1. mode: 做模型
  2. group: 構成模型的各個子部分
  3. material:往各個子部分上貼材質
  4. joint:骨骼結點,現在還沒想清楚這些 joint 的作用是什麼
  5. animation:設定關鍵禎,產生動畫
  6. 產生的模型格式是 .ms3d

Milkshape 的教程可在這裡
下載。

儲存模型有很多種格式,如 md2、ms3d、3ds、obj等。

  1. md2 速度快,但資料量大,典型的以空間換時間; ms3d 儲存的資料量小,但需要的計算比較多
  2. md2 的動畫可能不如 ms3d 細膩
OpenGL based Graphics

It's a state machine - Setup the stage, lighting, actors... Then draw it!

使用 OpenGL 進行渲染的總體思路
  • clear the screen:glClear (XXX | XXX)
  • Reset the view:glLoadIdentity
  • move / rotate axis:glTranslatef (x, y, z), glRotatef ()
  • draw scene: glBegin, ..., glEnd
  • glDrawArray/glDrawElements 完成點、線、多邊形等基本圖元的繪製
貼紋理產生紋理

OpenGL 中的紋理通過一個唯一號引用,通過函數 glBindTexture() 實現。你
可以自己指定這個唯一號,或者通過調用 glGenTextures () 函數產生一個唯一
號。

GLuint        texture[1];
glGenTextures(1, &texture[0]);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, 3, sizeX, sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, pData);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
此時 pdata 裡的資料就可以釋放了

紋理管理器

在實際開發時,往往需要一個紋理管理器提供紋理的裝載、釋放、擷取等功能,
下面的文章介紹了一個紋理管理器的設計和實現。

A Singleton Texture Manager for OpenGL

如何貼紋理

以片來自:http://blogs.msdn.com/danlehen/

核心繪製代碼如下所示:

glVertexPointer(3, GL_FLOAT, 0, m_vertices);
glNormalPointer(GL_FLOAT, 0, m_normals);

glBindTexture(GL_TEXTURE_2D, m_textureId);
glTexCoordPointer(2, GL_FLOAT, 0, m_uvs);

glDrawElements(GL_TRIANGLES, m_triangleNums * 3, GL_UNSIGNED_SHORT,m_indices);

或者

glDrawArrays(GL_TRIANGLES, 0, m_triangleNums * 3);

在嵌入式裝置上的用 OpenGL ES 貼紋理,以 Androiod 為例

在 android 平台上,紋理圖的長寬必須是 2 的冪,紋理圖尺寸超過 512x512
時 fps 會顯著降低。一般的做法是將所有紋理組合到成一張 256x256 或者
512x512 的大圖。

使用紋理有兩種方法,一是直接用建模工具映射好,二是通過動態調整紋理座標
來擷取需要的紋理。前者不做介紹,重點介紹下後者。

調整紋理座標的方法有兩種:一是手工修改現有紋理代碼,二是通過
glMatrixMode (glTexture) 對紋理座標進行操作。

glMatrixMode (glTexture) 調整紋理座標的代碼如下:

    // move texture
glMatrixMode(GL_TEXTURE);
glPushMatrix();
glLoadIdentity();

// move the texture - this does not work
glTranslatef(offset, 0.0f, 0.0f);
glTranslatef(0.0f, offset, 0.0f);
glTranslatef(0.0f, 0.0f, offset);

glPopMatrix();


注意:

  • 紋理座標的位移值範圍為:-1~1
  • 紋理座標的原點在紋理圖的左下角,若沿 Y 軸向下位移,位移量為負
  • glMatrixMode (GL_TEXTURE)往往與 glMatrixMode (GL_MODELVIEW)混用,注意儲存好現場。
gluPerspective 和 gluLookAt 1

函數原型如下所示:

gluLookAt(GLdoble eyex,GLdouble eyey,GLdouble eyez,
GLdouble centerx,GLdouble centery,GLdouble centerz,
GLdouble upx,GLdouble upy,GLdouble upz);

gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar);

gluPerspective

首先得設定 gluPerspective,來看看它的參數都表示什麼意思:

  • fovy,可以理解成眼睛睜開的角度,即,視角的大小,如果設定為0,相當你閉上眼睛了,所以什麼也看不到,如果為180,那麼可以認為你的視界很廣闊;
  • aspect,實際視窗的縱橫比,即x/y;
  • zNear,表示你近處的裁面;
  • zFar,表示遠處的裁面。

我們知道,遠處的東西看起來要小一些,近處的東西看起來會大一些,這就是透視
(perspective)原理,如所示:

假設那兩條線表示公路,理論上講,它們的兩條邊是平行的,但現實情況中,它
們在遠方(可以無限遠)總要相交於一點,實際線段 AB 的長度等於 CD 的長度,
只是在此例中使用了透視角,故會有如上的效果,是不是很接近現實的情況?

結合我們剛才這兩個函數:

  • zNear,眼睛距離近處的距離,假設為10米遠,請不要設定為負值,OpenGl 就傻了,不知道怎麼算了;
  • zFar,表示遠處的裁面,假設為1000米遠;

就是這兩個參數的意義了。

再解釋下那個"眼睛睜開的角度"是什麼意思,首先假設我們現在距離物體有50個
單位距離遠的位置,在眼睛睜開角度設定為 45 時,請看大螢幕:

我們可以看到,在遠處一個球;現在我們將眼睛再張開點看,將“眼睛睜開的角
度”設定為 178 (180度表示平角,那時候我們將什麼也看不到,眼睛睜太大了,眼
大無神)。

我們只看到一個點,因為我們看的範圍太大了,這個球本身大小沒有改變,但是
它在我們的“視界”內太小了。

反之,我們將眼睛閉小些,改為 1 度看看會出現什麼情況呢?

在我們距離該物體 3000 距離遠,“眼睛睜開的角度”為 1 時,我們似乎走進了
這個球內,這個是不是類似於相機的焦距?

當我們將“透視角”設定為 0 時,我們相當於閉上雙眼,這個世界清靜了,我們
什麼也看不到了。

gluLookAt

現在來看 gluLookAt 函數。

它共接受三對座標,分別為 eye,center,up。

  • eye, 表示我們眼睛在“全局座標系”中的位置;
  • center, 表示眼睛“看”的那個點的座標;
  • up,表示觀察者本身的方向。

如果將觀察點比喻成我們的眼睛,那麼這個 up 則表示我們是正立還是倒立。從
不同角度看,所看的影像大不相同。若需要指明我們現在正立,那麼 X,Z 軸為
0,Y 軸為正即可,通常將其設定為 1,只要表示一個向上的向量(方向)即可。
我們指定 0.1f 或 0.00001f 或 1000.0f,效果是一樣的,只要能表示方向即可。

球是畫在全局座標系的原點上的,即 O(0,0,0) 座標上,我們的眼睛位於觀察點
A(0,0,100),Z 軸向螢幕裡看去的方向為負,螢幕外我們的位置,Z 軸為正值,
其實很好理解,即我們距離原點的距離,設定 100,將觀察到如所示的影像。

如果我們向前或向後移動,則相應的映像會變大或變小,這裡其實就是運用了透
視原理,近處的物體大,遠處的物體小,實際物體的大小是不變的。

同理改變 center 座標(眼睛看去的那個點,可簡單理解為視線的終點)也會影響
球的大小,同樣可以認為是改變了物體與觀察點的距離所致。

測試

以上理解了之後,來做一個測試。

透視圖不變,最遠處仍為 3000,近處為 0.1。

gluPerspective                    // 設定透視圖
(45, // 透視角設定為 45 度,在Y方向上以角度為單位的視野
(GLfloat)x/(GLfloat)y, // 視窗的寬與高比
0.1f, // 視野透視深度:近點1.0f
3000.0f // 視野透視深度:始點0.1f遠點1000.0f
);

將我們的觀察點置於 A(0,10,0),將觀察位置(視線終點)座標置於(0,0,0),然
後在原點開始繪圖,畫一個 V 字形,並將 Z 軸的值從 -1000 遞增加到 +1000,
增量為10,代碼如下:

    glColor3f(0.5f, 0.7f, 1.0f);

glBegin(GL_LINES);
for(int i=-1000;i<=1000;i+=10)
{
glVertex3f(0,0,i);
glVertex3f(10,10,i);

glVertex3f(0,0,i);
glVertex3f(-10,10,i);
}
glEnd();

如下所示:

OpenGL 的座標系統變換

OpenGL 的重要功能之一是將三維的全局座標經過變換、投影等計算,最終算出它在顯示裝置上對應的位置。

每次計算螢幕輸出時,OpenGL 採取這樣的方式進行:

    螢幕座標點 = 3D 模型點 * 幾何變換棧矩陣(n...1) * 投影變換棧矩陣    (n...1)。其中 n...1 表示從棧頂到棧底。

更複雜的流程是:

    螢幕座標點 = 3D 模型座標 -〉 (幾何變換矩陣) -〉 人眼座標 -〉(投影變換矩陣) -〉 正則裝置座標(相對於 OpenGL 螢幕座標原點)
-〉 校正成視窗座標(相對於視窗座標)

簡單的講,OpenGL 中從三維情境到螢幕圖形要經曆如下所示的變換過程:

模型座標-〉全局座標-〉觀察座標-〉投影座標-〉裝置座標

其中四種座標經常要在程式中用到:物體座標(也叫模型座標、局部座標),全局座標,眼座標(也叫觀察座標)和裝置座標.

物體座標:

以物體某一點為原點而建立的“全局座標”,該座標系僅對該物體適用,用來簡化對物體各部分座標的描述。物體放到情境中時,各部分經曆的座標變換相同,相對位置不變,所以可視為一個整體,與人類的思維習慣一致;

全局座標:

是OpenGL中用來描述情境的座標,Z+軸垂直螢幕向外,X+從左至右,Y+軸從下到上,是右手笛卡爾座標系統。我們用這個座標系來描述物體及光
源的位置。 OpenGL 中有一個座標轉換矩陣棧 (Modeview),棧頂就是當前座標變換矩陣,進入 OpenGL 管道的每個座標
(齊次座標)都會乘上這個矩陣,結果才是對應點在情境中的全局座標。將物體放到情境中也就是將物體平移到特定位置、旋轉一定角度,這些操作都是座標變換。

眼座標:

是以視點為原點,以視線的方向為Z+軸正方向的座標系中的方向。OpenGL管道會將全局座標先變換到眼座標,然後進行裁剪,只有在視線範圍(視見
體)之內的情境才會進入下一階段的計算。 OpenGL 有個投影變換矩陣棧
(Projection),棧頂矩陣就是當前投影變換矩陣,負責將情境各座標變換到眼座標。由於所得到的結果是裁剪後的情境部分,稱為裁剪座標。

裝置座標:

OpenGL的重要功能之一就是將三維的全局座標經過變換、投影等計算,最終算出它在顯示裝置上對應的位置,這個位置就稱為裝置座標。在螢幕、印表機等裝置上的座標是二維座標。

幾何變換:平移、旋轉、縮放
  • glTranslatef (x, y, z)
  • glRotatef (alpha, x, y, z);
  • glScalef (x, y, z);

幾何變換棧:為了維護幾何變換, OpenGL 維護一個幾何變換棧,每次幾何變換,
都用相應的矩陣乘對應的棧頂元素,並替換掉上次的棧頂。

對於幾何變換棧,有兩個操作可以使用:

  • glPushMatrix(): 儲存當前座標系 ,複製當前棧頂,並把複製的內容再次放到棧頂,能夠保護棧上以前的內容;
  • glPopMatrix(): 恢複當前座標系。
投影變換 : 正交投影 (正射投影、平行投影),透視投影
  • 正交投影:將 3D 模型平行的映射到平面上,glOrtho(xleft, xright, ybottom, ytop, znear, zfar);
  • 透視投影:將 3D 模型映射到相對某個觀察點的平面上, gluPerspective(fovy, aspect, znear, zfar);

深入探討透視投影變換:http://www.cppblog.com/zmj/archive/2008/08/28/58936.html

glOrtho 函數的意義: http://flyingliang.spaces.live.com/blog/cns!9112491EC93C817B!1228.entry

正交投影的特點:

無論物體距離相機多遠,投影后的物體大小尺寸不變。這種投影通常用在建築藍圖繪製和電腦輔助設計等方面,這些行業要求投影后的物體尺寸及相互間的角度不變,以便施工或製造時物體比例大小正確。

透視投影的特點:離觀察者越遠的對象越小。

切換當前棧
  • 切換當前操作的棧為投影變換棧:glMatrixMode(GL_PROJECTION);
  • 切換當前操作的棧為幾何變換棧:glMatrixMode(GL_MODELVIEW);
  • 清除當前操作棧的內容:glLoadIdentity();
變化觀察模型的角度,可以在切換到幾何變換棧的時候進行:
  • glMatrixMode(GL_MODELVIEW); // 模型視圖矩陣
  • glLoadIdentity();
  • gluLookAt(6.0, 8.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // 旋轉視點
一段典型的 OpenGL 代碼
    // Init *
glClearColor (0.0f, 0.0f, 0.0f, 0.0f); /* Set background color*/
glClearDepth (1.0);

glShadeModel (GL_SMOOTH);

glMatrixMode (GL_PROJECTION); /* 設定投影變換 */
glLoadIdentity ();
gluPerspective (......);
glMatrixMode (GL_MODEVIEW);

//* 清屏 *
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity ();

glTranslatef (x, y, z); /* 平移 */
glRotatef (angle, x, y, z); /* 旋轉 */

glBegin (GL_POLYGON);
glColor3f (...);
glVertex3f (...);
... ...
glEnd ();

/* ReSizeScene */
glViewport (0, 0, width, height);
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
gluPerspective(... ...);
glMatrixMode(GL_MODELVIEW);

How to walk around and explore the world
  • move camera around and draw the 3D environment relative to the camera positon. But it is slow and hard to code.
  • Rotate / translate the world in the opposite manner.
    1. Rotate and translate the camera position according to user commands
    2. Rotate the world around the origin in the opposite direction of the
      camera rotation (giving the illusion that the camera has been rotated)
    3. Translate the world in the opposite manner that the camera has been
      translated (again, giving the illusion that the camera has moved)
Reference
  1. 終於搞明白 gluPerspective 和 gluLookAt 的關係了

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.