OPenGL ES 摘要 - iOS開發(fā)

OpenGL ES

OpenGL ES是OpenGL的子集,是基于c語言的API,可以無縫移植到OC中,然后通過創(chuàng)建上下文來接受命令和操縱幀緩存;在 iOS中,可以使用GLKView將OpenGL ES繪制的內(nèi)容渲染到屏幕上,還可以使用CAEAGLLayer圖層將動(dòng)畫與視圖相結(jié)合。

注意:當(dāng)應(yīng)用處于后臺(tái)狀態(tài)時(shí),不能調(diào)用OpenGL ES中的函數(shù),否則應(yīng)用便會(huì)被終止,應(yīng)當(dāng)在進(jìn)入后臺(tái)時(shí)調(diào)用glfinish()來表明所有提交的指令均被執(zhí)行完畢,而且上下文也不能在同一時(shí)刻被不同的線程訪問;

EAGLContext

OpenGL ES中需要先創(chuàng)建一個(gè)EAGLContext的渲染上下文對(duì)象。而且每個(gè)線程都只能對(duì)應(yīng)一個(gè)上下文,在同一個(gè)線程中切換不同的上下文時(shí),需要對(duì)上下文進(jìn)行強(qiáng)引用以防止其被釋放,并且在切換之前應(yīng)屌用glFlush()函數(shù)將當(dāng)前的上下文提交的指令傳到徒刑硬件中去。有兩個(gè)重要的函數(shù)設(shè)置和獲取當(dāng)前的上下文:

 +(BOOL)setCurrentContext:(EAGLContext *)context;
 +(EAGLContext *) currentContext;

在創(chuàng)建上下文時(shí),不同的設(shè)備可能支持的OpenGL ES版本也不同,不過基本都是向后兼容的,若在創(chuàng)建時(shí)返回值為nil,那么表示設(shè)備不支持制定版本的OpenGL ES。

 - (instantype) initWithAPI:(EAGLRenderingAPI) api;
 - (instantype) initWithAPI:(EAGLRenderingAPI)api sharegroup:(EAGLSharegroup *)sharegroup;

sharegroup作用:在一個(gè)線程中,上下文的狀態(tài)與上下文對(duì)象是分離的,其狀態(tài)都保存在group實(shí)力對(duì)象中,該對(duì)象是透明的,不應(yīng)該主動(dòng)創(chuàng)建該類的實(shí)例,這種設(shè)計(jì)方式是為了節(jié)約系統(tǒng)資源,對(duì)于不同的上下文可能擁有相同的上下文狀態(tài),那么這種設(shè)計(jì)方式十分便利。如需要在子線程中加載數(shù)據(jù),在主線程中進(jìn)行渲染,那么當(dāng)數(shù)據(jù)加載完成之后,可以直接將子線程中的上下文狀態(tài)綁定到主線程上下文中;

GLKView和CAEAGLLayer

GLKView

GLKView類為渲染提供了一個(gè)顯示視圖,在創(chuàng)建GLKView之后,要將其與上下文綁定,可以手動(dòng)創(chuàng)建,通過以下函數(shù):

- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;

也可以將某個(gè)UIView強(qiáng)制轉(zhuǎn)換成GLKView,并綁定上下文,設(shè)置屬性:

GLKView *view = (GLKView *)self.view;
view.context =[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
view.srawableColorFormat = GLKViewDrawableColorFormatRGBA8888;

當(dāng)創(chuàng)建了GLKView之后,就可以通過重寫drawRect:方法來進(jìn)行繪制:

 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;

注意:ELKit提供了GLKBaseEffect著色器對(duì)象來進(jìn)行繪圖,下面這段代碼來講述著色器的使用;

@property (nonatomic , strong) GLKBaseEffect* mEffect;
self.mEffect = [[GLKBaseEffect alloc] init];
self.mEffect.texture2d0.enabled = GL_TRUE;
 self.mEffect.texture2d0.name = textureInfo.name;
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//啟動(dòng)著色器
[self.mEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
}

CAEAGLLayer

當(dāng)使用CAEAGLLayer圖層來進(jìn)行渲染時(shí),可以將某個(gè)view的layer強(qiáng)制轉(zhuǎn)換成CAEAGLLLayer,設(shè)置放大倍數(shù),描繪屬性,并且重寫改view的layerClass方法,下面這塊代碼將講述改圖層的初始設(shè)置;

+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (void)setupLayer
{
    self.myEagLayer = (CAEAGLLayer*) self.layer;
    //設(shè)置放大倍數(shù)
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    
    // CALayer 默認(rèn)是透明的,必須將它設(shè)為不透明才能讓其可見
    self.myEagLayer.opaque = YES;
    
    // 設(shè)置描繪屬性,在這里設(shè)置不維持渲染內(nèi)容以及顏色格式為 RGBA8
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];    
}

VBO和EBO

頂點(diǎn)緩沖對(duì)象(VBO)和索引緩沖對(duì)象(EBO)是兩種管理頂點(diǎn)內(nèi)存的方式,開始繪制圖形之前,我們必須先給OpenGL輸入一些頂點(diǎn)數(shù)據(jù)。它會(huì)在GPU內(nèi)存(通常被稱為顯存)中儲(chǔ)存大量頂點(diǎn)。使用這些緩沖對(duì)象的好處是我們可以一次性的發(fā)送一大批數(shù)據(jù)到顯卡上,而不是每個(gè)頂點(diǎn)發(fā)送一次。從CPU把數(shù)據(jù)發(fā)送到顯卡相對(duì)較慢,所以只要可能我們都要嘗試盡量一次性發(fā)送盡可能多的數(shù)據(jù)。當(dāng)數(shù)據(jù)發(fā)送至顯卡的內(nèi)存中后,頂點(diǎn)著色器幾乎能立即訪問頂點(diǎn),這是個(gè)非??斓倪^程。
如下過程是創(chuàng)建一個(gè)VBO的過程:

float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};
unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO); 
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 3);

在這里,我們可以使用glGenBuffers函數(shù)和一個(gè)緩沖ID生成一個(gè)VBO對(duì)象,使用glBindBuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標(biāo)上,然后我們可以調(diào)用glBufferData函數(shù),它會(huì)把之前定義的頂點(diǎn)數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中,最后調(diào)用glDrawArrays來進(jìn)行渲染頂點(diǎn)數(shù)據(jù)。
glBufferData是一個(gè)專門用來把用戶定義的數(shù)據(jù)復(fù)制到當(dāng)前綁定緩沖的函數(shù)。它的第一個(gè)參數(shù)是目標(biāo)緩沖的類型:頂點(diǎn)緩沖對(duì)象當(dāng)前綁定到GL_ARRAY_BUFFER目標(biāo)上。第二個(gè)參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位);用一個(gè)簡(jiǎn)單的sizeof計(jì)算出頂點(diǎn)數(shù)據(jù)大小就行。第三個(gè)參數(shù)是我們希望發(fā)送的實(shí)際數(shù)據(jù),第四個(gè)參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)。它有三種形式:

  • GL_STATIC_DRAW :數(shù)據(jù)不會(huì)或幾乎不會(huì)改變。
  • GL_DYNAMIC_DRAW:數(shù)據(jù)會(huì)被改變很多。
  • GL_STREAM_DRAW :數(shù)據(jù)每次繪制時(shí)都會(huì)改變。

接下來,我們?cè)賮砜碋BO的創(chuàng)建過程:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個(gè)三角形
    1, 2, 3  // 第二個(gè)三角形
};
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

與VBO類似,我們先綁定EBO然后用glBufferData把索引復(fù)制到緩沖里。同樣,和VBO類似,我們會(huì)把這些函數(shù)調(diào)用放在綁定和解綁函數(shù)調(diào)用之間,只不過這次我們把緩沖的類型定義為GL_ELEMENT_ARRAY_BUFFER。
要注意的是,我們傳遞了GL_ELEMENT_ARRAY_BUFFER當(dāng)作緩沖目標(biāo)。最后一件要做的事是用glDrawElements來替換glDrawArrays函數(shù),來指明我們從索引緩沖渲染

著色器shader

我們需要先了解一個(gè)概念,圖形渲染管線:圖形渲染管線(Graphics Pipeline,大多譯為管線,實(shí)際上指的是一堆原始圖形數(shù)據(jù)途經(jīng)一個(gè)輸送管道,期間經(jīng)過各種變化處理最終出現(xiàn)在屏幕的過程)管理的,圖形渲染管線可以被劃分為幾個(gè)階段,每個(gè)階段將會(huì)把前一個(gè)階段的輸出作為輸入,并且很容易并行執(zhí)行。正是由于它們具有并行執(zhí)行的特性,當(dāng)今大多數(shù)顯卡都有成千上萬的小處理核心,它們?cè)贕PU上為每一個(gè)(渲染管線)階段運(yùn)行各自的小程序,從而在圖形渲染管線中快速處理你的數(shù)據(jù)。這些小程序叫做著色器(Shader)。
在這里,我們只需要關(guān)心頂點(diǎn)著色器和片段著色器,如果我們打算做渲染的話,現(xiàn)代OpenGL需要我們至少設(shè)置一個(gè)頂點(diǎn)和一個(gè)片段著色器,接下來,我們就逐一介紹這兩個(gè)著色器

頂點(diǎn)著色器

它把一個(gè)單獨(dú)的頂點(diǎn)作為輸入。頂點(diǎn)著色器主要的目的是把3D坐標(biāo)轉(zhuǎn)為另一種3D坐標(biāo),同時(shí)頂點(diǎn)著色器允許我們對(duì)頂點(diǎn)屬性進(jìn)行一些基本處理。接下來我先上一段著色器的代碼:

#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

著色器語言要求我們必須指定版本號(hào),所以開頭指明330;
下一步,使用in關(guān)鍵字,在頂點(diǎn)著色器中聲明所有的輸入頂點(diǎn)屬性(Input Vertex Attribute),我們看到這里用到了location = 0,這里我們給這個(gè)輸入變量制定了一個(gè)位置值,便于之后將數(shù)據(jù)綁定到這個(gè)輸入變量上,這里也可以不用location值,我們直接有函數(shù)可以拿到某個(gè)變量的位置值:

GL_API int GL_APIENTRY glGetAttribLocation (GLuint program, const GLchar* name)  __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

為了設(shè)置頂點(diǎn)著色器的輸出,我們必須把位置數(shù)據(jù)賦值給預(yù)定義的gl_Position變量,它在幕后是vec4類型的。在main函數(shù)的最后,我們將gl_Position設(shè)置的值會(huì)成為該頂點(diǎn)著色器的輸出。由于我們的輸入是一個(gè)3分量的向量,我們必須把它轉(zhuǎn)換為4分量的,當(dāng)然頂點(diǎn)著色器可以有多個(gè)輸出,out關(guān)鍵字可以指定變量為輸出變量;

片段著色器

片段著色器的主要目的是計(jì)算一個(gè)像素的最終顏色,這也是所有OpenGL高級(jí)效果產(chǎn)生的地方。通常,片段著色器包含3D場(chǎng)景的數(shù)據(jù)(比如光照、陰影、光的顏色等等),這些數(shù)據(jù)可以被用來計(jì)算最終像素的顏色。片段著色器的編寫同頂點(diǎn)著色器一樣,不過片段著色器只能有一個(gè)輸出,將輸出賦值給gl_FragColor,是一個(gè)四分量的值。

編譯著色器

當(dāng)我們編寫了著色器程序之后,需要對(duì)其進(jìn)行編譯,鏈接,最終才能進(jìn)行執(zhí)行,接下來這段代碼表明了如何編譯一個(gè)著色器:

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

我們首先要做的是創(chuàng)建一個(gè)著色器對(duì)象,注意還是用ID來引用的。所以我們儲(chǔ)存這個(gè)頂點(diǎn)著色器為unsigned int,然后用glCreateShader創(chuàng)建這個(gè)著色器,我們把需要?jiǎng)?chuàng)建的著色器類型以參數(shù)形式提供給glCreateShader。由于我們正在創(chuàng)建一個(gè)頂點(diǎn)著色器,傳遞的參數(shù)是GL_VERTEX_SHADER,當(dāng)需要?jiǎng)?chuàng)建一個(gè)片段著色器時(shí)我們需要傳入GL_FRAGMENT_SHADER;
接下來我們把這個(gè)著色器源碼附加到著色器對(duì)象上,然后對(duì)其進(jìn)行編譯,glShaderSource函數(shù)把要編譯的著色器對(duì)象作為第一個(gè)參數(shù)。第二參數(shù)指定了傳遞的源碼字符串?dāng)?shù)量,這里只有一個(gè)。第三個(gè)參數(shù)是頂點(diǎn)著色器真正的源碼,第四個(gè)參數(shù)我們先設(shè)置為NULL。

著色器程序

著色器程序?qū)ο?Shader Program Object)是多個(gè)著色器合并之后并最終鏈接完成的版本。如果要使用剛才編譯的著色器我們必須把它們鏈接(Link)為一個(gè)著色器程序?qū)ο?,然后在渲染?duì)象的時(shí)候激活這個(gè)著色器程序。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時(shí)候被使用。

當(dāng)鏈接著色器至一個(gè)程序的時(shí)候,它會(huì)把每個(gè)著色器的輸出鏈接到下個(gè)著色器的輸入。當(dāng)輸出和輸入不匹配的時(shí)候,你會(huì)得到一個(gè)連接錯(cuò)誤。

下面是一個(gè)鏈接著色器程序的例子:

unsigned int shaderProgram;
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

glCreateProgram函數(shù)創(chuàng)建一個(gè)程序,并返回新創(chuàng)建程序?qū)ο蟮腎D引用?,F(xiàn)在我們需要把之前編譯的著色器附加到程序?qū)ο笊?,然后用glLinkProgram鏈接它們,得到的結(jié)果就是一個(gè)程序?qū)ο螅覀兛梢哉{(diào)用glUseProgram函數(shù),用剛創(chuàng)建的程序?qū)ο笞鳛樗膮?shù),以激活這個(gè)程序?qū)ο?在glUseProgram函數(shù)調(diào)用之后,每個(gè)著色器調(diào)用和渲染調(diào)用都會(huì)使用這個(gè)程序?qū)ο螅ㄒ簿褪侵皩懙闹?了。在把著色器對(duì)象鏈接到程序?qū)ο笠院?,記得刪除著色器對(duì)象,我們不再需要它們了。

鏈接頂點(diǎn)屬性

現(xiàn)在,我們已經(jīng)創(chuàng)建了著色器程序和編譯連接了他們,那么我們之前說的著色器的輸入屬性是何時(shí)有值的,這個(gè)時(shí)候,我們需要把頂點(diǎn)數(shù)據(jù)跟頂點(diǎn)屬性關(guān)聯(lián)起來,用到了以下函數(shù):

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer函數(shù)的參數(shù)非常多,所以我會(huì)逐一介紹它們:

  • 第一個(gè)參數(shù)指定我們要配置的頂點(diǎn)屬性。還記得我們?cè)陧旤c(diǎn)著色器中使用layout(location = 0)定義了position頂點(diǎn)屬性的位置值(Location)嗎?它可以把頂點(diǎn)屬性的位置值設(shè)置為0。因?yàn)槲覀兿M褦?shù)據(jù)傳遞到這一個(gè)頂點(diǎn)屬性中,所以這里我們傳入0。
  • 第二個(gè)參數(shù)指定頂點(diǎn)屬性的大小。頂點(diǎn)屬性是一個(gè)vec3,它由3個(gè)值組成,所以大小是3。
  • 第三個(gè)參數(shù)指定數(shù)據(jù)的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點(diǎn)數(shù)值組成的)。
  • 下個(gè)參數(shù)定義我們是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們?cè)O(shè)置為GL_TRUE,所有數(shù)據(jù)都會(huì)被映射到0(對(duì)于有符號(hào)型signed數(shù)據(jù)是-1)到1之間。我們把它設(shè)置為GL_FALSE。
  • 第五個(gè)參數(shù)叫做步長(zhǎng)(Stride),它告訴我們?cè)谶B續(xù)的頂點(diǎn)屬性組之間的間隔。由于下個(gè)組位置數(shù)據(jù)在3個(gè)float之后,我們把步長(zhǎng)設(shè)置為3 * sizeof(float)。要注意的是由于我們知道這個(gè)數(shù)組是緊密排列的(在兩個(gè)頂點(diǎn)屬性之間沒有空隙)我們也可以設(shè)置為0來讓OpenGL決定具體步長(zhǎng)是多少(只有當(dāng)數(shù)值是緊密排列時(shí)才可用)。一旦我們有更多的頂點(diǎn)屬性,我們就必須更小心地定義每個(gè)頂點(diǎn)屬性之間的間隔,我們?cè)诤竺鏁?huì)看到更多的例子(譯注: 這個(gè)參數(shù)的意思簡(jiǎn)單說就是從這個(gè)屬性第二次出現(xiàn)的地方到整個(gè)數(shù)組0位置之間有多少字節(jié))。
  • 最后一個(gè)參數(shù)的類型是void*,所以需要我們進(jìn)行這個(gè)奇怪的強(qiáng)制類型轉(zhuǎn)換。它表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset)。由于位置數(shù)據(jù)在數(shù)組的開頭,所以這里是0。我們會(huì)在后面詳細(xì)解釋這個(gè)參數(shù)。

最后,我們應(yīng)該使用glEnableVertexAttribArray,以頂點(diǎn)屬性位置值作為參數(shù),啟用頂點(diǎn)屬性;頂點(diǎn)屬性默認(rèn)是禁用的;

紋理

紋理是一個(gè)2D圖片,它可以用來添加物體的細(xì)節(jié),紋理坐標(biāo)起始于(0, 0),也就是紋理圖片的左下角,終始于(1, 1),即紋理圖片的右上角,接下來我們用一段程序來看看紋理的創(chuàng)建:

unsigned int texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0); // 在綁定紋理之前先激活紋理單元
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

glGenTextures函數(shù)首先需要輸入生成紋理的數(shù)量,然后把它們儲(chǔ)存在第二個(gè)參數(shù)的unsigned int數(shù)組中,就像其他對(duì)象一樣,我們需要綁定它,讓之后任何的紋理指令都可以配置當(dāng)前綁定的紋理,其后是設(shè)置的一些參數(shù),設(shè)置紋理的環(huán)繞方式以及過濾方式;我們可以使用載入的圖片數(shù)據(jù)生成一個(gè)紋理了。紋理可以通過glTexImage2D來生成,函數(shù)很長(zhǎng),參數(shù)也不少,所以我們一個(gè)一個(gè)地講解:

  • 第一個(gè)參數(shù)指定了紋理目標(biāo)(Target)。設(shè)置為GL_TEXTURE_2D意味著會(huì)生成與當(dāng)前綁定的紋理對(duì)象在同一個(gè)目標(biāo)上的紋理(任何綁定到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會(huì)受到影響)。
  • 第二個(gè)參數(shù)為紋理指定多級(jí)漸遠(yuǎn)紋理的級(jí)別,如果你希望單獨(dú)手動(dòng)設(shè)置每個(gè)多級(jí)漸遠(yuǎn)紋理的級(jí)別的話。這里我們填0,也就是基本級(jí)別。
  • 第三個(gè)參數(shù)告訴OpenGL我們希望把紋理儲(chǔ)存為何種格式。我們的圖像只有RGB值,因此我們也把紋理儲(chǔ)存為RGB值。
  • 第四個(gè)和第五個(gè)參數(shù)設(shè)置最終的紋理的寬度和高度。我們之前加載圖像的時(shí)候儲(chǔ)存了它們,所以我們使用對(duì)應(yīng)的變量。
  • 下個(gè)參數(shù)應(yīng)該總是被設(shè)為0(歷史遺留的問題)。
  • 第七第八個(gè)參數(shù)定義了源圖的格式和數(shù)據(jù)類型。我們使用RGB值加載這個(gè)圖像,并把它們儲(chǔ)存為char(byte)數(shù)組,我們將會(huì)傳入對(duì)應(yīng)值。
  • 最后一個(gè)參數(shù)是真正的圖像數(shù)據(jù)。

Uniform

Uniform是一種從CPU中的應(yīng)用向GPU中的著色器發(fā)送數(shù)據(jù)的方式,但uniform和頂點(diǎn)屬性有些不同。首先,uniform是全局的(Global)。全局意味著uniform變量必須在每個(gè)著色器程序?qū)ο笾卸际仟?dú)一無二的,而且它可以被著色器程序的任意著色器在任意階段訪問;
我們可以用glGetUniformLocation查詢uniform 屬性的位置值。我們?yōu)椴樵兒瘮?shù)提供著色器程序和uniform的名字。如果glGetUniformLocation返回-1就代表沒有找到這個(gè)位置值。最后,我們可以通過glUniform類的函數(shù)函數(shù)設(shè)置uniform值,例如:glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);。

注意,查詢uniform地址不要求你之前使用過著色器程序,但是更新一個(gè)uniform之前你必須先使用程序(調(diào)用glUseProgram),因?yàn)樗窃诋?dāng)前激活的著色器程序中設(shè)置uniform的。

FBO

幀緩沖區(qū)對(duì)象(FBO),在OpenGL渲染管線中幾何數(shù)據(jù)和紋理經(jīng)過變換和一些測(cè)試處理,最終會(huì)被展示到屏幕上。OpenGL渲染管線的最終位置是在幀緩沖區(qū)中。幀緩沖區(qū)是一系列二維的像素存儲(chǔ)數(shù)組,包括了顏色緩沖區(qū)、深度緩沖區(qū)、模板緩沖區(qū)以及累積緩沖區(qū)。默認(rèn)情況下OpenGL使用的是窗口系統(tǒng)提供的幀緩沖區(qū)。
OpenGL的GL_ARB_framebuffer_object這個(gè)擴(kuò)展提供了一種方式來創(chuàng)建額外的幀緩沖區(qū)對(duì)象(FBO)。使用幀緩沖區(qū)對(duì)象,OpenGL可以將原先繪制到窗口提供的幀緩沖區(qū)重定向到FBO之中。
FBO中有兩類綁定的對(duì)象:紋理圖像(texture images)和渲染圖像(renderbuffer images)。如果紋理對(duì)象綁定到FBO,那么OpenGL就會(huì)執(zhí)行渲染到紋理(render to texture)的操作,如果渲染對(duì)象綁定到FBO,那么OpenGL會(huì)執(zhí)行離屏渲染(offscreen rendering),F(xiàn)BO可以理解為包含了許多掛接點(diǎn)的一個(gè)對(duì)象,他提供了一種可以快速切換外部紋理對(duì)象和渲染對(duì)象掛接點(diǎn)的方式,在FBO中必然包含一個(gè)深度緩沖區(qū)掛接點(diǎn)和一個(gè)模板緩沖區(qū)掛接點(diǎn),同時(shí)還包含許多顏色緩沖區(qū)掛節(jié)點(diǎn)。FBO提供了一種快速有效的方法掛接或者解綁這些外部的對(duì)象,對(duì)于紋理對(duì)象使用 glFramebufferTexture2D,對(duì)于渲染對(duì)象使用glFramebufferRenderbuffer ;

渲染到紋理

(1):創(chuàng)建幀緩存并綁定,glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
(2):創(chuàng)建紋理緩存并綁定;
glBindTexture(GL_TEXTURE_2D, texture);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

glActiveTexture(GL_TEXTURE1);
glGenFramebuffers(1, &_outputFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _outputFrameBuffer);
glBindTexture(CVOpenGLESTextureGetTarget(_renderTexture), CVOpenGLESTextureGetName(_renderTexture));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(_renderTexture), 0);

這里的GL_COLOR_ATTACHMENT0是顏色緩沖,還可以再指定深度緩沖,這里的顏色緩沖區(qū)域有多個(gè),具體多少個(gè)受OpenGL實(shí)現(xiàn)的影響,可以通過GL_MAX_COLOR_ATTACHMENTS使用glGet查詢;

離屏渲染

(1):創(chuàng)建幀緩存并綁定,glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
(2):創(chuàng)建顏色渲染緩存并綁定,glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
(3):創(chuàng)建渲染深度并綁定,glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);//這一步可以不用;
其實(shí)渲染到紋理和離屏渲染的渲染目標(biāo)不一樣,離屏渲染最終渲染到renderbuffer之上,我們可以調(diào)用
[context presentRenderbuffer:GL_RENDERBUFFER];
將離屏渲染的圖像繪制出來,在每一次繪制之前,我們需要調(diào)用
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
清空顏色和深度緩沖區(qū),可以使用glViewport()函數(shù)指定渲染的視頻區(qū)域, glViewport函數(shù)前兩個(gè)參數(shù)控制窗口左下角的位置。第三個(gè)和第四個(gè)參數(shù)控制渲染窗口的寬度和高度(像素);

在同一個(gè)線程中切換不同的上下文時(shí),需要注意應(yīng)用應(yīng)自己對(duì)上下文進(jìn)行強(qiáng)引用以防止其被釋放,并且在切換之前應(yīng)調(diào)用 glFlush 函數(shù)將當(dāng)前上下文提交的指令傳到圖形硬件中去。

最后,可以去看看OpenGL的教程~~

參考文獻(xiàn):
Learn OpenGL
OpenGL緩沖區(qū)對(duì)象之FBO
IOS OpenGL ES Guide

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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