繪制OpenGL ES和GLKit
GLKit框架提供視圖和視圖控制器類,可以消除為繪制和動畫化OpenGL ES內(nèi)容而需要的設(shè)置和維護代碼。 GLKView類管理OpenGL ES基礎(chǔ)架構(gòu),為您的繪圖代碼提供一個地方,GLKViewController類為GLKit視圖中OpenGL ES內(nèi)容的平滑動畫提供了一個渲染循環(huán)。這些類擴展了用于繪制視圖內(nèi)容和管理視圖呈現(xiàn)的標準UIKit設(shè)計模式。因此,您可以將重點放在OpenGL ES渲染代碼上,并使您的應(yīng)用程序快速啟動并運行。 GLKit框架還提供了其他功能來簡化OpenGL ES 2.0和3.0開發(fā)。
GLKit視圖根據(jù)需要繪制OpenGL ES內(nèi)容
GLKView類提供與標準UIView繪圖周期相當?shù)腛penGL ES。 UIView實例自動配置其圖形上下文,以便您的drawRect:實現(xiàn)只需要執(zhí)行Quartz 2D繪圖命令,并且GLKView實例自動配置,以便您的繪圖方法只需執(zhí)行OpenGL ES繪圖命令。 GLKView類通過維護保存OpenGL ES繪圖命令結(jié)果的framebuffer對象來提供此功能,然后在繪圖方法返回后自動將其顯示給Core Animation。
像標準的UIKit視圖一樣,GLKit視圖根據(jù)需要呈現(xiàn)其內(nèi)容。當您的視圖第一次顯示時,它會調(diào)用您的繪圖方法 - Core Animation緩存渲染的輸出,并在顯示視圖時顯示它。當您想要更改視圖的內(nèi)容時,請調(diào)用其setNeedsDisplay方法,再次調(diào)用繪圖方法,緩存生成的圖像,并將其顯示在屏幕上。當用于渲染圖像的數(shù)據(jù)不經(jīng)常更改或僅響應(yīng)于用戶操作時,此方法非常有用。通過僅在需要時才提供新的視圖內(nèi)容,您可以節(jié)省設(shè)備上的電池電量,并為設(shè)備執(zhí)行其他操作留出更多時間
創(chuàng)建和配置GLKit視圖
您可以以編程方式或使用Interface Builder創(chuàng)建和配置GLKView對象。在使用它繪制之前,必須將其與EAGLContext對象相關(guān)聯(lián)(請參閱配置OpenGL ES上下文)。
- 以編程方式創(chuàng)建視圖時,首先創(chuàng)建上下文,然后將其傳遞給視圖的initWithFrame:context:方法。
- 從故事板加載視圖后,創(chuàng)建上下文并將其設(shè)置為視圖的上下文屬性的值。
GLKit視圖會自動創(chuàng)建和配置自己的OpenGL ES framebuffer對象和renderbuffers。您可以使用視圖的可繪制屬性來控制這些對象的屬性,如清單3-1所示。如果更改GLKit視圖的大小,比例因子或可繪制屬性,則會在下次繪制內(nèi)容時自動刪除并重新創(chuàng)建相應(yīng)的framebuffer對象和renderbuffers。
Listing3-1
- (void)viewDidLoad
{
[super viewDidLoad];
// Create an OpenGL ES context and assign it to the view loaded from storyboard
GLKView *view = (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Configure renderbuffers created by the view
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
// Enable multisampling
view.drawableMultisample = GLKViewDrawableMultisample4X;
}
您可以使用其drawableMultisample屬性為GLKView實例啟用多采樣。多采樣是一種抗鋸齒形式,可以平滑鋸齒狀邊緣,以更多的內(nèi)存和片段處理時間為代價,以大多數(shù)3D應(yīng)用程序的圖像質(zhì)量提升,如果啟用多采樣,則始終測試應(yīng)用程序的性能,以確保其仍然可以接受。
繪制GLKit視圖
圖3-1概述了繪制OpenGL ES內(nèi)容的三個步驟:準備OpenGL ES基礎(chǔ)設(shè)施,發(fā)布繪圖命令,并將呈現(xiàn)的內(nèi)容呈現(xiàn)給Core Animation進行顯示。 GLKView類實現(xiàn)了第一和第三步。對于第二步,您將實現(xiàn)一個繪圖方法,如清單3-2中的示例所示。
Listing3-2
- (void)drawRect:(CGRect)rect
{
// Clear the framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw using previously configured texture, shader, uniforms, and 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);
}
注意:glClear函數(shù)提示OpenGL ES可以丟棄任何現(xiàn)有的幀緩沖區(qū)內(nèi)容,避免了昂貴的內(nèi)存操作將以前的內(nèi)容加載到內(nèi)存中。為了確保最佳性能,您應(yīng)該在繪制之前始終調(diào)用此函數(shù)。
GLKView類能夠為OpenGL ES繪圖提供一個簡單的界面,因為它可以管理OpenGL ES渲染過程的標準部分
- 在調(diào)用繪圖方法之前,該視圖:
- 使其EAGLContext對象成為當前上下文
- 根據(jù)當前大小,比例因子和可繪制屬性(如果需要)創(chuàng)建一個framebuffer對象和renderbuffers
- 將framebuffer對象綁定為繪制命令的當前目標
- 設(shè)置OpenGL ES視口以匹配幀緩沖區(qū)大小
- 在您的繪圖方法返回后,視圖:
- 解決多采樣緩沖區(qū)(如果啟用了多次采樣)
- 丟棄其內(nèi)容不再需要的renderbuffers
- 向Core Animation呈現(xiàn)renderbuffer內(nèi)容以進行緩存和顯示
使用委托對象呈現(xiàn)
許多OpenGL ES應(yīng)用程序在自定義類中實現(xiàn)渲染代碼。這種方法的優(yōu)點在于它允許您通過為每個渲染算法定義不同的渲染器類來輕松支持多種渲染算法。共享公共功能的渲染算法可以從超類繼承。例如,您可以使用不同的渲染器類來支持OpenGL ES 2.0和3.0(請參閱配置OpenGL ES上下文)。或者您可以使用它們來定制渲染,從而在具有更強大硬件的設(shè)備上獲得更好的圖像質(zhì)量
GLKit非常適合這種方法 - 您可以使您的渲染器對象成為標準GLKView實例的委托。您的渲染器類不是將GLKView子類化并實現(xiàn)drawRect:方法,而是使用GLKViewDelegate協(xié)議并實現(xiàn)glkView:drawInRect:方法。程序清單3-3演示了在應(yīng)用程序啟動時基于硬件功能選擇渲染器類
Listing3-3
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create a context so we can test for features
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:context];
// Choose a rendering class based on device features
GLint maxTextureSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
if (maxTextureSize > 2048)
self.renderer = [[MyBigTextureRenderer alloc] initWithContext:context];
else
self.renderer = [[MyRenderer alloc] initWithContext:context];
// Make the renderer the delegate for the view loaded from the main storyboard
GLKView *view = (GLKView *)self.window.rootViewController.view;
view.delegate = self.renderer;
// Give the OpenGL ES context to the view so it can draw
view.context = context;
return YES;
}
GLKit視圖控制器動畫化OpenGL ES內(nèi)容
默認情況下,GLKView對象根據(jù)需要呈現(xiàn)其內(nèi)容。也就是說,使用OpenGL ES繪制的一個關(guān)鍵優(yōu)點是它能夠使用圖形處理硬件來連續(xù)制作復(fù)雜場景 - 諸如游戲和模擬等應(yīng)用程序很少呈現(xiàn)靜態(tài)圖像。對于這些情況,GLKit框架提供了一個視圖控制器類,它為其管理的GLKView對象維護一個動畫循環(huán)。該循環(huán)遵循游戲和模擬中常見的設(shè)計模式,分為兩個階段:更新和顯示。圖3-2顯示了動畫循環(huán)的簡化示例。
了解動畫循環(huán)
對于更新階段,視圖控制器調(diào)用其自己的更新方法(或其委托的glkViewControllerUpdate:方法)。在這種方法中,你應(yīng)該準備繪制下一幀。例如,游戲可能會使用這種方法根據(jù)自最后一幀以來接收到的輸入事件來確定玩家和敵人角色的位置,科學(xué)可視化可能會使用此方法來運行其模擬步驟。如果您需要時間信息來確定應(yīng)用的下一幀的狀態(tài),請使用其中一個視圖控制器的時間屬性,如timeSinceLastUpdate屬性。在圖3-2中,更新階段增加一個角度變量,并使用它來計算變換矩陣。
對于顯示階段,視圖控制器調(diào)用其視圖的顯示方法,該方法又調(diào)用您的繪圖方法。在繪圖方法中,您可以向GPU提交OpenGL ES繪圖命令以呈現(xiàn)內(nèi)容。為了獲得最佳性能,您的應(yīng)用程序應(yīng)在渲染新幀時開始修改OpenGL ES對象,之后提交繪圖命令。在圖3-2中,顯示階段將著色器程序中的均勻變量設(shè)置為在更新階段計算的矩陣,然后提交繪圖命令以呈現(xiàn)新內(nèi)容。
動畫循環(huán)按照視圖控制器的framePerSecond屬性指示的速率在這兩個階段之間進行交替。您可以使用preferredFramesPerSecond屬性設(shè)置所需的幀速率,以優(yōu)化當前顯示硬件的性能,視圖控制器會自動選擇接近您的首選值的最佳幀速率。
重要提示:為獲得最佳效果,請選擇應(yīng)用程序可以始終如一地實現(xiàn)的幀率平滑,一致的幀速率產(chǎn)生比不規(guī)則變化的幀速率更愉快的用戶體驗。
使用GLKit視圖控制器
清單3-4演示了使用GLKViewController子類和GLKView實例渲染動畫OpenGL ES內(nèi)容的典型策略。
@implementation PlanetViewController // subclass of GLKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Create an OpenGL ES context and assign it to the view loaded from storyboard
GLKView *view = (GLKView *)self.view;
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Set animation frame rate
self.preferredFramesPerSecond = 60;
// Not shown: load shaders, textures and vertex arrays, set up projection matrix
[self setupGL];
}
- (void)update
{
_rotation += self.timeSinceLastUpdate * M_PI_2; // one quarter rotation per second
// Set up transform matrices for the rotating planet
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
{
// Clear the framebuffer
glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Set shader uniforms to values calculated in -update
glUseProgram(_diffuseShading);
glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
glUniformMatrix3fv(_uniformNormalMatrix, 1, 0, _normalMatrix.m);
// Draw using previously configured texture and vertex array
glBindTexture(GL_TEXTURE_2D, _planetTexture);
glBindVertexArrayOES(_planetMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
}
@end
在此示例中,PlanetViewController類(自定義GLKViewController子類)的實例從故事板加載,以及標準GLKView實例及其可繪制屬性。 viewDidLoad方法創(chuàng)建一個OpenGL ES上下文并將其提供給視圖,并且還設(shè)置動畫循環(huán)的幀速率。
視圖控制器自動地代表其視圖,因此它實現(xiàn)了動畫循環(huán)的更新和顯示階段。在更新方法中,它計算顯示旋轉(zhuǎn)行星所需的變換矩陣。在glkView:drawInRect:方法中,它將這些矩陣提供給著色器程序,并提交繪圖命令來渲染行星幾何。
使用GLKit開發(fā)您的渲染器
除了查看和查看控制器基礎(chǔ)設(shè)施外,GLKit框架還提供了幾個其他功能來簡化iOS上的OpenGL ES開發(fā)。
處理矢量和矩陣數(shù)學(xué)
OpenGL ES 2.0及更高版本不提供用于創(chuàng)建或指定變換矩陣的內(nèi)置函數(shù)。相反,可編程著色器提供頂點變換,并使用通用的均勻變量指定著色器輸入。 GLKit框架包括矢量和矩陣類型和功能的綜合庫,針對iOS硬件上的高性能進行了優(yōu)化。 (見GLKit框架參考。)
從OpenGL ES 1.1固定功能管道遷移
OpenGL ES 2.0及更高版本刪除與OpenGL ES 1.1固定功能圖形管道相關(guān)聯(lián)的所有功能。 GLKBaseEffect類為OpenGL ES 1.1流水線的轉(zhuǎn)換,照明和陰影階段提供了Objective-C模擬,GLKSkyboxEffect和GLKReflectionMapEffect類增加了對常見視覺效果的支持。有關(guān)詳細信息,請參閱這些類的參考文檔。
加載紋理數(shù)據(jù)
GLKTextureLoader類提供了一種簡單的方法來將紋理數(shù)據(jù)從iOS支持的任何圖像格式加載到OpenGL ES上下文中,同步或異步。 (請參閱使用GLKit框架加載紋理數(shù)據(jù)。)