本案例的主要目的在于理解GLSL語言自定義著色器以及自定義著色器是如何使用的
案例的效果是利用GLSL自定義的著色去加載一張圖片,效果圖如下

案例的整體流程圖如下

流程中主要分為4個模塊
- 準備工作:項目的創(chuàng)建及自定義視圖、屬性等
- 自定義著色器:利用GLSL編寫自定義的頂點、片元著色器
- 初始化:創(chuàng)建layer、context,清空緩存區(qū),以及設(shè)置Render和Frame緩存區(qū)
- 繪制:主要是GLSL加載、頂點數(shù)據(jù)處理以及加載紋理,最后繪制到屏幕上
準備工作
項目的創(chuàng)建及自定義視圖創(chuàng)建等,這里不作過多說明,主要說說著色器文件是如何創(chuàng)建的
command + N,開始新建文件
-
選擇 ios --> Other --> Empty,點擊next
創(chuàng)建empty -
輸入文件名稱,例如
shaderv.vsh,點擊create,即創(chuàng)建成功
命名
自定義著色器
自定義的著色器本質(zhì)上其實是一個字符串,且在Xcode中編寫時,是沒有任何提示的,所以需要格外仔細!
頂點著色器
- 定義兩個
attribute修飾的變量,分別表示頂點坐標position和紋理坐標textCoordinate - 定義一個與片元橋接的變量
varyTextCoord,用來將紋理坐標從頂點著色器傳遞到片元著色器 - main函數(shù):如果頂點沒有任何變換操作,則直接將頂點坐標賦值給內(nèi)建變量
gl_Position,如果頂點有變換,將變換后的結(jié)果 即最終的頂點坐標數(shù)據(jù),賦值給內(nèi)建變量
//頂點坐標
attribute vec4 position;
紋理坐標
attribute vec2 textCoordinate;
//紋理坐標
varying lowp vec2 varyTextCoord;
void main(){
//通過varying 修飾的varyTextCoord,將紋理坐標傳遞到片元著色器
varyTextCoord = textCoordinate;
//給內(nèi)江變量gl_Position賦值
gl_Position = position;
}
片與著色器
- 片元著色器中float類型的精度,如果不寫,可能會報一些異常的錯誤
- 定義一個與頂點著色器的橋接變量
varyTextCoord,即紋理坐標,必須與頂點著色器中一模一樣,如果不一致,紋理坐標數(shù)據(jù)將無法傳遞 - 定義一個
unifom修飾的紋理采樣器colorMap,用于獲取紋理坐標每個像素點的紋素 - main函數(shù):主要是紋理顏色的填充,通過
texture2D內(nèi)建函數(shù)獲取最終的顏色值,有兩個參數(shù),參數(shù)1是紋理圖片,參數(shù)2是紋理坐標,且返回值是一個vec4類型的顏色值,并將該值結(jié)果賦值給內(nèi)建變量
//指定float的默認精度
precision highp float;
//紋理坐標
varying lowp vec2 varyTextCoord;
//紋理采樣器(獲取對應(yīng)的紋理ID)
uniform sampler2D colorMap;
void main(){
//texture2D(紋理采樣器,紋理坐標),獲取對應(yīng)坐標紋素
//紋理坐標添加到對應(yīng)像素點上,即將讀取的紋素賦值給內(nèi)建變量 gl_FragColor
gl_FragColor = texture2D(colorMap, varyTextCoord);
}
初始化
初始化主要分為4部分
- setupLayer:創(chuàng)建圖層
- setupContext:創(chuàng)建上下文
- deleteRenderAndFrameBuffer:清理緩存區(qū)
- setupRenderBuffer、setupFrameBuffer:設(shè)置RenderBuffer & FrameBuffer
setupLayer函數(shù):創(chuàng)建圖層
layer主要是用于顯示OpenGL ES繪制內(nèi)容的載體
函數(shù)流程如下

- 創(chuàng)建特殊圖層,有兩種方式
- 1)直接使用view自帶的layer
由于UIView中自帶的layer是繼承自CALayer的,而需要創(chuàng)建的layer是繼承自CAEAGLLayer的,所以需要重寫類方法layerClass,返回[CAEAGLLayer class] - 2)使用init創(chuàng)建圖層
可以直接使用[[CAEAGLLayer alloc] init]創(chuàng)建一個新的layer,將其add到layer上 - 在本案例中,使用的是view自帶的layer
- 1)直接使用view自帶的layer
- 設(shè)置scale,將layer的大小設(shè)置為跟屏幕大小一致
- 設(shè)置描述屬性
- kEAGLDrawablePropertyRetainedBacking 只有true 或者 false兩種
- kEAGLDrawablePropertyColorFormat有以下三種值
| kEAGLDrawablePropertyColorFormat枚舉值 | 描述 |
|---|---|
| kEAGLColorFormatRGBA8 | 32位的RGBA顏色值(每個表示8位,所以4*8=32位) |
| kEAGLColorFormatRGB565 | 16位的RGB顏色值 |
| kEAGLColorFormatSRGBA8 | 表示標準的紅、綠、藍,sRGB的色彩空間基于獨立的色彩坐標,可以使色彩在不同的設(shè)備使用傳輸中對應(yīng)于同一個色彩坐標體系,而不受這些設(shè)備各自具有的不同色彩坐標的影響。 |
下面是兩個屬性的一些說明
| 屬性 | 說明 | 默認值 |
|---|---|---|
| kEAGLDrawablePropertyRetainedBacking | 表示繪圖表面顯示后,是否保留其內(nèi)容 | false |
| kEAGLDrawablePropertyColorFormat | 可繪制表面的內(nèi)部顏色緩存區(qū)格式 | kEAGLColorFormatRGBA8 |
//1、創(chuàng)建圖層
- (void)setupLayer{
// 1、創(chuàng)建特殊圖層
self.myEagLayer = (CAEAGLLayer*)self.layer;
// 2、設(shè)置scale
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];
// 3、設(shè)置描述屬性
self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false, kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}
+ (Class)layerClass{
return [CAEAGLLayer class];
}
setupContext函數(shù):創(chuàng)建上下文
上下文主要是用于保存OpenGL ES中的狀態(tài),是一個狀態(tài)機,不論是GLKIt還是GLSL,都是需要context的,主要創(chuàng)建流程如下

- 創(chuàng)建Context,并指定OpenGL ES 渲染API的版本號(2、3均可),且判斷是否創(chuàng)建成功
- 設(shè)置當(dāng)前的context為創(chuàng)建的context,并判斷是都設(shè)置成功
- 將其賦值給全局的context,
//2、創(chuàng)建上下文
- (void)setupContext{
// 1、指定OpenGL ES 渲染API版本
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
// 2、創(chuàng)建圖形上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
// 3、判斷是否創(chuàng)建成功
if (!context) {
NSLog(@"Create context failed!");
return;
}
// 4、設(shè)置圖形上下文
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"setCurrentContext failed");
return;
}
// 5、將局部變量賦值給全局變量
self.myContext = context;
}
deleteRenderAndFrameBuffer函數(shù):清理緩存區(qū)
清理緩沖區(qū)的目的在于清除殘留數(shù)據(jù),防止殘留數(shù)據(jù)對本次操作造成影響

需要清空兩個緩存區(qū):RenderBuffer和FrameBuffer
//3、清空緩存區(qū)
- (void)deleteRenderAndFrameBuffer{
// 清空渲染緩存區(qū)
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
// 清空幀緩存區(qū)
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
設(shè)置RenderBUffe & FrameBuffer
在設(shè)置之前,首先說說RenderBuffer和FrameBuffer
- RenderBuffer:是一個通過應(yīng)用分配的2D圖像緩沖區(qū),需要附著在FrameBuffer上
- FrameBuffer:是一個收集顏色、深度和模板緩存區(qū)的附著點,簡稱
FBO,即是一個管理者,用來管理RenderBuffer,且FrameBuffer沒有實際的存儲功能,真正實現(xiàn)存儲的是RenderBuffer
下面這張圖很形象的表示了兩者間的關(guān)系

- FrameBuffer有3個附著點
- 顏色附著點(Color Attachment):管理紋理、顏色緩沖區(qū)
- 深度附著點(depth Attachment):會影響顏色緩沖區(qū),管理深度緩沖區(qū)(Depth Buffer)
- 模板附著點(Stencil Attachment):管理模板緩沖區(qū)(Stencil Buffer)
- RenderBuffer有3種緩存區(qū)
- 深度緩存區(qū)(Depth Buffer):存儲深度值等
- 紋理緩存區(qū):存儲紋理坐標中對應(yīng)的紋素、顏色值等
- 模板緩存區(qū)(Stencil Buffer):存儲模板
setupRenderBuffer函數(shù)
主要是創(chuàng)建RenderBufferID并申請標識符,將標識符綁定至GL_RENDERBUFFER,并且將layer的相關(guān)存儲綁定到RenderBuffer對象
設(shè)置的流程如下

//4、設(shè)置RenderBuffer
- (void)setupRenderBuffer{
// 1、定義一個緩存區(qū)ID
GLuint buffer;
// 2、申請一個緩存區(qū)標識符
glGenRenderbuffers(1, &buffer);
// 3、賦值給全局變量
self.myColorRenderBuffer = buffer;
// 4、將標識符綁定到GL_RENDERBUFFER
glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
// 5、將可繪制對象drawable object的CAEAGLLayer的存儲綁定到OpenGL ES renderBuffer對象
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
setupFrameBuffer函數(shù)
主要是創(chuàng)建FrameBuffer的ID并申請標識符,將標識符綁定至GL_FRAMEBUFFER,然后將RenderBuffer通過glFramebufferRenderbuffer函數(shù)綁定到FrameBuffer中的GL_COLOR_ATTACHMENT0附著點上,通過FrameBuffer來管理RenderBuffer,RenderBuffer存儲相關(guān)數(shù)據(jù)到相應(yīng)緩存區(qū)
設(shè)置流程如下

//5、設(shè)置FrameBuffer
- (void)setupFrameBuffer{
// 1、定義一個ID
GLuint buffer;
// 2、申請一個緩存區(qū)標識符
glGenBuffers(1, &buffer);
// 3、賦值給全局變量
self.myColorFrameBuffer = buffer;
// 4、將標識符綁定到GL_FRAMEBUFFER
glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
// 5、將渲染緩存區(qū)的myColorRenderBuffer 通過 glFramebufferRenderbuffer函數(shù)綁定到GL_COLOR_ATTACHMENT0上
/*
glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
參數(shù)1:綁定到的目標
參數(shù)2:FrameBuffer的附著點
參數(shù)3:需要綁定的渲染緩沖區(qū)目標
參數(shù)4:渲染緩沖區(qū)
*/
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}
注:綁定renderBuffer和FrameBuffer是有順序的,先有RenderBuffer,才有FrameBuffer
繪制
繪制的整體流程圖圖所示

主要包含5部分
- 初始化:初始化背景顏色,清理緩存,并設(shè)置視口大小
- GLSL自定義著色器加載:對自定義著色器進行加載,大致步驟為
讀取-->加載-->編譯-->program鏈接-->使用 - 頂點數(shù)據(jù)設(shè)置及處理:將頂點坐標和紋理坐標讀取到自定義的頂點著色器中
- 加載紋理:將png/jpg圖片解壓成位圖,并讀取紋理每個像素點的紋素
- 繪制:開始繪制,存儲到RenderBuffer,從RenderBuffer將圖片顯示到屏幕上
初始化
需要注意的是,需要將視口的大小設(shè)置為與屏幕大小一致
// 設(shè)置清屏顏色 & 清除屏幕
glClearColor(0.3f, 0.45f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 1、設(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);
GLSL自定義著色器加載
自定義著色器的加載主要分為以下幾步
- 讀取自定義著色器
- compileShader & loadShaders函數(shù):編譯&加載著色器
- 鏈接program & 判斷鏈接是否成功
- 使用program
讀取自定義著色器
讀取自定義著色器文件的前提是需要獲得文件的路徑,將其傳入loadShaders函數(shù)進行加載
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
loadShaders函數(shù) & compileShader函數(shù)
-
【compileShader函數(shù)】:在將著色器加載/附著到program上時,需要先進行編譯,分為以下幾步
compileShader函數(shù)流程- 根據(jù)文件路徑讀取著色器文件中的源碼字符串,并將其轉(zhuǎn)換為c中的字符串,類型為
GLchar - 根據(jù)傳入的著色器類型
type,調(diào)用glCreateShader函數(shù)創(chuàng)建一個帶有唯一標識ID的著色器,此時著色器中并沒有附加相對應(yīng)的源碼 - 將讀取的著色器源碼通過
glShaderSource函數(shù)附加到創(chuàng)建的shader上,并將shader的ID返回給loadShaders函數(shù)中shader,以ID來獲取并使用對應(yīng)的著色器 - 通過
glCompileShader函數(shù)將shader上附加的源碼編譯成目標代碼
- 根據(jù)文件路徑讀取著色器文件中的源碼字符串,并將其轉(zhuǎn)換為c中的字符串,類型為
//編譯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];
NSLog(@"content %@, source %s", content, source);
//2.創(chuàng)建一個shader(根據(jù)type類型)
*shader = glCreateShader(type);
//3.將著色器源碼附加到著色器對象上。
//參數(shù)1:shader,要編譯的著色器對象 *shader
//參數(shù)2:numOfStrings,傳遞的源碼字符串?dāng)?shù)量 1個
//參數(shù)3:strings,著色器程序的源碼(真正的著色器程序源碼)
//參數(shù)4:lenOfStrings,長度,具有每個字符串長度的數(shù)組,或NULL,這意味著字符串是NULL終止的
glShaderSource(*shader, 1, &source,NULL);
NSLog(@"shader %d", *shader);
//4.把著色器源代碼編譯成目標代碼
glCompileShader(*shader);
}
- 【loadShaders函數(shù)】:分別將頂點著色器和片元著色器編譯完成后,并返回著色器對應(yīng)的ID,然后通過
glAttachShader函數(shù)將頂點和片元的shader分別附著到program上,然后釋放不再使用的shader,并賦值給全局的program
loadShaders函數(shù)流程
//加載shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
//1.定義2個零時著色器對象
GLuint verShader, fragShader;
//創(chuàng)建program
GLint program = glCreateProgram();
//2.編譯頂點著色程序、片元著色器程序
//參數(shù)1:編譯完存儲的底層地址
//參數(shù)2:編譯的類型,GL_VERTEX_SHADER(頂點)、GL_FRAGMENT_SHADER(片元)
//參數(shù)3:文件路徑
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
NSLog(@"verShader %d, fragShader: %d", verShader, fragShader);
//3.創(chuàng)建最終的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//4.釋放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
鏈接program
- 通過
glLinkProgram函數(shù)鏈接program - 可以通過
glGetProgramiv函數(shù)通過制定值GL_LINK_STATUS獲取鏈接的狀態(tài),判斷鏈接是成功還是失敗 - 如果鏈接失敗,可以通過
glGetProgramIngoLog函數(shù)獲取錯誤信息日志,根據(jù)錯誤信息一致去排查問題
注1:如果提示鏈接失敗,需要做如下檢查
- 一般shader 有問題:
- 1、檢查
編寫的shader 是否有誤 - 2、檢查
傳遞值的地方 標識是否寫錯了
- 1、檢查
- 如果確認shader沒有問題,再看看
layoutSubviews中函數(shù)調(diào)用是否有誤(博主遇到的一個問題就是由于調(diào)用寫錯了,導(dǎo)致鏈接失敗,最關(guān)鍵的是還沒有錯誤信息,通過斷點調(diào)試發(fā)現(xiàn)shader一直是0,著色器文件也是正長的,然后瘋狂排查問題,最后發(fā)現(xiàn)是在調(diào)用時粗心了,context的調(diào)用寫成了layer的調(diào)用)
注2:如果提示鏈接成功,但是圖片沒有加載出來
- 檢查RenderBuffer和FrameBuffer的設(shè)置是否有問題
使用program
通過glUseProgram函數(shù)來使用鏈接成功的program
glUseProgram(self.myPrograme);
頂點數(shù)據(jù)設(shè)置及處理
通過數(shù)組存儲頂點數(shù)據(jù),并將頂點坐標和紋理坐標讀取到自定義的頂點著色器中
分為以下三步
- 設(shè)置頂點數(shù)據(jù):主要是初始化頂點坐標和紋理坐標
- 開辟頂點緩存區(qū):用于將頂點數(shù)據(jù)從CPU拷貝至GPU
- 打開頂點/片元的通道
設(shè)置頂點數(shù)據(jù)沒什么好說的,就是以一個一維數(shù)組,下面說說后面的兩步
開辟頂點緩存區(qū)
開啟頂點緩存區(qū),這部分其實跟之前使用GLKit框架開啟緩存區(qū)步驟是一致的,沒什么變化,有以下四步
- 通過
GLuint定義一個頂點緩存區(qū)ID - 通過
glGenBuffers函數(shù),申請一個頂點緩存區(qū)標識符 - 通過
glBindBuffers函數(shù),將緩存區(qū)的標識符綁定到GL_ARRAY_BUFFER - 通過
glBufferData函數(shù),將頂點數(shù)據(jù)copy到GPU中
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
打開頂點/片元的通道
在iOS中,attribute通道默認是關(guān)閉的,需要手動開啟,而數(shù)據(jù)有頂點坐標和紋理坐標兩種,需要分別開啟兩次,這里的開啟與GLKit框架中是有所區(qū)別的,
- 由于本案例使用的是自定義著色器,所以需要自己獲取vertex attribut的入口,
- 在GLKit中使用的是封裝好的固定著色器,直接指定入口即可
使用自定義著色器打開通道,一般有以下三步(相對于GLKit而言,只多了一個獲取入口的步驟,后面兩步是沒有多大變化的)
- 通過
glGetAttribLocation函數(shù),獲取vertex attribute的入口,需要傳入兩個參數(shù),一個是program,一個是自定義著色器文件中變量名字符串,這里著重強調(diào)下!??!第二個參數(shù)的字符串必須與著色器文件中對應(yīng)的變量名保持一致! - 通過
glEnableVertexAttribArray函數(shù),設(shè)置合適的格式從buffer里讀取數(shù)據(jù),即設(shè)置讀取入口 - 通過
glVertexAttribPointer函數(shù),設(shè)置讀取方式
// (1)注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:position保持一致
GLuint position = glGetAttribLocation(self.myPrograme, "position");
// (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(position);
// (3).設(shè)置讀取方式
// 參數(shù)1:index,頂點數(shù)據(jù)的索引
// 參數(shù)2:size,每個頂點屬性的組件數(shù)量,1,2,3,或者4.默認初始值是4.
// 參數(shù)3:type,數(shù)據(jù)中的每個組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認初始值為GL_FLOAT
// 參數(shù)4:normalized,固定點數(shù)據(jù)值是否應(yīng)該歸一化,或者直接轉(zhuǎn)換為固定值。(GL_FALSE)
// 參數(shù)5:stride,連續(xù)頂點屬性之間的偏移量,默認為0;
// 參數(shù)6:指定一個指針,指向數(shù)組中的第一個頂點屬性的第一個組件。默認為0
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
// 9、處理紋理數(shù)據(jù)
// (1).glGetAttribLocation,用來獲取vertex attribute的入口的.
// 注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
// (2).設(shè)置合適的格式從buffer里面讀取數(shù)據(jù)
// (3).設(shè)置讀取方式
GLuint textColor = glGetAttribLocation(self.myPrograme, "textCoordinate");
glEnableVertexAttribArray(textColor);
glVertexAttribPointer(textColor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL+3);
加載紋理
這部分的內(nèi)容主要是將png/jpg圖片解壓成位圖,并通過自定義著色器讀取紋理每個像素點的紋素,包含兩部分
setupTexture函數(shù)
將png/jpg解壓成位圖,加載成紋理數(shù)據(jù),其中紋理的解壓縮使用的都是CoreGraphic,加載紋理的流程如下圖

- 紋理解壓縮:將UIImage轉(zhuǎn)換為CGImageRef
- 圖片重繪:使用
CGContextRef常見的上下文,調(diào)用CGContextDrawImage函數(shù)使用默認方式進行繪制,再繪制之前,需要獲取圖片的大小、寬、高等數(shù)據(jù),因為繪制時需要使用這些數(shù)據(jù) - 綁定紋理:通過
glBindTexture函數(shù)綁定,當(dāng)只有一個紋理的時候,默認的紋理ID是0,且0一直是激活狀態(tài),因此是可以省略glGenTexture的 - 設(shè)置紋理屬性:通過
glTexParameteri函數(shù)分別設(shè)置放大/縮小的過濾方式和S/T的環(huán)繞模式 - 載入紋理:通過
glTexImage2D函數(shù)載入紋理,載入完成后,釋放指向紋理數(shù)據(jù)的指針
// 從圖片中加載紋理
- (GLuint)setupTexture: (NSString *)fileName {
// 1、將UIImage轉(zhuǎn)換為CGImageRef & 判斷圖片是否獲取成功
CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
if (!spriteImage) {
NSLog(@"Failed to lead image %@", fileName);
exit(1);
}
// 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)存中像素的每個組件的位數(shù),比如32位RGBA,就設(shè)置為8
參數(shù)5:bytesPerRow,bitmap的沒一行的內(nèi)存所占的比特數(shù)
參數(shù)6:colorSpace,bitmap上使用的顏色空間 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
NSLog(@"kCGImageAlphaPremultipliedLast %d", kCGImageAlphaPremultipliedLast);
// 5、在CGContextRef上 --- 將圖片繪制出來
/*
CGContextDrawImage 使用的是Core Graphics框架,坐標系與UIKit 不一樣。UIKit框架的原點在屏幕的左上角,Core Graphics框架的原點在屏幕的左下角。
CGContextDrawImage
參數(shù)1:繪圖上下文
參數(shù)2:rect坐標
參數(shù)3:繪制的圖片
*/
CGRect rect = CGRectMake(0, 0, width, height);
// 6、使用默認方式繪制
CGContextDrawImage(spriteContext, rect, spriteImage);
// 7、畫圖完畢就釋放上下文
CGContextRelease(spriteContext);
// 8、綁定紋理到默認的紋理ID
glBindTexture(GL_TEXTURE_2D, 0);
// 9、設(shè)置紋理屬性
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ù)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
// 11、釋放spriteData
free(spriteData);
return 0;
}
- 【總結(jié)】圖片解壓縮的一般步驟
- 將UIImage 轉(zhuǎn)換為CGImageRef & 判斷圖片是否獲取成功
- 設(shè)置圖的大小,寬和高
- 獲取圖片字節(jié)數(shù) = 寬高4(rgba)
- 創(chuàng)建CGContextRef上下文
- 使用
CGContextDrawImage繪制圖片
設(shè)置紋理采樣器
主要是獲取紋理中對應(yīng)像素點的的顏色值,即紋素
- 通過
glGetUniformLocation函數(shù),獲取fragment uniform的入口,需要傳入兩個參數(shù),一個是program,一個是自定義片元著色器文件中變量名字符串colorMap,這里著重強調(diào)下?。?!第二個參數(shù)的字符串必須與著色器文件中對應(yīng)的變量名保持一致! - 通過
glUniform1i函數(shù)獲取紋素,有兩個參數(shù),第一個參數(shù)是 fragment uniform的入口,本質(zhì)也是一個ID,第二個參數(shù)是紋理的ID,使用的是默認的ID 0
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
繪制
開始繪制,存儲到RenderBuffer,從RenderBuffer將圖片顯示到屏幕上
- 調(diào)用
glDrawArrays函數(shù)指定圖元連接方式進行繪制 - context調(diào)用
presentRenderbuffer函數(shù)將繪制好的圖片渲染到屏幕上進行顯示
// 12、繪制
glDrawArrays(GL_TRIANGLES, 0, 6);
// 13、從渲染緩存區(qū)顯示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
完整的代碼見github - 10_GLSL_01_加載圖片



