【cocos2d-x 2.x 学习与应用总结】11: 理解CCGLProgram

【cocos2d-x 2.x 学习与应用总结】11: 理解CCGLProgram,第1张

概述前言 本文介绍了cocos2d-x中CCGLProgram这个类, 这篇文章假设读者已经了解OpenGL ES程序的基本渲染流程:顶点和片段着色器的源代码的编写、着色器的创建、program的创建、源代码绑定、编译、链接、OpenGL客户端-服务端传值等。CCGLProgram这个类正是对OpenGL ES渲染流程中涉及到着色器的编译、链接、传值等部分的抽象。 本文先总起介绍CCGLProgram 前言

本文介绍了cocos2d-x中CCGLProgram这个类,这篇文章假设读者已经了解OpenGL ES程序的基本渲染流程:顶点和片段着色器的源代码的编写、着色器的创建、program的创建、源代码绑定、编译、链接、OpenGL客户端-服务端传值等。CCGLProgram这个类正是对OpenGL ES渲染流程中涉及到着色器的编译、链接、传值等部分的抽象。

本文先总起介绍CCGLProgram所做的工作,然后分析其源代码中的关键实现,最后给出CCGLProgram的使用实例。

总体上认识CCGLProgram

CCGLProgram,人如其名,它是一个OpenGL Program,通过一个顶点着色器和一个片段着色器代码(对于不支持shader即时编译功能的显卡,传入编译好的二进制着色器数据)来创建一个CCGLProgram.

CCGLProgram内部做下面这些工作:

compileShader方法,内部调用glCreateShader,glShaderSource,glCompileShader等OpenGL API创建建顶点和片段着色器,并编译

调用glCreateProgram等OpenGL API完成program创建

link方法调用gllinkProgram完成program的链接

use方法调用gluseProgram以启用program

定义了8个预定义的uniform,

#define kCCUniformPMatrix_s "CC_PMatrix"#define kCCUniformMVMatrix_s "CC_MVMatrix"#define kCCUniformMVPMatrix_s "CC_MVPMatrix"#define kCCUniformTime_s "CC_Time"#define kCCUniformSinTime_s "CC_SinTime"#define kCCUniformCosTime_s "CC_CosTime"#define kCCUniformRandom01_s "CC_Random01"#define kCCUniformSampler_s "CC_Texture0"#define kCCUniformAlphaTestValue "CC_Alpha_value"

updateUniforms方法,完成预定义的8个uniform的绑定

setUniformsForBuiltins方法完成在绘制开始时,给预定义的uniform的传值工作

setUniformlocationWith_<n>_[fiv]系列方法完成客户端数值上传到OpenGL服务器

使用一个哈希表m_pHashForUniforms作为缓存,保存每一个uniform的值,如果不发生变化就不会重复上传该uniform的值到OpenGL服务器

CCGLProgram关键代码分析 成员变量
gluint                      m_uProgram;                     // 着色器程序,glCreateProgram()的返回值gluint                      m_uVertShader;                  // 顶点着色器,glCreateShader(GL_VERTEX_SHADER)返回值gluint                      m_uFragShader;                  // 片段着色器,glCreateShader(GL_FRAGMENT_SHADER)返回值Glint                       m_uUniforms[kCCUniform_MAX];    // 预定以的8个uniform常量struct _hashUniformEntry*   m_pHashForUniforms;             // 着色器程序中uniform常量的缓存bool                        m_bUsesTime;                    // 是否在着色器程序中使用时间bool                        m_hasshadercompiler;            // GPU是否支持online compile,即是否有shader的编译功能
构造类方法
// 1. 使用字节数组来初始化bool initWithVertexShaderByteArray(const GLchar* vShaderByteArray,const GLchar* fShaderByteArray);// 2. 使用两个着色器文件名字来初始化bool initWithVertexShaderfilename(const char* vShaderfilename,const char* fShaderfilename);// 3. 在不支持即时编译的平台上,使用预编译的shader字节数组来初始化 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)    /** Initializes the CCGLProgram with @R_50_3013@ shader program */    bool initWith@R_50_3013@ProgramByteArray(const GLchar* vShaderByteArray,const GLchar* fShaderByteArray);#endif

我还没搞过预编译的shader,主要来说说前两种初始化方法的实现:

// 使用文件名的初始化方式内部调用了第一种初始化方式。bool CCGLProgram::initWithVertexShaderfilename(const char* vShaderfilename,const char* fShaderfilename){    const GLchar * vertexSource = (GLchar*) CCString::createWithContentsOffile(CCfileUtils::sharedfileUtils()->fullPathForfilename(vShaderfilename).c_@R_403_5742@)->getCString();    const GLchar * fragmentSource = (GLchar*) CCString::createWithContentsOffile(CCfileUtils::sharedfileUtils()->fullPathForfilename(fShaderfilename).c_@R_403_5742@)->getCString();    return initWithVertexShaderByteArray(vertexSource,fragmentSource);}bool CCGLProgram::initWithVertexShaderByteArray(const GLchar* vShaderByteArray,const GLchar* fShaderByteArray){    // 在不支持即时编译的平台使用initWith@R_50_3013@ProgramByteArray方法。#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)    GLboolean hasCompiler = false;    glGetBooleanv(GL_SHADER_COMPILER,&hasCompiler);    m_hasshadercompiler = (hasCompiler == GL_TRUE);    if(!m_hasshadercompiler)    {        return initWith@R_50_3013@ProgramByteArray(vShaderByteArray,fShaderByteArray);    }#endif    // 创建program    m_uProgram = glCreateProgram();    CHECK_GL_ERROR_DEBUG();    m_uVertShader = m_uFragShader = 0;    if (vShaderByteArray)    {        // 创建并编译顶点着色器        if (!compileShader(&m_uVertShader,GL_VERTEX_SHADER,vShaderByteArray))        {            cclOG("cocos2d: ERROR: Failed to compile vertex shader");            return false;       }    }    // Create and compile fragment shader    if (fShaderByteArray)    {        // 创建并编译片段着色器        if (!compileShader(&m_uFragShader,GL_FRAGMENT_SHADER,fShaderByteArray))        {            cclOG("cocos2d: ERROR: Failed to compile fragment shader");            return false;        }    }    if (m_uVertShader)    {        // 顶点着色器关联到program        glAttachShader(m_uProgram,m_uVertShader);    }    CHECK_GL_ERROR_DEBUG();    if (m_uFragShader)    {        // 片段着色器关联到program        glAttachShader(m_uProgram,m_uFragShader);    }    m_pHashForUniforms = NulL;    CHECK_GL_ERROR_DEBUG();#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)    m_shaderID = CC@R_50_3013@Shaders::shared@R_50_3013@Shaders()->addShaders(vShaderByteArray,fShaderByteArray);#endif    return true;}
辅助类方法

1. 着色器的编译

bool CCGLProgram::compileShader(gluint * shader,GLenum type,const GLchar* source){    Glint status;    if (!source)    {        return false;    }    // 为着色器添加全局的uniform常量    const GLchar *sources[] = {#if (CC_TARGET_PLATFORM != CC_PLATFORM_WIN32 && CC_TARGET_PLATFORM != CC_PLATFORM_liNUX && CC_TARGET_PLATFORM != CC_PLATFORM_MAC)        (type == GL_VERTEX_SHADER ? "precision highp float;\n" : "precision mediump float;\n"),#endif        "uniform mat4 CC_PMatrix;\n"        "uniform mat4 CC_MVMatrix;\n"        "uniform mat4 CC_MVPMatrix;\n"        "uniform vec4 CC_Time;\n"        "uniform vec4 CC_SinTime;\n"        "uniform vec4 CC_CosTime;\n"        "uniform vec4 CC_Random01;\n"        "//CC INCLUDES END\n\n",source,};    // 创建shader,上传源码,编译    *shader = glCreateShader(type);    glShaderSource(*shader,sizeof(sources)/sizeof(*sources),sources,NulL);    glCompileShader(*shader);    glGetShaderiv(*shader,GL_COMPILE_STATUS,&status);    if (! status)    {        GLsizei length;        glGetShaderiv(*shader,GL_SHADER_SOURCE_LENGTH,&length);        GLchar* src = (GLchar *)malloc(sizeof(GLchar) * length);        glGetShaderSource(*shader,length,NulL,src);        cclOG("cocos2d: ERROR: Failed to compile shader:\n%s",src);        if (type == GL_VERTEX_SHADER)        {            cclOG("cocos2d: %s",vertexShaderLog());        }        else        {            cclOG("cocos2d: %s",fragmentShaderLog());        }        free(src);#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)        return false;#else        abort();#endif    }    return (status == GL_TRUE);}

2. 链接程序

bool CCGLProgram::link(){    CCAssert(m_uProgram != 0,"Cannot link invalID program");#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)    if(!m_hasshadercompiler)    {        // @R_50_3013@ shader program is already linked        return true;    }#endif    Glint status = GL_TRUE;    // 链接    gllinkProgram(m_uProgram);    // 删除掉两个着色器程序,    if (m_uVertShader)    {        glDeleteShader(m_uVertShader);    }    if (m_uFragShader)    {        glDeleteShader(m_uFragShader);    }    // 这里对应了CCGLProgram的析构函数里的两个assert,在析构CCGLProgram的时候,不需要删除着色器程序了    m_uVertShader = m_uFragShader = 0;#if DEBUG || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8)    glGetProgramiv(m_uProgram,GL_link_STATUS,&status);    // 如果链接失败,删除program    if (status == GL_FALSE)    {        cclOG("cocos2d: ERROR: Failed to link program: %i",m_uProgram);        ccGLDeleteProgram(m_uProgram);        m_uProgram = 0;    }#endif#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)    if (status == GL_TRUE)    {        CC@R_50_3013@Shaders::shared@R_50_3013@Shaders()->addProgram(m_uProgram,m_shaderID);    }#endif    return (status == GL_TRUE);}

3. use.

在进行绘制之前进行调用

voID CCGLProgram::use(){    ccgluseProgram(m_uProgram);}

4. uniform缓存检查,返回false代表不要更新uniform的值

bool CCGLProgram::updateUniformlocation(Glint location,GLvoID* data,unsigned int bytes){    if (location < 0)    {        return false;    }    bool updated = true;    tHashUniformEntry *element = NulL;    HASH_FIND_INT(m_pHashForUniforms,&location,element);    if (! element)    {        element = (tHashUniformEntry*)malloc( sizeof(*element) );        // key        element->location = location;        // value        element->value = malloc( bytes );        memcpy(element->value,data,bytes );        HASH_ADD_INT(m_pHashForUniforms,location,element);    }    else    {        if (memcmp(element->value,bytes) == 0)        {            updated = false;        }        else        {            memcpy(element->value,bytes);        }    }    return updated;}

5. log函数

// 顶点着色器的logconst char* CCGLProgram::vertexShaderLog(){    return this->logForOpenGLObject(m_uVertShader,(GlinfoFunction)&glGetShaderiv,(GLLogFunction)&glGetShaderInfolog);}// 片段着色器的logconst char* CCGLProgram::fragmentShaderLog(){    return this->logForOpenGLObject(m_uFragShader,(GLLogFunction)&glGetShaderInfolog);}// program的logconst char* CCGLProgram::programLog(){    return this->logForOpenGLObject(m_uProgram,(GlinfoFunction)&glGetProgramiv,(GLLogFunction)&glGetProgramInfolog);}
绘制过程相关

1. 预定义的8个uniform的赋值

voID CCGLProgram::setUniformsForBuiltins(){    kmMat4 matrixP;    kmMat4 matrixMV;    kmMat4 matrixMVP;    kmGLGetMatrix(KM_GL_PROJECTION,&matrixP);    kmGLGetMatrix(KM_GL_MODELVIEW,&matrixMV);    kmMat4Multiply(&matrixMVP,&matrixP,&matrixMV);    // 给投影矩阵传值    setUniformlocationWithmatrix4fv(m_uUniforms[kCCUniformPMatrix],matrixP.mat,1);    setUniformlocationWithmatrix4fv(m_uUniforms[kCCUniformMVMatrix],matrixMV.mat,1);    setUniformlocationWithmatrix4fv(m_uUniforms[kCCUniformMVPMatrix],matrixMVP.mat,1);    // 如果使用time,给time uniform传值    if(m_bUsesTime)    {        CCDirector *director = CCDirector::sharedDirector();        // This doesn't give the most accurate global time value. // Cocos2D doesn't store a high precision time value,so this will have to do.        // Getting Mach time per frame per shader using time Could be extremely expensive.        float time = director->getTotalFrames() * director->getAnimationInterval();        setUniformlocationWith4f(m_uUniforms[kCCUniformTime],time/10.0,time,time*2,time*4);        setUniformlocationWith4f(m_uUniforms[kCCUniformSinTime],time/8.0,time/4.0,time/2.0,sinf(time));        setUniformlocationWith4f(m_uUniforms[kCCUniformCosTime],cosf(time));    }    // 随机数    if (m_uUniforms[kCCUniformRandom01] != -1)    {        setUniformlocationWith4f(m_uUniforms[kCCUniformRandom01],CCRANDOM_0_1(),CCRANDOM_0_1());    }}

2. 更新uniform的位置。在创建完program,并且在使用其进行绘制之前,调用此函数来绑定shader中预定义的8个uniform常量

voID CCGLProgram::updateUniforms(){    m_uUniforms[kCCUniformPMatrix] = glGetUniformlocation(m_uProgram,kCCUniformPMatrix_s);    m_uUniforms[kCCUniformMVMatrix] = glGetUniformlocation(m_uProgram,kCCUniformMVMatrix_s);    m_uUniforms[kCCUniformMVPMatrix] = glGetUniformlocation(m_uProgram,kCCUniformMVPMatrix_s);    m_uUniforms[kCCUniformTime] = glGetUniformlocation(m_uProgram,kCCUniformTime_s);    m_uUniforms[kCCUniformSinTime] = glGetUniformlocation(m_uProgram,kCCUniformSinTime_s);    m_uUniforms[kCCUniformCosTime] = glGetUniformlocation(m_uProgram,kCCUniformCosTime_s);    m_bUsesTime = (                 m_uUniforms[kCCUniformTime] != -1 ||                 m_uUniforms[kCCUniformSinTime] != -1 ||                 m_uUniforms[kCCUniformCosTime] != -1                 );    m_uUniforms[kCCUniformRandom01] = glGetUniformlocation(m_uProgram,kCCUniformRandom01_s);    m_uUniforms[kCCUniformSampler] = glGetUniformlocation(m_uProgram,kCCUniformSampler_s);    this->use();    // Since sample most probably won't change,set it to 0 Now.    this->setUniformlocationWith1i(m_uUniforms[kCCUniformSampler],0);}

3. 绑定属性变量

voID CCGLProgram::addAttribute(const char* attributename,gluint index){    glBindAttribLocation(m_uProgram,index,attributename);}

4. 上传uniform值

这类函数都是以 setUniformlocationWith开头,先检查uniform缓存,再决定是否需要上传新的uniform值。

voID CCGLProgram::setUniformlocationWith1f(Glint location,GLfloat f1){    bool updated =  updateUniformlocation(location,&f1,sizeof(f1)*1);    if( updated )    {        gluniform1f( (Glint)location,f1);    }}voID CCGLProgram::setUniformlocationWith2f(Glint location,GLfloat f1,GLfloat f2){    GLfloat floats[2] = {f1,f2};    bool updated =  updateUniformlocation(location,floats,sizeof(floats));    if( updated )    {        gluniform2f( (Glint)location,f1,f2);    }}voID CCGLProgram::setUniformlocationWith3f(Glint location,GLfloat f2,GLfloat f3){    GLfloat floats[3] = {f1,f2,f3};    bool updated =  updateUniformlocation(location,sizeof(floats));    if( updated )    {        gluniform3f( (Glint)location,f3);    }}// ...... 还有很多这种函数,不再列举。
CCGLProgram使用实例

在cocos引擎自身中,有一个类对CCGLProgram用的最多,它就是:CCshadercache

从名字就能知道,这个类专门就是用来缓存shader的,这个shader就是CCGLProgram类型的对象实例。

在第一次获取CCshadercache这个单例类的示例的时候,会调用下面这个方法:loadDefaultShader(),它会往字典里存放一些预定以的shader program。

voID CCshadercache::loadDefaultShaders(){    // position Texture color shader    CCGLProgram *p = new CCGLProgram();                             // 创建一个CCGLProgram对象    loadDefaultShader(p,kCCShaderType_positionTexturecolor);       // 初始化program    m_pPrograms->setobject(p,kCCShader_positionTexturecolor);      // 放入字典    p->release();                                                   // 仅在字典里维持一个引用计数    // 下面省略了其他的默认shader program的创建过程    // position Texture color Alpha test    //    // position,color shader    //    //    // position Texture shader    //    //    // position,Texture attribs,1 color as uniform shader    //    //    // position Texture A8 color shader    //    //    // position and 1 color passed as a uniform (to simulate glcolor4ub )    //    //    // position,Legth(TexCoords,color (used by Draw Node basically )    //    //    // ControlSwitch    //}// 下面是初始化CCGLProgram的过程voID CCshadercache::loadDefaultShader(CCGLProgram *p,int type){    switch (type) {        case kCCShaderType_positionTexturecolor:            // 使用自带的shader源代码来初始化program.            p->initWithVertexShaderByteArray(ccpositionTexturecolor_vert,ccpositionTexturecolor_frag);            // 绑定三个顶点属性,三个顶点属性分别对应顶点着色器中的三个attribute            /* attribute vec4 a_position; attribute vec2 a_texCoord; attribute vec4 a_color; #ifdef GL_ES varying lowp vec4 v_fragmentcolor; varying mediump vec2 v_texCoord; #else varying vec4 v_fragmentcolor; varying vec2 v_texCoord; #endif voID main() { gl_position = CC_MVPMatrix * a_position; v_fragmentcolor = a_color; v_texCoord = a_texCoord; } */            p->addAttribute(kCCAttributenameposition,kCCVertexAttrib_position);            p->addAttribute(kCCAttributenamecolor,kCCVertexAttrib_color);            p->addAttribute(kCCAttributenameTexCoord,kCCVertexAttrib_TexCoords);            break;        // 省略了其他默认shader的初始化过程        case kCCShaderType_positionTexturecolorAlphaTest:        case kCCShaderType_positioncolor:          case kCCShaderType_positionTexture:        case kCCShaderType_positionTexture_ucolor:        case kCCShaderType_positionTextureA8color:        case kCCShaderType_position_ucolor:        case kCCShaderType_positionLengthTexurecolor:        case kCCShaderType_ControlSwitch:        default:            cclOG("cocos2d: %s:%d,error shader type",__FUNCTION__,__liNE__);            return;    }    // 链接program    p->link();    // 绑定shader中预定义的8个uniform    p->updateUniforms();    CHECK_GL_ERROR_DEBUG();}

经过:initWithVertexShaderByteArray,link,updateUniforms这三个步骤,CCGLProgram就已经进入就绪状态,可以被用来进行绘制了。

下面来看CCGLProgram在绘制时候的使用,以CCSprite的绘制为例,因为它使用了刚才CCshadercache中的第一个default shader: kCCShader_positionTexturecolor.

voID CCSprite::draw(voID) { // 省略掉了无关代码...... // 涉及到CCGLProgram的使用的就这一句,这个宏的定义在下面 CC_NODE_DRAW_SETUP();       glDrawArrays(GL_TRIANGLE_STRIP,0,4);    // 省略掉了无关代码......    CC_INCREMENT_GL_DRAWS(1);}// defined in ccMacros.h#define CC_NODE_DRAW_SETUP() \do { \    ccGLEnable(m_eGLServerState); \    CCAssert(getShaderProgram(),"No shader program set for this node"); \    { \        getShaderProgram()->use(); \        getShaderProgram()->setUniformsForBuiltins(); \    } \} while(0)

可以看到,在绘制的时候,调用了CCGLProgram的use()和setUniformsForBuiltins()这两个方法。

因此,对CCGLProgram的使用过程可以总结如下:

创建阶段

1. new CCGLProgram()
2. initWithVertexShaderByteArray 或者 initWithVertexShaderfilename 来完成顶点着色器和片段着色器的定义和编译
3. [可选]:如果有的话,绑定自定义的attribute
4. link() 链接program
5. updateUniforms 绑定预定义的8个uniform
6. [可选]:如果有的话,绑定自定义的uniform

绘制使用阶段

7. setShaderProgram(program) 设置为活动的program

开始绘制

8. use() 使用program
9. setUniformsForBuiltins() 设置预定义的8个uniform的值 8,9两步可以用CC_NODE_DRAW_SETUP这个宏来完成。
10. 其它的对shader中attribute和uniform的 *** 作,这里已经跟CCGLProgram关系不大。

作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

在这里也能看到这篇文章:github博客,CSDN博客,欢迎访问

总结

以上是内存溢出为你收集整理的【cocos2d-x 2.x 学习与应用总结】11: 理解CCGLProgram全部内容,希望文章能够帮你解决【cocos2d-x 2.x 学习与应用总结】11: 理解CCGLProgram所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1076738.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-26
下一篇2022-05-26

发表评论

登录后才能评论

评论列表(0条)

    保存