OpenGL ES 入門之旅 -- GLSL加載圖片案例

在上篇文章GLSL初始著色器語言中已經(jīng)介紹過如何編寫一個(gè)著色器文件,以及如何連接程序?qū)ο蠛椭鞯膶?duì)象的方法函數(shù),那么接下來通過一個(gè)實(shí)際的案例來看一下這些方法的具體使用。

在學(xué)習(xí)案例之前,先來看一下什么是FrameBuffer和RenderBuffer?

幀緩沖區(qū)對(duì)象FrameBuffer(FBO)

在OpenGL渲染管線中,幾何數(shù)據(jù)和紋理經(jīng)過多次轉(zhuǎn)化和多次測(cè)試,最后以二維像素的形式顯示在屏幕上。OpenGL管線的最終渲染目的地被稱作幀緩沖(framebuffer)。幀緩沖是一些二維數(shù)組和OpenG所使用的存儲(chǔ)區(qū)的集合:顏色緩沖、深度緩沖、模板緩沖和累計(jì)緩沖。默認(rèn)情況下,OpenGL將幀緩沖區(qū)作為渲染最終目的地。此幀緩沖區(qū)完全由系統(tǒng)生成和管理。

我們知道,在應(yīng)用程序調(diào)用任何的OpenGL ES命令之前,需要首先創(chuàng)建一個(gè)渲染上下文和繪圖表面,并使之成為現(xiàn)行上下文和表面,之前在渲染的時(shí)候,其實(shí)一直使用的是原生窗口系統(tǒng)(比如EAGL,GLFW)提供的渲染上下文和繪圖表面(即幀緩沖區(qū))。
一般情況下,我們只需要系統(tǒng)提供的幀緩沖區(qū)作為繪圖表面,但是又有些特殊情況,比如陰影貼圖、動(dòng)態(tài)反射、處理后特效等需要渲染到紋理操作的,如果使用系統(tǒng)提供的幀緩沖區(qū),效率會(huì)比較低低下,因此需要自定義自己的幀緩沖區(qū)。

幀緩沖區(qū)對(duì)象API支持如下操作:
·僅使用OpenGL ES 命令創(chuàng)建幀緩沖區(qū)對(duì)象
·在單一EGL上下文中創(chuàng)建和使用多個(gè)緩沖區(qū)對(duì)象,也就是說,不需要每一個(gè)幀緩沖區(qū)都有一個(gè)渲染上下文。
·創(chuàng)建屏幕外顏色,深度或者模板渲染緩沖區(qū)和紋理,并將它們鏈接到幀緩沖區(qū)對(duì)象
·在多個(gè)幀緩沖區(qū)之間共享顏色,深度或者模板緩沖區(qū)
·將紋理直接鏈接到幀緩沖區(qū)作為顏色或者深度,從而避免了進(jìn)行復(fù)制操作的必要
·在幀緩沖區(qū)之間復(fù)制并使幀緩沖區(qū)內(nèi)容失效。

創(chuàng)建幀緩沖區(qū)對(duì)象

//定義一個(gè)緩存區(qū)ID
GLuint buffer;
//申請(qǐng)一個(gè)緩存區(qū)標(biāo)志
glGenFramebuffers(1, &buffer);
// 然后綁定
glBindFramebuffer(GL_FRAMEBUFFER, buffer);

glGenFramebuffers (GLsizei n, GLuint* framebuffers) :
第一個(gè)參數(shù)是要?jiǎng)?chuàng)建的幀緩存的數(shù)目,
第二個(gè)參數(shù)是指向存儲(chǔ)一個(gè)或者多個(gè)ID的變量或數(shù)組的指針。
它返回未使用的幀緩沖區(qū)對(duì)象的ID。ID為0表示默認(rèn)幀緩存,即系統(tǒng)提供的幀緩存。
一旦一個(gè)FBO被創(chuàng)建,在使用它之前必須綁定
glBindFramebuffer (GLenum target, GLuint framebuffer):
第一個(gè)參數(shù)target是GL_FRAMEBUFFER,
第二個(gè)參數(shù)是幀緩沖區(qū)對(duì)象的ID。
一旦幀緩沖區(qū)對(duì)象被綁定,之后的所有的OpenGL操作都會(huì)對(duì)當(dāng)前所綁定的幀緩沖區(qū)對(duì)象造成影響。ID為0表示缺省幀緩存,即默認(rèn)的系統(tǒng)提供的幀緩存。因此,在glBindFramebuffer()中將ID設(shè)置為0可以解綁定當(dāng)前幀緩沖區(qū)對(duì)象。

在綁定到GL_FRAMEBUFFER目標(biāo)之后,所有的讀取和寫入幀緩沖的操作將會(huì)影響當(dāng)前綁定的幀緩沖。我們也可以使用GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER,將一個(gè)幀緩沖分別綁定到讀取目標(biāo)或?qū)懭肽繕?biāo)。綁定到GL_READ_FRAMEBUFFER的幀緩沖將會(huì)使用在所有像是glReadPixels的讀取操作中,而綁定到GL_DRAW_FRAMEBUFFER的幀緩沖將會(huì)被用作渲染、清除等寫入操作的目標(biāo)。大部分情況都不需要區(qū)分它們,通常都會(huì)使用GL_FRAMEBUFFER。

刪除緩沖區(qū)對(duì)象
在緩沖區(qū)對(duì)象不再被使用時(shí),緩沖區(qū)對(duì)象可以通過調(diào)用glDeleteFramebuffers (GLsizei n, const GLuint* framebuffers)來刪除。

glDeleteFramebuffers(1, &buffer);

和系統(tǒng)的幀緩沖區(qū)一樣,幀緩沖區(qū)對(duì)象也包括顏色緩沖區(qū)、深度和模版緩沖區(qū),這些邏輯上的緩沖區(qū)在幀緩沖區(qū)對(duì)象中稱之為可附加的圖像,它們是可以附加到幀緩沖區(qū)對(duì)象的二維像素?cái)?shù)組。
FBO包含兩種類型的附加圖像:紋理圖像(texture images)和渲染緩存圖像(renderbuffer images)。如果紋理對(duì)象的圖像數(shù)據(jù)關(guān)聯(lián)到幀緩存,OpenGL執(zhí)行的是“渲染到紋理”(render to texture)操作。如果渲染緩存的圖像數(shù)據(jù)關(guān)聯(lián)到幀緩存,OpenGL執(zhí)行的是離屏渲染(offscreen rendering)。

渲染緩存區(qū)對(duì)象RenderBuffer(RBO)

渲染緩存是為離線渲染而新引進(jìn)的。它允許將一個(gè)場(chǎng)景直接渲染到一個(gè)渲染緩存對(duì)象中,而不是渲染到紋理對(duì)象中。渲染緩存對(duì)象是用于存儲(chǔ)單幅圖像的數(shù)據(jù)存儲(chǔ)區(qū)域。該圖像按照一種可渲染的內(nèi)部格式存儲(chǔ)。它用于存儲(chǔ)沒有相關(guān)紋理格式的OpenGL邏輯緩存,比如模板緩存或者深度緩存。

一個(gè)renderbuffer對(duì)象是通過應(yīng)用程序分配的一個(gè)2D圖像緩沖區(qū),renderbuffer可以用于分配和存儲(chǔ)顏色,深度或者模板值,也可以作為一個(gè)framebuffer的顏色,深度,模板的附著,一個(gè)renderbuffer是一個(gè)類似于屏幕外的窗口系統(tǒng)提供的可繪制表面。但是renderbuffer不能直接用作GL紋理。

創(chuàng)建渲染緩沖區(qū)

 //1.定義一個(gè)緩存區(qū)ID
    GLuint buffer;
 //2.申請(qǐng)一個(gè)緩存區(qū)標(biāo)志
    glGenRenderbuffers(1, &buffer);
//3.將標(biāo)識(shí)符綁定到GL_RENDERBUFFER
    glBindRenderbuffer(GL_RENDERBUFFER, buffer);

和幀緩沖區(qū)對(duì)象一樣,在引用渲染緩沖區(qū)對(duì)象之前必須綁定當(dāng)前渲染緩沖對(duì)象,調(diào)用函數(shù)glBindRenderbuffer (GLenum target, GLuint renderbuffer)進(jìn)行綁定,
第一個(gè)參數(shù)target是GL_RENDERBUFFER,
第二個(gè)參數(shù)是渲染緩沖區(qū)對(duì)象的ID。

刪除緩沖區(qū)對(duì)象
在緩沖區(qū)對(duì)象不再被使用時(shí),緩沖區(qū)對(duì)象可以通過調(diào)用glDeleteRenderbuffers (GLsizei n, const GLuint* renderbuffers)來刪除。

glDeleteRenderbuffers (1, &buffer);

當(dāng)一個(gè)渲染緩存被創(chuàng)建,它沒有任何數(shù)據(jù)存儲(chǔ)區(qū)域,所以還要為它分配空間,這可以通過用glRenderbufferStorage (GLenum target, GLenum internalformat, GLsizei width, GLsizei height)實(shí)現(xiàn)。

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 600, 800);

第一個(gè)參數(shù)必須是GL_RENDERBUFFER,
第二個(gè)參數(shù)是可用于顏色,深度,模板的格式,
width和height是渲染緩存圖像的像素維度

附加渲染緩沖對(duì)象
最后,生成幀緩沖區(qū)之后,則需要將renderbuffer跟framebuffer進(jìn)行綁定,調(diào)用glFramebufferRenderbuffer函數(shù)進(jìn)行綁定到對(duì)應(yīng)的附著點(diǎn)上,后面的繪制才能起作用

//將渲染緩存區(qū)myColorRenderBuffer 通過glFramebufferRenderbuffer函數(shù)綁定到 GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);

下圖展示了幀緩沖區(qū)對(duì)象,渲染緩沖區(qū)對(duì)象和紋理之間的關(guān)系,一個(gè)幀緩沖區(qū)對(duì)象中只能有一個(gè)顏色,深度,模板附著。
緩沖區(qū)關(guān)系.png

簡(jiǎn)單來說就是framebuffer只是一個(gè)管理者,其本身并沒有任何存儲(chǔ)區(qū)(沒有存儲(chǔ)紋理,頂點(diǎn),顏色等數(shù)據(jù)),只是有顏色,深度,模板附著點(diǎn),而真正存儲(chǔ)這些數(shù)據(jù)的是renderbuffer。

GLSL渲染圖片

這個(gè)案例大概實(shí)現(xiàn)以下這些內(nèi)容:
·用EAGL 創(chuàng)建屏幕上的渲染表面
·加載頂點(diǎn)/片元著?器
·創(chuàng)建一個(gè)程序?qū)ο?并鏈接頂點(diǎn)/?元著?器,并鏈接程序?qū)ο?br> ·設(shè)置視口
·清除顏色緩存區(qū)
·渲染簡(jiǎn)單圖元
·使顏?緩存區(qū)的內(nèi)容在EAGL 窗?表現(xiàn)呈現(xiàn)

1.創(chuàng)建頂點(diǎn)/片元著色器文件

著色器文件一般以.vsh/.fsh/.gsl為文件后綴名
頂點(diǎn)著色器shaderv.vsh

// 頂點(diǎn)坐標(biāo)
attribute highp vec4 position;
// 紋理坐標(biāo)
attribute highp vec2 textCoordinate;
// 紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
void main() {
    varyTextCoord = textCoordinate;
    gl_Position = position;
}

在著色器文件中最好不要加中文注釋,以防編譯無法通過,此處的中文注釋只作為理解注釋。

片元著色器shaderf.fsh

// 紋理坐標(biāo)
varying lowp vec2 varyTextCoord;
// 紋理采樣器(獲取對(duì)應(yīng)的紋理ID)
uniform sampler2D colorMap;
void main() {
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

創(chuàng)建一個(gè)UIView,并導(dǎo)入頭文件#import <OpenGLES/ES2/gl.h>,此次用GLSL渲染圖片的代碼全部書寫在這個(gè)UIView中。

//在iOS和tvOS上繪制OpenGL ES內(nèi)容的圖層,繼承與CALayer
@property(nonatomic,strong)CAEAGLLayer *zhEagLayer;
@property(nonatomic,strong)EAGLContext *zhContext;
@property(nonatomic,assign)GLuint zhColorRenderBuffer;
@property(nonatomic,assign)GLuint zhColorFrameBuffer;
@property(nonatomic,assign)GLuint zhPrograme;
2.設(shè)置圖層setupLayer
 //1.創(chuàng)建特殊圖層
//這里需要重寫layerClass,將ZHView返回的圖層從CALayer替換成CAEAGLLayer
self.zhEagLayer = (CAEAGLLayer *)self.layer;
    
//2.設(shè)置scale
[self setContentScaleFactor:[[UIScreen mainScreen]scale]];
//3.設(shè)置描述屬性,這里設(shè)置不維持渲染內(nèi)容以及顏色格式為RGBA8
self.zhEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];

對(duì)于drawableProperties設(shè)置描述屬性,
kEAGLDrawablePropertyRetainedBacking:表示繪圖表面顯示后,是否保留其內(nèi)容
kEAGLDrawablePropertyColorFormat:表示可繪制表面的內(nèi)部顏色緩存區(qū)格式,這個(gè)key對(duì)應(yīng)的值是一個(gè)NSString指定特定顏色緩存區(qū)對(duì)象。默認(rèn)是kEAGLColorFormatRGBA8;
· kEAGLColorFormatRGBA8:32位RGBA的顏色
· kEAGLColorFormatRGB565:16位RGB的顏色
· kEAGLColorFormatSRGBA8:sRGB代表了標(biāo)準(zhǔn)的紅、綠、藍(lán),即CRT顯示器、LCD顯示器、投影機(jī)、打印機(jī)以及其他設(shè)備中色彩再現(xiàn)所使用的三個(gè)基本色素。sRGB的色彩空間基于獨(dú)立的色彩坐標(biāo),可以使色彩在不同的設(shè)備使用傳輸中對(duì)應(yīng)于同一個(gè)色彩坐標(biāo)體系,而不受這些設(shè)備各自具有的不同色彩坐標(biāo)的影響。

重寫layerClass

+(Class)layerClass
{
    return [CAEAGLLayer class];
}
3.設(shè)置渲染上下文setupContext
//1.指定OpenGL ES 渲染API版本
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
//2.創(chuàng)建圖形上下文
EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
//3.判斷是否創(chuàng)建成功
if (!context) {
        NSLog(@"Create failed!");
        return;
}
//4.設(shè)置圖形上下文
if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"Set failed!");
        return;
}
//5.將局部context賦值成全局的context
self.zhContext = context;
4.清空緩沖區(qū)deleteRenderAndFrameBuffer
//清空幀緩沖區(qū)
glDeleteBuffers(1, &_zhColorFrameBuffer);
self.zhColorFrameBuffer = 0;
//清空渲染緩沖區(qū)
glDeleteBuffers(1, &_zhColorRenderBuffer);
self.zhColorRenderBuffer = 0;

清空緩沖區(qū)的代碼也可以寫成:

glDeleteFramebuffers(1, &_zhColorFrameBuffer);
self.zhColorFrameBuffer = 0;

glDeleteRenderbuffers(1, &_zhColorRenderBuffer);
self.zhColorRenderBuffer = 0;
5.設(shè)置RenderBuffer
//1.定義一個(gè)緩存區(qū)ID
GLuint buffer;
//2.申請(qǐng)一個(gè)緩存區(qū)標(biāo)志
glGenRenderbuffers(1, &buffer);
//3.將當(dāng)前申請(qǐng)的buffer變成全局的
self.zhColorRenderBuffer = buffer;
//4.將標(biāo)識(shí)符綁定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.zhColorRenderBuffer);
//5.將可繪制對(duì)象drawable object's  CAEAGLLayer的存儲(chǔ)綁定到OpenGL ES renderBuffer對(duì)象
[self.zhContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.zhEagLayer];
6.設(shè)置FrameBuffer
//1.定義一個(gè)緩存區(qū)ID
GLuint buffer;
//2.申請(qǐng)一個(gè)緩存區(qū)標(biāo)志
glGenRenderbuffers(1, &buffer);
//3.將buffer變成全局的
self.zhColorFrameBuffer = buffer;  
//4.綁定標(biāo)識(shí)符到GL_FRAMEBUFFER
glBindFramebuffer(GL_FRAMEBUFFER, self.zhColorFrameBuffer);

/*生成幀緩存區(qū)之后,則需要將renderbuffer跟framebuffer進(jìn)行綁定,
調(diào)用glFramebufferRenderbuffer函數(shù)進(jìn)行綁定到對(duì)應(yīng)的附著點(diǎn)上,后面的繪制才能起作用*/
    
//5.將渲染緩存區(qū)myColorRenderBuffer 通過glFramebufferRenderbuffer函數(shù)綁定到 GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.zhColorRenderBuffer);
7.開始繪制renderLayer

1.設(shè)置清屏顏色

glClearColor(0.3f, 0.45f, 0.5f, 1.0f);

2.清除屏幕

glClear(GL_COLOR_BUFFER_BIT);

3.設(shè)置視口大小

CGFloat scale = [[UIScreen mainScreen]scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);

4.讀取頂點(diǎn)著色程序、片元著色程序

NSString *vertFile = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];

5.加載著色器shader

self.zhPrograme = [self loadShaders:vertFile Withfrag:fragFile];

//加載shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
    //1.定義2個(gè)臨時(shí)的著色器對(duì)象
    GLuint verShader, fragShader;
    //創(chuàng)建program
    GLint program = glCreateProgram();
    //2.編譯頂點(diǎn)著色程序和片元著色器程序
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    /*
    關(guān)于這個(gè)compileShader:type:file:方法傳入的三個(gè)參數(shù):
    參數(shù)1:編譯完存儲(chǔ)的底層地址
    參數(shù)2:編譯的著色器的類型,GL_VERTEX_SHADER(頂點(diǎn))、GL_FRAGMENT_SHADER(片元)
    參數(shù)3:文件路徑
    */
    
    //3.鏈接著色器對(duì)象和程序?qū)ο?    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    //4.釋放不需要的shader
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}

//編譯shader
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
    
    //1.讀取文件路徑字符串
    NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    const GLchar* source = (GLchar *)[content UTF8String];
    //2.根據(jù)type類型創(chuàng)建一個(gè)shader
    *shader = glCreateShader(type);
    //3.將著色器源碼附加到著色器對(duì)象上。
    glShaderSource(*shader, 1, &source,NULL);
    /*
    參數(shù)1:shader,要編譯的著色器對(duì)象 *shader
    參數(shù)2:numOfStrings,傳遞的源碼字符串?dāng)?shù)量 1個(gè)
    參數(shù)3:strings,著色器程序的源碼(真正的著色器程序源碼)
    參數(shù)4:lenOfStrings,長(zhǎng)度,具有每個(gè)字符串長(zhǎng)度的數(shù)組,或NULL,這意味著字符串是NULL終止的
    */
    //4.把著色器源代碼編譯成目標(biāo)代碼
    glCompileShader(*shader);  
}

6.鏈接程序?qū)ο?/strong>

glLinkProgram(self.zhPrograme);
//檢查鏈接是否成功
GLint linkStatus;
//獲取鏈接狀態(tài)
glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
 if (linkStatus == GL_FALSE) {
        GLchar message[512];
        glGetProgramInfoLog(self.zhPrograme, sizeof(message), 0, &message[0]);
        NSString *messageString = [NSString stringWithUTF8String:message];
        NSLog(@"program link error:%@",messageString);
        return;
  }
NSLog(@"program link success!");

7.使用程序?qū)ο?/strong>

glUseProgram(self.zhPrograme);

8.設(shè)置頂點(diǎn)坐標(biāo)和紋理坐標(biāo)

//坐標(biāo)數(shù)組
GLfloat attrArr[] =
    {
        1.0f, -1.0f, -1.0f,     1.0f, 0.0f,
        -1.0f, 1.0f, -1.0f,     0.0f, 1.0f,
        -1.0f, -1.0f, -1.0f,    0.0f, 0.0f,
        
        1.0f, 1.0f, -1.0f,      1.0f, 1.0f,
        -1.0f, 1.0f, -1.0f,     0.0f, 1.0f,
        1.0f, -1.0f, -1.0f,     1.0f, 0.0f,
    };
//每一行的前3位為頂點(diǎn)坐標(biāo),后兩位為紋理坐標(biāo)

9.處理頂點(diǎn)數(shù)據(jù)

//(1)頂點(diǎn)緩存區(qū)
GLuint attrBuffer;
//(2)申請(qǐng)一個(gè)緩存區(qū)標(biāo)識(shí)符
glGenBuffers(1, &attrBuffer);
//(3)將attrBuffer綁定到GL_ARRAY_BUFFER標(biāo)識(shí)符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
//(4)把頂點(diǎn)數(shù)據(jù)從CPU內(nèi)存復(fù)制到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

10.將頂點(diǎn)數(shù)據(jù)傳入到頂點(diǎn)著色器對(duì)象
將頂點(diǎn)數(shù)據(jù)通過self.zhPrograme中的傳遞到頂點(diǎn)著色器程序的position,通過以下三個(gè)函數(shù)處理頂點(diǎn)數(shù)據(jù)
·glGetAttribLocation,用來獲取vertex attribute的入口。
·告訴OpenGL ES,通過glEnableVertexAttribArray從buffer讀取數(shù)據(jù)(打開通道)
·glVertexAttribPointer設(shè)置讀取數(shù)據(jù)的方式

//第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:position保持一致
GLuint position = glGetAttribLocation(self.zhPrograme, "position");
//設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(position);
//設(shè)置讀取方式
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);

glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
參數(shù)1:index,頂點(diǎn)數(shù)據(jù)的索引
參數(shù)2:size,每個(gè)頂點(diǎn)屬性的組件數(shù)量,1,2,3,或者4.默認(rèn)初始值是4.
參數(shù)3:type,數(shù)據(jù)中的每個(gè)組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認(rèn)初始值為GL_FLOAT
參數(shù)4:normalized,固定點(diǎn)數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值。(GL_FALSE)
參數(shù)5:stride,連續(xù)頂點(diǎn)屬性之間的偏移量,默認(rèn)為0;
參數(shù)6:指定一個(gè)指針,指向數(shù)組中的第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。默認(rèn)為0

11.處理紋理數(shù)據(jù)

//第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
GLuint textCoor = glGetAttribLocation(self.zhPrograme, "textCoordinate");
//設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(textCoor);
//設(shè)置讀取方式
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);

12.加載紋理

//1、將 UIImage 轉(zhuǎn)換為 CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
//判斷圖片是否獲取成功
if (!spriteImage) {
       NSLog(@"load image failed: %@", fileName);
       return;
   }
    
//2、讀取圖片的大小,寬和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
//3.獲取圖片字節(jié)數(shù) 寬*高*4(RGBA)
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
//4.創(chuàng)建上下文
/*
     參數(shù)1:data,指向要渲染的繪制圖像的內(nèi)存地址
     參數(shù)2:width,bitmap的寬度,單位為像素
     參數(shù)3:height,bitmap的高度,單位為像素
     參數(shù)4:bitPerComponent,內(nèi)存中像素的每個(gè)組件的位數(shù),比如32位RGBA,就設(shè)置為8
     參數(shù)5:bytesPerRow,bitmap的沒一行的內(nèi)存所占的比特?cái)?shù)
     參數(shù)6:colorSpace,bitmap上使用的顏色空間  kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
//5、在CGContextRef上--> 將圖片繪制出來
/*
     CGContextDrawImage 使用的是Core Graphics框架,坐標(biāo)系與UIKit 不一樣。UIKit框架的原點(diǎn)在屏幕的左上角,Core Graphics框架的原點(diǎn)在屏幕的左下角。
     CGContextDrawImage 
     參數(shù)1:繪圖上下文
     參數(shù)2:rect坐標(biāo)
     參數(shù)3:繪制的圖片
*/
CGRect rect = CGRectMake(0, 0, width, height);
//6.使用默認(rèn)方式繪制
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextDrawImage(spriteContext, rect, spriteImage);
//7、畫圖完畢就釋放上下文
CGContextRelease(spriteContext);
//8、綁定紋理到默認(rèn)的紋理ID(
glBindTexture(GL_TEXTURE_2D, 0);
//9.設(shè)置紋理屬性
/*
     參數(shù)1:紋理維度
     參數(shù)2:線性過濾、為s,t坐標(biāo)設(shè)置模式
     參數(shù)3:wrapMode,環(huán)繞模式
*/
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

float fw = width, fh = height;
//10.載入紋理2D數(shù)據(jù)
/*
     參數(shù)1:紋理模式(綁定紋理對(duì)象的種類),GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
     參數(shù)2:加載的層次,一般設(shè)置為0, 0表示沒有進(jìn)行縮小的原始圖片等級(jí)。
     參數(shù)3:紋理的顏色值GL_RGBA, 表示了紋理所采用的內(nèi)部格式,內(nèi)部格式是我們的像素?cái)?shù)據(jù)在顯卡中存儲(chǔ)的格式,這里的GL_RGB顯然就表示紋理中像素的顏色值是以RGB的格式存儲(chǔ)的。
     參數(shù)4:紋理的寬
     參數(shù)5:紋理的高
     參數(shù)6:border,邊界寬度,通常為0.
     參數(shù)7:format(描述了像素在內(nèi)存中的存儲(chǔ)格式)
     參數(shù)8:type(描述了像素在內(nèi)存中的數(shù)據(jù)類型)
     參數(shù)9:紋理數(shù)據(jù)
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
//11.釋放spriteData
free(spriteData); 

13.設(shè)置紋理采樣器 sampler2D

glUniform1i(glGetUniformLocation(self.zhPrograme, "colorMap"), 0);

14.繪圖

glDrawArrays(GL_TRIANGLES, 0, 6);

15.從渲染緩沖區(qū)顯示到屏幕上

[self.zhContext presentRenderbuffer:GL_RENDERBUFFER];

至此用GLSL渲染圖片的代碼已經(jīng)基本完成,如果沒有處理圖片的翻轉(zhuǎn)問題的話,運(yùn)行的效果顯示出來圖片是翻轉(zhuǎn)的,至于圖片翻轉(zhuǎn)的原因和解決圖片翻轉(zhuǎn)的方法已在上一篇文章中做了詳細(xì)的介紹,這里就不再一一贅述。

下面將上述的代碼步驟做一個(gè)梳理,如下圖所示:
GLSL渲染圖片.png

文中部分內(nèi)容參考:《OpenGL ES 3.0 編程指南》

最后編輯于
?著作權(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)容