cocos2d-x 2.x 学习与应用总结11: 理解CCGLProgram
Posted elloop
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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, data, bytes) == 0)
updated = false;
else
memcpy(element->value, data, 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, (GLInfoFunction)&glGetShaderiv, (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], time/8.0, time/4.0, time/2.0, cosf(time));
// 随机数
if (m_uUniforms[kCCUniformRandom01] != -1)
setUniformLocationWith4f(m_uUniforms[kCCUniformRandom01], CCRANDOM_0_1(), CCRANDOM_0_1(), 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 f1, GLfloat f2, GLfloat f3)
GLfloat floats[3] = f1,f2,f3;
bool updated = updateUniformLocation(location, floats, sizeof(floats));
if( updated )
glUniform3f( (GLint)location, f1, f2, 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 学习与应用总结最近一段时间使用cocos2d-x lua的总结
cocos2d-x 学习与应用总结最近一段时间使用cocos2d-x lua的总结
cocos2d-x 学习与应用总结最近一段时间使用cocos2d-x lua的总结