




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
從零開始學習OpenGLES之一-基本概念
byfengliu
我曾寫過一些文章介紹iPhoneOpenGLES編程,但大部分針對的是已經至
少懂得一些3D編程知識的人。作為起點,請下載我的OpenGLXcode項目
模板,而不要使用Apple提供的模板。你可以解壓到下面位置安裝此模板:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Projec
tTemplates/Application/
已經有大量有關OpenGL的好教程和書籍。但是,卻沒有多少是關于OpenGL
ES,而且沒有(至少在我撰寫此文時D是專門針對學習iPhone上3D編程的。
因為大部分有關學習OpenGL的材料是從所謂“直接模式(directmode)”開
始的,而OpenGLES并不支持此模式,對于沒有3D背景知識的iPhone開發(fā)
者而言,使用現(xiàn)有的書籍和教程是十分困難的。為滿足一些開發(fā)者的要求,
我決定撰寫一個針對3D初學者的博文系列。這是此系列的第一篇文章。
OpenGL數據類型
首先我們要討論的是OpenGL的數據類型。因為OpenGL是一個跨平臺的API,
數據類型的大小會隨使用的編程語言以及處理器(64位,32位,16位)等的
不同而不同,所以OpenGL定義了自己的數據類型。當傳遞數據到OpenGL
時.,你應該堅持使用這些OpenGL的數據類型,從而保證傳遞數據的尺寸和
精度正確。不這樣做的后果是可能會導致無法預料的結果或由于運行時的數
據轉換造成效率低下。不論平臺或語言實現(xiàn)的OpenGL都采用這種方式定義
數據類型以保證在各平臺上數據的尺寸一致,并使平臺間OpenGL代碼移植
更為容易。
下面是OpenGL的各種數據類型:
?GLenum:用于GL枚舉的無符號整型。通常用于通知OpenGL由指針傳遞
的存儲于數組中數據的類型(例如,GL_FLOAT用于指示數組由GLfloat
組成)。
?GLboolean:用于單布爾值。OpenGLES還定義了其自己的“真”和“假”值
(GL_TRUE和GL_FALSE)以避免平臺和語言的差別。當向OpenGL
傳遞希爾值時,請椀用這些值而不是使用YES或N。(盡管由于它們的
定義實際沒有區(qū)別,即使你不小心使用了YES或NO。但是,使用GL-
定義值是一個好的習慣。)
?GLbitfield:用于將多個布爾值(最多32個)打包到單個使用位操作變量
的四字節(jié)整型。我們將在第一次使用位域變量時詳細介紹,請參
閱Wikipedia
?GLbyte:有符號單字節(jié)整型,包含數值從-128到127
?GLshort:有符號雙字節(jié)整型,包含數值從-32,768到32,767
?GLint:有符號四字節(jié)整型,包含數值從-2,147,483,648至U
2,147,483,647
?GLsizei:有符號四字節(jié)整型,用于代表數據的尺寸(字節(jié)),類似于C中
的size_t
?GLubyte,無符號單字節(jié)整型,包含數值從0到255。
?GLushort:無符號雙字節(jié)整型,包含數值從0到65,535
?GLuint:無符號四字節(jié)整型,包含數值從0至!J4,294,967,295
?GLfloat:四字節(jié)精度IEEE754-1985浮點數
?GLclampf:這也是四字節(jié)精度浮點數,但OpenGL使用GLclampf特別表示
數值為0.0至U1.0
?GLvoid:void值用于指示一個函數沒有返回值,或沒有參數
?GLfixed:定點數使用整型數存儲實數。由于大部分計算機處理器在處理整
型數比處理浮點數快很多,這通常是對3D系統(tǒng)的優(yōu)化方式。但因為
iPhone具有用于浮點運算的矢量處理器,我們將不討論定點運算或
GLfixed數據類型。
?GLclampx:另一種定點型,用于使用定點運算來表示0.0到1.0之間的實
數。正如GLfixed,我們不會討論或使用它。
OpenGLES(至少iPhone目前所使用的版本)不支持8字節(jié)(64位)數據類
型,如long或double。OpenGL其實具有這些大型數據類型,但考慮到大部
分嵌入式設備屏幕尺寸以及可能為它們所寫的程序類型而且使用它們有可能
對性能造成不利的影響,最后的決定是在OpenGLES中排除這些數據類型。
點或頂點
3D圖像的最小單位稱為點(point)或者頂點vertex。它們代表三維空間
中的一個點并用來建造更復雜的物體。多邊形就是由點構成,而物體是由多
個多邊形組成。盡管通常OpenGL支持多種多邊形,但OpenGLES只支持三
邊形(即三角形)。
如果你回憶高中學過的幾何學,你可能會記得所謂笛卡爾坐標?;靖拍钍?/p>
在空間中任選一點,稱作原點。然后你可以通過參照原點并使用三個代表三
維的數值指定空間中的任意一點,坐標是由三個想象的通過原點線表示的。
從左至右的想象直線叫x-軸。沿著x-軸從左至右數值變大,向左移動數值變小。
原點左方x為負值,右邊為正值。另外兩軸同理。沿y軸向上,y值增加,向下
y值減小。原點上方y(tǒng)為正,原點下方為負。對于z軸,當物體離開觀察者,數
值變小,向觀察者移動(或超出觀察者),數值變大。原點前方z值為正,原
點之后為負。下圖幫助說明了這一點:
Note:iPhone上另--種繪圖框架CoreGraphics使用了稍微不同的坐標系統(tǒng),
當向屏幕上方移動時y值減小,而向下移動y值增加。
沿各軸增加或減小的數值是以任意刻度進行的-它們不代表任何真實單位,
如英尺,英寸或米等。你可以選擇任何對你的程序有意義的刻度。如果你想
設計的游戲以英尺為單位,你可以那樣做。如果你希望單位為毫米,同樣可
行。OpenGL不管它對最終用戶代表什么,只是將它作為單位處理,保證它
們具有相同的距離。
由于任何物體在三維空間中的方位可以由三個數值表示,物體的位置通常在
OpenGL中由使用一個三維數組的三個GLfloat變量表示,數組中的第一項(索
引0)為x位置,第二項(索弓11)為y位置,第三項(索弓|2)為z位置。下面
是一個創(chuàng)建OpenGLES頂點的簡單例子:
GLfloatvertex[3];
vertex[0]=10.0;//x
vertex[1]=23.75;//y
vertex[2]=-12.532;//z
在OpenGLES中,通常將場景中所有構成所有或部分物體的提交為頂點數組。
一個頂點數組是包括場景中部分或所有頂點數據的簡單數組。我將在系列的
下一篇教程中討論,有關頂點數組要記住的是它們的大小是基于呈現(xiàn)的頂點
數乘以三(三維空間繪圖)或二(二維空間繪圖)。所以一個包含六個三維
空間中的三角形的頂點數組由54個GLfloat組成,因為每個三角形有三個頂點,
而每個頂點有三個坐標,即6x3x3=540
處理所有這些GLfloat是很痛苦的事情。幸運的是,有一個容易的方法。我
們可以定義一個數據結構了保存多個頂點,像這樣:
typedefstruct{
GLfloatx;
GLfloaty;
GLfloatz;
}Vertex3D;通過這樣做,我們的代碼可讀性更強:
Vertex3Dvertex;vertex.x=10.0;vertex.y=23.75;vertex.z=-12.532;
現(xiàn)在由于Vertex3D由三個GLfloat組成,向Vertex3D傳遞指針與向數組傳遞一
個包含三個GLfloat的數組的指針完全一樣。對于電腦而言毫無分別;兩者具
有同樣的尺寸和同樣的字節(jié)數以及OpenGL需要的同樣的順序。將數據分組
到數據結構只是讓程序員感到更容易,處理起來更方便。如果你下載了文章
開頭處的Xcode模板,你會發(fā)現(xiàn)此數據結構以及我后面將討論的各種函數都
定義在文件OpenGLCommon.h中。還有一個內聯(lián)函數用于創(chuàng)建單個頂點:
staticinlineVertex3DVertex3DMake(CGFIoatinX,CGFIoatinY,CGFIoat
inZ){Vertex3Dret;ret.x=inX;ret.y=inY;ret.z=inZ;returnret;}
如果你回憶起幾何學(如果不記得也不要緊)的內容,你會知道空間中兩點
間的距離是使用下面公式計算的:
22
V(X2-Xj+(y2-yj+(Z2-zy
我們可以在一個簡單的內聯(lián)函數中實現(xiàn)這個公式來計算三維空間中任何兩點
間的直線距離:
staticinlineGLfloatVertex3DCalculateDistanceBetweenVertices(Vertex3D
first,Vertex3Dsecond){GLfloatdeltaX=second.x-first.x;GLfloat
deltaY=second.y-first.y;GLfloatdeltaZ=second.z-first.z;return
sqrtf(deltaX*deltaX+deltaY*deltaY+deltaZ*deltaZ);};
三角形
由于OpenGLES僅支持三角形,因此我們可以通過創(chuàng)建一個數據結構將三個
頂點組合成一個三角形物體。
typedefstruct{
Vertex3Dvl;
Vertex3Dv2;
Vertex3Dv3;
}Triangle3D;
?個Triangle3D實際上與一個九個GLfloat構成的數組是完全一樣的,因為我
們通過頂點和三角形而不是GLfloat數組來構建物體,所以它能幫助我們更容
易地處理我們的代碼。
然而關于三角形你需要知道更多的事情。在OpenGL中有一個概念叫卷繞
(winding),它表示頂點繪制的次序是重要的。不像真實世界中的物體,
OpenGL中的多邊形通常都不會有兩面。它們只有一面,被當做frontface
(前面),三角形只有其frontface面對觀察者時才可見。可以設置OpenGL
將多邊形作為兩面處理,但默認狀態(tài)下,三角形只有一個可見面。通過知道
哪一個面是多邊形的前面或可見面,才能使OpenGL只做一半的計算。
盡管有時多邊形也可以獨立存在,需要繪制其背面,但通常三角形是一個大
物體的一部分,其面對物體內部的一面永遠也不可見。不被繪制的一面稱為
backface(背面),OpenGL是通過觀察頂點的繪制次序來確定frontface
和backface的。以反時針次序繪制頂點的構成的面是frontface(默認,可以
改變)。由于OpenGL可以很容易確定哪個三角形對用戶可見,所以它使用
了-一種稱為BackfaceCulling(隱面消除)的技術來避免繪制視窗中多邊
形的不可見面。下一篇文章將討論視窗,現(xiàn)在你可將其想象成一個虛擬攝像
或觀察OpenGL世界的虛擬窗口。
上圖中,左邊青色的三角形是backface,因此將不可見。而右方的三角形是
frontface,所以將被繪制。
本系列的下一篇文章將設定一個OpenGL的虛擬世界并使用
Vertex3D和Triangle3D進行一些基本繪圖。再后,我們將討論變換,它使
用線性代數在虛擬世界中移動物體。
從零開始學習OpenGLES之二-簡單繪圖概
述byfengliu
還有許多理論知識需要討論,但與其花許多時間在復雜的數學公式或難以理
解的概念上,還不如讓我們開始熟悉OpenGLES的基本繪圖功能。請下載
OpenGLXcode項目模板。我們使用此模板而不是Apple提供的模板。你可以
解壓到下面目錄來安裝它:
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Pr
ojectTemplates/Application/
此模板用于全屏OpenGL程序,它具有一個OpenGL視圖以及相應的視圖控制
器。大部分時候你不需要動到此視圖。此視圖用于處理一些諸如緩存切換之
類的事物,但在兩處調用了其控制器類。
首先,當設定視圖時.,調用了一次控制器。調用視圖控制器的setupView:方
法使控制器有機會增加所需的設定工作。這里是你設定視口,添加光源以及
進行其他項目相關設定的地方?,F(xiàn)在我們將忽略此方法。此方法中已經有非
?;镜脑O定以允許你進行簡單地繪圖。
控制器的drawView:方法根據常數kRenderingFrequency的值定期地被調用。
kRenderingFrequency的初始值為15.0,表示drawView:方法每秒鐘被調用
15次。如果你希望改變渲染的頻率,你可以在ConstantsAndMacros.h中找
到此常數的定義。
首先加入下列代碼到GLViewController.m的drawView:方法中:
?-(void)drawView:(GLView*)view;
?{
?Vertex3Dvertexl=Vertex3DMake(0.0,1.0,-3.0);
?Vertex3Dvertex2=Vertex3DMake(1.0,0.0,-3.0);
?Vertex3Dvertex3=Vertex3DMake(-1.0,0.0,-3.0);
?Triangle3Dtriangle=Triangle3DMake(vertex1,vertex2,vertex3);
?glLoadldentity();
?glClearColor(0.7,0.7,0.7,1.0);
?glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
?glEnableClientState(GL_VERTEX_ARRAY);
?glColor4f(1.0,0.0,0.0,1.0);
?glVertexPointer(3,GL_FLOAT,0,>triangle);
?glDrawArrays(GL_TRIANGLES,0,9);
?glDisableClientState(GL_VERTEX_ARRAY);
?}
在討論我們到底做了什么之前,先運行一下,你應該看到以下畫面:
這是個簡單的方法;如果你試過了,你可能已經知道我們到底做了什么,但
這里我們還是一起過一?遍。因為我們的任務是畫三角形,所以需要三個頂點,
因此我們創(chuàng)建三個上一篇文章中討論過的Vertex3D對象:
Vertex3Dvertexl=Vertex3DMake(0.0,1.0,-3.0);
Vertex3Dvertex2=Vertex3DMake(1.0,0.0,-3.0);
Vertex3Dvertex3=Vertex3DMake(-1.0,0.0,-3.0);
你應該注意到了三個頂點的z值是一樣的,其值(-3.0)是處于原點“之后”的。
因為我們還沒有做任何改變,所以我們是站在原點上觀察虛擬世界的,這是
默認的起點位置。將三角形放置在z值為-3處,可以保證我們可以在屏幕上看
到它。
隨后,我們創(chuàng)建一個由這三個頂點構成的三角形。
Triangle3Dtriangle=Triangle3DMake(vertexlzvertex2,
vertex3);
這些代碼很容易理解,對嗎?但是,在幕后,電腦是將其視為一個包含9
個GLfloat的數組。如下:
GLfloattriangle[]={0.0,1.0,-3.0,1.0,0.0,-3.0,-1.0,0.0,
-3.0};
并不是完全相同-這里有一個很小但很重要的區(qū)別。在我們的示例中,我們
傳遞給OpenGL的是Triangle3D對象的地址(即&triangle),但在第二個
使用數組的示例中,由于C數組是指針,我們傳遞的是數組。現(xiàn)在不需要考慮
太多,因為這將是最后一次我用這種方式(第二種方法)定義一個Triangle3D
對象。等一下我將解釋原因,但現(xiàn)在讓我們先過一遍代碼。下一步我們做的
是加載單位矩陣。我將花至少一整篇文章討論變換矩陣。我們暫且將其視為
OpenGL的“復位開關”。它將清除虛擬世界中的一切旋轉,移動或其他變化并
將觀察者置于原點。
glLoadldentity();
之后,我們告訴OpenGL所有的繪制工作是在一個灰色背景上進行的。
OpenGL通常需要用四個鉗位值來定義顏色。上一篇文章中有提過,鉗位浮
點數是0.0到1.0之間的浮點數。我們通過定義紅,綠,藍以及alpha元素來
定義顏色,alpha值定義了顏色之后物體的透視程度。現(xiàn)在暫時不用管它,
將其設為1.0,代表完全不透明。
glClearColor(0.7,0.7,0.7,1.0);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
在OpenGL中要定義白色,我行需要將四年元素全部設痂.0。要定義不透明
的黑色,則定義紅,綠,藍為0.0,alpha為1.0。上例代碼的第二行是通知
OpenGL清除以前的一切圖形并將其設為clear顏色。
你可能想知道glClear。的兩個參數是什么意思。簡單地說,它們是存儲與位
域中的常量。OpenGL保存了一系列緩存(buffers),即用于繪圖各方面
的內存塊。將這兩個值進行邏輯或是通知OpenGL清除兩個不同的緩存-顏
色緩存(colorbuifer)和深度緩存(depthbuffer)。顏色緩存保存當
前幀各像素的顏色?;旧暇褪悄阍谄聊簧峡吹降?。深度緩存(有時也稱為
"z-buffer")保存每個潛在像素離觀察者距離的信息。使用此信息可以確定一
個像素是否需要被繪制出來。這兩個緩存是OpenGL中最常見的緩存。還有
其他一些緩存,如模板緩存(stencilbuffer)和累積緩存(accumulationbuffer),
但現(xiàn)在我們暫時不討論這些。我們現(xiàn)在只需記住在繪制一幀之前,必須清除
這兩個緩存以保證不會和以前的內容混雜。
然后,我們要啟動OpenGL的一項稱為vertexarrays(頂點數組)的特性。
此特性可能只需要setupView:方法中啟動一次,但作為基本準則,我喜歡啟
動和禁止我使用的功能。你永遠也不會知道是否另一段代碼會做不同處理。
如果你打開你需要的功能然后關閉它,產生問題的兒率將大為減小。就本例
來說,如果另一個類不使用頂點數組而使用頂點緩存的話,任何一段代碼遺
留了啟動了的特性或沒有顯性啟動其需要的特性,這一段或兩段代碼都會導
致不可預知的結果。
glEnableClientState(GL_VERTEX_ARRAY);
接下來我們設置了繪圖時所需的顏色。血行代碼將繪圖顏色設為鮮艷的紅色。
glColor4f(1.0,0.0,0.0,1.0);
現(xiàn)在,直到下次調用glColor4fo前所有的圖形都是以紅色繪制。有一些例外
的情況,例如繪制紋理形狀時,但基本上,這樣設定顏色可以使顏色保持。
由于我們使用頂點數組,我們必須通知OpenGL頂點的數組在什么地方。記
住,頂點數組只是一個GLfloat的C數組,每三個值代表一個頂點。我們創(chuàng)建
了Triangle3D對象,但在內存中,它完全等同于9個連續(xù)的GLfloat,所以我
們可以傳遞此三角形對象的地址。
glVertexPointer(3,GL_FL0AT,0,&triangle);
glVertexPointer()的第一個參數指示了多少個GLfloat代表一個頂點。根據你
是在進行二維或三維繪圖,你可以傳遞2或者3。盡管我們的物體是存在于一
個平面的,我們仍然將其繪制在三維虛擬世界中,因此每個頂點用三個數值
表示,所以我們傳遞3給函數。然后,我們傳遞一個枚舉值告訴OpenGL頂點
是由GLfloat構成。OpenGLES允許你在當地數組中使用大部分的數據類型,
但除GL_FLOAT外,其他都很少見。下一個參數…現(xiàn)在不需要考慮下一個參
數。那是以后討論的主題。現(xiàn)在,它始終為0。在以后的文章中,我將討論怎
樣使用此參數將同一對象以不同的數據類型混雜在一個數據結構中。
隨后,我們通知OpenGL通過剛才提交的頂點數組來繪制三角形。
glDrawArrays(GL_TRIANGLES,0,9);
你可能已經可以猜到「第一個枚舉值是告訴OpenGL繪制什么。盡管OpenGL
ES不支持繪制三角形之外的四邊形或其他多邊形,但它仍然支持一些其他繪
圖模式,如繪制點,線,線回路,三角形條和三角形扇。稍后我們將討論這
些繪圖模式。
最后,我們要禁止先前啟動了的特性以保證不會被其他地方的代碼弄混。本
例中沒有其他的代碼了,但通常你可以使用OpenGL繪制多個物體。
glDisableClientState(GL_VERTEX_ARRAY);
好了,我們的代碼可以工作了盡管它不宜那么引人入勝而且不是十分高效,
但它確確實實可以工作。每秒鐘我們的代碼被調用數次。不相信?加入下列
黑體代碼再次運行:
1(void)drawView:(GLView*)view;
2.{
3.staticGLfloatrotation=0.0;
4.
5.Vertex3Dvertexl=Vertex3DMake(0.0,1.0,-3.0);
6.Vertex3Dvertex2=Vertex3DMake(1.0,0.0,-3.0);
7.Vertex3Dvertex3=Vertex3DMake(-1.0,0.0,-3.0);
8.Triangle3Dtriangle=Triangle3DMake(vertex1,vertex2,vertex3);
9.
10.glLoadldentity();
11.gIRotatef(rotation,0.0,0.0,1.0);
12.glClearColor(0.7,0.7,0.7,1.0);
13.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
14.glEnableClientState(GL_VERTEX_ARRAY);
15.glColor4f(1.0,0.0,0.0,1.0);
16.glVertexPointer(3,GL_FLOAT,0,>triangle);
17.glDrawArrays(GL_TRIANGLES,0,9);
18.glDisableClientState(GL_VERTEX_ARRAY);
19.
20.rotation+=0.5;
21.}
當你再次運行時,三角形將沿著原點緩緩轉動。先不需要關注太多旋轉的邏
輯。我只是想告訴你我們的代碼每秒鐘被調用了多次。
如果你想畫正方形怎么辦?OpenGLES并不支持正方形,所以我們只能通過
三角形來定義正方形。這很簡單-一個正方形可以通過兩個三角形構成。我
們要怎樣調整上敘代碼來繪制兩個三角形?是不是可以創(chuàng)建兩個Triangle3D?
是的,你可以這樣做,但那沒有效率。我們最好將兩個三角形置入同一個頂
點數組中。我們可以通過定義一個包含兩個Triangle3D對象的數組,或分配
大小等于兩個Triangle3D對象或18個GLfloat的內存.
這是一種方法:
1(void)drawView:(GLView*)view;
2.{
3.Triangle3Dtriangle[2];
4.triangle[0].v1=Vertex3DMake(0.0,1.0,-3.0);
5.triangle[0].v2=Vertex3DMake(1.0,0.0,-3.0);
6.triangle[0].v3=Vertex3DMake(-1.0,0.0,-3.0);
7.triangle[1].v1=Vertex3DMake(-1.0,0.0,-3.0);
8.triangle[1].v2=Vertex3DMake(1.0,0.0,-3.0);
9.triangle[1].v3=Vertex3DMake(0.0,-1.0,-3.0);
10.
11.glLoadldentity();
12.glClearColor(0.7,0.7,0.7,1.0);
13.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
14.glEnableClientState(GL_VERTEX_ARRAY);
15.glColor4f(1.0,0.0,0.0,1.0);
16.glVertexPointer(3,GL_FLOAT,0,>triangle);
17.glDrawArrays(GL_TRIANGLES,0,18);
18.glDisableClientState(GL_VERTEX_ARRAY);
19.}
運行,你將看到如下屏幕:
由于Vertex3DMake()方法在堆中創(chuàng)建新的Vertex3D然后復制其值到數組中
而造成額外的內存被占用,因此上述代碼并不理想。
對于這樣簡單的情況是沒有什么問題的,但是在一個更為復雜的情況下,如
果定義的3D物體很大時,那么你不會希望將其分配在堆中而且你不會希望對
一個頂點不止一次地進行內存分配,所以最好是養(yǎng)成習慣通過我們的老朋
友malloc。(我更喜歡用calloc(),因為它會將所有值設為0,比較易于查找
錯誤)將頂點分配在棧中。首先我們需要一個函數設定現(xiàn)存頂點的值而不是
像Vertex3DMake()-一樣創(chuàng)建一個新對象。如下:
1.staticinlinevoidVertex3DSet(Vertex3D*vertex,CGFIoatinX,CGFIoatin
Y,CGFIoatinZ)
2.{
3.vertex->x=inX;
4.vertex->y=inY;
5.vertex->z=inZ;
6.}
現(xiàn)在,我們使用新方法將兩個三角形分配在棧中,重寫代碼:
1(void)drawView:(GLView*)view;
2.{
3.Triangle3D*triangles=malloc(sizeof(Triangle3D)*2);
4.」
5.Vertex3DSet(>triangles[0].v1,0.0,1.0,-3.0);
6.Vertex3DSet(>triangles[0].v2,1.0,0.0,-3.0);
7.Vertex3DSet(>triangles[0].v3,-1.0,0.0,-3.0);
8.Vertex3DSet(>triangles[1].v1,-1.0,0.0,-3.0);
9.Vertex3DSet(>triangles[1].v2,1.0,0.0,-3.0);
10.Vertex3DSet(>triangles[1].v3,0.0,-1.0,-3.0);
11.
12.glLoadldentity();
13.glClearColor(0.7,0.7,0.7,1.0);
14.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
15.glEnableClientState(GL_VERTEX_ARRAY);
16.glColor4f(1.0,0.0,0.0,1.0);
17.glVertexPointer(3,GL_FLOAT,0,triangles);
18.glDrawArrays(GL_TRIANGLES,0,18);
19.glDisableClientState(GL_VERTEX_ARRAY);
20.
21.if(triangles!=NULL)
22.free(triangles);
23.}'
好了,我們已經討論了許多基礎知識,我們現(xiàn)在更深入一點。記住我說過
OpenGLES不止一種繪圖方式嗎?現(xiàn)在正方形需要6個頂點(18個GLfloat),
實際上我們可以使用trianglestrips(GL_TRIANGLE_STRIP)方法通過四
個頂點(12個GLfloat)來繪制正方形。
這里是三角形條的基本概念:第一個三角形條是由前三個頂點構成(索引0,1,
2)o第二個三角形條是由前一個三角形的兩個頂點加上數組中的下一個頂點
構成,繼續(xù)直到整個數組結束。看下圖更清楚-第一個三角形由頂點1,2,3
構成,下一個三角形由頂點2,3,4構成,等等:
所以,我們的正方形是這樣構成的:
13
204
代碼如下:
1(void)drawView:(GLView*)view;
2.{
3.Vertex3D*vertices=malloc(sizeof(Vertex3D)*4);
4.
5.Vertex3DSet(>vertices[0],0.0,1.0,-3.0);
6.Vertex3DSet(>vertices[1],1.0,0.0,-3.0);
7.Vertex3DSet(>vertices[2],-1.0,0.0,-3.0);
8.Vertex3DSet(>vertices[3],0.0,-1.0,-3.0);
9.
10.glLoadldentity();
11.
12.glClearColor(0.7,0.7,0.7,1.0);
13.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
14.glEnableClientState(GL_VERTEX_ARRAY);
15.glColor4f(1.0,0.0,0.0,1.0);
16.glVertexPointer(3,GL_FLOAT,0,vertices);
17.glDrawArrays(GL_TRIANGLE_STRIP,0,12);
18.glDisableClientState(GL_VERTEX_ARRAY);
19.
20.if(vertices!=NULL)
21.free(vertices);
22.}
我們再返回到第一段代碼看看。記住我們是怎樣繪制第一個三角形嗎?我們
使用glColor4f()設置顏色并且說設置的顏色一直適用于隨后的代碼。那意味著
定義于頂點數組中的物體必須用同--種顏色繪制。很有局限性,對嗎?
并非如此。正如OpenGLES允許你將所有頂點置于一個數組中,它還允許
你將每個頂點使用的顏色置于一個顏色數組(colorarray)中。如果你選
擇使用顏色數組,那么你需要為每個頂點設置顏色(四個GLfloat值)。通過
下面方法啟動顏色數組:
glEnableClientState(GL_C0L0R_ARRAY);
我們可以象頂點數組一樣定義一不包含四個GLfloat成員的Color3D結構。下
面是怎樣為原始三角形分配不同顏色的示例:
1(void)drawView:(GLView*)view;
2.{
3.Vertex3Dvertexl=Vertex3DMake(0.0,1.0,-3.0);
4.Vertex3Dvertex2=Vertex3DMake(1.0,0.0,-3.0);
5.Vertex3Dvertex3=Vertex3DMake(-1.0,0.0,-3.0);
6.Triangle3Dtriangle=Triangle3DMake(vertex1,vertex2,vertex3);
7.」
8.Color3D*colors=malloc(sizeof(Color3D)*3);
9.Color3DSet(>colors[0],1.0,0.0,0.0,1.0);
10.Color3DSet(>colors[1],0.0,1.0,0.0,1.0);
11.Color3DSet(>colors[2],0,0,0.0,1.0,1.0);
12.
13.glLoadldentity();
14.glClearColor(0.7,0.7,0.7,1.0);
15.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
16.glEnableClientState(GL_VERTEX_ARRAY);
17.glEnableClientState(GL_COLOR_ARRAY);
18.glColor4f(1.0,0.0,0.0,1.0);
19.glVertexPointer(3,GL_FLOAT,0,>triangle);
20.glColorPointer(4,GL_FLOAT,0,colors);
21.glDrawArrays(GL_TRIANGLES,0,9);
22.glDisableClientState(GL_VERTEX_ARRAY);
23.glDisableClientState(GL_COLORjARRAY);
24.
25.if(colors!=NULL)
26.free(colors);
27.)
運行,屏幕如下:
今天我們還要討論一個話題。如果我們不止一次使用一個頂點(三角形條或
三角形扇的相鄰頂點除外),我們至今使用的方法存在一個問題,我們必須
多次向OpenGL傳遞同一頂點。那不是什么大問題,但通常我們需要盡量減
少向OpenGL傳遞的數據量,所以一次又一次地傳遞同一個4字節(jié)浮點數是非
常地不理想。在一些物體中,某個頂點會用在七個甚至更多的不同三角形中,
因此你的頂點數組可能會增大許多倍。
當處理這類復雜的幾何體時,有一種方法可以避免多次傳遞同一個頂點,就
是使用通過頂點對應于頂點數組中的索引的方法,此方法稱之為元素
(elements)o其原理是創(chuàng)建一個每個頂點只使用一次的數組。然后使用
另一個使用最小的無符號整型數的數組來保存所需的唯一頂點號。換句話說,
如果頂點數組具有小于256個頂點,那么你應創(chuàng)建一個GLubyte數組,如果大
于256,但小于65,536,應使用GLushorto你可以通過映射頂點在第一個
數組中的索引值來創(chuàng)建三角形(或其他形狀)。所以,如果你創(chuàng)建了一個具
有12個頂點的數組,那么數組中的第一個頂點為0。你可以按以前一樣的方法
繪制圖形,只不過不是調用glDrawArrays。,而是調用不同的函
數glDrawElements。并傳遞整數數組。
讓我們以一個真實的,如假包換的3D形狀來結束我們的教程:一個二十面體。
每個人都使用正方體,但我們要更怪異一點,我們要畫一個二十面體。替換
drawView:
1(void)drawView:(GLView*)view;
2.{
3.
4.staticGLfloatrot=0.0;
5.
6.//ThisisthesameresultasusingVertex3D,justfastertotypeand
7.//canbemadeconstthisway
8.staticconstVertex3Dvertices[]={
9.{0,-0.525731,0.850651),//vertices[0]
10.{0.850651,0,0.525731),//vertices[1]
11.{0.850651,0,-0.525731),//vertices[2]
12.{-0.850651,0,-0.525731),//vertices[3]
13.{-0.850651,0,0.525731),//vertices[4]
14.{-0.525731,0,850651,0},//vertices[5]
15.{0.525731,0.850651,0},//vertices[6]
16.{0.525731,-0.850651,0},//vertices[7]
17.{-0.525731,-0.850651,0},//vertices[8]
18.{0,-0.525731,-0.850651),//vertices[9]
19.{0,0.525731,-0.850651),//vertices[10]
20.{0,0.525731,0.850651}//vertices[11]
21.);
22.
23.staticconstColor3Dcolors[]={
24.{1.0,0.0,0.0,1.0},
25.{1.0,0.5,0.0,1.0},
26.{1.0,1.0,0.0,1.0},
27.{0.5,1.0,0.0,1.0),
28.{0.0,1.0,0.0,1.0},
29.{0.0,1.0,0.5,1.0},
30.{0.0,1.0,1.0,1.0},
31.{0.0,0.5,1.0,1.0},
32.{0.0,0.0,1.0,1.0},
33.{0.5,0.0,1.0,1.0),
34.{1.0,0.0,1.0,1.0},
35.{1.0,0.0,0.5,1.0}
36.};
37.
38.staticconstGLubyteicosahedronFaces[]={
39.1,2,6,
40.1,7,2,
41.3,4,5,
42.4,3,8,
43.6,5,11,
44.5,6,10,
45.9,10,2,
46.10,9,3,
47.7,8,9,
48.8,7,0,
49.11,0,1,
50.0,11,4,
51.6,2,10,
52.1,6,11,
53.3,5,10,
54.5,4,11,
55.2,7,9,
56.7,1,0,
57.3,9,8,
58.4,8,0,
59.};
60.
61.glLoadldentity();
62.glTranslatef(0.0f,0.0f,-3.0f);
63.glRotatef(rot,1.Of,1.Of,1.Of);
64.glClearColor(0.7,0.7,0,7,1.0);
65.glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
66.glEnableClientState(GL_VERTEX_ARRAY);
67.glEnableClientState(GL_COLORjARRAY);
68.glVertexPointer(3,GL_FLOAT,0,vertices);
69.glColorPointer(4,GL_FLOAT,0,colors);
70.
71.glDrawElements(GL_TRIANGLES,60,GL_UNSIGNED_BYTE,ic
osahedronFaces);
72.
73.glDisableClientState(GL_VERTEX_ARRAY);
74.glDisableClientState(GL_COLORjARRAY);
75.staticNSTimelntervallastDrawTime;
76.if(lastDrawTime)
77.{
78.NSTimelntervaltimeSinceLastDraw=[NSDatetimelntervalSinc
eReferenceDate]-lastDrawTime;
79.rot+=50*timeSinceLastDraw;
80.}
81.lastDrawTime=[NSDatetimelntervalSinceReferenceDate];
82.}
運行,屏幕上將看到一個漂亮的旋轉物體:
因為我們沒有使用光源而且即使使用了光源我們也沒有告訴OpenGL怎樣進
行光源反射,所以它看上去還不是一個完美的3D物體。有關光線的部分將在
后續(xù)文章中討論。
這里我們做了什么?首先,我們建立了一個靜態(tài)變量來跟蹤物體的旋轉。
staticGLfloatrot=0.0;
然后我們定義頂點數組。我們使用了一個與前不同的方法,但結果是一樣的。
由于我們的兒何體根本不會變化,所以我們將其定義為const,這樣就不需要
每一幀都分配/清除內存:
staticconstVertex3Dvertices[]=
0,-0.525731,0.850651),//vertices[0]
0.850651,0,0.525731},//vertices[1]
0.850651,0,-0.525731),//vertices[2]
-0.850651,0,-0.525731),//vertices[3]
-0.850651,0,0.525731J,//vertices[4]
-0.525731,0.850651,0},//vertices[5]
0.525731,0.850651,0},//vertices[6]
0.525731,-0.850651,0},//vertices[7]
-0.525731,-0.850651,01,//vertices[8]
0,-0.525731,-0.850651),//vertices[9]
0,0.525731,-0.850651),//vertices[10]
0,0.525731,0.850651)//vertices[11]
);
然后用同樣方法建立一個顏色數組。創(chuàng)建一個Color3D對象數組,每項對應
于前一個數組的頂點:
staticconstColor3Dcolors[]=
1.0,0.0,0.0,1.0
1.oz0.5,0.0,1.0
1.0,1.0,0.0,1.0
0.5,1.0z0.0,1.0
0.0,1.0,0.0,1.0
0.0,1.0,0.5,1.0
0.0,1.0,1.0,1.0
0.0,0.5,1.0,1.0
0.0,0.0z1.0,1.0
0.5,0.0,1.0,1.0
1.0,0.0,1.oz1.0
1.0,0.0,0.5,1.0
);
最后,創(chuàng)建二十面體。上述十二個頂點本身并未描述形狀。OpenGL需要知
道怎樣將它們聯(lián)系在一起,所以我們創(chuàng)建了一個整型數組(GLubyte)指向
構成各三角形的頂點。
staticconstGLubyteicosahedronFaces[]
1,2,6,
lz7,2,
3,4,5,
4,3,8Z
6,5,11,
5,6,10,
9,10,2Z
10,9,3,
7,8,9,
8,7,0,
11,0,1,
0,11,4,
6,2,10,
1,6,11,
3,5Z10,
5,4f11,
2,7,9,
7,1,0,
3,9,8Z
4,8,0,
);
二十面體的第一個面的三個數是126,代表繪制處于索引1(0.850651,0,
0.525731),2(0.850651,0,0.525731),和6(0.525731,0.850651,0)之間
的三角形。
下面一段代碼沒有新內容,只是加載單元矩陣(所以變換復位),移動并旋
轉幾何體,設置背景色,清除緩存,啟動頂點和顏色數組,然后提供頂點數
組數據給OpenGL。所有這些在前一個例子中都有涉及。
glLoadldentity();
glTranslatef(0.Ofz0.Of,-3.Of);
glRotatef(rot,1.Ofz1.Ofz1.Of);
glClearColor(0.7,0.7,0.7,1.0);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);一
glEnableClientState(GL二COLORjRRAY);
glColor4f(1.0z0.0,0.0,1.0?;
glVertexPointer(3,GL_FLOATz0,vertices);
glColorPointer(4,GL_FLOATz0,colors);
然后,我們沒有使用glDrawArrays。。而是調用glDrawElements():
glDrawElementS(GL_TRIANGLES,60,GL_UNSIGNED_BYTE,
icosahedronFaces)*
接著,執(zhí)行禁止功德,根據距上一幀繪制的時間增加旋轉變量值:
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
staticNSTimeIntervallastDrawTime;
if(lastDrawTime)
(
NSTimelntervaltimesinceLastDraw=[NSDate
timelntervalSinceReferenceDate]-lastDrawTime;
rot+=50*timesinceLastDraw;
)
lastDrawTime=[NSDatetimelntervalSinceReferenceDate];
記住:如果你按繪制的正確次序提供頂點,那么你應該使用glDrawArrays(),
但是如果你提供一個數組然后用另一個以索引值區(qū)分頂點次序的數組的話,
那么你應該使用glDrawElementS。。
請花些時間測試繪圖代碼,添加更多的多邊形,改變顏色等。OpenGL繪圖
功能遠超過本文涉及的內容,但你應該清楚iPhone上3D物體繪制的基本概
念了:創(chuàng)建一塊內存保存所有的頂點,傳遞頂點數組給OpenGL,然后由
OpenGL繪制出來。
從零開始學習OpenGLES之三-透視by
fengliu
現(xiàn)在你已經知道OpenGL是怎樣繪圖的了,讓我們回頭談談一個很重要的概
念:OpenGL視口(viewport)。許多人對3D編程還很陌生,那些使用過
像Maya,Blender,或Lightwave之類3D圖形程序的人都試圖在OpenGL虛
擬世界中找到‘攝像機”。但OpenGL并不存在這樣的東西。它所有的是在3D
空間中定義可見的物體。虛擬世界是沒有邊界的,但計算機不可能處理無限
的空間,所以OpenGL需要我們定義一個可以被觀察者看到的空間。
如果我們從大部分3D程序具有的攝像機對象的角度出發(fā)來考慮,視口端點的
中心就是攝像機。也就是觀察者站的位置。它是一個觀察虛擬世界的虛擬窗
口。觀察者可見的空間有一定限制。她看不見她身后的東西。她也看不見視
角之外的東西。而且她還不能看見太遠的東西??梢哉J為視口是通過“觀察者
可見”參數所確定的形狀。很簡單,對嗎?
不幸的是,并非如此。要解釋原因,我們首先需要討論的是在OpenGLES中
具有的兩種不同的視口類型:正交和透視。
正交和透視
為更好地理解,我們先看看鐵路軌道,好嗎?要正常工作,鐵路的兩條鐵軌
之間必須具有固定的距離。其固定的距離是由鐵軌根據承載什么樣的火車而
決定。重要的是鐵軌(以及火車的輪子)必須具有相同的距離。如果不是這
樣,火車根本不可能運行。
如果我們從上方觀察鐵軌,這個事實很明顯。
但是如果你站在鐵軌上向下觀察會怎么樣。不要說“你會被火車撞”,我假設你
會足夠聰明,會在沒有火車開動時進行觀察。
是的,鐵軌看上去越遠與靠近。感謝二年級美術老師,可能你已經知道這就
是所謂透視(perspective)。
OpenGL可以設定的視口中的一種就是使用透視。當你這樣設置視口時,物
體會隨著移遠而越來越小,視線會在物體移離觀察者時最終交匯。這是對真
實視覺的模擬;人們就是以這種方式觀察世界的。
另一一種看設置的視口稱為正交(orthogonal)視口。這種類型的視口,視
線永遠不會交匯而且物體不會改變其大小。沒有透視效果。對于CAD程序以
及其他各種目的是十分方便的,但因為它不像人們眼睛觀察的方式所以看上
去是不真實的,通常也不是你所希望的。
對于正交視口,你看將攝像機置于鐵軌上,但這些鐵軌永遠不會交匯。它們
將隨著遠離你的視線而繼續(xù)保持等距。即使你定義了一個無限大的視口
(OpenGLES并不支持),這些線仍保持等距。
正交視口的優(yōu)點是容易定義。因為線永不交匯,你只需定義一個像箱子一樣
的3D空間,像這樣:
設置正交視口
在使用glViewport。函數定義視口前,你可以通過glOrthof。通知OpenGLES
你希望使用正交視口。下面是一個簡單的例子:
CGRectrect=view.bounds;
glOrthof(-1.0,//Left
1.0z//Right
-1.0/(rect.size.width/rect.size.height),//Bottom
1.0/(rect.size.width/rect.size.height),//Top
0.01,//Near
10000.0);//Far
glViewport(0z0,rect.size.width,rect.size.height);
這不難理解。首先我們獲取視窗的尺寸。然后設定視口空間的寬度為兩個單
位,沿x軸從-1.0到+1.0o很容易吧。
接著怎樣設定底部和頂部?我們希望我們定義空間的X和丫坐標的寬高比與
視窗的寬高比(也就是iPhone全屏時的寬高比)一樣。由于iPhone的寬度與
高度不同,我們需要確保視口的x和y坐標不同,但遵循一樣的比例。
之后,我們定義了near(遠)和far(近)范圍來描述觀察的深度。near參
數說明了視口開始的位置。如果我們站在原點處,視口就位于我們的面前,
所以習慣上使用.01或.001作為正交視口的起點。這使得視口處于原點'前
方”一點點。far可以根據你程序的需要來設定。如果你程序中的物體永遠不
會遠過20個單位,那么你不需要將far設置為20,000個單位。具體的數字隨
程序的不同而不同。
調用glOrthof。之后,我們使用視窗矩形來調用glViewport()o
這是比較簡單的情況。
設置透視視口
另一種情況就不那么簡單,這里是原因。如果物體隨著遠離觀察者而變小,
那么它和你定義的可見空間的形狀有什么關系。隨著視線越來越遠,你可以
看到更廣闊的世界,所以如果你使用透視,那么你定義的空間將不是一個立
方體。是的,當使用透視時可見空間的形狀稱為錐臺(frustum)。是的,我
知道,奇怪的名字。但卻是真實的。我們的錐臺看上去像這樣:
請注意當我們離視口越來越遠時(換句話說,當z值減小時),觀察體的x和y
坐標都會越來越大。
要設置透視視口,我們不使用glOrthof。,我們使用一個不同的函
數glFrustumf。。此函數使用同樣的六個參數。很容易理解,但我們應該怎樣
確定傳遞給glFrustumf。的參數?
near和far容易理解。你可以同樣方式理解它們。near使用類似.001的數值,
然后根據不同程序的需要確定far值。
但是left,right,bottom,和top呢?為設置這些值,我們需要一點點數學計
算。
要計算錐臺,我們首先要理解視野(fieldofvision)的概念,它是由兩個
角度定義的。讓我們這樣做:伸出雙臂手掌合攏伸向前方。你的手臂現(xiàn)在指
向你自己錐臺的z軸,對嗎?好,現(xiàn)在慢慢分開你的雙臂。由于在你
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 藥物市場調研及分析能力試題及答案
- 激光技術的專利申請與保護策略試題及答案
- 戰(zhàn)略分析筆試題目及答案
- 衛(wèi)生管理證書及能力評估題及答案
- 患者發(fā)生腦疝試題及答案
- 目標明確2025年初級會計師試題及答案
- 藥品使用與經濟性的關系探討試題及答案
- 藥學研究方法論試題及答案
- 藥物質量標準的檢查試題及答案
- 電梯營銷考試題及答案
- 星際求職指南-札記
- 《初中生物實驗教學的創(chuàng)新與實踐》
- 控制計劃課件教材-2024年
- 期中檢測卷(試題)-2023-2024學年人教PEP版英語六年級下冊
- 共同辦展會合作協(xié)議書范文范本
- 《如何有效組織幼兒開展體能大循環(huán)活動》課件
- 浙教版高中信息技術必修2 1.1“信息技術與信息系統(tǒng)”教學設計(PDF版)
- 第19課+資本主義國家的新變化+教學設計 高一下學期統(tǒng)編版(2019)必修中外歷史綱要下
- 醫(yī)學課件慢性胰腺炎2
- CMOS光電圖像傳感器課件
- 基于BOPPPS模型的微觀經濟學網課教學設計探析-以市場結構理論的教學為例
評論
0/150
提交評論