使用GLSL語言自定義著色器案例

??之前我們學習中锌蓄,不管是使用OpenGL,還是OpenGL ES下的GLKit加載圖片的時候撑柔,我們使用的著色器都是固定管線下的固定著色器,也就是系統(tǒng)提供的著色器您访。今天我們先學習如何使用GLSL語言來自定義著色器铅忿。OpenGL ES入門的文章里,我們了解了一下EGL的一些概念灵汪,然后我們還需要知道EGL的主要功能

  • 和本地窗口系統(tǒng)(native windowing system)通訊
  • 查詢可用的配置
  • 創(chuàng)建OpenGL ES可用的“繪圖表面“(drawing surface
  • 同步不同類別的API之間的渲染檀训,比如在OpenGL ESOpenVG之間同步,或者在OpenGL和本地窗口的繪圖命令之間
  • 管理”渲染資源“享言,比如紋理映射(Rendering map

GLSL語言

??xcode中不支持GLSL語言對頂點/片元著色器的編譯和連接峻凫,因此需要在項目中創(chuàng)建兩個空文件如下圖:


??文件名可以隨意編寫,但是為了區(qū)分览露,一般默認頂點著色器后綴為vsh荧琼,片元著色器的后綴為fsh。在iOS中差牛,頂點著色器和片元著色器可以認為是字符串命锄。注意:最好不要使用中文注釋,因為可能會發(fā)生未知錯誤偏化。接下來看看GLSL語言的API總結(jié)脐恩。

向量數(shù)據(jù)類型
類型 描述
vec2,vec3,vec4(默認) 2分量、3分量侦讨、4分量浮點向量
ivec2,ivec3,ivec4 2分量驶冒、3分量、4分量整型向量
uvec2,uvec3,uvec4 2分量韵卤、3分量骗污、4分量無符號整型向量
bvec2,bvec3,bvec4 2分量、3分量怜俐、4分量bool型向量
矩陣數(shù)據(jù)類型
類型 描述
mat2,mat2x2 兩?兩列
mat3,mat3x3 (常用) 三行三列
mat4,mat4x4(常用) 四行四列
mat2x3 三行兩列
mat2x4 四行兩列
mat3x2 兩行三列
mat3x4 四行三列
mat4x2 兩行四列
mat4x3 三行四列
變量存儲限定符
限定符 描述
<none> 只是普通的本地變量身堡,外部不見,外部不可訪問
const ?個編譯常量拍鲤,或者說是?個對函數(shù)來說為只讀的參數(shù)
in/varying 從以前階段傳遞過來的變量
in/varying centroid ?個從以前的階段傳遞過來的變量贴谎,使?質(zhì)?插值
out/attribute 傳遞到下?個處理階段或者在?個函數(shù)中指定?個返回值
out/attribute centroid 傳遞到下?個處理階段汞扎,質(zhì)心插值
uniform ?個從客戶端代碼傳遞過來的變量,在頂點之間不做改變

??我們先簡單自定義兩個著色器然后看看如何編寫:
頂點著色器代碼

attribute vec4 position;
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main()
{
    varyTextCoord = textCoordinate;
    gl_Position = position;
}

??attribute擅这,uniform跟我們之前文章里講述的修飾方法澈魄,和作用對象完全一致:

1.attribute用來修飾紋理坐標和頂點坐標attribute vec4attribute vec2
2.從頂點著色器往片元著色器傳遞紋理坐標的方法,就是用varying在頂點著色器里定義一個紋理坐標仲翎,然后在片元著色器里痹扇,定義一個一模一樣名字的紋理坐標。即varyTextCoord
3.最后把頂點著色器計算的頂點數(shù)據(jù)賦值給gl_Position

片元著色器代碼

//float 在片元著色器里的精度
precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

1.uniform可以從OpenGL ES客戶端傳遞到頂點和片元著色器溯香,一般為常量鲫构。
2.片元著色器需要使用內(nèi)建函數(shù)texture2D來計算紋素,即紋理坐標下的像素值玫坛,然后賦值給內(nèi)建變量gl_FragColor结笨。

OpenGL ES 錯誤處理

??如果不正確使用OpenGL ES命令,應(yīng)用程序就會產(chǎn)生一個錯誤編碼湿镀,可以用glGetError查詢炕吸,一旦查詢到錯誤代碼,當前的錯誤代碼就會復位為GL_NO_ERROR

錯誤代碼 描述
GL_NO_ERROR 從上?次調(diào)?glGetError以來沒有生成任何錯誤
GL_INVALID_ENUM GLenum 參數(shù)超出范圍,忽略生成錯誤命令
GL_INVALID_VALUE 數(shù)值型 參數(shù)超出范圍,忽略生成錯誤命令
GL_INVALID_OPERATION 特定命令在當前OpenGL ES 狀態(tài)?法執(zhí)?
GL_OUT_OF_MEMORY 內(nèi)存不足時執(zhí)?該命令,如果遇到這個錯誤,除?當前錯誤代碼,否則OpenGL ES管線的狀態(tài)被認為未定義

GLSL案例

??下面是我們這次的重點勉痴,用實際案例赫模,來說明如何使用GLSL語言自定義著色器實現(xiàn)紋理加載,首先我們需要知道蒸矛,著色器和程序之間的流程圖(圖著色器和程序)瀑罗。熟悉了這個流程我們才能使用它進行紋理的渲染。接下來我們看看這個流程下的GLSL語法的API莉钙。

獲取連接后的著色器對象步驟
著色器和程序
1.創(chuàng)建著色器
//type — 創(chuàng)建著?器的類型,GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER 
//返回值 — 是指向新著?器對象的句柄.可以調(diào)?glDeleteShader 刪除
GLuint glCreateShader(GLenum type); 
2.鏈接著色器
//shader — 指向著?器對象的句柄
//count — 著?器源字符串的數(shù)量,著?器可以由多個源字符串組成,但是每個著?器只有?個main函數(shù)
//string — 指向保存數(shù)量的count 的著?器源字符串的數(shù)組指針
//length — 指向保存每個著?器字符串??且元素數(shù)量為count 的整數(shù)數(shù)組指針
void glShaderSource(GLuint shader , GLSizei count ,const GLChar * const *string, const GLint*length); 
3.編譯著色器
//shader — 需要編譯的著?器對象句柄
void glCompileShader(GLuint shader); 

//shader — 需要編譯的著?器對象句柄
//pname — 獲取的信息參數(shù),可以為 GL_COMPILE_STATUS/GL_DELETE_STATUS/
//GL_INFO_LOG_LENGTH/GL_SHADER_SOURCE_LENGTH/ GL_SHADER_TYPE 
//params — 指向查詢結(jié)果的整數(shù)存儲位置的指針.
void glGetShaderiv(GLuint shader , GLenum pname , GLint *params ); 

//shader — 需要獲取信息?志的著?器對象句柄
//maxLength — 保存信息?志的緩存區(qū)??
//length — 寫?的信息?志的?度(減去null 終?符); 如果不需要知道?度. 這個參數(shù)可以為Null 
//infoLog — 指向保存信息?志的字符緩存區(qū)的指針
void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *length , GLChar *infoLog); 
4.創(chuàng)建程序?qū)ο?/h6>
//創(chuàng)建?個程序?qū)ο?返回值: 返回?個執(zhí)?新程序?qū)ο蟮木浔?GLUint glCreateProgram() 

//program : 指向需要刪除的程序?qū)ο缶浔?void glDeleteProgram( GLuint program ) 
5.鏈接著色器和程序
//著?器與程序連接/附著
//program : 指向程序?qū)ο蟮木浔?//shader : 指向程序連接的著?器對象的句柄
void glAttachShader( GLuint program , GLuint shader ); 

//斷開連接
//program : 指向程序?qū)ο蟮木浔?//shader : 指向程序斷開連接的著?器對象句柄
void glDetachShader(GLuint program); 

6.鏈接和使用
//program: 指向程序?qū)ο缶浔?glLinkProgram(GLuint program) 

//鏈接程序之后, 需要檢查鏈接是否成功. 你可以使?glGetProgramiv 檢查鏈接狀態(tài): 
//program: 需要獲取信息的程序?qū)ο缶浔?//pname : 獲取信息的參數(shù)
//params : 指向查詢結(jié)果整數(shù)存儲位置的指針
void glGetProgramiv (GLuint program,GLenum pname, GLint *params); 

//從程序信息?志中獲取信息
//program : 指向需要獲取信息的程序?qū)ο缶浔?//maxLength : 存儲信息?志的緩存區(qū)??
//length : 寫?的信息?志?度(減去null 終?符),如果不需要知道?度,這個參數(shù)可以為Null. 
//infoLog : 指向存儲信息?志的字符緩存區(qū)的指針
void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSizei *length , GLChar *infoLog) 

//program: 設(shè)置為活動程序的程序?qū)ο缶浔?
void glUseProgram(GLuint program) 

??我們可以用方法檢查一下日志信息廓脆,
??接下來是整個案例的流程(如下圖),流程比較長磁玉, 代碼比較多停忿,但是我盡量詳細描述。


渲染流程
1.設(shè)置圖層

??CAEAGLLayer主要是用于顯示OpenGL ES繪制內(nèi)容的載體,這個圖層是核心動畫里的特殊圖層蚊伞,中間的EAGL就是我們之前說的為OpenGL ES提供載體的圖層席赂。創(chuàng)建一個特殊圖層[[CAEAGLLayer alloc] init]然后添加到視圖的layer上。

-(void)setupLayer{
    self.myLayer = [[CAEAGLLayer alloc]init];
    self.myLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
    [self.layer addSublayer:self.myLayer];
    self.myLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                       @false,
                                       kEAGLDrawablePropertyRetainedBacking,
                                       kEAGLColorFormatRGBA8,
                                       kEAGLDrawablePropertyColorFormat,nil];
}
2.設(shè)置上下文

??上下文主要是用于保存OpenGL ES中的狀態(tài)时迫,是一個狀態(tài)機颅停,可以理解為一個管理繪制過程的統(tǒng)籌對象,所有的繪制相關(guān)的狀態(tài)掠拳,都交給context來保存癞揉。

-(void)setupContext{
    self.myContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!self.myContext) {
        NSLog(@"create context failed");
        return;
    }
    if (![EAGLContext setCurrentContext:self.myContext]) {
        NSLog(@"set context failed");
        return;
    }
}
3.清空緩存區(qū)

??需要清空兩個緩存區(qū):RenderBufferFrameBuffer

-(void)deleteRenderAndFrameBuffer{
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
}
4.創(chuàng)建渲染緩沖區(qū)

??frameBufferrenderBuffer的關(guān)系:frameBuffer(FBO)是renderBuffer的管理著,或者叫做附著點喊熟。frameBuffer不存儲內(nèi)容柏肪,所有關(guān)于紋理的顏色,深度和模板等都存在renderBuffer中芥牌。具體關(guān)系如下圖:

-(void)setupRenderBuffer{
    glGenRenderbuffers(1, &_myColorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myLayer];
}
5.創(chuàng)建幀緩沖區(qū)
-(void)setupFrameBuffer{
    glGenRenderbuffers(1, &_myColorFrameBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorFrameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,
                              GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER,
                              self.myColorRenderBuffer);
}
6.開始繪制
  • 初始化:初始化背景顏色烦味,清理緩存,并設(shè)置視口大小
  • GLSL自定義著色器加載:對自定義著色器進行加載壁拉,步驟就是我們上文中講述的谬俄。創(chuàng)建->鏈接著色器->編譯著色器->創(chuàng)建程序->鏈接程序著色器->使用。
  • 頂點數(shù)據(jù)設(shè)置及處理:將頂點坐標和紋理坐標讀取到自定義的頂點著色器中弃理,并且打開通道溃论。
  • 加載紋理:將png/jpg圖片解壓成位圖,并讀取紋理每個像素點的紋素
  • 繪制:開始繪制痘昌。把渲染緩沖區(qū)的內(nèi)容顯示到屏幕上蔬芥。
-(void)renderLayer{
    glClearColor(0.3, 0.2, 0.1, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
    NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"fsh"];
    self.myPrograme = [self loaderShaders:vertFile withFrag:fragFile];
    glLinkProgram(self.myPrograme);
    GLint linkStatus;
    glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar message[512];
        glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
        NSString *messageString = [NSString stringWithUTF8String:message];
        NSLog(@"程序連接著色器失敗");
    }
    glUseProgram(self.myPrograme);
    GLfloat attrArr[] =
    {
        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,    0.0f, 0.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 attrBuffer;
    glGenBuffers(1, &attrBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    GLuint position = glGetAttribLocation(self.myPrograme, "position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");
    glEnableVertexAttribArray(textCoor);
    glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
    [self setupTexture:@"tutu"];
    glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}
-(GLuint)setupTexture:(NSString *)fileName{
     CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
     if (!spriteImage) {
         NSLog(@"Failed to load image %@", fileName);
         exit(1);
     }
     size_t width = CGImageGetWidth(spriteImage);
     size_t height = CGImageGetHeight(spriteImage);
     GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
     CGContextRef spriteContext = CGBitmapContextCreate(spriteData,
                                                        width,
                                                        height,
                                                        8,
                                                        width*4,
                                                        CGImageGetColorSpace(spriteImage),
                                                        kCGImageAlphaPremultipliedLast);
     CGRect rect = CGRectMake(0, 0, width, height);
     CGContextDrawImage(spriteContext, rect, spriteImage);
     CGContextRelease(spriteContext);
     glBindTexture(GL_TEXTURE_2D, 0);
     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;
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
     free(spriteData);
     return 0;
}

完整Demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市控汉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌返吻,老刑警劉巖姑子,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異测僵,居然都是意外死亡街佑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門捍靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沐旨,“玉大人,你說我怎么就攤上這事榨婆〈判” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵良风,是天一觀的道長谊迄。 經(jīng)常有香客問我,道長烟央,這世上最難降的妖魔是什么统诺? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮疑俭,結(jié)果婚禮上粮呢,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好啄寡,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布豪硅。 她就那樣靜靜地躺著,像睡著了一般这难。 火紅的嫁衣襯著肌膚如雪舟误。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天姻乓,我揣著相機與錄音嵌溢,去河邊找鬼。 笑死蹋岩,一個胖子當著我的面吹牛赖草,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剪个,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秧骑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扣囊?” 一聲冷哼從身側(cè)響起乎折,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侵歇,沒想到半個月后骂澄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡惕虑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年坟冲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溃蔫。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡健提,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伟叛,到底是詐尸還是另有隱情私痹,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布统刮,位于F島的核電站侄榴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏网沾。R本人自食惡果不足惜癞蚕,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辉哥。 院中可真熱鬧桦山,春花似錦攒射、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钉凌,卻和暖如春咧最,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背御雕。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工矢沿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酸纲。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓捣鲸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闽坡。 傳聞我的和親對象是個殘疾皇子栽惶,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354