一、由最简单的GLSL ES程序开始

2019-03-05  本文已影响0人  猫爸iYao

基于OpenGL ES 2.0 for Android。

自OpenGL ES 2.0开始,可编程着色器(Programmable Shader)开始逐渐替代1.x时代的部分固定功能图形API。它的出现使得OpenGL ES编程更加灵活和强大。OpenGL ES 2.0至今仍是最流行、最广泛、也是最可靠的移动端图形API。

最开始,可编程着色器采用一种混合开发的方式,这种方式存在着不直观、程序复杂的问题,对开发者很不友好。因此,混合开发方式并没有完全释放可编程着色器强大的可编程能量。后来,一种更加直观的GPU程序设计语言出现了,它就是GLSL(OpenGL Shading Language)。它的出现真正促进了OpenGL的发展和繁荣。

GLSL不仅仅是一门开发语言,它同样是一个GPU图形程序设计标准。它具有以下几种显而易见的好处:

程序的入口main函数

GLSL是基于C语言修改的编程语言。一个最简单的顶点着色器(Vertex Shader)是这样的:

void main() {
    gl_Position = vec4(0);
}

所有的GLSL实现的着色器都有一个类似于C语言入口函数的函数:void main(void),它同样是GLSL程序的入口函数。其中,参数类型void是可选的。

众所周知,一个类C程序从源代码到可执行文件通常需要经过编译和链接。GLSL也不例外,不过它的编译和链接方式从命令行/GUI变成了OpenGL ES的API。

一个编译并使用GLSL着色器程序的完整过程是这样的:

  1. 创建着色器对象(Shader Object),并获得对象引用
  2. 为着色器对象注入GLSL源代码,并编译GLSL源代码
  3. 创建一个GLSL程序对象(Program Object),并挂载着色器对象
  4. 链接GLSL程序
  5. 使用GLSL程序

在Android中,通常是这样一段代码。

//GLSL 顶点着色器源代码
val shaderCodeString = """
    void main() {
        glPosition = vec4(0);
    }
""".trimIndent()

//1,创建着色器对象(Shader Object),并获得对象引用
val shaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)

//2,为着色器对象注入GLSL源代码,并编译GLSL源代码
GLES20.glShaderSource(shaderHandle, shaderCodeString)
GLES20.glCompileShader(shaderHandle)

//3,创建一个GLSL程序对象(Program Object),并挂载着色器对象
val program = GLES20.glCreateProgram()
GLES20.glAttachShader(program, shaderHandle)

//4,链接GLSL程序
GLES20.glLinkProgram(program)

//5,使用GLSL程序
GLES20.glUseProgram(program)

尽管这是一个完整的GLSL程序使用流程,但是这样做的结果是程序运行到第四步,链接GLSL程序(GLES20.glLinkProgram(program)),就已经出错了。

那么,如何获取到整个过程中的的错误信息呢?GLES方法glGetError()可以获得函数执行的错误代码,当然这种方式只能获得一些简单的信息。我们可以通过它定位到出错的函数,然后去查阅OpenGL ES官方文档了解函数的报错信息和正确用法。另外,glGetProgramInfoLog(int program)可以获取GLSL程序编译过程中较为详细的日志。例如,上面例子中,我们可以在步骤4执行后得到如下日志信息:

Link failed because of missing fragment shader.

日志显示,链接过程失败了,原因是缺少片元着色器。要知道OpenGL ES的着色器有两大类:顶点着色器、片元着色器(Fragment Shader)。这两类着色器必须成对且仅有一对出现在一个GLSL程序中才能够链接通过。一个正确的GLSL程序应当创建一个顶点着色器和一个片元着色器并挂载到同一个GLSL程序对象,之后再链接,使用。

下面看一看重新整理后的流程:

  1. 创建并编译一个顶点着色器对象
  2. 创建并编译一个片元着色器对象
  3. 创建一个GLSL程序对象,并挂载1,2中创建的着色器对象,然后链接GLSL程序对象
  4. 使用3中的GLSL程序对象
fun loadProgram(vertexCode: String, fragmentCode: String): Int  {
    //1. 创建并编译一个顶点着色器对象
    val vertexHandle = loadShader(GLES20.GL_VERTEX_SHADER, vertexCode)
    //2. 创建并编译一个片元着色器对象 
    val fragmentHandle =loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode)
    //3. 创建一个GLSL程序对象,并挂载1,2中创建的着色器对象,然后链接GLSL程序对象
    return linkProgram(vertexHandle, fragmentHandle)
}

fun loadShader(type: Int, code: String): Int {
    val shader = GLES20.glCreateShader(type)
    GLES20.glShaderSource(shader, code)
    GLES20.glCompileShader(shader)
    return shader
}

fun linkProgram(vertexShader: Int, fragmentShader: Int): Int {
    val program = GLES20.glCreateProgram()
    GLES20.glAttachShader(program, vertexShader)
    GLES20.glAttachShader(program, fragmentShader)
    GLES20.glLinkProgram(program)
    return program
}

虽然整个流程十分简单,但是新手由于对API不熟悉,仍可能出错。因此学会使用诸如glGetError()glGetProgramInfoLog(int)glGetShaderInfoLog(int)等调试工具很重要。一个好的习惯是开发阶段每一次API调用都打印日志,这能够帮助新手快速定位问题,节约时间。同时,尽量实现封装,减少重复代码出错率。

上一篇 下一篇

猜你喜欢

热点阅读