配置 OpenGL ES 上下文
每一個OpenGL ES的實現都提供了一種創建上下文的方式去管理OpenGL ES規范所必須的狀態。在一個上下文中處理這些狀態,多個應用共享圖形硬件而不會干擾其它的狀態。我們將會詳細介紹如何在iOS中創建和配置上下文
EAGL是iOS中OpenGL ES渲染上下文的實現
在你的應用調用OpenGL ES函數前,必須初始化一個
EAGLContext
對象。EAGLContext
類同時也提供了用於將OpenGL ES內容與核心動畫集成的方法。
當前上下文是OpenGL ES函數調用的目標
iOS應用中的每一個線程都有一個當前上下文,當你調用OpenGL ES函數,通過這個調用上下文的狀態會被改變。要設置線程的當前上下文,使用
[EAGLContext setCurrentContext:myContext]
,獲取當前上下文的[EAGLContext currentContext]
。
* 如果在同一個線程中兩個或更多上下文中切換,在設置一個新的上下文為當前上下文前要調用glFlush
函數。這樣確保之前提交的命令及時交付圖形硬件。
OpenGL ES對EAGLContext對象擁有一個強引用,對應於當前上下文。(如果使用手動引用計數器,OpenGL ES持有該對象)當調用
setCurrentContext:
方法改變當前上下文,OpenGL ES不再引用之前的上下文。為了防止EAGLContext對象從不是當前上下文中被銷毀,你的應用必須保持強引用(or retain)這些對象。
每個上下文面向一個指定的OpenGL ES版本
一個EAGLContext對象僅一個OpenGL ES版本。為OpenGL ES 1.1版本寫的代碼不兼容2.0或3.0的上下文。使用OpenGL ES 2.0核心功能代碼是與3。0上下文兼容,為OpenGL ES 2.0擴展設計的代碼通常可以使用在3.0的上下文中,只需很小的修改。3.0的很多新特性和增強硬件的能力必須要3.0的上下文。當你創建和初始化EAGLContext對象時,你的應用將會決定支持哪個版本的OpenGL ES。如果你的設備支持要求版本的OpenGL ES,
initWithAPI:
方法會返回nil
。應用在使用前必須要測試確保上下文初始化成功。為了支持多個版本的OpenGL ES在你的應用中作為渲染選項,你應該首先嘗試初始化一個最新版本的上下文。如果返回nil
,再初始化一個較老的版本。示例如下:
EAGLContext *createBestEAGLContext() {
EAGLContext *ctx = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES3];
if (ctx == nil) {
ctx = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
}
return ctx;
}
一個 EAGL ShareGroup 為上下文管理OpenGL ES對象組
盡管上下文持有OpenGL ES的狀態,但不直接管理OpenGL ES對象組。相反,OpenGL ES對象組由EAGLSharegroup對象創建和維護的。每個上下文包含一個EAGLSharegroup對象,它將對象的創建委托給這個對象。
在兩個或多個上下文引用同一個sharegroup時,它的優勢是顯而易見的。在多個上下文被連接到共同的sharegroup,在任一上下文中創建OpenGL ES對象可以用在所有的上下文中。如果綁定到另一個上下文中與創建它的上下文相同對象標識符,則引用相同的OpenGL ES對象。在移動設備上資源是很稀缺的,在不同上下文中同一份內容創建多個副本是很浪費的。共享公用的資源可以更好的利用設備上圖形資源
sharegroup是一個抽象對象,沒有方法和屬性可以直接調用。使用sharegroup對象的上下文對它保持一個強引用。
- sharegroups在以下兩種場景中非常有用:
- 大部分資源在上下文之間共享且不改變
- 當你的應用想在另一個線程而非主線程為渲染器創建新的OpenGL ES對象組時。在這種情況下,第二個上下文運行在獨立的線程,專心的獲取數據和創建資源。在資源加載完成,第一個上下文可以綁定到這個對象上且可以立即使用。
GLKTextureLoader
類使用此種模式提供紋理異步加載。
// 為了創建多個上下文引用相同的sharegroup,第一個上下文通過調用 initWithAPI: 來初始化,sharegroup會自動被這個上下文創建
// 第二個或后面的上下文通過第一個上下文的sharegroup調用 initWithAPI: sharegroup:來初始化。
EAGLContext *firstCtx = createBestEAGLContext()
EAGLContext *secondCtx = [[EAGLContext alloc] initWithAPI: [firstCtx API] sharegroup: [firstCtx sharegroup]];
-
當sharegroup被共享在多個上下文中時,應用要負責管理OpenGL ES對象組的狀態改變。規則如下:
- 如果對象沒有被修改,你的應用可能會同時跨多個上下文訪問該對象
- 當對象通過傳送到上下文的命令正在被修改,該對象不准被任何其它上下文讀或修改
- 在該對象修改完成,所有的上下文必須重新綁定該對象以查看更改。如果上下文在綁定對象之前引用它,則對象的內容是未定義的。
-
你的應用應當按照如下步驟更新OpenGL ES對象
-
- 在可能使用該對象的每一個上下文中調用 glFlush
-
- 在想修改該對象的上下文中,調用一個或多個OpenGL ES 函數更改該對象
-
- 在接收狀態修改命令的上下文中調用 glFlush
-
- 在其它上下文中,重新綁定該對象的標識符
-
* 另一種方式共享對象是使用單一渲染上下文,但多個目標幀緩存。在渲染時,你的應用綁定合適的幀緩存,並根據需要渲染幀。因為所有的OpenGL ES對象都是從一個上下文中引用的,所以它們查看的是相同的OpenGL ES數據。此種模式使用更少的資源,但僅對單線程應用程序有用,在單線程中可以詳細的控制上下文的狀態。
用OpenGL ES和GLKit繪圖
GLKit提供了視圖和視圖控制器的類,用來消除繪圖和動畫OpenGL ES內容所需的創建和維護代碼。GLKView類管理OpenGL ES基礎結構為繪制代碼提供地方,GLKViewController類為GLKit視圖中OpenGL ES內容動畫平滑提供了一個呈現循環。這些類擴展了標准的UIKit的設計模式來繪制視圖內容和管理視圖呈現。這樣可以把精力主要放在OpenGL ES渲染代碼和快速啟動和運行你的應用。GLKit還提供了其它特性來簡化OpenGL ES2.0和3.0的開發。
根據需要用GLKit視圖繪制OpenGL ES內容
GLKView類提供一個基於OpenGL ES與UiView相同的繪制循環。一個UIView實例會自動配置自己的圖形上下文,因此在drawRect:方法實現中僅僅需要執行Quartz 2D繪制命令即可,所以GLKView的實例會自動配置你繪制的方法只需執行OpenGL ES繪圖命令。GLKView類是通過維護一個 framebuffer 對象(此對象持有OpenGL ES的繪制命令)來提供此功能。最后一旦繪制方法返回會自動呈現到 Core Animation。像標准UIKit的視圖一樣, GLKit的視圖也會根據需要去渲染自身內容。如果你的視圖是首次展現,會調用你的繪制方法(Core Animation 會緩存渲染輸出結果和當視圖要出現時會展示)。當你想改變視圖的內容時,要調用
setNeedsDisplay
方法,這樣視圖將會再次調用繪制方法,緩存生成的圖像,將其顯示在屏幕上。這個方法對渲染圖像不經常或僅只在用戶操作做出響應時很有用。通過只在需要時才呈現新的視圖的內容,你可以節省設備電池電量,並為設備執行其它操作留有更多的時間。
創建和配置一個GLKit視圖
當然創建和配置一個GLKView對象都可以通代碼或 Xib。在使用它繪制前,你必須要關聯一個
EAGLContext
對象。
代碼創建視圖,首先要創建一個上下文,然后將它傳遞給視圖的initWithFrame:context:
方法
從storyboard加載一個視圖,創建一個上下文並將其賦值繪視圖的context
屬性
GLKit視圖會自動創建和配置自己的OpenGL ES 的framebuffer對象和renderbuffers。你使用視圖的可繪制的屬性來控制這些對象的屬性(下面有示例)。如若你要改變GLKit視圖尺寸、紳縮因子或可繪制屬性,它會在下次內容重繪時自動刪除和重新創建合適的framebuffer對象和renderbuffers。
- (void)viewDidLoad{
[super viewDidLoad];
// 創建OpenGL ES上下文並賦值給storyboard加載的視圖
GLKView *view = (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
// 配置視圖的renderbuffers
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.drawableStencilFormat - GLKViewDrawableStencilFormat8;
// 可多重采樣
view.drawableMultisample = GLKViewDrawableMultisample4X;
}
用GLKit視圖繪制
上面的示例代碼通過三步繪制OpenGL ES內容:准備OpenGL ES的基礎部分、分配繪制命令、將呈現內容顯示到Core Animation中。 GLKView類實現了第一步和第三步,第二步你的實現可以參考下面
- (void)drawRect:(CGRect)rect {
// 清除 framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 使用之前配置好的 texture, shader, uniforms, vertex array 繪制
glBindTexture(GL_TEXTURE_2D, _planetTexture);
glUseProgram(_diffuseShading);
glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
glBindVertexArrayOES(_planetMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT);
}
GLKView類能夠為OpenGL ES繪制提供簡單的接口,因為它管理標准OpenGL ES渲染的過程:
- 在調用繪制方法前,視圖
+ 會讓EAGLContext對象是當前上下文
+ 根據當前尺寸、紳縮因子、可繪制屬性(如果需要)創建framebuffer對象和renderbuffers
+ 綁定framebuffer對象作為當前繪制命令的目標
+ 設置OpenGL ES視圖端口以匹配framebuffer的大小
- 繪制方法返回后,視圖
+ 解析多采樣緩沖區(如果啟用了)
+ 丟棄不再需要的renderbuffer內容
+ 呈現renderbuffer內容到CoreAniamtion中進行緩存和顯示
使用代理對象渲染
很多OpenGL ES的應用實現渲染的代碼在自定義的類中。這種做法的有利之處在於可以通過定義不同的渲染類,很容易支持多個渲染算法。渲染算法可以共享從父類繼承的功能。例如你可能使用不同的渲染類支持OpenGL ES 2.0和3.0。或者你可能自定義渲染算法只為更好的圖片質量。GLKit非常適合這種方法,你可以使你的render對象成為一個標准GLKView實例的代理。取代子類化一個GLKView並實現drawRect:方法,你的render類遵循GLKViewDelegate協議和實現glkView:drawInRect:方法。如下示例
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 創建上下文來測試特性
EAGLContext *context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext: context];
// 根據設備特性選擇一個render類
GLint maxTextureSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
if (maxTextureSize > 2048) {
self.renderer = [MyBigTextureRender alloc] initWithContext: context];
} else {
self.renderer = [MyRenderer alloc] initWithContext: context];
}
// 使renderer成為視圖的代理
GLKView *view = (GLKView * )self.window.rootViewController.view;
view.delegate = self.renderer;
// 將OpenGL ES上下文給這個視圖就可以繪制
view.context = context;
return YES;
}
GLKit視圖控制器可使OpenGL ES內容動畫
通常,一個GLKView對象根據要求渲染內容。也就是說使用OpenGL ES繪圖的一個關鍵優勢是它能夠使用圖形硬件對復雜場景進行連續動畫——游戲和模擬等應用很少呈現靜態圖像。對這些情況下,GLKit框架提供一個視圖控制器類,用於維護它所管理的GLKView對象的動畫循環。這個循環遵循游戲和模擬中常見的設計模式,分為兩個階段:更新和顯示。
理解動畫循環:在update階段,控制器調用自己的update方法(或自身代理的glkViewControllerUpdate:方法)。在這個方法中你要准備下一幀的繪制。例如,一個游戲可能會使用這種方法來根據上一幀之后接收到的輸入事件來確定玩家和敵人角色的位置,而一個科學的可視化可能會使用這種方法來運行模擬一個步驟。如果你需要時間信息來決定你應用下一幀的狀態,可使用控制器的時間屬性如timeSinceLastUpdate屬性。在display階段:視圖控制器調用視圖的display方法,視圖的顯示方法會調用視圖的繪圖方法。在繪圖方法中,你提交OpenGL ES繪圖命令給GPU去渲染你的內容。為了獲得最佳性能,你的應用應該在開始新的幀渲染時修改OpenGL ES對象,並隨后提交繪圖命令。
動畫循環以視圖控制器的framesPerSecond屬性所指示的速率在這兩個階段之間交替。你可以使用preferredFramesPerSecond屬性來設置所需的幀速率———為了優化當前顯示硬件的性能,視圖控制器會自動選擇一個接近你的首選值的最優幀速率。
使用GLKit視圖控制器
下面是使用GLKViewController子類和GLKView實例呈現動畫OpenGL ES內容的典型策略
@implementation PlanetViewController // subclass of GLKViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 創建上下文並賦值給視圖
GLKView *view= (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
// 設置動畫幀率
self.preferredFramesPerSecond = 60;
// 加載shaders, texttures, vertex arrays, 設置工程矩陣
[self setupGL];
}
- (void)update{
_rotation += self.timeSinceLastUpdate * M_PI_2; // 1/4 旋轉/s
// 設置變換矩陣
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(_rotation, 0.0f, 1.0f, 0.0f);
_normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
_modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, modelViewMatrix);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
// 清除framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 設置uniforms shader
glUseProgram(_diffuseShading);
glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
glUniformMatrix3fv(_uniformNormalMatrix, 1, 0, _normalMatrix.m);
// 使用先前配置的texture, vertex array繪圖
glBindTexture(GL_TEXTURE_2D, _planetTexture);
glBindVertexArrayOES(_planetMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
}
@end
使用GLKit開發渲染器
除了視圖和視圖控制器基礎結構之外,GLKit框架還提供了其它幾個特性,以簡化iOS上的OpenGL ES的開發
處理向量和矩陣數學OpenGL ES2.0及以后的版本不提供用於創建或指定轉換矩陣的內置函數。相反,可編程着色器提供了頂點變換,你可以使用通用的統一變量來指定着色器的輸入。GLKit框架包含一個全面向量和矩陣類型和函數庫,針對iOS硬件的高性能進行了優化。
OpenGL ES2.0及以后的版本刪除了所有與OpenGL ES1.1固定函數圖形管道相關的功能。GLKBaseEffect類為OpenGL ES1.1管道的轉換、點亮、陰影階段提供了OC模擬,而GLKSkyboxEffect和GLKReflectionMapEffect類增加了對常見視覺效果的支持。
GLKTextureLoader類提供了一種簡單的方法,可以同步或異步地將來自iOS支持的任何圖像格式的紋理數據加載到OpenGL ES上下文中。