OpenGL ES初探(快速了解OpenGL ES) 以及第一個(gè)案例

注意:本系列不會(huì)就OpenGL/OpenGL ES的專業(yè)名詞和概念以及渲染流程做過(guò)多闡述,有還未清楚的或者剛接觸OpenGL/OpenGL ES的請(qǐng)參考[OpenGL入門(mén)系列]
(http://www.itdecent.cn/nb/36794104)
提示: 希望從零基礎(chǔ)學(xué)習(xí)OpenGL ES的同學(xué)個(gè)人建議先去了解OpenGL(因?yàn)楣潭ü芫€更方便初學(xué)者入門(mén))

OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對(duì)手機(jī)、PDA和游戲主機(jī)等嵌入式設(shè)備而設(shè)計(jì)。該API由Khronos集團(tuán)定義推廣,Khronos是一個(gè)圖形軟硬件行業(yè)協(xié)會(huì),該協(xié)會(huì)主要關(guān)注圖形和多媒體方面的開(kāi)放標(biāo)準(zhǔn)。
OpenGL ES是以手持和嵌入式為目標(biāo)的高級(jí)3D圖形應(yīng)用程序編程接口(API).是目前智能手機(jī)中占據(jù)統(tǒng)治地位的圖形API.

OpenGL ES是OpenGL的簡(jiǎn)化版本,它消除了冗余功能,提供了一個(gè)即易于學(xué)習(xí)又更易于在移動(dòng)圖形硬件中實(shí)現(xiàn)的庫(kù)。

支持平臺(tái)、機(jī)型

  • 支持iPad, iPhone3GS 和后續(xù)版本,以及iPodTouch3代和后續(xù)版本。

  • 支持Android平臺(tái)從Android 2.2版本開(kāi)始。

  • 支持Android NDK從Android 2.0版本開(kāi)始。

  • 支持BlackBerryPlayBook黑莓。

  • 支持Pandora潘多拉控制臺(tái)的3D庫(kù)。

  • WebGL支持:瀏覽器支持OpenGL

  • 支持少數(shù)新款Nokia諾基亞手機(jī),比如N900上的Maemo和N8上的Symbian3塞班3系統(tǒng)。

  • 支持多款三星手機(jī),包括Galaxy S和Wave。

  • 使用開(kāi)發(fā)插件可以支持Palm webOS。

  • 支持Archos 愛(ài)可視上網(wǎng)本:70 IT, 101 IT

有關(guān)OpenGL ES API和OpenGL ES著色語(yǔ)言的完整參考,請(qǐng)參閱計(jì)劃使用的OpenGL ES版本集合:

1. OpenGL ES as a Client-Server Architecture - OpenGL ES作為客戶端 - 服務(wù)器架構(gòu)

OpenGL ES和OpenGL相同,架構(gòu)上分為服務(wù)端和客戶端。應(yīng)用程序?qū)顟B(tài)更改,紋理和頂點(diǎn)數(shù)據(jù)以及渲染命令傳達(dá)給OpenGL ES客戶端。 客戶端將這些數(shù)據(jù)轉(zhuǎn)換成圖形硬件理解的格式,并將其轉(zhuǎn)發(fā)到GPU。
簡(jiǎn)單理解:
我們編寫(xiě)的一系列代碼例如調(diào)用OpenGL/OpenGL ES提供的各種API都可視為客戶端部分,和其內(nèi)部核心實(shí)現(xiàn)以及具體個(gè)GPU進(jìn)行調(diào)度交互的功能部分屬于服務(wù)端部分。


OpenGL ES Client-Server Architecture

2. OpenGL ES as a Graphics Pipeline - OpenGL ES作為圖形流水線

下圖可視化OpenGL ES作為圖形管道。 您的應(yīng)用程序配置圖形流水線,然后執(zhí)行繪圖命令以將頂點(diǎn)數(shù)據(jù)發(fā)送到流水線。 管道的連續(xù)階段運(yùn)行一個(gè)頂點(diǎn)著色器來(lái)處理頂點(diǎn)數(shù)據(jù),將頂點(diǎn)組合成圖元,將原始圖元柵格化成片段,運(yùn)行片段著色器來(lái)計(jì)算每個(gè)片段的顏色和深度值,并將片段混合到一個(gè)幀緩沖區(qū)中進(jìn)行顯示。
而OpenGL中的固定管線則是把該流程封裝好,作為固定流水線,暴漏出API以供客戶端調(diào)用,在OpenGL ES3.0棄用。使用GLSL(OpenGL著色語(yǔ)言O(shè)penGL Shading Language)可編程管線,iOS系統(tǒng)中蘋(píng)果原生提供了GLKit封裝了簡(jiǎn)單圖形繪制庫(kù)(后續(xù)會(huì)專門(mén)寫(xiě)篇文章介紹)。


OpenGL ES 渲染管線

3. Use Double Buffering to Avoid Resource Conflicts - 使用雙緩沖來(lái)避免資源沖突

當(dāng)您的應(yīng)用程序和OpenGL ES同時(shí)訪問(wèn)OpenGL ES對(duì)象時(shí),會(huì)發(fā)生資源沖突。 當(dāng)一個(gè)參與者嘗試修改由另一個(gè)使用的OpenGL ES對(duì)象時(shí),它們可能會(huì)阻塞,直到對(duì)象不再使用。 一旦他們開(kāi)始修改對(duì)象,其他參與者可能不會(huì)訪問(wèn)對(duì)象,直到修改完成。 或者,OpenGL ES可能會(huì)隱式復(fù)制對(duì)象,以便兩個(gè)參與者可以繼續(xù)執(zhí)行命令。 這兩個(gè)選項(xiàng)都是安全的,但每個(gè)選項(xiàng)都可能會(huì)成為您應(yīng)用程序中的瓶頸。 下圖顯示了這個(gè)問(wèn)題。 在這個(gè)例子中,有一個(gè)單一的紋理對(duì)象,OpenGL ES和你的應(yīng)用都要使用。 當(dāng)應(yīng)用程序嘗試更改紋理時(shí),必須等到之前提交的繪圖命令完成 - CPU才能與GPU同步。

要解決這個(gè)問(wèn)題,您的應(yīng)用程序可以在更改對(duì)象和繪圖之間執(zhí)行其他工作。 但是,如果您的應(yīng)用程序沒(méi)有可以執(zhí)行的其他工作,它應(yīng)該顯式地創(chuàng)建兩個(gè)相同大小的對(duì)象; 而一個(gè)參與者讀取一個(gè)對(duì)象,另一個(gè)參與者修改另一個(gè)對(duì)象。 下圖說(shuō)明了雙緩沖方式。 當(dāng)GPU在一個(gè)紋理上運(yùn)行時(shí),CPU會(huì)修改另一個(gè)紋理。 初始啟動(dòng)后,CPU或GPU都不空閑。 雖然為紋理顯示,此解決方案適用于幾乎任何類型的OpenGL ES對(duì)象。

雙重緩沖對(duì)于大多數(shù)應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠了,但是要求兩位參與者在大致相同的時(shí)間內(nèi)完成處理命令。 為了避免阻塞,您可以添加更多緩沖區(qū); 這實(shí)現(xiàn)了傳統(tǒng)的生產(chǎn)者 - 消費(fèi)者模式。 如果生產(chǎn)者在消費(fèi)者完成處理命令之前完成,則它需要空閑緩沖區(qū)并繼續(xù)處理命令。 在這種情況下,生產(chǎn)者只有在消費(fèi)者落后的情況下才會(huì)閑置。

雙重和三重緩沖器消除額外的內(nèi)存,以防止管道停滯。 額外使用內(nèi)存可能會(huì)對(duì)應(yīng)用程序的其他部分造成壓力。 在iOS設(shè)備上,內(nèi)存可能很少; 您的設(shè)計(jì)可能需要平衡使用更多的內(nèi)存與其他應(yīng)用程序優(yōu)化。

OpenGL ES 定義了跨平臺(tái)的接口來(lái)使用 GPU 的硬件性能加速圖形的渲染,其中由渲染上下文來(lái)執(zhí)行渲染命令,幀緩存存儲(chǔ)渲染結(jié)果,渲染目標(biāo)顯示幀緩存中結(jié)果。對(duì)應(yīng)到 iOS 中,EAGLContext 是實(shí)現(xiàn)上下文的類,GLKView 和 CAEAGLLayer 則用來(lái)顯示最終的渲染結(jié)果。

入門(mén)案例:OpenGL ES的‘hello word’

這里我們使用GLkit
1.新建工程 ,導(dǎo)入GLKit, ViewController.h中類別修改為繼承自GLKViewController

#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>

@interface ViewController : GLKViewController

@end

2.viewController.m中導(dǎo)入

#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
  • OpenGL ES 相關(guān)初始化
-(void)setUpConfig
{
    //1.初始化上下文&設(shè)置當(dāng)前上下文
    /*
     EAGLContext 是蘋(píng)果iOS平臺(tái)下實(shí)現(xiàn)OpenGLES 渲染層.
     kEAGLRenderingAPIOpenGLES1 = 1, 固定管線
     kEAGLRenderingAPIOpenGLES2 = 2,
     kEAGLRenderingAPIOpenGLES3 = 3,
     */
    context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
    //判斷context是否創(chuàng)建成功
    if (!context) {
        NSLog(@"Create ES context Failed");
    }
    //設(shè)置當(dāng)前上下文
    [EAGLContext setCurrentContext:context];
    
    //2.獲取GLKView & 設(shè)置context
    GLKView *view =(GLKView *) self.view;
    view.context = context;
    
    /*3.配置視圖創(chuàng)建的渲染緩存區(qū).
     
     (1). drawableColorFormat: 顏色緩存區(qū)格式.
     簡(jiǎn)介:  OpenGL ES 有一個(gè)緩存區(qū),它用以存儲(chǔ)將在屏幕中顯示的顏色。你可以使用其屬性來(lái)設(shè)置緩沖區(qū)中的每個(gè)像素的顏色格式。
     
     GLKViewDrawableColorFormatRGBA8888 = 0,
     默認(rèn).緩存區(qū)的每個(gè)像素的最小組成部分(RGBA)使用8個(gè)bit,(所以每個(gè)像素4個(gè)字節(jié),4*8個(gè)bit)。
     
     GLKViewDrawableColorFormatRGB565,
     如果你的APP允許更小范圍的顏色,即可設(shè)置這個(gè)。會(huì)讓你的APP消耗更小的資源(內(nèi)存和處理時(shí)間)
     
     (2). drawableDepthFormat: 深度緩存區(qū)格式
     
     GLKViewDrawableDepthFormatNone = 0,意味著完全沒(méi)有深度緩沖區(qū)
     GLKViewDrawableDepthFormat16,
     GLKViewDrawableDepthFormat24,
     如果你要使用這個(gè)屬性(一般用于3D游戲),你應(yīng)該選擇GLKViewDrawableDepthFormat16
     或GLKViewDrawableDepthFormat24。這里的差別是使用GLKViewDrawableDepthFormat16
     將消耗更少的資源
     
     */
    
    //3.配置視圖創(chuàng)建的渲染緩存區(qū).
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
    
    //4.設(shè)置背景顏色
    glClearColor(0.7, 0.7, 0.7, 1.0);
}
  • 加載頂點(diǎn)/紋理坐標(biāo)數(shù)據(jù)
-(void)setUpVertexData
{
    //1.設(shè)置頂點(diǎn)數(shù)組(頂點(diǎn)坐標(biāo),紋理坐標(biāo))
    /*
     紋理坐標(biāo)系取值范圍[0,1];原點(diǎn)是左下角(0,0);
     故而(0,0)是紋理圖像的左下角, 點(diǎn)(1,1)是右上角.
     */
    GLfloat vertexData[] = {
        
        0.5, -0.5, 0.0f,    1.0f, 0.0f, //右下
        0.5, 0.5, -0.0f,    1.0f, 1.0f, //右上
        -0.5, 0.5, 0.0f,    0.0f, 1.0f, //左上
        
        0.5, -0.5, 0.0f,    1.0f, 0.0f, //右下
        -0.5, 0.5, 0.0f,    0.0f, 1.0f, //左上
        -0.5, -0.5, 0.0f,   0.0f, 0.0f, //左下
    };
 
    /*
     頂點(diǎn)數(shù)組: 開(kāi)發(fā)者可以選擇設(shè)定函數(shù)指針,在調(diào)用繪制方法的時(shí)候,直接由內(nèi)存?zhèn)魅腠旤c(diǎn)數(shù)據(jù),也就是說(shuō)這部分?jǐn)?shù)據(jù)之前是存儲(chǔ)在內(nèi)存當(dāng)中的,被稱為頂點(diǎn)數(shù)組
     
     頂點(diǎn)緩存區(qū): 性能更高的做法是,提前分配一塊顯存,將頂點(diǎn)數(shù)據(jù)預(yù)先傳入到顯存當(dāng)中。這部分的顯存,就被稱為頂點(diǎn)緩沖區(qū)
     */
    
    //2.開(kāi)辟頂點(diǎn)緩存區(qū)
    //(1).創(chuàng)建頂點(diǎn)緩存區(qū)標(biāo)識(shí)符ID
    GLuint bufferID;
    glGenBuffers(1, &bufferID);
    //(2).綁定頂點(diǎn)緩存區(qū).(明確作用)
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    //(3).將頂點(diǎn)數(shù)組的數(shù)據(jù)copy到頂點(diǎn)緩存區(qū)中(GPU顯存中)
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    
    //3.打開(kāi)讀取通道.
    /*
     (1)在iOS中, 默認(rèn)情況下,出于性能考慮,所有頂點(diǎn)著色器的屬性(Attribute)變量都是關(guān)閉的.
     意味著,頂點(diǎn)數(shù)據(jù)在著色器端(服務(wù)端)是不可用的. 即使你已經(jīng)使用glBufferData方法,將頂點(diǎn)數(shù)據(jù)從內(nèi)存拷貝到頂點(diǎn)緩存區(qū)中(GPU顯存中).
     所以, 必須由glEnableVertexAttribArray 方法打開(kāi)通道.指定訪問(wèn)屬性.才能讓頂點(diǎn)著色器能夠訪問(wèn)到從CPU復(fù)制到GPU的數(shù)據(jù).
     注意: 數(shù)據(jù)在GPU端是否可見(jiàn),即,著色器能否讀取到數(shù)據(jù),由是否啟用了對(duì)應(yīng)的屬性決定,這就是glEnableVertexAttribArray的功能,允許頂點(diǎn)著色器讀取GPU(服務(wù)器端)數(shù)據(jù)。
   
    (2)方法簡(jiǎn)介
    glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
   
    功能: 上傳頂點(diǎn)數(shù)據(jù)到顯存的方法(設(shè)置合適的方式從buffer里面讀取數(shù)據(jù))
    參數(shù)列表:
        index,指定要修改的頂點(diǎn)屬性的索引值,例如
        size, 每次讀取數(shù)量。(如position是由3個(gè)(x,y,z)組成,而顏色是4個(gè)(r,g,b,a),紋理則是2個(gè).)
        type,指定數(shù)組中每個(gè)組件的數(shù)據(jù)類型??捎玫姆?hào)常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值為GL_FLOAT。
        normalized,指定當(dāng)被訪問(wèn)時(shí),固定點(diǎn)數(shù)據(jù)值是否應(yīng)該被歸一化(GL_TRUE)或者直接轉(zhuǎn)換為固定點(diǎn)值(GL_FALSE)
        stride,指定連續(xù)頂點(diǎn)屬性之間的偏移量。如果為0,那么頂點(diǎn)屬性會(huì)被理解為:它們是緊密排列在一起的。初始值為0
        ptr指定一個(gè)指針,指向數(shù)組中第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。初始值為0
     */
    
    //頂點(diǎn)坐標(biāo)數(shù)據(jù)
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
    
    
    //紋理坐標(biāo)數(shù)據(jù)
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
 
}
  • 加載紋理數(shù)據(jù)(使用GLBaseEffect)
-(void)setUpTexture
{
    //1.獲取紋理圖片路徑
    NSString *filePath = [[NSBundle mainBundle]pathForResource:@"logOnbg" ofType:@"png"];
    
    //2.設(shè)置紋理參數(shù)
    //紋理坐標(biāo)原點(diǎn)是左下角,但是圖片顯示原點(diǎn)應(yīng)該是左上角.
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
    
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
    
    //3.使用蘋(píng)果GLKit 提供GLKBaseEffect 完成著色器工作(頂點(diǎn)/片元)
    cEffect = [[GLKBaseEffect alloc]init];
    cEffect.texture2d0.enabled = GL_TRUE;
    cEffect.texture2d0.name = textureInfo.name;
}
  • 繪制視圖的內(nèi)容 GLKViewDelegate
//繪制視圖的內(nèi)容
/*
 GLKView對(duì)象使其OpenGL ES上下文成為當(dāng)前上下文,并將其framebuffer綁定為OpenGL ES呈現(xiàn)命令的目標(biāo)。然后,委托方法應(yīng)該繪制視圖的內(nèi)容。
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    //1.
    glClear(GL_COLOR_BUFFER_BIT);
    
    //2.準(zhǔn)備繪制
    [cEffect prepareToDraw];
    
    //3.開(kāi)始繪制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
}
  • 最后在viewDidLoad中依次調(diào)用方法就可以了
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //1.OpenGL ES 相關(guān)初始化
    [self setUpConfig];
    
    //2.加載頂點(diǎn)/紋理坐標(biāo)數(shù)據(jù)
    [self setUpVertexData];
    
    //3.加載紋理數(shù)據(jù)(使用GLBaseEffect)
    [self setUpTexture];
}

顯示結(jié)果:


image.png

最后一個(gè)小提示OpenGL ES使用GPU的部分在Xcode的模擬器上運(yùn)行是可以使用的,模擬器會(huì)用CPU模擬GPU功能,Metal則不可以。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容