iOS视觉(十一) -- 初用GLSL(一):GLSL的使用

1,461 阅读6分钟

一、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个步骤:

  1. 创建一个顶点着色器对象与片元着色器对象
  2. 源代码链接到每个着色器对象
  3. 编译着色器对象
  4. 创建一个程序对象
  5. 编译后的着色器对象链接到程序对象
  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];

}