在前一節(jié),我們對OpenGL進行概述和其中一些專有名詞幅恋⌒铀溃或許有些人剛開始對于接觸這些名詞,會有一頭霧水的感覺捆交,這是正常淑翼。
現(xiàn)在我們通過創(chuàng)建第一個項目,在屏幕中繪制出一個三角形品追,來理解這些名詞玄括。
我使用的iOS框架中的GLKViewController來繪制三角形,這個Controller已經(jīng)將OpenGL所需要的環(huán)境和變量進行了一次封裝肉瓦,可以方便開發(fā)者使用遭京。如果需要用復雜的并且可以控制的,也可以選擇用普通的View來轉(zhuǎn)化為OpenGL需要使用的環(huán)境泞莉,自己控制每一過程哪雕。
構(gòu)建OpenGL環(huán)境
OpenGL es有和我們使用圖像繪制一樣有個管理上下文的類,叫:EAGLContext鲫趁。創(chuàng)建時候會讓我們選擇OpenGL的Api版本斯嚎,我們這里選擇OpenGLES2
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(@"初始化失敗");
exit(1);
}
設(shè)置視圖環(huán)境和編碼
每個視圖在處理不同的圖片的時候都有著不同的視圖編碼,先進行設(shè)置挨厚,避免后面會有錯誤
GLKView *view = (GLKView *)self.view;
[view setContext:self.context];
view.drawableDepthFormat = GLKViewDrawableColorFormatRGBA8888;
//檢查當前上下文是否是我們穿件的 Context
if (![EAGLContext setCurrentContext:self.context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
這里我們就完成了對于OpenGL的環(huán)境的搭建和設(shè)置堡僻,接下來就要開始看看如何創(chuàng)建三角形,創(chuàng)建三角形都需要什么疫剃?
頂點輸入
開始繪制圖形之前苦始,我們必須給OpenGL輸入一些頂點數(shù)據(jù),也就是這個三角形需要在屏幕的什么位置慌申。
OpenGL是一個3D圖形庫陌选,所以我們在OpenGL中指定的所有坐標都是3D坐標(x、y蹄溉、z)咨油。OpenGL不是簡單地把所有的3D坐標變換為屏幕上的2D像素;OpenGL僅當3D坐標在3個軸(x柒爵、y和z)上都為-1.0到1.0的范圍內(nèi)時才處理它役电。
由于我們要創(chuàng)建一個2D的三角形,我們要給三個頂點坐標棉胀,每個頂點都表示一個在3D空間的位置法瑟。我們會將它們以標準化設(shè)備坐標的形式(OpenGL的可見區(qū)域)定義為一個float數(shù)組冀膝。
//頂點數(shù)據(jù)
const GLfloat vertices[] = {
0.5, -0.5, 0.0f, 1.0f, 0.0f, 0.0f, //右下(x,y,z坐標 + rgb顏色)
-0.5, 0.5, 0.0f, 0.0f, 1.0f, 0.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, 1.0f, //左下
};
因為我們要創(chuàng)建的是一個2D的三角形,所以這個三角形的深度都是一樣的霎挟,我們都給0.0f窝剖。
通常深度可以理解為z坐標,它代表一個像素在空間中和你的距離酥夭,如果離你遠就可能被別的像素遮擋赐纱,你就看不到它了,它會被丟棄熬北,以節(jié)省資源疙描。
另外我們可以創(chuàng)建一個索引坐標,用來控制三角形繪制順序讶隐。也可以不需要起胰,不需要的話,在最后的繪制方法需要有所變化巫延。這里我們使用索引來控制待错。和頂點坐標一樣需要創(chuàng)建一個數(shù)組:
const GLuint indices[] = {
0,1,2
};
定義這樣的頂點數(shù)據(jù)以后,我們會把它作為輸入發(fā)送給圖形渲染管線的第一個處理階段:頂點著色器烈评。它會在GPU上創(chuàng)建內(nèi)存用于儲存我們的頂點數(shù)據(jù)火俄,還要配置OpenGL如何解釋這些內(nèi)存,并且指定其如何發(fā)送給顯卡讲冠。
我們通過頂點緩沖對象 (Vertex Buffer Objects, VBO)管理這個內(nèi)存瓜客,它會在GPU內(nèi)存(通常被稱為顯存)中儲存大量頂點。使用這些緩沖對象的好處是我們可以一次性的發(fā)送一大批數(shù)據(jù)到顯卡上竿开,而不是每個頂點發(fā)送一次谱仪。從CPU把數(shù)據(jù)發(fā)送到顯卡相對較慢,所以只要可能我們都要嘗試盡量一次性發(fā)送盡可能多的數(shù)據(jù)否彩。當數(shù)據(jù)發(fā)送至顯卡的內(nèi)存中后疯攒,頂點著色器幾乎能立即訪問頂點,這是個非沉欣螅快的過程敬尺。
綁定頂點數(shù)據(jù)
頂點緩沖對象是中第一個出現(xiàn)的OpenGL對象。就像OpenGL中的其它對象一樣贴浙,這個緩沖有一個獨一無二的ID:
//用于跟蹤每個頂點信息的
GLuint verticesBuffer;
glGenBuffers(1, &verticesBuffer);//創(chuàng)建一個緩存對象
OpenGL有很多緩沖對象類型砂吞,頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER。OpenGL允許我們同時綁定多個緩沖崎溃,只要它們是不同的緩沖類型蜻直。我們可以使用glBindBuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標上:
//激活緩沖對象. OpenGL有很多緩沖對象類型,頂點緩沖對象的緩沖類型
glBindBuffer(GL_ARRAY_BUFFER, verticesBuffer);
從這一刻起,我們使用的任何(在GL_ARRAY_BUFFER目標上的)緩沖調(diào)用都會用來配置當前綁定的緩沖(頂點數(shù)據(jù)VBO)概而。然后我們可以調(diào)用glBufferData函數(shù)呼巷,它會把之前定義的頂點數(shù)據(jù)復制到緩沖的內(nèi)存中:
/**
GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變。
GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多赎瑰。
GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變王悍。
*/
//調(diào)用glBufferData函數(shù),它會把之前定義的頂點數(shù)據(jù)復制到緩沖的內(nèi)存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData是一個專門用來把用戶定義的數(shù)據(jù)復制到當前綁定緩沖的函數(shù)乡范。
- 第一個參數(shù)是目標緩沖的類型:頂點緩沖對象當前綁定到GL_ARRAY_BUFFER目標上。
- 第二個參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位)啤咽;用一個簡單的sizeof計算出頂點數(shù)據(jù)大小就行晋辆。
- 第三個參數(shù)是我們希望發(fā)送的實際數(shù)據(jù)。
- 第四個參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)宇整。它有三種形式:
- GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變瓶佳。
- GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多。
- GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變鳞青。
三角形的位置數(shù)據(jù)不會改變霸饲,每次渲染調(diào)用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW臂拓。
索引的使用流程和頂點數(shù)據(jù)的流程一樣厚脉,同樣需要綁定,當是索引綁定的類型不同
//用于跟蹤組成每個三角形的索引信息
GLuint indicesBuffer;
glGenBuffers(1, &indicesBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
鏈接頂點屬性(啟動頂點數(shù)據(jù))
頂點著色器允許我們指定任何以頂點屬性為形式的輸入胶惰。這使其具有很強的靈活性的同時傻工,它還的確意味著我們必須手動指定輸入數(shù)據(jù)的哪一個部分對應(yīng)頂點著色器的哪一個頂點屬性。所以孵滞,我們必須在渲染前指定OpenGL該如何解釋頂點數(shù)據(jù)中捆。
我們的頂點緩沖數(shù)據(jù)會被解析為下面這樣子:
- 位置數(shù)據(jù)被儲存為32位(4字節(jié))浮點值。
- 每個位置包含3個這樣的值坊饶。
- 在這3個值之間沒有空隙(或其他值)泄伪。這幾個值在數(shù)組中緊密排列(Tightly Packed)。
- 數(shù)據(jù)中第一個值在緩沖開始的位置匿级。
有了這些信息我們就可以使用glVertexAttribPointer函數(shù)告訴OpenGL該如何解析頂點數(shù)據(jù)(應(yīng)用到逐個頂點屬性上)了:
//開啟對應(yīng)的頂點屬性(坐標蟋滴,紋理坐標,顏色等)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribColor);
/**
為頂點屬性(坐標痘绎,紋理坐標脓杉,顏色等)配置合適的值
參數(shù)1:GLuint indx 聲明這個屬性的名稱
參數(shù)2:GLint size定義這個屬性由多少個值組成。譬如說position是由3個GLfloat組成
參數(shù)3: GLenum type聲明每一個值是什么類型简逮。我們都用了GL_FLOAT
參數(shù)4:GLboolean normalized , GL_FALSE就好了
參數(shù)5:GLsizei stride stride的大小球散,描述每個vertex數(shù)據(jù)的大小
參數(shù)6:const GLvoid* ptr , 數(shù)據(jù)結(jié)構(gòu)的偏移量。從這個結(jié)構(gòu)中哪里開始獲取值散庶。
Position的值在前面蕉堰,所以傳(GLfloat *)NULL + 0進去就可以了凌净。
而TexCoord是緊接著位置的數(shù)據(jù),而position的大小是3個float的大小屋讶,所以是從(GLfloat *)NULL + 3開始的
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL + 0);
glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL + 3);
這過程的完整代碼
- (void)setupVBOs {
//用于跟蹤每個頂點信息的
GLuint verticesBuffer;
glGenBuffers(1, &verticesBuffer);//創(chuàng)建一個緩存對象
glBindBuffer(GL_ARRAY_BUFFER, verticesBuffer);//激活緩沖對象. OpenGL有很多緩沖對象類型冰寻,頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER
/**
GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變。
GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多皿渗。
GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變斩芭。
*/
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//調(diào)用glBufferData函數(shù),它會把之前定義的頂點數(shù)據(jù)復制到緩沖的內(nèi)存中
//用于跟蹤組成每個三角形的索引信息
GLuint indicesBuffer;
glGenBuffers(1, &indicesBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//開啟對應(yīng)的頂點屬性(坐標乐疆,紋理坐標划乖,顏色等)
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribColor);
/**
為頂點屬性(坐標,紋理坐標挤土,顏色等)配置合適的值
參數(shù)1:GLuint indx 聲明這個屬性的名稱
參數(shù)2:GLint size定義這個屬性由多少個值組成琴庵。譬如說position是由3個GLfloat組成
參數(shù)3: GLenum type聲明每一個值是什么類型。我們都用了GL_FLOAT
參數(shù)4:GLboolean normalized , GL_FALSE就好了
參數(shù)5:GLsizei stride stride的大小仰美,描述每個vertex數(shù)據(jù)的大小
參數(shù)6:const GLvoid* ptr , 數(shù)據(jù)結(jié)構(gòu)的偏移量迷殿。從這個結(jié)構(gòu)中哪里開始獲取值。
Position的值在前面咖杂,所以傳(GLfloat *)NULL + 0進去就可以了庆寺。
而TexCoord是緊接著位置的數(shù)據(jù),而position的大小是3個float的大小诉字,所以是從(GLfloat *)NULL + 3開始的
*/
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL + 0);
glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (GLfloat *)NULL + 3);
}
頂點著色器
頂點著色器(Vertex Shader)是幾個可編程著色器中的一個止邮。如果我們打算做渲染的話,現(xiàn)代OpenGL需要我們至少設(shè)置一個頂點和一個片段著色器奏窑。
片段著色器
片段著色器(Fragment Shader)是第二個也是最后一個我們打算創(chuàng)建的用于渲染三角形的著色器导披。片段著色器所做的是計算像素最后的顏色輸出。
這里我們使用iOS系統(tǒng)框架中已經(jīng)構(gòu)建好的編譯類來完成
- (void)setupBaseEffect {
self.mEffect = [[GLKBaseEffect alloc] init];
//啟動著色器
[self.mEffect prepareToDraw];
}
在初始化這個類的時候埃唯,系統(tǒng)內(nèi)部就進行了對著色器的編譯撩匕。我們只要對它進行啟用就好。
在iOS的GLKit中有多種基礎(chǔ)的著色器類
-
- 提供基于著色器的OpenGL渲染效果的對象的標準界面墨叛。
-
- 用于基于著色器的OpenGL渲染中的一個簡單的照明和著色系統(tǒng)止毕。
-
- 用于基于著色器的OpenGL渲染的支持反射映射的照明和著色系統(tǒng)。
-
- 用于基于著色器的OpenGL渲染的一個簡單的天空盒視覺效果漠趁。
繪制三角形
終于到最后一步開始繪制了扁凛。
因為這類是繼承于GLKViewController,它的會自動執(zhí)行一個代理函數(shù)GLKViewDelegate闯传,我們在那進行繪制三角形谨朝。
官方文檔對于這個方法的描述是該方法的語義與drawRect:方法相同,用來繪制view的內(nèi)容的。
#pragma mark - GLKViewDelegate
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);//設(shè)置清除顏色
//把窗口清除為當前顏色 和 清除深度緩沖區(qū)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
}
重點說下glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
- 第一個參數(shù):表示繪制的基本圖元類型 GL_POINTS, GL_LINE_STRIP等字币。
- 第二個參數(shù):參數(shù)count表示使用的EBO(索引緩沖區(qū)對象)中索引元素的個數(shù)
- 第三個參數(shù):參數(shù)type 表示索引數(shù)據(jù)的數(shù)據(jù)類型则披。必須取 GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, 或者 GL_UNSIGNED_INT 三者之一。
- 第四個參數(shù):indices 表示EBO中索引的偏移量洗出。
還有需要注意的是千萬不要在繪圖方法中去調(diào)用display方法士复,什么是繪圖方法,比如說UIView的drawRect:(CGRect)rect
方法還有glkView:(GLKView *)view drawInRect:(CGRect)rect
翩活。因為會循環(huán)調(diào)用阱洪,循環(huán)調(diào)用導致崩潰
完成
如果不使用索引來構(gòu)建三角形?提示:glDrawArrays(GL_TRIANGLES, 0, 3);
如果把三角形變成一個矩形呢菠镇?提示:兩個三角形組成一個矩形
不用索引來構(gòu)建一個矩形冗荸?