本节书摘来自华章出版社《OpenGL ES应用开发实践指南:Android卷》一 书中的第3章,第3.2节,作者:(美)Kevin Brothaler ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.2 编译着色器
现在我们已经把着色器源代码从文件中读出来了,下一步就是编译每个着色器了。我们要创建一个新的辅助类,它可以创建新的OpenGL着色器对象、编译着色器代码并且返回代表那段着色器代码的着色器对象。一旦写出样板代码,在未来的项目中就可以重用了。
作为开始,创建一个名为ShaderHelper的新类,并在类中添加如下代码:
这些代码会作为着色器辅助类的基础。与以前一样,不要忘了把导入加进代码中;如果你在使用静态导入时碰到什么问题,请参考1.5节;在本书的剩余部分,我们会一直遵循这个样式。
在下一节中,我们会逐步构建compileShader()方法。
3.2.1 创建一个新的着色器对象
我们应该做的第一件事就是创建一个新的着色器对象,并且检查这个创建是否成功。在compileShader()中加入如下代码:
这里,用glCreateShader()调用创建了一个新的着色器对象,并把这个对象的ID存入变量shaderObjectId。这个type可以是代表顶点着色器的GL_VERTEX_SHADER,或者是代表片段着色器的GL_FRAGMENT_SHADER。剩下的代码也用同样的方式。
记住我们是如何创建对象并检查它是否有效的;这个模式将在OpenGL里广泛使用:
1.首先使用一个如glCreateShader()一样的调用创建一个对象,这个调用会返回一个整型值(integer)。
2.这个整型值就是OpenGL对象的引用。无论后面什么时候想要引用这个对象,就要把这个整型值传回OpenGL。
3.返回值0表示这个对象创建失败,它类似于Java代码中返回null值。
如果对象创建失败,就给调用代码返回0。为什么返回0而不是抛出一个异常呢?这是因为OpenGL内部实际不会抛出任何异常;相反,我们会得到返回值0,并且OpenGL通过glGetError()告诉我们这个错误,这个方法可以让我们询问OpenGL是不是某个API调用导致了错误。我们会一直遵从这个惯例。
附录B介绍了更多关于glGetError()的知识以及其他调试OpenGL代码的方法。
3.2.2 上传和编译着色器源代码
让我们加入如下代码把着色器源代码上传到着色器对象里:
一旦有了有效的着色器对象,就可以调用glShaderSource(shaderObjectId, shaderCode)上传源代码了。这个调用告诉OpenGL读入字符串shaderCode定义的源代码,并把它与shaderObjectId所引用的着色器对象关联起来。然后,可以调用glCompileShader(shaderObjectId)编译这个着色器:
这个调用告诉OpenGL编译先前上传到shaderObjectId的源代码。
3.2.3 取出编译状态
让我们加入如下代码检查OpenGL是否能成功地编译这个着色器:
为了检查编译是失败还是成功,首先要创建一个新的长度为1的int数组,称为compileStatus;然后调用glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0)。这就告诉OpenGL读取与shaderObjectId关联的编译状态,并把它写入compileStatus的第0个元素。
这是Android平台上的OpenGL的另外一个通用模式。为了取出一个值,我们通常会使用一个长度为1的数组,并把这个数组传进一个OpenGL调用。在同一个调用中,我们告诉OpenGL把结果存进数组的第一个元素中。
3.2.4 取出着色器信息日志
当我们获得编译状态的时候,OpenGL只给出一个简单的是或否的回答。难道没兴趣知道发生了什么错误以及哪里出问题了吗?事实证明,我们可以通过调用glGetShaderInfoLog(shaderObjectId)获得一个可读的消息。如果OpenGL有什么关于着色器的有用内容,它就会把消息存到着色器的信息日志里。
让我们加入如下代码获取着色器信息日志:
我们把日志输出到Android的日志输出中,并把一切都封装在那个检查LoggerConfg.ON值的if语句里。通过把这个常量赋值为“false”,我们可以很容易地关闭这些日志。
3.2.5 验证编译状态并返回着色器对象ID
既然我们已经记录了着色器信息日志,就可以查看一下编译是否成功了:
我们所需要做的就是检查在3.2.3节那步的返回值,它是不是0。如果它是0,编译就失败了,这种情况下,我们就不再需要着色器对象了,因此告诉OpenGL把它删除并返回0给调用代码;如果编译成功,着色器对象就是有效的,我们就可以在代码中使用它了。
这就是为了编译一个着色器所需要的全部内容,让我们返回那个新的着色器对象ID:
3.2.6 在Renderer类中编译着色器
现在是时候充分利用我们刚刚写过的代码了。切换到AirHockeyRender.java,并在onSurfaceCreated()的结尾处加入如下代码:
让我们回顾一下本节所完成的工作。首先,我们创建了一个新类ShaderHelper,并加入了一个用来创建、编译新着色器对象的方法;我们也创建了LoggerConfig,一个用来在单一代码行打开或者关闭日志的类。
如果你再看一下ShaderHelper,就会发现我们实际上定义了三个方法。
compileShader():这个compileShader(type, shaderCode)方法使用了着色器源代码和类型;type可以是代表顶点着色器的GL_VERTEX_SHADER,或者是代表片段着色器的GL_FRAGMENT_SHADER。如果OpenGL能成功编译这个着色器,这个方法就会给调用代码返回着色器对象的ID,否则,它就会返回0。
compileVertexShader():这个compileVertexShader(shaderCode)方法是调用compileShader()的辅助方法,使用GL_VERTEX_SHADER作为着色器类型。
compileFragmentShader():这个compileVertexShder(shaderCode)方法也是调用compileShader()的辅助方法,它使用GL_FRAGMENT_SHADER作为着色器类型。
如你所见,这段代码的实质内容都在compileshader()中;其他两个方法就是使用GL_VERTEX_SHADER或GL_FRAGMENT_SHADER调用它。