OpenGL ES 紋理繪制

本篇文章主要講解如何使用GLSL語言來進(jìn)行紋理的繪制。
首先我們需要了解如何編寫GLSL程序飞傀,經(jīng)過之前的學(xué)習(xí)我們知道皇型,我們能操作的只有 頂點(diǎn)著色器片元著色器。所以我們需要?jiǎng)?chuàng)建兩個(gè)空文件分別命名為shaderv.vshshaderf.fsh:


當(dāng)然文件名字看自己的習(xí)慣助析,可以修改。shaderv.vshshaderf.fsh分別表示 頂點(diǎn)著色器片元著色器 的程序文件椅您。

GLSL代碼

先來看下 shaderv.vsh 文件的代碼:

attribute vec4 position;
attribute vec2 textureCoordinate;
varying lowp vec2 varyTextCoord

void main()
{
    varyTextCoord = textureCoordinate;
    gl_Position = position;
}

position:頂點(diǎn)坐標(biāo)
textureCoordinate:紋理坐標(biāo)
varyTextCoord:用來傳遞紋理坐標(biāo)到片元著色器外冀。
gl_Position:內(nèi)建變量,用來接收頂點(diǎn)坐標(biāo)掀泳。

shaderf.fsh 文件的代碼:

precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;

void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

precision highp float;:設(shè)置默認(rèn)精度雪隧。
varying lowp vec2 varyTextCoord;:用來接收頂點(diǎn)著色器傳過來的紋理坐標(biāo)。需要跟shaderv.vsh 文件的名字保持一致员舵,完全一樣。
colorMap;:顏色貼圖(也就是紋理)
gl_FragColor:內(nèi)建變量

繪制流程

簡單了解了GLSL程序書寫位置,下面我們介紹下繪制流程护昧。
首先我們需要將viewlayer替換為CAEAGLLayer類型的layer:

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

然后在layoutSubviews方法里進(jìn)行操作繪制:

 - (void)layoutSubviews {
    //1.設(shè)置圖層
    [self setupLayer];
    //2.設(shè)置上下文
    [self setupContext];
    //3.清空緩存區(qū)
    [self clearColorBuffer];
    //4.設(shè)置RenderBuffer
    [self setupRenderBuffer];
    //5.設(shè)置FrameBuffer
    [self setupFrameBuffer];
    //6.渲染圖層
    [self renderLayer];
}

該段代碼大概分為6步:1倍啥、設(shè)置圖層 2、設(shè)置上下文 3韭邓、清空緩存區(qū) 4措近、設(shè)置RenderBuffer 5、設(shè)置FrameBuffer 6女淑、渲染圖層瞭郑。這些都是自定義的方法。

1鸭你、設(shè)置圖層

- (void)setupLayer {
    self.eaglLayer = (CAEAGLLayer *)self.layer;
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    self.eaglLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking:@(0), 
        kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
    };
}

drawableProperties繪制屬性:
kEAGLDrawablePropertyRetainedBacking 表示繪圖表面顯示后屈张,是否保留其內(nèi)容, true保留擒权, false不保留。
kEAGLDrawablePropertyColorFormat 可繪制表面的內(nèi)部顏色緩存區(qū)格式阁谆,這個(gè)key對(duì)應(yīng)的值是一個(gè)NSString指定特定顏色緩存區(qū)對(duì)象碳抄。默認(rèn)是kEAGLColorFormatRGBA8
2笛厦、設(shè)置上下文

    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"context set failed");
        return;
    }
    self.currentContext = context;

3纳鼎、清空緩存區(qū)

    glDeleteFramebuffers(1, &_colorFrameBuffer);
    self.colorFrameBuffer = 0;
    glDeleteRenderbuffers(0, &_colorRenderBuffer);
    self.colorRenderBuffer = 0;

4、設(shè)置RenderBuffer

    GLuint renderBufferID;
    glGenRenderbuffers(1, &renderBufferID);
    self.colorRenderBuffer = renderBufferID;
    //綁定(將標(biāo)識(shí)符綁定到GL_RENDERBUFFER)
    glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
    //綁定layer到GL_RENDERBUFFER
    [self.currentContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];

5裳凸、設(shè)置FrameBuffer

    GLuint frameBufferID;
    glGenFramebuffers(1, &frameBufferID);
    self.colorFrameBuffer = frameBufferID;
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
    
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);

生成幀緩存區(qū)之后贱鄙,則需要將renderbufferframebuffer進(jìn)行綁定,調(diào)用glFramebufferRenderbuffer函數(shù)進(jìn)行綁定到對(duì)應(yīng)的附著點(diǎn)上姨谷,后面的繪制才能起作用逗宁。
6、渲染圖層
6.1 基本操作

    glClearColor(0.2, 0.6, 0.4, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    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);

這段代碼大家應(yīng)該很熟悉了梦湘,都是些常規(guī)的操作瞎颗。

6.2 加載shader程序

    NSString *vPath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fPath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    NSLog(@"vPath:%@ \n fPath:%@",vPath, fPath); 
    self.program = [self loadShaderWithVPath:vPath fPath:fPath];

我們重點(diǎn)看下loadShaderWithVPath:fPath:代碼:

- (GLuint)loadShaderWithVPath:(NSString *)vPath fPath:(NSString *)fPath {
    //創(chuàng)建program
    GLuint program = glCreateProgram();
    //編譯頂點(diǎn)著色程序、片元著色器程序
    GLuint vShader, fShader;
    [self compileShader:&vShader type:GL_VERTEX_SHADER file:vPath];
    [self compileShader:&fShader type:GL_FRAGMENT_SHADER file:fPath];
    //將頂點(diǎn)著色程序捌议、片元著色器程序附著在program上
    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    //使用完之后刪除
    glDeleteShader(vShader);
    glDeleteShader(fShader);
    return program;
}

- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)path {
    *shader = glCreateShader(type);
    NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    const GLchar *source = (GLchar *)[content UTF8String];

    glShaderSource(*shader, 1, &source, NULL);

    //著色器源代碼編譯成目標(biāo)代碼
    glCompileShader(*shader);
}
  • glShaderSource(*shader, 1, &source, NULL);將著色器源碼附加到著色器對(duì)象上:
  • shader:要編譯的著色器對(duì)*shader哼拔。
  • numOfStrings:傳遞的源碼字符串?dāng)?shù)量 1個(gè)
  • strings:著色器程序的源碼(真正的著色器程序源碼&sourc
  • lenOfStrings:長度,具有每個(gè)字符串長度的數(shù)組瓣颅,或NULL倦逐,這意味著字符串是NULL終止的.

6.3 鏈接

    glLinkProgram(self.program);
    GLint linkStatus;// 鏈接的狀態(tài)
    glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar errorMessage[512];
        glGetProgramInfoLog(self.program, sizeof(errorMessage), 0, &errorMessage[0]);
        NSString *errorString = [NSString stringWithUTF8String:errorMessage];
        NSLog(@"Program Link Error:%@", errorString);
        return;;
    }
    NSLog(@"Program Link Success!");

6.4 使用program

glUseProgram(self.program);

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

    GLfloat attrArr[] =
    {
        -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
        
        0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    };
    GLuint vBuffer;
    glGenBuffers(1, &vBuffer);
    //將attrBuffer綁定到GL_ARRAY_BUFFER標(biāo)識(shí)符上
    glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
    //把頂點(diǎn)數(shù)據(jù)從CPU內(nèi)存復(fù)制到GPU上
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

6.6 設(shè)置讀取方式

    //頂點(diǎn)數(shù)據(jù)
    GLuint position = glGetAttribLocation(self.program, "position");
    glEnableVertexAttribArray(position);//打開頂點(diǎn)數(shù)據(jù)讀取通道
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
 
    //紋理數(shù)據(jù)
    GLuint textureCoordinate = glGetAttribLocation(self.program, "textureCoordinate");
    glEnableVertexAttribArray(textureCoordinate);//打開紋理數(shù)據(jù)讀取通道
    glVertexAttribPointer(textureCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
  • glGetAttribLocation(self.program, "position");
    program讀取頂點(diǎn)數(shù)據(jù)position,注意這里的"position"一定要跟shaderv.vsh文件中的position保持一致宫补。
  • glEnableVertexAttribArray(position);
    打開頂點(diǎn)數(shù)據(jù)讀取通道檬姥,默認(rèn)是關(guān)閉的。
  • glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);設(shè)置讀取方式:
    index:頂點(diǎn)數(shù)據(jù)的索引 position
    size:每個(gè)頂點(diǎn)屬性的組件數(shù)量粉怕,1健民、2、3或者4贫贝。默認(rèn)初始值是4秉犹。我們這里是3
    type:數(shù)據(jù)中的每個(gè)組件的類型稚晚,常用的有GL_FLOAT,GL_BYTE,GL_SHORT凤优。默認(rèn)初始值為GL_FLOAT
    normalized:固定點(diǎn)數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值蜈彼。(GL_FALSE
    stride:連續(xù)頂點(diǎn)屬性之間的偏移量筑辨,默認(rèn)為0。我們這里一組數(shù)據(jù)有5個(gè)幸逆,所以需要乘以5棍辕。
    NULL:指定一個(gè)指針暮现,指向數(shù)組中的第一個(gè)頂點(diǎn)屬性的第一個(gè)組件。默認(rèn)為0楚昭。

6.7 加載紋理圖片

- (GLuint)setupTexture:(NSString *)imageName {
    CGImageRef imageRef = [UIImage imageNamed:imageName].CGImage;
    if (!imageRef) {
        NSLog(@"failed image!");
        exit(1);
    } 
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    //獲取圖片字節(jié)數(shù)
    GLubyte *imageData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    //創(chuàng)建上下文
    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
    CGRect rect = CGRectMake(0, 0, width, height);
    //繪制
    CGContextDrawImage(context, rect, imageRef);
    CGContextRelease(context); 

    glBindBuffer(GL_TEXTURE_2D, 0);

    //設(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;
    
    //載入紋理2D數(shù)據(jù)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); 

    free(imageData);
    return 0;
}
  • calloc(width * height * 4, sizeof(GLubyte));
    獲取圖片字節(jié)數(shù) 寬4,因?yàn)槲覀冇玫念伾?RGBA 4分量抚太。
  • CGBitmapContextCreate();創(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上使用的顏色空間
    參數(shù)7:kCGImageAlphaPremultipliedLast:RGBA
  • CGContextDrawImage()繪制:
    使用的是Core Graphics框架,坐標(biāo)系與UIKit 不一樣庆亡。UIKit框架的原點(diǎn)在屏幕的左上角匾乓,Core Graphics框架的原點(diǎn)在屏幕的左下角。
  • glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);載入紋理2D數(shù)據(jù):
    參數(shù)1:紋理模式又谋,GL_TEXTURE_1D拼缝、GL_TEXTURE_2D、GL_TEXTURE_3D
    參數(shù)2:加載的層次彰亥,一般設(shè)置為0
    參數(shù)3:紋理的顏色值GL_RGBA
    參數(shù)4:寬
    參數(shù)5:高
    參數(shù)6:border咧七,邊界寬度
    參數(shù)7:format
    參數(shù)8:type
    參數(shù)9:紋理數(shù)據(jù)

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

    GLint colorMap = glGetUniformLocation(self.program, "colorMap");
    glUniform1i(colorMap, 0);

"colorMap"需要跟shaderf.fsh文件中保持一致。

6.9 繪制

    glDrawArrays(GL_TRIANGLES, 0, 6);

6.10 從渲染緩存區(qū)顯示到屏幕上

    [self.currentContext presentRenderbuffer:GL_RENDERBUFFER];

最后我們看下繪制的效果:



我們發(fā)現(xiàn)繪制的效果是倒過來的任斋,如何讓這張圖正過來继阻,相信大家都有自己的想法,這篇文章就不做介紹了仁卷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末穴翩,一起剝皮案震驚了整個(gè)濱河市犬第,隨后出現(xiàn)的幾起案子锦积,更是在濱河造成了極大的恐慌,老刑警劉巖歉嗓,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丰介,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鉴分,警方通過查閱死者的電腦和手機(jī)哮幢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來志珍,“玉大人橙垢,你說我怎么就攤上這事÷着矗” “怎么了柜某?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵嗽元,是天一觀的道長。 經(jīng)常有香客問我喂击,道長剂癌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任翰绊,我火速辦了婚禮佩谷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘监嗜。我一直安慰自己谐檀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布秤茅。 她就那樣靜靜地躺著稚补,像睡著了一般。 火紅的嫁衣襯著肌膚如雪框喳。 梳的紋絲不亂的頭發(fā)上课幕,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音五垮,去河邊找鬼乍惊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛放仗,可吹牛的內(nèi)容都是我干的润绎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诞挨,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼莉撇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惶傻,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤棍郎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后银室,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涂佃,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蜈敢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辜荠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抓狭,死狀恐怖伯病,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情否过,我是刑警寧澤午笛,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布膨蛮,位于F島的核電站,受9級(jí)特大地震影響季研,放射性物質(zhì)發(fā)生泄漏敞葛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一与涡、第九天 我趴在偏房一處隱蔽的房頂上張望惹谐。 院中可真熱鬧,春花似錦驼卖、人聲如沸氨肌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怎囚。三九已至,卻和暖如春桥胞,著一層夾襖步出監(jiān)牢的瞬間恳守,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國打工贩虾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留催烘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓缎罢,卻偏偏與公主長得像伊群,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子策精,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354