OpenGL ES-06-GLSL语言&自定义着色器、程序API

399 阅读8分钟

一、认识EAGL

1、EGL&EAGL

在介绍OpenGL ES的文章中提到过,什么是EGL,什么是EAGL。这里我们简单概述一下:

EGL:

起因:OpenGL ES 命令需要渲染上下⽂绘制表⾯才能完成图形图像的绘制。

但是,OpenGL ES API 并没有提供如何创建渲染上下⽂或者上下⽂如何连接到原⽣窗⼝系统。

于是,EGL就为我们提供了这么一个接口,用于渲染API和原⽣窗⼝系统之间的链接。

渲染上下⽂:存储相关OpenGL ES 状态.
绘制表⾯:是⽤于绘制图元的表⾯,它指定渲染所需要的缓存区类型,例如颜⾊缓存区,深度缓冲区和模板缓存区.

EAGL:

因为iOS平台不支持EGL。于是 Apple 提供⾃⼰的EGL API的iOS实现,称为EAGL。

2、EGL/EAGL的主要功能

  1. 和本地窗⼝系统(native windowing system)通讯;
  2. 查询可⽤的配置;
  3. 创建OpenGL ES可⽤的“绘图表⾯”(drawing surface);
  4. 同步不同类别的API之间的渲染,⽐如在OpenGL ES和OpenVG之间同步,或者在 OpenGL和本地窗⼝的绘图命令之间;
  5. 管理“渲染资源”,⽐如纹理映射(rendering map)。

二、GLSL语言

OpenGL规定GLSL语言是可编程着色器(顶点、片元)的编程语言。针对iOS开发来说,Xcode是无法支持GLSL语言的创建、编译,需要开发者手动进行编译。

1、注意事项

  1. 因为需要手动编译,我们一般是创建2个空文件,并且以.vsh为后缀表示顶点着色器、以.fsh为后缀表示片元着色器。(这里并不限制只能使用vsh、fsh做标记,自己能区分开就好)
  2. vsh、fsh文件的本质上是一个字符串,最终也是转成字符串来编译的。
  3. vsh、fsh文件中不要使用中文注释,因为最终编译成字符串会出现难以排查的问题。
  4. 并不建议直接使用NSString,因为代码结构不够清晰的话,易读性很差。如果花大量时间去排版,也没必要

2、向量数据类型

常用的其实只有:vec2,vec3,vec4
float类型,GSLS是强类型语法,不一致会造成编译的失败。

3、矩阵数据类型

常用的其实只有:mat3,mat4。 (简写的方阵)
float类型。

4、变量存储使用的限定符

常用的三个:attributeuniformvarying

5、三个常用变量修饰符

attribute:

  1. 只能传到顶点着色器,也只能在顶点着色器里进行使用
  2. 用途:修饰 顶点、纹理坐标、颜色、法线等,与坐标、颜色有关的数据(例如:attribute vec4 position;
  3. 客户端通过调用glVertex...开头的方法进行传递 (例如:glVertexAttribPointer
  4. 纹理坐标在这里无法使用,传进来的目的是:桥接到片元着色器,方法:需要利用varying修饰符,在顶点和片元着色器进行一模一样的声明,才能进行桥接

uniform:

  1. 既能传递到顶点着色器,又传递到片元着色器。传递一些不会在着色器中进行改变的变量,也可以看成是一个常量
  2. 用途:视图矩阵、投影矩阵、投影视图矩阵(例如:uniform mat4 viewProMatrix;
  3. 在客户端使用glUniform...方法进行传递
  4. 如果通过uniform 从客户端传递到顶点和片元2个着色器中,就需要在顶点和片元着色器中进行 一模一样的声明

varying:

  1. 因为attribute无法把纹理坐标传递到片元着色器。此时,用varying来修饰,做一个桥接作用(例如:varying lowp vec2 varyTextCoord;
  2. 但是,必须要保证顶点着色器片元着色器中保持一模一样的声明,才能让片元着色器拿到纹理坐标。

顶点着色器.vsh

//注意:在真实项目中不要使用中文注释,因为最终编译成字符串会出现难以排查的问题。 
attribute vec4 position;//4维向量-顶点坐标
attribute ve2 textCoordinate;//2维向量-纹理坐标
varying lowp vec2 varyTextCoord;//与fsh中一模一样

void main()
{
    varyTextCoord = textCoordinate;//把纹理坐标 桥接到fsh
    gl_Position = position;//最终的顶点结果要赋值给GLSL的内建变量`gl_Position`
}

片元着色器.fsh

//注意:在真实项目中不要使用中文注释,因为最终编译成字符串会出现难以排查的问题。 
precision highp float;//表示:声明这里的float默认使用高精度,一定要写,不然会出小错误
varying lowp vec2 varyTextCoord;//与vsh中一模一样
uniform sampler2D colorMap;//纹理

void main()
{
    /*
     **注意**
     假如有1000个像素点,片元着色器会执行1000次。
     在 模拟器运行的时候其实没有GPU。是CPU来模拟GPU来执行的。 当我们用复杂特效的时候,就需要用真机跑了
     */
    //最终每个,单个像素的颜色赋值给内建变量gl_FragColor
    //texture2D(纹理,纹理坐标)return纹素。 (纹素是纹理对应像素点的颜色值)
    gl_FragColor = texture2D(colorMap,varyTextCoord);
    
}

6、OpenGL ES 错误处理

如果不正确使⽤OpenGL ES 命令,应⽤程序就会产⽣⼀个错误编码。 这个错误编码将被记录,可以⽤glGetError查询。
在应⽤程序⽤glGetError查询第⼀个错误代码之前,不会记录其他错误代码. ⼀旦查询到错误代码,当前错误代码便复位为GL_NO_ERROR

三、自定义着色器&自定义程序API

1、着色器和程序的编译、链接流程

要想使用着色器进行渲染操作,就需要要先创建2个基本对象:着色器对象、程度对象。
获取链接后着色器对象一般的编译&链接分为6步:

  1. 创建⼀个顶点着⾊器对象和⼀个⽚段着⾊器对象
  2. 将源代码链接到每个着⾊器对象
  3. 编译着⾊器对象
  4. 创建⼀个程序对象
  5. 将编译后的着⾊器对象连接到程序对象
  6. 链接程序对象

2、自定义着色器API

我们先看下对于着色器使用的一般操作步骤:

  1. 创建顶点shader和片元shader glCreateShader
  2. 指定shader的source glShaderSource
  3. 编译shader glCompileShader

创建着色器(句柄也就是一个id)

GLuint glCreateShader(GLenum type); 
type — 创建着⾊器的类型,GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER 
返回值 — 是指向新着⾊器对象的句柄.(句柄也就是一个id

删除

void glDeleteShader(GLuint shader); 
shader — 要删除的着⾊器对象句柄

指定着色器的source (count一般都是1)

void glShaderSource(GLuint shader , GLSizei count ,const GLChar * const *string, const GLint *length); 
shader — 指向着⾊器对象的句柄
count — 着⾊器源字符串的数量,着⾊器可以由多个源字符串组成,但是每个着⾊器只有⼀个main函数
string — 指向保存数量的count 的着⾊器源字符串的数组指针
length — 指向保存每个着⾊器字符串⼤⼩且元素数量为count 的整数数组指针

编译着色器

void glCompileShader(GLuint shader); 
shader — 需要编译的着⾊器对象句柄

获取着色器的一个参数

void glGetShaderiv(GLuint shader , GLenum pname , GLint *params ); 
shader — 需要编译的着⾊器对象句柄
pname — 获取的信息参数,可以为 GL_COMPILE_STATUS/GL_DELETE_STATUS/
GL_INFO_LOG_LENGTH/GL_SHADER_SOURCE_LENGTH/ GL_SHADER_TYPE 
params — 指向查询结果的整数存储位置的指针.

获取着色器信息日志

void glGetShaderInfolog(GLuint shader , GLSizei maxLength, GLSizei *length , GLChar *infoLog); 
shader — 需要获取信息⽇志的着⾊器对象句柄
maxLength — 保存信息⽇志的缓存区⼤⼩
length — 写⼊的信息⽇志的⻓度(减去null 终⽌符); 如果不需要知道⻓度. 这个参数可以为Null 
infoLog — 指向保存信息⽇志的字符缓存区的指针.

3、自定义程序API

我们先看下对于程序使用的一般操作步骤:

  1. 创建一个Program对象 glCreateProgram
  2. 着色器附着到程序上 glAttachShader
  3. 链接程序 glLinkProgram
  4. 使用程序 glUseProgram

创建⼀个程序对象

GLUint glCreateProgram( ) 
返回值: 返回⼀个执⾏新程序对象的句柄

删除

void glDeleteProgram( GLuint program ) 
program : 指向需要删除的程序对象句柄

着⾊器与程序附着(把着色器附着到程序上)

void glAttachShader( GLuint program , GLuint shader ); 
program : 指向程序对象的句柄
shader : 指向程序连接的着⾊器对象的句柄

断开链接

void glDetachShader(GLuint program); 
program : 指向程序对象的句柄
shader : 指向程序断开连接的着⾊器对象句柄

链接程序

glLinkProgram(GLuint program) 
program: 指向程序对象句柄

检查链接是否成功

void glGetProgramiv (GLuint program,GLenum pname, GLint *params); 
program: 需要获取信息的程序对象句柄
pname : 获取信息的参数,可以是: 
    GL_ACTIVE_ATTRIBUTES 
    GL_ACTIVE_ATTRIBUTES_MAX_LENGTH 
    GL_ACTIVE_UNIFORM_BLOCK 
    GL_ACTIVE_UNIFORM_BLOCK_MAX_LENGTH 
    GL_ACTIVE_UNIFROMS 
    GL_ACTIVE_UNIFORM_MAX_LENGTH 
    GL_ATTACHED_SHADERS 
    GL_DELETE_STATUS 
    GL_INFO_LOG_LENGTH 
    GL_LINK_STATUS 
    GL_PROGRAM_BINARY_RETRIEVABLE_HINT 
    GL_TRANSFORM_FEEDBACK_BUFFER_MODE 
    GL_TRANSFORM_FEEDBACK_VARYINGS 
    GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 
    GL_VALIDATE_STATUS 
params : 指向查询结果整数存储位置的指针

从程序信息⽇志中获取信息

void glGetPorgramInfoLog( GLuint program ,GLSizei maxLength, GLSizei *length , GLChar *infoLog ) 
program : 指向需要获取信息的程序对象句柄
maxLength : 存储信息⽇志的缓存区⼤⼩
length : 写⼊的信息⽇志⻓度(减去null 终⽌符),如果不需要知道⻓度,这个参数可以为Null. 
infoLog : 指向存储信息⽇志的字符缓存区的指针

使用程序

void glUseProgram(GLuint program) 
program: 设置为活动程序的程序对象句柄