GLSL Shader特效教程3:如何管理shader

2015年03月19日 10:29 0 点赞 0 评论 更新于 2025-11-21 17:38

在GLSL Shader特效的应用中,有两个比较重要的问题,下面将分别进行探讨:

  1. 如何管理自定义的shader?
  2. 如何解决Android上游戏从后台切换回来时,自定义shader不能自动加载的问题?

接下来,我们针对这两个问题进行解答。

从Cocos2d-x 2.2到3.0版本,引擎在shader部分基本没有太大变化。在引擎中使用自定义shader相对较为麻烦,尤其是在Android平台上。当app从后台切换回来时,自定义的shader可能不会自动加载,为了解决这个问题,开发者可能不得不修改引擎的内部代码,这并不是一个好的做法。不过,从3.2版本开始,引擎提供了GLProgramState来解决该问题。

下面我们先来分析一下Cocos2d-x shader的框架(以Cocos2d-x 3.0 Android为例):

  • 初始化:引擎内置的shader program由一个全局的ShaderCache进行初始化并持有。其初始化流程为:getInstance --> init() --> loadDefaultShaders();
  • 释放:释放操作通过Director::purgeDirector --> ShaderCache::destroyInstance();实现。
  • 后台返回(Javaactivity.cpp):当app从后台返回时,会执行cocos2d::ShaderCache::getInstance()->reloadDefaultGLPrograms();

需要注意的是,在3.2版本中,ShaderCache改名为ProgramCache,但其他部分基本保持不变。基于上述框架,如果用户要在Android上添加自己的shader,就需要在Javaactivity.cpp中添加相应的reload代码,这反映出当前引擎的shader部分存在一定不足。因此,笔者认为shader部分应该进行重写,并向开发者提供一个自定义的接口。

接下来,我们看一下在OpenGL中使用shader的一般流程:

  1. 编译vertex shader或者fragment shader:对顶点着色器和片段着色器进行编译。
  2. 链接shader:将编译好的着色器进行链接。
  3. 初始化需要向shader传递的变量:例如使用getUniformLocation函数获取变量的位置。
  4. 在render函数中,实时传递shader变量:例如使用setUniformLocation函数设置变量的值。

问题的关键在于第三步,对于不同的shader,需要初始化的shader变量各不相同,而且很难预测开发者需要向shader传递哪些变量。

为了解决这个问题,我们可以抽象出一个shaderNode父类,完成shader的前两个步骤,并提供一个抽象接口(如:virtual void buildCustomUniforms() = 0;)来完成第三步的初始化,让所有继承的子类来实现这个接口。

下面我们通过具体的例子来进行说明:

  • ShaderNode:作为所有shader的父类,提供公共接口的实现,例如shader的编译和链接,以及shader从后台切换回来时reload函数的实现。
  • CustomShader:开发者自定义的shader,继承自ShaderNode,并实现抽象接口buildCustomUniforms。在buildCustomUniforms函数中初始化自定义的shader变量。
  • ShaderCache:作为全局shader cache的持有者,使用shader_dec来描述所持有的ShaderNode。
  • shader_dec:ShaderNode的描述结构体,包含vertex shader和fragment shader的文件名、查找key以及ShaderNode指针(用于回调具体ShaderNode的reload函数)。

以下是具体的使用方法:

  1. 定义需要加载的shader table
    static cocos2d::shaderDes shader_tbl[] = {
    {shader_comm_vs, shader_a_fs, "a", NULL},
    {shader_comm_vs, shader_b_fs, "b", NULL},
    {shader_comm_vs, shader_c_fs, "c", NULL}
    };
    
  2. 在预加载页面(如:loading scene)
    for (int i = 0; i < ARRAYSIZE(shader_tbl); i++) {
    CustomShaderCache::getInstance()->addShader(shader_tbl[i]);
    }
    
  3. 使用shader
    xxxShader::create(shader_key); /* @shader_key是自定义的shader table中的key。*/
    

需要说明的是,附件的源代码没有修改引擎的shader cache的实现模块,而是另外实现了CustomShaderCache,并将其添加在Cocos2d-x-3.0\\cocos\\2d目录下,作为libcocos2d编译进引擎中。当然,开发者也可以选择重写引擎的shadercache模块。CustomShaderCache的实现和引擎的shadercache模块基本相似,不同之处在于CustomShaderCache向外提供了一个自定义shader的类,这样自定义的shader只需要继承ShaderNode即可,其他的reload等工作由CustomShaderCache负责实现。

源代码见文件。

以上就是关于GLSL Shader管理的相关内容,希望对开发者有所帮助。

作者信息

boke

boke

共发布了 3994 篇文章