那么可编程的GPU能带给我们什么好处呢?
我们可以利用可编程的着色器实现局部光照,绘制更复杂的图形,实现更炫的特效等等。下面我将利用一个最最简单的例子来给出如何在 Mac OS X Snow Leopard 上通过 XCode 来创建一个 OpenGL Shader 的工程。
在附件中,这个工程已经能很好地工作了。这里我们会看到一个 Shaders 目录。这里我们在项目中添加这个引用时不要把它关联到你的构建目标上,即构造器不需要对它进行解析。但是要在 Target 中添加“拷贝文件”。方法是:在你左侧的项目管理视图中找到 Targets,然后点一下三角,再点一下你的目 标名左侧的小三角,将会看到 Copy Bundle Resource 组,将你工程中刚才引用的两个 shader 文件拖到 Resource 组的名称上,测试会出现椭圆的高亮,然后松手。我们在构建整个项目时 就会把这两个 Shader 文件作为资源拷贝到我们的应用的 Bundle 中了。
下面介绍一下 OpenGL Shader 的运行步骤:
第一步:创建程序。
我们通过以下接口创建一个 Shader 程序:
|
这个函数返回一个程序对象。
第二步:编译 Shader。
这里我们有两个 Shader,一个是 vertex shader(顶点着色器);一个是 fragment shader(片断着色器)。我们使用自定义的- (BOOL) compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file 方法分别对顶点着色器和片断着色器进行编译。对于 OpenGL 着色器创建的基本步骤而言,我们要调用的 OpenGL API 的接口依次是:
|
这个接口用于创建一个着色器。这里的参数 shaderType 是个枚举值,用于指定要创建哪种类型的着色器。GL_VERTEX_SHADER 表示顶点着色器;GL_TESS_CONTROL_SHADER 用于细分 曲面的控制阶段(流水线级,OpenGL4.1);GL_TESS_EVALUATION_SHADER(OpenGL4.1)用于细分曲面的计算阶段;GL_GEOMETRY_SHADER 用于几何着色器;GL_FRAGMENT_SHADER 则用于片断处理器。这个函数的返回可看作为是一个着色器的句柄。
|
这个接口用于指定着色器源代码。参数介绍:
shader:指定着色器句柄
count:用于指定第三个参数string中含有多少个 const GLchar* 的元素
string:源代码文本,以字符串指针的形式给出
length:分别指定 string 中每个子串的长度
如果 length 为 NULL,那么 OpenGL 假定每个子串均以 NIL('\0')结尾。
|
这个接口用于编译着色器代码。我们可以通过下列两个接口来获取编译是否成功,若不成功,则获得错误信息:
|
这个接口可以用于获得编译结果信息的长度。第二个参数,我们传入 GL_INFO_LOG_LENGTH,那么长度值就会被写入 params 所指向的地址。
|
这个接口用于获得具体到日志信息。
maxLength:用于指定你所提供的存放日志信息的缓存的最大长度
length:用于返回实际日志的长度
infoLog:写日志信息的缓存
这里对上面的着色器类型再补充说明一下,GL_GEOMETRY_SHADER,即几何着色器是从 OpenGL 3.3 开始才引入的,因此 OpenGL2.1 没有支持这个着色器特征。因此,我们目前只能使用顶点着色器和片断着色器。但是这两个着色器足以满足我们绝大 多数的需求。而在 OpenGL 手册中也并不十分推荐几何着色器的使用。
另外,我们编译完着色器后必须调用 glGetShaderiv 接口。此时,pname 要传 GL_COMPILE_STATUS 进去,以获得编译状态。如果得到的结果为 0 说明编译失败,否则为成功。
通过依次调用这些接口,我们就完成了对着色器的编译过程。
第三步:分别将顶点着色器和片断着色器关联到程序对象上:
|
program是我们先前创建好的程序对象;而shader则是我们创建并编译完之后的着色器对象。
第四步:绑定属性位置。
|
program:指定程序对象
index:指定要被绑定的通用顶点属性的索引
name:指定index所绑定的通用顶点属性变量名
glBindAttribLocation 用于将程序中一个用户定义的属性变量与一个通用属性索引相关联。当所指定的程序对象变为当前状态的一部分时,通过通用顶点属性index提供的将会修改通过name所指定的用户自定义属性变量的值。
我们可以看一下我们这边的代码例子,以下是顶点着色器的代码:
|
我们可以看到,这边定义了两个属性变量,一个是 position,一个是 color。然后再看一下主机端上绑定这两个属性变量的代码:
|
这里,ATTRIB_VERTEX和ATTRIB_COLOR 是我们自定义的枚举值,分别是 0 和 1。我们在后面将会看到如何通过 glVertexAttribPointer 将顶点数据和颜色数据分别传递给顶点着色器中的 position 和 color。
第五步:连接程序。
在我们的代码中是用 - (BOOL) linkProgram:(GLuint)prog 这个方法来完成连接的。
而这个方法主要调用的接口是:
|
调用完成后。我们可以通过使用以下接口对来获取连接信息:
|
上面这两个接口的使用与着色器的日志获取的方法一样。
最后同样再调用一次 glGetProgramiv,此时 pname 传递的是 GL_LINK_STATUS 来获取连接状态。如果返回为 0 表示失败,否则表示成功。
在程序运行中,我们可以根据当前的 OpenGL 状态来查询设备端的 Shader 程序是否有效:
|
我们可以用与获得连接日志相同的方法来获取日志信息。接着,我们可以获取着色器程序中uniform变量的位置:
|
其实我们这个代码非常简单,没有用到uniform变量。但是我们保留了一个,仅用于描述这个获取步骤,呵呵。
第六步:释放着色器对象。
当我们成功地把程序构建完之后,我们先前创建的着色器对象已经没有用了,此时我们通过以下接口释放着色器对象:
|
我们接下去就可以进行安装程序:
|
这个接口用于安装指定的程序作为当前绘制上下文的一部分。