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

前端之家收集整理的这篇文章主要介绍了【cocos2d-x 2.x 学习与应用总结】11: 理解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 precompiled shader program */
    bool initWithPrecompiledProgramByteArray(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_str())->getCString();
    const GLchar * fragmentSource = (GLchar*) CCString::createWithContentsOfFile(CCFileUtils::sharedFileUtils()->fullPathForFilename(fShaderFilename).c_str())->getCString();

    return initWithVertexShaderByteArray(vertexSource,fragmentSource);
}

bool CCGLProgram::initWithVertexShaderByteArray(const GLchar* vShaderByteArray,const GLchar* fShaderByteArray)
{
    //不支持即时编译的平台使用initWithPrecompiledProgramByteArray方法#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 initWithPrecompiledProgramByteArray(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 = CCPrecompiledShaders::sharedPrecompiledShaders()->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)
    {
        // precompiled 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)
    {
        CCPrecompiledShaders::sharedPrecompiledShaders()->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函数

// 顶点着色器的log
const char* CCGLProgram::vertexShaderLog()
{
    return this->logForOpenGLObject(m_uVertShader,(GLInfoFunction)&glGetShaderiv,(GLLogFunction)&glGetShaderInfoLog);
}

// 片段着色器的log
const char* CCGLProgram::fragmentShaderLog()
{
    return this->logForOpenGLObject(m_uFragShader,(GLLogFunction)&glGetShaderInfoLog);
}

// program的log
const 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相关文章