??之前我們學習中锌蓄,不管是使用OpenGL
,還是OpenGL ES
下的GLKit
加載圖片的時候撑柔,我們使用的著色器都是固定管線下的固定著色器,也就是系統(tǒng)提供的著色器您访。今天我們先學習如何使用GLSL
語言來自定義著色器铅忿。OpenGL ES入門的文章里,我們了解了一下EGL
的一些概念灵汪,然后我們還需要知道EGL
的主要功能:
- 和本地窗口系統(tǒng)(
native windowing system
)通訊 - 查詢可用的配置
- 創(chuàng)建
OpenGL ES
可用的“繪圖表面“(drawing surface
) - 同步不同類別的
API
之間的渲染檀训,比如在OpenGL ES
和OpenVG
之間同步,或者在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 vec4
和attribute 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)
//創(chuàng)建?個程序?qū)ο?返回值: 返回?個執(zhí)?新程序?qū)ο蟮木浔?GLUint glCreateProgram()
//program : 指向需要刪除的程序?qū)ο缶浔?void glDeleteProgram( GLuint program )
//著?器與程序連接/附著
//program : 指向程序?qū)ο蟮木浔?//shader : 指向程序連接的著?器對象的句柄
void glAttachShader( GLuint program , GLuint shader );
//斷開連接
//program : 指向程序?qū)ο蟮木浔?//shader : 指向程序斷開連接的著?器對象句柄
void glDetachShader(GLuint program);
//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ū):RenderBuffer
和FrameBuffer
。
-(void)deleteRenderAndFrameBuffer{
glDeleteBuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteBuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
4.創(chuàng)建渲染緩沖區(qū)
??frameBuffer
和renderBuffer
的關(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