一、GLSL简介
GLSL指的就是OpenGL着色语言(OpenGL Shading Language)的简写,是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序
GLSL是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器)。负责运行顶点着色的是顶点着色器。它可以得到当前OpenGL 中的状态,GLSL内置变量进行传递。GLSL其使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。
二、GLSL的常用数据类型
在使用GLSL中,经常会使用一些向量、矩阵和一些变量,系统对其存在这些数据类型的定义:
-
向量数据类型
-
矩阵数据类型
-
变量存储限定符
三、GLSL的使用
在xcode中,编译器本身是不支持GLSL的编译和链接。所以我们需要手动进行创建GLSL文件, 并手动将GLSL文件编译并链接到程序上。
创建GLSL文件非常简单,只需要在xcode中创建两个空文件,并修改好后缀。就创建好了顶点、片元着色器文件。通常使用vsh/fsh来当做后缀,这个后缀是可以由开发者自己来定义。
vsh/fsh在OpenGL ES中只是一个NSString字符串而已,也可以直接在OpenGL ES代码中进行填写GLSL代码,但是并不建议这么做。而且GLSL中是不支持中文以及注释,所以尽量不要在GLSL文件中使用注释以及中文,以避免不必要的错误。
3.1、GLSL数据修饰类型
在GLSL中有3中数据修饰类型:
- uniform
- attribute
- varying(后续篇章中说明)
uniform: 主要是用于从app代码中传递到顶点和片元着色器变量中(使用gluniform等方法)。在着色器文件中,只是使用此修饰的变量,不会去进行改变。
attribute: 只能由app代码中传递到顶点着色器使用,不能直接传递到片元着色器。一般都用来修饰顶点数据、纹理左边等。
varying: varying变量是顶点和片元着色器之间做数据传递用的。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。因此varying变量在vertex和fragment shader二者之间的声明必须是一致的。
四、使用GLSL加载一张图片
前文中使用GLKit加载一张图片。这里就使用GLSL来加载一张图片。
4.1、vsh与fsh
首先来写好vsh与fsh文件,vsh顶点着色器需要顶点数据、纹理数据、以及需要将纹理数据传递到片元着色器中:
attribute vec4 position;//顶点坐标
attribute vec2 textCoordinate;//纹理坐标
varying lowp vec2 varyTextCoord;//纹理坐标传递
void main() {
varyTextCoord = textCoordinate;//传递过程
gl_Position = position;//将顶点数据提交
}
接下来是fsh片元着色器文件:
precision highp float;//定义float精度
varying lowp vec2 varyTextCoord;//顶点着色器传递过来的纹理坐标
uniform sampler2D colorMap;//纹理
int main() {
//texture2D(纹理, 纹理坐标),返回颜色值
gl_FragColor = texture2D(colorMap, varyTextCoord);//提交颜色值
}
4.2、GLSL编译、链接
编译与链接主要分为6个步骤:
- 创建一个顶点着色器对象与片元着色器对象
- 源代码链接到每个着色器对象
- 编译着色器对象
- 创建一个程序对象
- 编译后的着色器对象链接到程序对象
- 链接程序对象
4.2.1、GLSL相关函数
在程序中使用的函数如下:
GLuint glCreateShader(GLenum type)
创建一个着色器对象句柄
- type: 着色器类型(顶点或片元)
- return:返回着色器对象句柄
void glDeleteShader(GLuint shader)
删除一个着色器对象句柄
- shader: 需要删除的着色器对象句柄
void glShaderSource(GLuint shader, GLSize count, const GLChar * string, const GLint * length)
将源代码链接到着色器对象上
- shader:着色器对象句柄
- count:着色器源字符串的数量
- string: 指向保存数量的count的着色器源字符串的数组指针
- length:指向保存每个着色器字符串大小且元素数量为count的整数数组指针
void glCompileShader(GLuint shader)
编译着色器对象
- shader:需要编译的着色器对象
GLuint glCreateProgram()
创建程序对象
- return:返回执行程序对象的句柄
void glDeleteProgram(GLuint program)
创建程序对象
- program:删除的程序对象的句柄
void glAttachShader(GLuint program, GLuint shader)
着色器与程序链接
- program:程序对象的句柄
- shader:着色器对象
void glDetachShader(GLuint program)
断开连接
- program : 指向程序对象的句柄
- shader : 指向程序断开连接的着⾊器对象句柄
void glLinkProgram(GLuint program)
链接程序
- program: 指向程序对象句柄
*void glGetProgramiv (GLuint program,GLenum pname, GLint params)
检查是否链接成功
- program : 指向程序对象的句柄
- pname: 获取信息的参数
- params:指向查询结果整数存储位置的指针
4.2.2、编译、链接着色器
自定义方法读取vsh/fsh文件,将源码附着到着色器上,并编译:
//源码链接到着色器并编译
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
//读取路径
NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar * source = (GLchar *)[content UTF8String];
//创建对应的着色器
*shader = glCreateShader(type);
//将源码附着到着色器上
glShaderSource(*shader, 1, &source, NULL);
//编译
glCompileShader(*shader);
}
自定义一个方法用来调用编译方法编译两个着色器,并将着色器附着到程序上,返回程序program:
//链接着色器到程序上
- (GLuint)loaderShaders:(NSString *)vert withFrag:(NSString *)frag {
//顶点、片元着色器对象
GLuint verShader, fragShader;
//program
GLuint program = glCreateProgram();
//编译顶点着色器、片元着色器
[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
//编译好的着色器附着到程序上
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
//删除shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
4.2.3、链接程序对象
通过自定义方法获取到处理完毕的程序programe,链接程序对象,并判断是否链接成功进行使用。
处理完纹理数据后,将纹理数据传递到fsh里面去:
- (void)renderLayer {
//省略...(清除颜色、设置视口相关)
//读取顶点/片元着色器
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" 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(@"Program Link Error: %@", messageString);
return;
}
NSLog(@"Program Link Success!");
//引用程序对象
glUseProgram(self.myPrograme);
//省略...(设置顶点数据,加载纹理相关)
//纹理传递
glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 6);
//渲染到屏幕
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}