iOS OpenGL ES FBO 幀快取區 渲染快取區詳解

jeffasd發表於2017-10-11

參考文章:http://www.jianshu.com/p/c516e899e606

原文地址:

https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/WorkingwithEAGLContexts/WorkingwithEAGLContexts.html#//apple_ref/doc/uid/TP40008793-CH103-SW6

繪製到其他渲染目的地

Framebuffer物件是渲染命令的目標。當您建立一個framebuffer物件時,您可以精確控制其儲存的顏色,深度和模板資料。您可以通過將影象附加到幀緩衝區來提供此儲存,如圖4-1所示。最常見的影象附件是一個renderbuffer物件。您還可以將OpenGL ES紋理附加到幀緩衝區的顏色附加點,這意味著任何繪圖命令都將呈現到紋理中。之後,紋理可以作為未來渲染命令的輸入。您還可以在單​​個渲染上下文中建立多個幀緩衝區物件。您可以這樣做,以便您可以在多個幀緩衝區之間共享相同的渲染管道和OpenGL ES資源。


圖4-1

所有這些方法都需要手動建立framebuffer和renderbuffer物件來儲存來自OpenGL ES上下文的渲染結果,以及編寫附加程式碼以將其內容顯示在螢幕上,如果需要,執行動畫迴圈

建立一個 Framebuffer Object

根據您的應用程式要執行的任務,您的應用程式會配置不同的物件以附加到framebuffer物件。在大多數情況下,配置幀緩衝區的區別在於什麼物件附加到framebuffer物件的顏色附著點

  • 要使用幀緩衝區進行螢幕外影象處理,請附加一個renderbuffer。請參閱建立Offscreen Framebuffer物件。
  • 要使用幀緩衝影象作為後續渲染步驟的輸入,請附加紋理。請參閱使用Framebuffer物件渲染到紋理。
  • 要在Core Animation圖層組合中使用framebuffer,請使用特殊的Core Animation感知renderbuffer。請參閱渲染到核心動畫層。
建立 Offscreen Framebuffer Objects

用於螢幕外渲染的幀緩衝區將其所有附件分配為OpenGL ES渲染緩衝區。以下程式碼分配帶有顏色和深度附件的framebuffer物件。

  1. 建立幀緩衝區並繫結它。
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  2. 建立一個彩色渲染緩衝區,為其分配儲存空間,並將其附加到framebuffer的顏色附著點。
    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
  3. 建立一個深度或深度/模板renderbuffer,為其分配儲存,並將其附加到framebuffer的深度附件點。
    GLuint depthRenderbuffer;
    glGenRenderbuffers(1, &depthRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
  4. 測試framebuffer的完整性。只有當幀緩衝區的配置更改時,才需要執行此測試。
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
    if(status != GL_FRAMEBUFFER_COMPLETE) {
     NSLog(@"failed to make complete framebuffer object %x", status);
    }

繪製到螢幕外的renderbuffer後,可以將其內容返回給CPU,以便使用glReadPixels函式進一步處理

Using Framebuffer Objects to Render to a Texture 將Framebuffer物件的記憶體渲染到紋理中 

建立此幀緩衝區的程式碼與螢幕外的示例幾乎相同,但是現在將分配紋理並附加到顏色附加點

  • 建立framebuffer物件(使用與建立Offscreen Framebuffer物件相同的過程)。
  • 建立目標紋理,並將其附加到framebuffer的顏色附件點。
    // create the texture
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
  • 分配並附加深度緩衝區(如前所述)。
  • 測試framebuffer的完整性(如前所述)。

儘管此示例假定您要渲染為顏色紋理,但其他選項也是可能的。例如,使用OES_depth_texture擴充套件,您可以將紋理附加到深度附件點,以將場景中的深度資訊儲存到紋理中。您可以使用此深度資訊來計算最終渲染場景中的陰影。

渲染到核心動畫層

核心動畫是iOS上圖形渲染和動畫的核心基礎設施。您可以使用主持使用不同iOS子系統(如UIKit,Quartz 2D和OpenGL ES)呈現的內容的圖層來構成應用的使用者介面或其他視覺顯示。 OpenGL ES通過CAEAGLLayer類連線到Core Animation,這是一種特殊型別的Core Animation層,其內容來自OpenGL ES renderbuffer。 Core Animation將renderbuffer的內容與其他圖層複合,並在螢幕上顯示生成的影象。對應於其他系統和此Renderbuffer相連線的FBO稱為 視窗系統提供的"幀快取區".


圖4-2

CAEAGLLayer通過提供兩個關鍵功能向OpenGL ES提供此支援。首先,它為renderbuffer分配共享儲存。其次,它將渲染緩衝區呈現給Core Animation,將該圖層的以前內容替換為renderbuffer中的資料。該模型的優點在於,只有當渲染緩衝區的內容發生變化時, Core Animation layer 才需要進行繪製,Core Animation layer核心動畫層的內容不需要在每個幀中繪製。
注意:GLKView類會自動執行以下步驟,因此當您需要將OpenGL ES的內容繪製到包含layer的檢視上時,您應該使用GLKView。

註解:當需要將OpenGL ES的內容繪製到iOS的UIView上時,需要使用GLKView類或CAEAGLLayer類來實現將OpenGL ES的內容繪製到iOS的UIView上。

為OpenGL ES渲染使用Core Animation層

  1. 建立CAEAGLLayer物件並配置其屬性。
    為獲得最佳效能,請將圖層的不透明屬性的值設定為YES。看到注意核心動畫合成效能。可選地,通過為CAEAGLLayer物件的drawableProperties屬性分配一個新的值字典來配置渲染表面的表面屬性。您可以指定renderbuffer的畫素格式,並指定在將它們傳送到Core Animation之後,renderbuffer的內容是否被丟棄。有關允許金鑰的列表,請參閱EAGLDrawable Protocol Reference。
  2. 分配OpenGL ES上下文並使其成為當前上下文。請參閱配置OpenGL ES上下文。
  3. 建立framebuffer物件(如上面的建立Offscreen Framebuffer物件)。
  4. 建立一個顏色renderbuffer,通過呼叫上下文的renderbufferStorage:fromDrawable:method分配其儲存,並傳遞層物件作為引數。寬度,高度和畫素格式取自層,用於為renderbuffer分配儲存空間
    GLuint colorRenderbuffer;
    glGenRenderbuffers(1, &colorRenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
    注意:當核心動畫層的界限或屬性更改時,應用程式應重新分配renderbuffer的儲存空間。如果不重新分配renderbuffers,renderbuffer大小將不匹配圖層的大小;在這種情況下,Core Animation可以縮放影象的內容以適應圖層。
  5. 檢索顏色renderbuffer的高度和寬度。
    GLint width;
    GLint height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    在前面的例子中,renderbuffers的寬度和高度被明確地提供給緩衝區的分配儲存。這裡,程式碼在分配儲存後從顏色renderbuffer中檢索寬度和高度。您的應用程式執行此操作是因為顏色renderbuffer的實際尺寸是根據圖層的邊界和比例因子計算的。附加到幀緩衝區的其他渲染緩衝區必須具有相同的尺寸。除了使用高度和寬度來分配深度緩衝區之外,還可以使用它們來分配OpenGL ES視口,並幫助確定應用程式紋理和模型所需的詳細程度。請參閱支援高解析度顯示器。
  6. 分配並附加深度緩衝區(如前所述)。
  7. 測試framebuffer的完整性(如前所述)。
  8. 將CAEAGLLayer物件新增到Core Animation層次結構,將其傳遞給可見層的addSublayer:方法。

繪製到Framebuffer物件

現在你有一個framebuffer物件,你需要填寫它。本節介紹渲染新幀並將其呈現給使用者所需的步驟。渲染到紋理或螢幕外框架緩衝區的作用類似,僅在應用程式使用最終幀時有所不同。

按需渲染或動畫迴圈

當渲染到Core Animation層時,您必須選擇何時繪製OpenGL ES內容,就像使用GLKit檢視和檢視控制器進行繪製時一樣。如果渲染到螢幕外的幀緩衝區或紋理,則繪製每當適用於使用這些幀緩衝區的情況時。

對於按需繪圖,實現自己的方法來繪製和呈現您的renderbuffer,並且每當您要顯示新內容時呼叫它。

要使用動畫迴圈繪製,請使用CADisplayLink物件。顯示連結是Core Animation提供的一種定時器,可讓您將繪圖同步到畫面的重新整理率。清單4-1顯示瞭如何檢索顯示檢視的螢幕,使用該螢幕建立新的顯示連結物件,並將顯示連結物件新增到執行迴圈。
注意:GLKViewController類可自動使用CADisplayLink物件來動畫化GLKView內容。僅當您需要超出GLKit框架提供的行為時才直接使用CADisplayLink類。

Listing4-1

displayLink = [myView.window.screen displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

在drawFrame方法的實現之內,讀取顯示連結的timestamp屬性以獲取要渲染的下一個幀的時間戳。它可以使用該值來計算下一幀中的物件的位置。

通常,每次螢幕重新整理時觸發顯示連結物件;該值通常為60 Hz,但可能會因不同的裝置而異。大多數應用程式不需要每秒更新螢幕60次。您可以將顯示連結的frameInterval屬性設定為在呼叫方法之前執行的實際幀數。例如,如果幀間隔設定為3,則您的應用程式每三幀呼叫一次,或大約每秒20幀。
重要提示:為獲得最佳效果,請選擇應用程式可以始終如一地實現的幀率平滑,一致的幀速率產生比不規則變化的幀速率更愉快的使用者體驗。

渲染視訊幀
圖4-3顯示了OpenGL ES應用程式在iOS上呈現和呈現視訊幀
的步驟。這些步驟包括提高應用程式效能的許多提示


圖4-3

清除緩衝區

在每幀的開始,擦除所有幀緩衝附件的內容,其中不需要前一幀的內容來繪製下一幀。呼叫glClear函式,將所有緩衝區的位掩碼傳遞給清除,如清單4-2所示。
Listing4-2

glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

對OpenGL ES使用glClear“提示”,可以丟棄renderbuffer或紋理的現有內容,避免將以前的內容載入到記憶體中的昂貴的操作。

準備資源並執行繪圖命令

這兩個步驟包括您在設計應用程式架構時所做的大多數關鍵決策。首先,您決定要向使用者顯示什麼,並配置相應的OpenGL ES物件(如頂點緩衝區物件,紋理,著色器程式及其輸入變數)以上傳到GPU。接下來,您提交繪圖通知,告訴GPU如何使用這些資源來渲染幀。

OpenGL ES設計指南中更詳細地介紹了渲染器設計。現在,要注意的最重要的效能優化是,只有在渲染新幀時才能更快地修改OpenGL ES物件。雖然您的應用程式可以在修改物件和提交繪圖命令之間交替(如圖4-3中的虛線所示),它的執行速度更快,如果它每幀只執行一次

執行繪圖命令

此步驟將使用您在上一步中準備的物件,並提交繪圖命令以使用它們。在OpenGL ES設計指南中詳細介紹了將此部分渲染程式碼設計為高效執行。現在,要注意的最重要的效能優化是,如果在開始渲染新幀時僅修改OpenGL ES物件,則應用程式執行速度更快。雖然您的應用程式可以在修改物件和提交繪圖命令之間交替(如虛線所示),但如果它只執行一次,則執行速度更快。

解決多重取樣

如果您的應用程式使用多重取樣來提高影象質量,則應用程式必須在呈現給使用者之前解析畫素。多采樣在使用多采樣來提高影象質量方面有詳細的介紹。

丟棄不需要的Renderbuffers

丟棄操作是一種效能提示,它告訴OpenGL ES,不再需要一個或多個渲染緩衝區的內容。通過暗示OpenGL ES,您不需要renderbuffer的內容,緩衝區中的資料可以被丟棄,並且可以避免更新這些緩衝區內容的昂貴任務。

在渲染迴圈的這個階段,您的應用程式已經提交了框架的所有繪圖命令。當您的應用程式需要彩色renderbuffer顯示到螢幕時,它可能不需要深度緩衝區的內容。清單4-3放棄了深度緩衝區的內容。
Listing4-3

const GLenum discards[]  = {GL_DEPTH_ATTACHMENT};
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glDiscardFramebufferEXT(GL_FRAMEBUFFER,1,discards);

注意:glDiscardFramebufferEXT函式由OpenGL ES 1.1和2.0的EXT_discard_framebuffer擴充套件提供。在OpenGL ES 3.0上下文中,使用glInvalidateFramebuffer函式。

將結果呈現給核心動畫

在此步驟中,顏色渲染緩衝區儲存完成的框架,因此您需要做的就是將其呈現給使用者。清單4-4將renderbuffer繫結到上下文並呈現它。這將導致完成的框架被交給核心動畫。
Listing4-4

glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

預設情況下,您必須假定在應用程式呈現renderbuffer後,renderbuffer的內容將被丟棄。這意味著,每當您的應用程式呈現幀時,它必須在渲染新幀時完全重新建立幀的內容。由於這個原因,上面的程式碼總是擦除顏色緩衝區。

如果您的應用程式要保留幀之間的顏色renderbuffer的內容,請將kEAGLDrawablePropertyRetainedBacking金鑰新增到CAEAGLLayer物件的drawableProperties屬性中儲存的字典中,並從較早的glClear函式呼叫中刪除GL_COLOR_BUFFER_BIT常量。保留的支援可能需要iOS才能分配額外的記憶體來保留緩衝區的內容,這可能會降低應用程式的效能。

使用多重取樣來提高影象質量

多采樣是一種抗鋸齒形式,可以在大多數3D應用程式中平滑鋸齒狀邊緣並提高影象質量。 OpenGL ES 3.0包括多采樣作為核心規範的一部分,iOS通過APPLE_framebuffer_multisample擴充套件在OpenGL ES 1.1和2.0中提供。多采樣使用更多的記憶體和片段處理時間來渲染影象,但它可以以比使用其他方法更低的效能成本來提高影象質量。

圖4-4顯示了多采樣如何工作。而不是建立一個framebuffer物件,您的應用程式建立兩個。多重取樣緩衝區包含渲染內容所需的所有附件(通常為彩色和深度緩衝區)。解析緩衝區僅包含向使用者顯示渲染影象所必需的附件(通常為彩色渲染緩衝區,但可能是紋理),使用“建立幀緩衝區物件”中的相應過程建立。多重取樣渲染緩衝區使用與解析幀緩衝區相同的維度進行分配,但每個包含一個附加引數,該引數指定為每個畫素儲存的取樣數。您的應用程式將其所有渲染執行到多重取樣緩衝區,然後通過將這些樣本解析為解析緩衝區來生成最終的抗鋸齒影象。


圖4-4

清單4-5顯示了建立多采樣緩衝區的程式碼。此程式碼使用先前建立的緩衝區的寬度和高度。它呼叫glRenderbufferStorageMultisampleAPPLE函式為renderbuffer建立多采樣儲存。
Listing4-5

glGenFramebuffers(1, &sampleFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);

glGenRenderbuffers(1, &sampleColorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleColorRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);

glGenRenderbuffers(1, &sampleDepthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleDepthRenderbuffer);
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sampleDepthRenderbuffer);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));

以下是修改渲染程式碼以支援多采樣的步驟:

  1. 在清除緩衝區步驟中,清除多重取樣幀緩衝區的內容。
    glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
    glViewport(0, 0, framebufferWidth, framebufferHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  2. 提交繪圖命令後,將內容從多重取樣緩衝區解析為解析緩衝區。為每個畫素儲存的樣本被合併到解析緩衝區中的單個樣本中。
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
    glResolveMultisampleFramebufferAPPLE();
  3. 在“丟棄”步驟中,可以丟棄附加到多重取樣幀緩衝區的兩個renderbuffer。這是因為您計劃呈現的內容儲存在解析幀緩衝區中。
    const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
  4. 在當前結果步驟中,您將呈現附加到解析幀緩衝區的顏色renderbuffer。
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];

多次取樣不是免費的;需要額外的記憶體來儲存附加樣本,並將樣本解析為解析幀緩衝區需要時間。如果您嚮應用程式新增多重取樣,請始終測試應用程式的效能,以確保其仍然可以接受。
注意:上述程式碼假定為OpenGL ES 1.1或2.0上下文。多采樣是OpenGL ES 3.0 API核心的一部分,但功能不同。詳見規範。

相關文章