OpenGL ES渲染之 Shader准备
Cocos2dx底层图形绘制是使用OpenGL ES协议的。OpenGL ES是什么呢?
OpenGL ES(OpenGl for Embedded System)是OpenGL三维图形API的子集,针对手机,PDA和游戏主机等嵌入式设备而设计.该API有Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准.
OpenGL ES是从OpenGL剪裁或定制过来了,去除了glBegin/glEnd,四边形(GL_QUADS),多边形(GL_POLYGON)等复杂图元等许多非必要的特性.经过多年发展,现在主要有两个版本,OpenGLES1.x针对固定管线硬件,OpenGL ES2.x针对可编程管线硬件.OpenGL ES1.0是以OpenGL1.3规范为基础的,OpenGL ES1.1是以OpenGL1.5为基础的,他们分别又支持common和commonlite两种profile.OpenGL ES2.0是参照OpenGL2.0规范定义的.
从Cocos2dx 2.x版本开始,Cocos2dx底层图形渲染使用OpenGL ES2.x新特性可编程着色器(Shader),下面首先介绍Shader的使用流程
xxxxx… //Shader程序
1、创建着色器对象
glCreateShader
2、着色器对象关联着色器代码
glShaderSource
3、把着色器源代码编译成目标代码
glCompileShader
4、验证着色器是否已经变异通过
glGetShaderiv glGetShaderInfoLog
5、创建一个着色器程序
glCreatePragram
6、把着色器链接到着色器程序中
glAttachShader
7、链接着色器程序
glLinkProgram
8、验证着色器程序是否链接成功
glGetProgramiv glGetProgramInfoLog
9、使用着色器程序进行定点或片段处理
glUseProgram
在Cocos2dx引擎中GLProgramCache类扮演着一个重要的角色,初始化并且保存Shader程序;并且为需要渲染的元素提供需要的Shader程序;
- classCC_DLL GLProgramCache : public Ref
- {
- public:
- /**
- * @构造函数
- */
- GLProgramCache();
- /**
- * @析构函数
- */
- ~GLProgramCache();
- /** 单例方法 */
- static GLProgramCache* getInstance();
- /**清除单例*/
- static void destroyInstance();
- /**加载Shader程序*/
- void loadDefaultGLPrograms();
- CC_DEPRECATED_ATTRIBUTE void loadDefaultShaders(){ loadDefaultGLPrograms(); }
- /**重新加载Shader程序 */
- void reloadDefaultGLPrograms();
- CC_DEPRECATED_ATTRIBUTE void reloadDefaultShaders(){ reloadDefaultGLPrograms(); }
- /** 使用Key获取Shader程序
- */
- GLProgram * getGLProgram(const std::string &key);
- CC_DEPRECATED_ATTRIBUTE GLProgram * getProgram(conststd::string &key) { return getGLProgram(key); }
- CC_DEPRECATED_ATTRIBUTE GLProgram * programForKey(conststd::string &key){ return getGLProgram(key); }
- /** 将Shader程序加入GLProgramCache单例中 */
- void addGLProgram(GLProgram* program, conststd::string &key);
- CC_DEPRECATED_ATTRIBUTE void addProgram(GLProgram*program, const std::string &key) { addGLProgram(program, key); }
- private:
- bool init();
- void loadDefaultGLProgram(GLProgram *program,int type);
- //使用字典programs保存所有的Shader程序
- std::unordered_map<std::string, GLProgram*>_programs;
- };
下面为单例方法getInstance:
- staticGLProgramCache *_sharedGLProgramCache = 0;
- GLProgramCache*GLProgramCache::getInstance()
- {
- if (!_sharedGLProgramCache) {
- _sharedGLProgramCache = new GLProgramCache();
- if (!_sharedGLProgramCache->init())
- {
- CC_SAFE_DELETE(_sharedGLProgramCache);
- }
- }
- return _sharedGLProgramCache;
- }
1、 第一次调用GLProgramCache::getInstance()方法时会new一个GLProgramCache实例方法
2、 初始化GLProgramCache实例方法
3、 方法单例_sharedGLProgramCache
下面为GLProgramCache的init方法:
- boolGLProgramCache::init()
- {
- loadDefaultGLPrograms();
- return true;
- }
- voidGLProgramCache::loadDefaultGLPrograms()
- {
- GLProgram *p = new GLProgram();
- loadDefaultGLProgram(p, kShaderType_PositionTextureColor);_programs.insert( std::make_pair( GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR,p ) );
- ……
- }
1、在GLProgramCache::init中会调用加载Shader方法loadDefaultGLPrograms
2、在loadDefaultGLPrograms方法中首先会创建一个GLProgram对象
3、将对应名称的Shader加载到GLProgram对象中
4、将GLProgram对象插入到字典_programs中
在loadDefaultGLProgram方法中:
- voidGLProgramCache::loadDefaultGLProgram(GLProgram *p, int type)
- {
- switch (type) {
- case kShaderType_PositionTextureColor:
- p->initWithByteArrays(ccPositionTextureColor_vert,ccPositionTextureColor_frag);
- break;
- ………
- default:
- CCLOG("cocos2d: %s:%d, errorshader type", __FUNCTION__, __LINE__);
- return;
- }
- p->link();
- p->updateUniforms();
- CHECK_GL_ERROR_DEBUG();
- }
1、 根据GLProgram类型使用对应的shader程序初始化GLProgram;在initWithByteArrays中,会将上述Shader使用流程中1-6不走执行
2、 链接Program
3、 获取该Program中的一些Uniform变量,工后续使用
下面看一下Cocos2dx中Shader程序的保存方式:
在cocos2d\cocos\renderer\ccShaders.cpp中:
- #include"ccShader_Position_uColor.frag"
- #include"ccShader_Position_uColor.vert"
- ……
ccShader_Position_uColor.vert文件:
- constchar* ccPosition_uColor_vert = STRINGIFY(
- attributevec4 a_position;
- uniformvec4 u_color;
- uniformfloat u_pointSize;
- \n#ifdefGL_ES\n
- varyinglowp vec4 v_fragmentColor;
- \n#else\n
- varyingvec4 v_fragmentColor;
- \n#endif\n
- voidmain()
- {
- gl_Position = CC_MVPMatrix * a_position;
- gl_PointSize = u_pointSize;
- v_fragmentColor = u_color;
- }
- );
这里定义了ccPosition_uColor_vert变量,该顶点着色器的功能室使用矩阵计算OpenGL中顶点的位置;
ccShader_Position_uColor.frag文件:
- constchar* ccPosition_uColor_frag = STRINGIFY(
- \n#ifdefGL_ES\n
- precisionlowp float;
- \n#endif\n
- varyingvec4 v_fragmentColor;
- voidmain()
- {
- gl_FragColor = v_fragmentColor;
- }
- );
这里定义了ccPosition_uColor_frag变量,该片段Shader的功能就是设置顶点的颜色;
上面两段Shader程序会字符串的形式传入initWithByteArrays方法中,下面为initWithByteArrays方法:
- boolGLProgram::initWithByteArrays(const GLchar* vShaderByteArray, const GLchar* fShaderByteArray)
- {
- ……//Windows平台单独设定
- _program = glCreateProgram();
- CHECK_GL_ERROR_DEBUG();
- _vertShader = _fragShader = 0;
- if (vShaderByteArray)
- {
- if (!compileShader(&_vertShader, GL_VERTEX_SHADER,vShaderByteArray))
- {
- CCLOG("cocos2d: ERROR: Failedto compile vertex shader");
- return false;
- }
- }
- // Create and compile fragment shader
- if (fShaderByteArray)
- {
- if (!compileShader(&_fragShader, GL_FRAGMENT_SHADER,fShaderByteArray))
- {
- CCLOG("cocos2d: ERROR: Failedto compile fragment shader");
- return false;
- }
- }
- if (_vertShader)
- {
- glAttachShader(_program, _vertShader);
- }
- CHECK_GL_ERROR_DEBUG();
- if (_fragShader)
- {
- glAttachShader(_program, _fragShader);
- }
- _hashForUniforms = nullptr;
- CHECK_GL_ERROR_DEBUG();
- ……//Windows平台单独设定
- return true;
- }
1、如果顶点Shader不为空,编译顶点Shader
2、如果片段Shader不为空,编译片段Shader
3、将program和顶点Shader绑定
4、将program和片段Shader绑定
在compileShader方法中:
- boolGLProgram::compileShader(GLuint * shader, GLenum type, const GLchar* source)
- {
- GLint status;
- if (!source) return false;
- const GLchar *sources[] = {
- ……//特殊平台需要的Uniform变量
- "uniform mat4 CC_PMatrix;\n"
- "uniform mat4 CC_MVMatrix;\n"
- "uniform mat4CC_MVPMatrix;\n"
- "uniform vec4 CC_Time;\n"
- "uniform vec4 CC_SinTime;\n"
- "uniform vec4 CC_CosTime;\n"
- "uniform vec4 CC_Random01;\n"
- "uniform sampler2DCC_Texture0;\n"
- "uniform sampler2DCC_Texture1;\n"
- "uniform sampler2DCC_Texture2;\n"
- "uniform sampler2DCC_Texture3;\n"
- "//CC INCLUDES END\n\n",
- source,
- };
- *shader = glCreateShader(type);
- glShaderSource(*shader, sizeof(sources)/sizeof(*sources),sources, nullptr);
- 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, nullptr,src);
- CCLOG("cocos2d: ERROR: Failed tocompile shader:\n%s", src);
- if (type == GL_VERTEX_SHADER)
- CCLOG("cocos2d: %s", getVertexShaderLog().c_str());
- else
- CCLOG("cocos2d: %s", getFragmentShaderLog().c_str());
- free(src);
- return false;;
- }
- return (status == GL_TRUE);
- }
1、在Shader程序字符串之前加入Shader执行时可能需要的Uniform变量,形成新的字符串
2、执行上述Shader使用流程中步骤1-3
3、验证该Shader有没有编译成功
此时Cocos2dx中需要使用到的Shader程序都已经准备好了,如何使用后面的博客中会继续讲述;对OpenGL Shader(GLSL)不是很了解的同学可以查询一下这方面的资料。
OpenGL ES渲染之LayerColor
在前面微博中讲述了Cocos2dx引擎OpenGL渲染准备Shader方面,本博客中将使用LayerColor来讲述OpenGL的渲染过程。
1、LayerColor对象创建
添加LayerColor元素到游戏中:
- autolayerColor = LayerColor::create(Color4B(255, 0, 0, 255), 100, 100);
- layerColor->setPosition(100,100);
- this->addChild(layerColor);
下面是LayerColor::create方法:
- LayerColor* LayerColor::create(const Color4B& color, GLfloat width, GLfloat height)
- {
- LayerColor * layer = new LayerColor();
- if( layer &&layer->initWithColor(color,width,height)) {
- layer->autorelease();
- return layer;
- }
- CC_SAFE_DELETE(layer);
- return nullptr;
- }
1、使用new操作符创建新LayerColor对象
2、使用initWithColor方法初始化新建的LayerColor对象
3、LayerColor创建并初始化成功后,将该对象加热自动内存管理
在LayerColor::initWithColor方法中:
- boolLayerColor::initWithColor(const Color4B& color, GLfloat w, GLfloat h){
- if (Layer::init()){
- _blendFunc =BlendFunc::ALPHA_NON_PREMULTIPLIED;
- _displayedColor.r = _realColor.r =color.r;
- _displayedColor.g = _realColor.g =color.g;
- _displayedColor.b = _realColor.b =color.b;
- _displayedOpacity = _realOpacity =color.a;
- for (size_t i = 0;i<sizeof(_squareVertices) / sizeof( _squareVertices[0]); i++ )
- {
- _squareVertices[i].x = 0.0f;
- _squareVertices[i].y = 0.0f;
- }
- updateColor();
- setContentSize(Size(w, h));
- setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_COLOR_NO_MVP));
- return true;
- }
- return false;
- }
1、调用Layer::init()方法,该方法的主要作用设置默认大小,其实在下面会重新设置LayerColor大小
2、设置混合模式为_blendFunc =BlendFunc::ALPHA_NON_PREMULTIPLIED
3、设置四个顶点的颜色
4、设置四个顶点的坐标,并设置Layer的大小
5、设置LayerColor渲染所使用Shader程序对应的GLProgramState
LayerColor其实是一个四边形,OpenGL会以四边形的方式渲染LayerColor,故需要设置四边形的顶点坐标&顶点颜色;所有的GLProgramState都会保持在GLProgramStateCache::_glProgramStates中,首次获取该GLProgramState时会新建GLProgramState对象,然后将该对象插入到GLProgramStateCache::_glProgramStates中。
2、LayerColor渲染
首先先看一下游戏的Function调用流程:
- Application::run()
- {
- ……
- DisplayLinkDirector::mainLoop()
- ……
- }
- DisplayLinkDirector::mainLoop()
- {
- ……
- Director::drawScene()
- ……
- ……
- }
- Director::drawScene()
- {
- ……
- Node::visit(…)
- ……
- Renderer::render()
- ……
- }
- Node::visit(…)
- {
- ……
- LayerColor::draw(…)
- ……
- }
LayerColor::draw(…)方法如下:
- voidLayerColor::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
- {
- _customCommand.init(_globalZOrder);
- _customCommand.func =CC_CALLBACK_0(LayerColor::onDraw, this, transform, flags);
- renderer->addCommand(&_customCommand);
- for(int i = 0; i < 4; ++i)
- {
- Vec4 pos;
- pos.x = _squareVertices[i].x; pos.y =_squareVertices[i].y; pos.z = _positionZ;
- pos.w = 1;
- _modelViewTransform.transformVector(&pos);
- _noMVPVertices[i] =Vec3(pos.x,pos.y,pos.z)/pos.w;
- }
- }
1、初始化_customCommand对象,_customCommand是一个渲染指令
2、设置_customCommand的回调函数为LayerColor::onDraw
3、将该LayerColor的渲染指令_customCommand加入到渲染队列中
4、分别将四个顶点坐标通过模型视图矩阵转换
Renderer::addCommand(…)方法如下:
- voidRenderer::addCommand(RenderCommand* command)
- {
- int renderQueue =_commandGroupStack.top();
- addCommand(command, renderQueue);
- }
- voidRenderer::addCommand(RenderCommand* command, int renderQueue)
- {
- _renderGroups[renderQueue].push_back(command);
- }
1、 获取_commandGroupStack的堆栈,其实_commandGroupStack存储的是_renderGroups数组的位置编号
2、 将新的渲染命令加入_renderGroups[renderQueue]堆栈中
Renderer::render()方法如下:
- voidRenderer::render()
- {
- _isRendering = true;
- if (_glViewAssigned)
- {
- _drawnBatches = _drawnVertices = 0;
- for (auto &renderqueue : _renderGroups){
- renderqueue.sort();
- }
- visitRenderQueue(_renderGroups[0]);
- flush();
- }
- clean();
- _isRendering = false;
- }
1、将_isRendering该帧是否渲染标志设为true
2、遍历_renderGroups,并对RenderQueue进行排序
3、真正的渲染部分,其实在_renderGroups中只目前只存在一个渲染集合
4、刷新OpenGL中参数
5、将_renderGroups能够所有CommandClean
RenderQueue::sort()渲染命令排序方法:
- voidRenderQueue::sort()
- {
- std::sort(std::begin(_queueNegZ),std::end(_queueNegZ), compareRenderCommand);
- std::sort(std::begin(_queuePosZ), std::end(_queuePosZ),compareRenderCommand);
- }
- staticbool compareRenderCommand(RenderCommand* a, RenderCommand* b)
- {
- return a->getGlobalOrder() < b->getGlobalOrder();
- }
1、将Command中元素Z坐标小于0的Command集合,按照重小到大排序
2、将Command中元素Z坐标大于0的Command集合,按照重小到大排序
Renderer::visitRenderQueue方法如下:
- voidRenderer::visitRenderQueue(const RenderQueue& queue)
- {
- ssize_t size = queue.size();
- for (ssize_t index = 0; index < size; ++index){
- auto command = queue[index];
- auto commandType = command->getType();
- if(RenderCommand::Type::QUAD_COMMAND ==commandType) {
- ……
- }else if(RenderCommand::Type::GROUP_COMMAND== commandType) {
- ……
- }else if(RenderCommand::Type::CUSTOM_COMMAND== commandType) {
- ……
- }else if(RenderCommand::Type::BATCH_COMMAND ==commandType) {
- ……
- }else if (RenderCommand::Type::MESH_COMMAND== commandType) {
- ……
- }
- }
- }
1、获取RenderQueue长度
2、遍历RenderQueue列表,并对不同类型的渲染Command分别处理
3、当渲染Commad类型为QUAD_COMMAND时
4、当渲染Commad类型为GROUP_COMMAND时
5、当渲染Commad类型为CUSTOM_COMMAND时
6、当渲染Commad类型为BATCH_COMMAND时
7、当渲染Commad类型为MESH_COMMAND时
由于篇幅有限,每种渲染的具体流程后续会分别讨论,由于LayerColor使用的是CustomCommand,下面会分析CustomCommand的具体渲染方法
- elseif(RenderCommand::Type::CUSTOM_COMMAND == commandType)
- {
- flush();
- auto cmd = static_cast<CustomCommand*>(command);
- cmd->execute();
- }
1、更新OpenGL参数,每次渲染前都需执行的操作,作用是将OpenGL参数设置为默认状态
2、请强制转换类型,并执行渲染Command
- voidCustomCommand::execute()
- {
- if(func) {
- func();
- }
- }
判断func是否为空,若func不为空,执行该方法;func是一个回调函数,在添加LayerColor渲染命时指定,如:_customCommand.func= CC_CALLBACK_0(LayerColor::onDraw, this, transform, flags)
LayerColor真正的渲染部分:
- voidLayerColor::onDraw(const Mat4& transform, uint32_t flags)
- {
- getGLProgram()->use();
- getGLProgram()->setUniformsForBuiltins(transform);
- GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION| GL::VERTEX_ATTRIB_FLAG_COLOR );
- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION,3, GL_FLOAT, GL_FALSE, 0, _noMVPVertices);
- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,4, GL_FLOAT, GL_FALSE, 0, _squareColors);
- GL::blendFunc( _blendFunc.src, _blendFunc.dst);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,4);
- }
1、使用对应的Shader程序
2、设置对应的Uniforms 矩阵
3、使能OpenGL中顶点数组和颜色数组功能
4、设置四边形的四个顶点和颜色
5、设置混合模式
6、glDrawArrays画四边形
LayerColor其实是一个有颜色的四边形,渲染LayerColor只需要在正确的位置画一个有颜色的四边形即可;Cocos2dx使用渲染方式是OpenGL ES(Windows平台不同),在OpenGL ES中并没有直接画四边形的方法,故需要借助画三角形的方法画四边形,如glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)为画四边形;