iOS動畫-OpenGL ES

使用完CocosCreator做了一個跑酷類的簡單遊戲之後,對圖像渲染之類的比較感興趣,之前學習過CoreAnimation之後,把iOS裡面的過程瞭解了一下,但是並沒有深入瞭解到GPU的內容。OpenGL ES就是查漏補缺吧。

OpenGL基礎

CPU & GPU

iOS動畫-OpenGL ES

CPU可以完成每秒十億次的運算,但是它只能夠每秒讀寫內存兩億次,所以要在每個數據上執行5個或者更多的運算,不然的話處理器的性能會處於次優狀態,這種狀態叫做“數據飢餓”。同樣,GPU上更明顯,GPU每秒執行數十億次但是每秒只能訪問內存2億次。所以GPU總是受限於內存的訪問性能上,並且通常需要在每塊數據上執行10~30次運算才不會影響整體的圖形輸出。

OpenGL流程

iOS動畫-OpenGL ES

著色器是獨立運行在GPU上的程序。

該流程圖展示說了OpenGL運行的過程,在我們定義了定點數據之後,首選是定點著色器處理定點數據,我們需要告訴OpenGL如果處理定點。圖元,是告訴OpenGL渲染怎樣的數據,這裡渲染的是三角形。幾何著色器生成輔助的幾何線段。光柵化是將幾何形狀轉換成像素,片段著色器決定最後每個像素的顏色(片段著色器包含3D場景的數據,比如光照、陰影、光的顏色等等),混合是混合不同層次的片段,因為在這個階段要混合alpha值,所以就算片段著色器確定了最終的顏色,但是混合之後也可能不同。

著色器

OpenGL ES在ios上的使用過程封裝了著色器的使用過程,不需要我們自己編譯和鏈接著色器。

紋理

2D紋理的座標是[0,1],將定點的座標和紋理的座標進行點對點的綁定之後,片段著色器會對紋理進行插值

我的理解,插值就是將紋理和頂點組成的幾何圖形之間進行映射,從而判斷每個像素的顏色。

MipMap(多級漸遠紋理)是用來解決遠處同一個物體的紋理問題,因為如果是遠處的小的相同物體,如果使用大的貼圖會有浪費內存的問題,所以需要傳入小的貼圖。MipMap像下面這個樣子(後一個比前一個小1/4,超過一定的閾值就會使用相應的圖片):

iOS動畫-OpenGL ES

CPU & GPU

CPU可以完成每秒十億次的運算,但是它只能夠每秒讀寫內存兩億次,所以要在每個數據上執行5個或者更多的運算,不然的話處理器的性能會處於次優狀態,這種狀態叫做“數據飢餓”。同樣,GPU上更明顯,GPU每秒執行數十億次但是每秒只能訪問內存2億次。所以GPU總是受限於內存的訪問性能上,並且通常需要在每塊數據上執行10~30次運算才不會影響整體的圖形輸出。

緩存(buffer)

GPU和CPU都有自己獨佔的內存區域,OpenGL ES為兩個內存區域間的數據交換定義了緩存(buffer)的概念。緩存是指圖形處理器能夠控制和管理的連續的RAM。幾乎所有程序提供給GPU的數據都應該放入緩存中。為緩存提供數據有如下7個步驟:

  1. Generate-> glGenBuffers()--請求OpenGL ES生成graphics processor控制的唯一標誌。
  2. Bind-> glBindBuffer()--告訴OpenGL ES使用緩存處理接下來的運算。
  3. Buffer Data-> glBufferData() or glBufferSubData()--告訴OpenGL ES為當前綁定的buffer分配並初始化足夠多的連續內存(通常是從CPU控制的內存複製數據到GPU控制的內存)。
  4. Eable or Disable -> glEnableVertexAttribArray() or glDisableVertexAttribArray()--告訴OpenGL ES在接下來的渲染中是否使用緩存中的數據。
  5. Set Pointer -> glVertexAttribPointer()--告訴OpenGL ES buffer裡的數據類型和訪問buffer數據的任何內存偏移量。
  6. Draw -> glDrawArrays() or glDrawElements()--告訴OpenGL ES使用當前綁定的和可以使用的緩存來渲染部分或者全部場景。
  7. Delete -> glDeleteBuffers()--告訴OpenGL ES去刪除之前生成的緩存和釋放相關聯的資源。

幀緩存(frame buffer)

就像GPU提供數據的緩存一樣,接收渲染結果的緩衝區叫做幀緩存。可以同時存在很多的幀緩存,並且可以通過OpenGL ES讓GPU把渲染結果存儲到任意的幀緩存中。有front frame buffer和back frame buffer的概念,front frame buffer控制屏幕上顯示的像素顏色和佈局,所以一般不會直接渲染到front frame buffer中,這樣的話就會讓用戶看到沒有渲染完成的圖像。相反程序會把渲染結果保存到包含back frame buffer的其他frame buffer中,當back frame buffer渲染成功之後就會立即與front frame buffer進行交換。

OpenGL ES context

Context 封裝了配置OpenGL ES保存在特定平臺的數據結構信息。因為OpenGL ES是一個狀態機,這意味著在一個程序中配置之後就會一直保留這個值,直到程序修改了這個值。上下文信息可能被保存在CPU所控制的內存中,也可能在GPU所控制的內存中。OpenGL ES Context的內部實現依賴於不同的嵌入式系統和GPU硬件,所以OpenGL ES提供了標準的ANSI C語言函數來與Context交互。OpenGL ES Context會跟蹤用於渲染的幀緩存,一會跟蹤用於幾何數據、顏色等的緩存。

Core Animation與OpenGL ES

iOS動畫-OpenGL ES

iOS操作系統不支持直接訪問front frame buffer和back frame buffer,有操作系統來操作,這樣的話方便隨時使用Core Animation Compositor來控制顯示的最終外觀。

這個也很好理解,因為core animation是一個單獨的進程,不僅僅要生成我們自己應用裡面的layer,還要控制app之間的切換,所以這是個系統的工作,沒必要暴露出來。

layer保存了所有繪製操作的結果。比如,iOS提供了對象有效的在layer上繪製視頻,在layer上繪製淡入淡出的圖片。layer content也可以用Core Graphics來繪製,也可以用OpenGL ES直接繪製。不過Core Animation Compositor使用的是OpenGL ES來管理GPU,混合圖層和賈環frame buffer的。所以一切通過Core Animation的繪製最終都會涉及OpenGL ES。

GLKit

ios封裝了OpenGL ES,我們可以直接使用GLKit來使用OpenGL ES。分別使用GLKView和GLKViewController就可以直接操作OpenGL ES了。

以下是個人理解:GLKBaseEffect封裝了片段著色器程序,所以只要向該對象傳入紋理或者顏色,就可以直接渲染出來。

OpenGL ES使用

OpenGL ES的使用過程就是操作Buffer的過程(見buffer一節的內容)。下面具體講一下每個函數:

  • glGenBuffers(1,&name):這個方法是用來生成緩存,數量是一個,傳入首地址標識。
  • glBindBuffer(GL_ ARRAY_BUFFER, name): 聲明是一個頂點數組類型的buffer,使用對應name的緩存來處理接下來的運算。
  • glBufferData(GL_ARRAY_BUFFER,size,data,usage): 第一個是類型,第二個是需要申請的內存大小,第三個是需要拷貝到GPU內存的數據,第四個是讀取頻率的標識,用於內存優化
  • glEnableVertexAttribArray(1),默認就禁止的,這裡需要開啟一下。
  • glVertexAttribPointer(index,size,GL_FLOAT,GL_FALSE,stribe,pointer)。該函數告訴OpenGL怎樣處理傳入的頂點。因為在頂點數組中,一般還要保存紋理的UV數據,所以需要指定類型(位置、顏色、紋理之類的)、大小、步幅以及偏移量,這樣的話就可以合理使用頂點。
  • 下面就是繪製頂點緩存和刪除頂點緩存了。

紋理

如果直接使用GLKit提供的方法來初始化紋理的話是非常簡單點的,首先根據CGImage對象生成一個GLKTextureInfo對象,然後將該對象的Target和name賦值給baseEffect對象的對應屬性就可以了。在準備渲染的時候配置定點的讀取規則就可以了。

如果不使用GLKit的話,需要自己手動生成、綁定和配置紋理,與生成緩存類似。

  • glGenTexture(1,&id)生成紋理。
  • glBindTexture(GL_ TEXTURE_2D, id),綁定紋理。
  • glTexImage2D(GL_ TEXTURE,0,GL_RGBA,width,height,0,GL_ RGBA,GL_ UNSIGNED_BYTE, [imageData bytes]),將圖片數據保存成紋理
  • glTextParameteri(GL_ TEXTURE_2D, GL _ TEXTURE_ MIN_ FILTER,GL_LINEAR)配置紋理的顯示。只要是紋理的大小大於或小於圖形大小,紋理的佈局方式。GL_LINEAR表示的是取顏色的方式是取周圍的混合色,這樣的紋理是模糊和漸變的。

透明度、混合和多重紋理

當紋理計算出一個完全不透明的fragment color的時候,會直接替換調frame buffer中的color render buffer中對應的pixel color。如果存在不透明的話就需要混合。通過glEnable(GL_BLEND)來開啟混合。glBlendFunc(sourceFactor,destinationFactor)來設置混合函數。在每幀渲染的過程中需要替換baseEffect中的Target和name,然後[baseeffect prepareToDraw]會同步狀態,這樣的話就會繪製兩個紋理了。

GLKit提供了多重紋理的渲染,可以直接給baseEffect指定兩個紋理,並且指定混合模式,就可以直接混合多種紋理。這也間接可以看出CoreAnimation的層設計也是以此為基礎的。

燈光

GPU首先為每個三角形的每個定點執行光線計算,然後把計算的結果插補在頂點之間來修改每個渲染的片元的最終顏色。因此模擬燈光的質量和光滑度要取決於組成每個3D物體的頂點的數量。光線的計算是以三角形為單位的,光線的向量與三角面的法向量的夾角決定了採光量的多少。所以在使用燈光的時候需要在定點中傳入法向量的值。

GLKit使用燈光的話就是直接在baseEffect對象裡面直接使用light0屬性進行設置。設置了這個屬性之後,就算沒有打開,顏色屬性也會失效,而是變成燈光的顏色屬性,所以如果還要渲染其他有顏色的物體的時候,需要新建baseEffect實例。

將baseEffect當做片段著色器,並且OpenGL ES Context是一個狀態機,所以完全可以將前幾個定點繪製成黃色,然後改變baseEffect的顏色填充屬性為綠色,再調用繪製,就會將剩下的定點繪製成綠色。

深度測試

深度測試是OpenGL自動完成的,我們只需要告訴OpenGL保存測試深度的大小,一般是16位和24位大小。然後調用深度函數來決定通過的門檻。一般使用默認的即可。在iOS裡面的代碼設置如下:

<code>glGenRenderbuffers(1, &depthRenderBuffer); // Step 1      glBindRenderbuffer(GL_RENDERBUFFER,        // Step 2         depthRenderBuffer);      glRenderbufferStorage(GL_RENDERBUFFER,     // Step 3          GL_DEPTH_COMPONENT16,          currentDrawableWidth,          currentDrawableHeight);      glFramebufferRenderbuffer(GL_FRAMEBUFFER,  // Step 4          GL_DEPTH_ATTACHMENT,          GL_RENDERBUFFER,          depthRenderBuffer);複製代碼/<code>

座標系統及變換

在以前使用3DMax(製作3D模型的軟件)的時候就會有很多的座標系可以選擇,在做遊戲的時候也是可以選擇世界座標或者是本地座標,原來是OpenGL規定的標準。

iOS動畫-OpenGL ES

對物體的每個點做矩陣變換就是對整個物體的進行移動、縮放、旋轉。OpenGL封裝好了變換的矩陣,只需要我們傳入改變的值就可以生成對應的矩陣。與iOS裡面的CoreGraphics或者是CALayer的transform矩陣變換是一樣的。列舉一下常用的幾種變換:

  • 圍繞一個點旋轉:1)平移到所需要的旋轉中心。2)施加所需要的旋轉。3)使用與第一步相反的平移值平移回來
  • 圍繞一個點縮放:1)平移到所需要的縮放中心。2)施加想要的縮放。3)使用與第一步相反的平移值平移回來

透視投影是以一個”平截投體“,例下圖所示:

iOS動畫-OpenGL ES

其設置的代碼如下所示(所傳入的屬性就是確定平截投體的大小的):

<code>self.baseEffect.transform.projectionMatrix =          GLKMatrix4MakeFrustum(         -1.0 ,  //left         1.0 ,   //right         -1.0, //bottom         1.0, //top         1.0,//near         60.0);//far複製代碼/<code>

OpenGL ES剩餘內容

剩下的還有動畫、讀取模型、特效(天空盒、粒子等)這些內容,這部分沒有仔細的實現demo,而是大概看了一下,前段時間看了遊戲相關的內容,這些部分感覺就是專門為遊戲和3D定製的,和應用類的ios開發有些靠不上,所以這裡等日後再學習。動畫就是在單位時間內(取決於刷新頻率)對定點做矩陣變化,骨骼動畫就是制定父節點,父節點發生矩陣變化的時候同樣也要對子節點做變換,這樣的話就會有聯動的效果了。因為傳遞到GPU的都是定點信息,那麼模型其實就是頂點信息的文件,讀取的過程就是取出頂點、紋理等信息。


分享到:


相關文章: