一、搭建開發環境
1、打開XCODE,新建一個工程
選擇:IOS-->ApplicationàSingle View Application模板。
取名為“HelloOpenGL”,勾選“UseStoryboards”,然后創建。

2、添加必要的框架
在“Build Phases”欄,添加進三個框架:

3、修改viewController.h
添加“#import <GLKit/glkit.h>”,並將它修改為繼承“GLKViewController”。

4、修改“view”的類
雙擊“MainStoryboard.storyboard”展開,選擇“view”。

然后,在其“Identity Inspector”中,將它的類改為“GLKView”。

至此,OpenGL環境已基本搭建出來了。運行,應該不會報錯,盡管它目前仍是黑屏。
二、開始堆代碼
基本上,所有的代碼都在“ViewController.m”中寫。
1、添加全局屬性聲明

當然,還得在實現部分補足“@synthesize context;”和“@synthesize effect;”。
2、添加一組頂點數據
這是一個正方形頂點的數組,實際上它是由二個三角形接合而成的。

每行頂點數據的排列含義是:
頂點X、頂點Y,頂點Z、法線X、法線Y、法線Z、紋理S、紋理T。
在后面解析此數組時,將參考此規則。
頂點位置用於確定在什么地方顯示,法線用於光照模型計算,紋理則用在貼圖中。
一般約定為“頂點以逆時針次序出現在屏幕上的面”為“正面”。
世界坐標是OpenGL中用來描述場景的坐標,Z+軸垂直屏幕向外,X+從左到右,Y+軸從下到上,是右手笛卡爾坐標系統。我們用這個坐標系來描述物體及光源的位置。
三、初始化OpenGL環境
1、基本的初始化代碼
在“viewDidLoad”方法內,補充初始化代碼:

第一部分:使用“ES2”創建一個“EAGLContext”實例。
第二部分:將“view”的context設置為這個“EAGLContext”實例的引用。並且設置顏色格式和深度格式。
第三部分:將此“EAGLContext”實例設置為OpenGL的“當前激活”的“Context”。這樣,以后所有“GL”的指令均作用在這個“Context”上。隨后,發送第一個“GL”指令:激活“深度檢測”。
第四部分:創建一個GLK內置的“着色效果”,並給它提供一個光源,光的顏色為綠色。
2、運行
現在應該是粉紅色屏幕了(目前場景仍是空的),說明初始化過程沒問題。
四、將頂點數據寫入通用的頂點屬性存儲區
1、寫入過程
首先將數據保存進GUP的一個緩沖區中,然后再按一定規則,將數據取出,復制到各個通用頂點屬性中。
注:如果頂點數據只有一種類型(如單純的位置坐標),換言之,在讀數據時,不需要確定第一個數據的內存位置(總是從0開始),則不必事先保存進緩沖區。
2、頂點數組保存進緩沖區

這幾行代碼表示的含義是:聲明一個緩沖區的標識(GLuint類型)à讓OpenGL自動分配一個緩沖區並且返回這個標識的值à綁定這個緩沖區到當前“Context”à最后,將我們前面預先定義的頂點數據“squareVertexData”復制進這個緩沖區中。
注:參數“GL_STATIC_DRAW”,它表示此緩沖區內容只能被修改一次,但可以無限次讀取。
3、將緩沖區的數據復制進通用頂點屬性中

首先,激活頂點屬性(默認它的關閉的)。“GLKVertexAttribPosition”是頂點屬性集中“位置Position”屬性的索引。
頂點屬性集中包含五種屬性:位置、法線、顏色、紋理0,紋理1。
它們的索引值是0到4。
激活后,接下來使用“glVertexAttribPointer”方法填充數據。
參數含義分別為:
頂點屬性索引(這里是位置)、3個分量的矢量、類型是浮點(GL_FLOAT)、填充時不需要單位化(GL_FALSE)、在數據數組中每行的跨度是32個字節(4*8=32。從預定義的數組中可看出,每行有8個GL_FLOAT浮點值,而GL_FLOAT占4個字節,因此每一行的跨度是4*8)。
最后一個參數是一個偏移量的指針,用來確定“第一個數據”將從內存數據塊的什么地方開始。
4、繼續復制其他數據
在前面預定義的頂點數據數組中,還包含了法線和紋理坐標,所以參照上面的方法,將剩余的數據分別復制進通用頂點屬性中。

原則上,必須先“激活”某個索引,才能將數據復制進這個索引表示的內存中。
因為紋理坐標只有兩個(S和T),所以上面參數是“2”。
五、執行渲染循環
萬事具備,現在可以讓OpenGL顯示一些東西了。
在GLKit框架中,盡管OpenGL的行為,是由“GLKViewController”和“GLKView”聯合控制的,但實際上“GLKView”類中完全不需要寫任何自己的代碼,因為,“GLKView”類中每幀觸發的兩個方法“update”和“glkView”,都轉交給“GLKViewController”代理執行了。
1、添加代理方法
在“ViewController.m”中添加兩個方法:

這兩個方法每幀都執行一次(循環執行),一般執行頻率與屏幕刷新率相同(但也可以更改)。
第一次循環時,先調用“glkView”再調用“update”。
一般,將場景數據變化放在“update”中,而渲染代碼則放在“glkView”中。
2、渲染場景

前兩行為渲染前的“清除”操作,清除顏色緩沖區和深度緩沖區中的內容,並且填充淡藍色背景(默認背景是黑色)。
“prepareToDraw”方法,是讓“效果Effect”針對當前“Context”的狀態進行一些配置,它始終把“GL_TEXTURE_PROGRAM”狀態定位到“Effect”對象的着色器上。此外,如果Effect使用了紋理,它也會修改“GL_TEXTURE_BINDING_2D”。
接下來,用“glDrawArrays”指令,讓OpenGL“畫出”兩個三角形(拼合為一個正方形)。OpenGL會自動從通用頂點屬性中取出這些數據、組裝、再用“Effect”內置的着色器渲染。
3、結果

渲染內容終於呈現了,藍色背景、還有一個綠色矩形(其實是兩個三角形)。綠色並非是此物體的本色,而受是綠色燈光影響。
PS:在前面的頂點數據定義中,期望得到一個正方形,但為什么顯示結果卻是一個矩形?
六、正確顯示正方形外觀
默認,“Effect”的投影矩陣是一個單位矩陣,它不做任何變換,將場景(-1,-1,-1)到(1,1,1)的立文體范圍的物體,投射到屏幕的X:-1,1,Y:-1,1。因此,當屏幕本身是非正方形時,正方形的物體將被拉伸,從而顯示為矩形。
實際上,默認的“Effect”模型視圖矩陣也是一個單位矩陣。
透視投影中的觀察點位於原點(0,0,0),並沿着Z軸的負方向進行觀察,就像是從屏幕內部看進去。
1、修改投影矩陣
為了正確顯示,需要修改投影矩陣。在“update”方法中添加下面的代碼:

首先計算出屏幕的縱橫比(aspect),然后縮放單位矩陣的Y軸,強制將Y軸的單位刻度與X軸保持一致。
2、渲染觀察效果

3、使用透視投影矩陣
把單位矩陣做拉伸,本質上仍然是一個正交投影。要模擬人眼觀察世界的效果,則必須使用透視投影。
把上面的代碼做一些修改:

使用GLKit自帶的方法創建出一個透視矩陣,視角、縱橫比、近平面、遠平面。
渲染效果如下:

4、修改模型視圖矩陣
上圖看起來感覺像一個正方形,但似乎左右兩邊沒顯示完整。
原因是,正方形與透視視點距離太近。
有兩個方法解決這個問題:一是修改原始的頂點數據(Z軸值),使之透視視點;二是通過所謂的“模型視圖矩陣”,將正方形“變換”到遠一點的位置。
添加以下代碼:

這樣,同樣再次顯出一個精確的正方形。
七、使用紋理
1、准備紋理
在PS中剪切、調節紋理尺寸(512*512),並保存為Tulips.JPG。本例中使用的圖像是一幅黃色的郁金香。
然后在XCODE,導入進工程中。

2、使用GLKTextureLoader加載紋理
在“viewDidLoad”方法的后面,追加下列代碼:

首先用“NSBundle”找到資源“Tulips.jpg”的路徑,然后用“GLKTextureLoader”類方法同步加載這個紋理,也可以用它的實例方法異步進行加載。
默認,此圖片加載進TEXTURE0,如果需要加載進其他單元,需要先用指令“glActiveTexure(GL_TEXTUREn)”。——n為1-(CL_COMBINED_TEXTURE_IMAGE_UNITS-1)中的一個數值。
加載成功后,該紋理的信息都保存在“textureInfo”中,以后,直接使用此變量的相關屬性,就可以在OpenGL中應用這個紋理了。
3、將紋理綁定到Effect
接着,繼續添加后續代碼:

4、渲染

與意料中的結果似乎有差距,黃色的花瓣變成了綠色?圖像是上下顛倒的?
八、紋理細節調整
造成上面錯誤是原因是:
在最初構造Effect光照時,使用了綠色,所以整個紋理被“染”成為綠色。
圖像顛倒是因為紋理的坐標原點不在左下角。
1、修改光照顏色

2、將紋理坐標原點改為左下角
GLKit加載紋理,默認都是把坐標設置在“左上角”。然而,OpenGL的紋理貼圖坐標卻是在左下角,這樣剛好顛倒。
在加載紋理之前,添加一個“options”:

這個參數可以讓系統在加載紋理后,做一些基本的處理。如預乘Alpha、創建“Mipmaps”等。
3、渲染,一切正常

九、使用自定義的着色器
迄今,例中只是簡單地調用了“GLKit”內置的着色程序進行渲染。但是,在某些情況下,可能需要使用自己的特殊的着色器。
1、編寫着色程序
一個着色器由兩個部分構成(可以是兩個文件,也可以是硬編碼嵌在程序中的兩段代碼字串)。
它們分別是:頂點着色程序和片段着色程序。
創建兩個“Empty”文件,分別命名為“v.shader”和“f.shader”。

然后,兩個文件分別寫入這些代碼:

2、加載、存儲、編譯、附着、鏈接
在OpenGL中使用自定義着色器,過程比較繁瑣。
首先需要加載這個文件-->把它轉換為GLChar(UTF8編碼)-->保存進GUP內存-->編譯內存中的字串代碼-->附着給“program”對象。
上述過程要進行兩次(分別為頂點程序和片段程序)。
最后,將“program”鏈接到當前“Context”,這樣才能在OpenGL中發揮作用。
為了簡化代碼,可以寫成兩個方法,作為公共的加載方法使用:

3、開始加載自定義的着色器
在“viewDidLoad”方法里追加以下代碼:

如何判斷着色器是否能正常工作?可以用:
glGetProgramiv(_program, GL_LINK_STATUS, ¶ms);
如果返回的“params”為1,則說明一切正常。
另外,上面代碼中“_program”為新添加進去的公共變量

4、為着色器提供參數
頂點着色程序需要一個屬性參數:position(表示頂點的位置)
5、在“glkView”方法后追加

渲染結果為:

屏幕上出現兩個矩形,有圖案的是用“effct”渲染的,上面紅色的是用“_program”自定義着色器渲染的。
十、增加着色器顯示紋理
上述着色器是一個超級簡單的着色器(幾乎沒實現什么功能,僅是簡單地着為紅色)。
下面逐漸增加它的功能。
1、修改着色器
給頂點着色器,增加紋理坐標屬性“TexCoord”和該坐標的輸出“coord”(此輸出將在片段着色器中使用)。

給片段着色器,增加紋理坐標輸入“coord”,以及統一的“sampler2D”變量。

2、綁定着色器變量
要使着色器正常工作,必須提供它需要的參數內容。
在“viewDidload”方法后,添加“綁定”代碼:

第一部分是綁定“position”屬性到通用的的頂點屬性索引“0”上,綁定“texCoord”到通用的頂點屬性索引“3”上。(索引1是法線,2是頂點顏色)。
綁定后,必須調用“glLinkProgram”方法才能生效。
第二部分,綁定“統一的紋理sampler2D”變量,到紋理0號單元——在使用“GLKTextureLoader”加載紋理時,默認是激活了“0”號單元。當然,如果是激活其他單元(例如8),則這里就相應的改為8。
綁定之前,必須調用“glUseProgram”才起作用。
3、運行渲染

十一、着色器頂點變換矩陣
在上述着色器代碼中,是直接使用:
gl_Position = position;
也就是說,頂點位置沒有經過任何變換,直接使用它的原始數據(所以它的圖像也被顯示為一個矩形)。
1、引入變換矩陣
修改頂點着色器代碼:

添加了一個統一的矩陣變量“modelViewProjectionMatrix”(模型、視圖、投影矩陣,是這三個變換矩陣合並后(乘法),得到一個單個的矩陣)。將來,要在主程序中將矩陣值傳入。
2、傳入矩陣值
在“update”方法中,追加下面的代碼:

查詢到“modelViewProjectionMatrix”變量à計算合並矩陣à傳給着色器。
傳入着色器的值是modelViewProjectionMatrix.m,注意后面的“m”,它表示是一維數組形式的矩陣。
3、再次渲染

因為,“Effect”的變換矩陣與“着色器”的渲染結果相同,所以,顯示為兩個完全重合的正方形。
4、偏離屏幕中心
為了更便於觀察,下面將“着色器”渲染的正方形偏離一下。
修改代碼:

在合並矩陣之前,先把“modelViewMatrix”做一個平移(1.0,1.0,-1.0)。
結果為:

注意到兩個圖像的顏色略有差別,這是因為“Effect”內置的着色器使用了光源。而自定義的着色器沒有光效代碼,它完全照搬了紋理的“原色”。另外,后面那個正方形變小了,是因為它更遠離了“相機”。
十二、動畫
所謂動畫,其實就是在“update”中有規律地修改一些Matrix參數,連續刷新時,即產生動畫的“錯覺”。
1、旋轉動畫
添加一些代碼,如下:

首先要添加一個全局旋轉變量“_rotation”。
然后讓它每幀旋轉一點點,並以此修改“modelVireMatrix”矩陣。
2、渲染動畫效果

結果是,正方形圍繞“自己”進行了旋轉。
如果希望它繞屏幕中心旋轉,怎么做?
3、理解矩陣堆棧
OpenGL的矩陣變換是放在一個矩陣堆棧中的(后進先出),代碼中矩陣變換的次序,決定了堆棧中矩陣的變換順序。所以,上述矩陣的變換實際上是倒過來進行的:先做平移,再做旋轉,這樣它就圍繞屏幕中心旋轉了。
把上面的代碼中“平移”和“旋轉”交換一下次序即可:

(完)