Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程

最近几天,我都在学习如何在Cocos2d-x3.2中使用OpenGL来实现对图形的渲染。在网上也看到了很多好的文章,我在它们的基础上做了这次的我个人认为比较完整的总结。当你了解了Cocos2d-x3.2中对图形渲染的流程,你就会觉得要学会写自己的shader才是最重要的。

第一,渲染流程从2.x到3.x的变化。

在2.x中,渲染过程是通过递归渲染树(Rendering tree)这种图关系来渲染关系图。递归调用visit()函数,并且在visit()函数中调用该节点的draw函数渲染各个节点,此时draw函数的作用是直接调用OpenGL代码进行图形的渲染。由于visit()和draw函数都是虚函数,所以要注意执行时的多态。那么我们来看看2.x版本中CCSprite的draw函数,如代码1。

代码1:

[cpp] view plaincopy

  1. //这是cocos2d-2.0-x-2.0.4版本的CCSprite的draw函数
  2. void CCSprite::draw(void)
  3. {
  4. CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
  5. CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called");
  6. CC_NODE_DRAW_SETUP();
  7. ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );
  8. if (m_pobTexture != NULL)
  9. {
  10. ccGLBindTexture2D( m_pobTexture->getName() );
  11. }
  12. else
  13. {
  14. ccGLBindTexture2D(0);
  15. }
  16. //
  17. // Attributes
  18. //
  19. ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );
  20. #define kQuadSize sizeof(m_sQuad.bl)
  21. long offset = (long)&m_sQuad;
  22. // vertex
  23. int diff = offsetof( ccV3F_C4B_T2F, vertices);
  24. glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));
  25. // texCoods
  26. diff = offsetof( ccV3F_C4B_T2F, texCoords);
  27. glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
  28. // color
  29. diff = offsetof( ccV3F_C4B_T2F, colors);
  30. glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));
  31. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  32. CHECK_GL_ERROR_DEBUG();
  33. #if CC_SPRITE_DEBUG_DRAW == 1
  34. // draw bounding box
  35. CCPoint vertices[4]={
  36. ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
  37. ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
  38. ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
  39. ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
  40. };
  41. ccDrawPoly(vertices, 4, true);
  42. #elif CC_SPRITE_DEBUG_DRAW == 2
  43. // draw texture box
  44. CCSize s = this->getTextureRect().size;
  45. CCPoint offsetPix = this->getOffsetPosition();
  46. CCPoint vertices[4] = {
  47. ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
  48. ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
  49. };
  50. ccDrawPoly(vertices, 4, true);
  51. #endif // CC_SPRITE_DEBUG_DRAW
  52. CC_INCREMENT_GL_DRAWS(1);
  53. CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
  54. }

那么我们也看看3.x中Sprite的draw函数,如代码2。

代码2:

[cpp] view plaincopy

  1. void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
  2. {
  3. // Don‘t do calculate the culling if the transform was not updated
  4. _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
  5. if(_insideBounds)
  6. {
  7. _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
  8. renderer->addCommand(&_quadCommand);
  9. #if CC_SPRITE_DEBUG_DRAW
  10. _customDebugDrawCommand.init(_globalZOrder);
  11. _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
  12. renderer->addCommand(&_customDebugDrawCommand);
  13. #endif //CC_SPRITE_DEBUG_DRAW
  14. }
  15. }

从代码1和代码2的对比中,我们很容易就发现2.x版本中的draw函数直接调用OpengGL代码进行图形渲染,而3.x版本中draw的作用是把RenderCommand添加到CommandQueue中,至于这样做的好处是,实际的渲染API进入其中一个与显卡直接交流的有独立线程的RenderQueue。

从Cocos2d-x3.0开始,Cocos2d-x引入了新的渲染流程,它不像2.x版本直接在每一个node中的draw函数中直接调用OpenGL代码进行图形渲染,而是通过各种RenderCommand封装起来,然后添加到一个CommandQueue队列里面去,而现在draw函数的作用就是在此函数中设置好相对应的RenderCommand参数,然后把此RenderCommand添加到CommandQueue中。最后在每一帧结束时调用renderer函数进行渲染,在renderer函数中会根据ID对RenderCommand进行排序,然后才进行渲染。

下面我们来看看图1、图2,这两个图形象地表现了Cocos2d-x3.x下RenderCommand的封装与传递与及RenderCommand的排序。

图1:

图2:

上面所说的各个方面都有点零碎,下面就对渲染的整个流程来一个从头到尾的梳理吧。下面是针对3.2版本的,对于2.x版本的梳理不做梳理,因为我用的是3.2版本。

首先,我们Cocos2d-x的执行是通过Application::run()来开始的,如代码3,此代码目录中在xx\cocos2d\cocos\platform\对应平台的目录下,这是与多平台实现有关的类,关于如何实现多平台的编译,你可以参考《cocos2d-x3.2源码分析(一)类FileUtils--实现把资源放在Resources文件目录下达到多平台的引用 》中我对平台编译的分析。以防篇幅过长,只截取了重要部分,如需详解,可以直接查看源码。

代码3:

[cpp] view plaincopy

  1. int Application::run()
  2. {
  3. ...
  4. director->mainLoop();
  5. ...
  6. }

从代码3中,它明显的启发着我们要继续追寻Director::mainLoop()函数。在Director中mainLoop()为纯函数,此子类DisplayLinkDirector才有其实现,如代码4。

代码4:

[cpp] view plaincopy

  1. void DisplayLinkDirector::mainLoop()
  2. {
  3. <span><span class="comment">//只有一种情况会调用到这里来,就是导演类调用end函数</span><span>  </span></span>
  4. if (_purgeDirectorInNextLoop)
  5. {
  6. _purgeDirectorInNextLoop = false;
  7. <span><span class="comment">//清除导演类</span><span></span></span>
  8. purgeDirector();
  9. }
  10. else if (! _invalid)
  11. {   <span><span class="comment">//绘制</span><span> </span></span>
  12. drawScene();
  13. //清除当前内存池中对象,即池中每一个对象--_referenceCount
  14. PoolManager::getInstance()->getCurrentPool()->clear();
  15. }
  16. }

mainLoop是主线程调用的循环,其中drawScene()是绘制函数,接着我们继续追寻它的代码,如代码5。

代码5:

[cpp] view plaincopy

  1. void Director::drawScene()
  2. {
  3. <span><span class="comment">//计算间隔时间</span><span> </span></span>
  4. calculateDeltaTime();
  5. //忽略该帧如果时间间隔接近0
  6. if(_deltaTime < FLT_EPSILON)
  7. {
  8. return;
  9. }
  10. if (_openGLView)
  11. {
  12. _openGLView->pollInputEvents();
  13. }
  14. //tick before glClear: issue #533
  15. if (! _paused)
  16. {
  17. _scheduler->update(_deltaTime);
  18. _eventDispatcher->dispatchEvent(_eventAfterUpdate);
  19. }
  20. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  21. /* to avoid flickr, nextScene MUST be here: after tick and before draw.
  22. XXX: Which bug is this one. It seems that it can‘t be reproduced with v0.9 */
  23. if (_nextScene)
  24. {
  25. setNextScene();
  26. }
  27. pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  28. // draw the scene
  29. if (_runningScene)
  30. {
  31. _runningScene->visit(_renderer, Mat4::IDENTITY, false);
  32. _eventDispatcher->dispatchEvent(_eventAfterVisit);
  33. }
  34. // draw the notifications node
  35. if (_notificationNode)
  36. {
  37. _notificationNode->visit(_renderer, Mat4::IDENTITY, false);
  38. }
  39. if (_displayStats)
  40. {
  41. showStats();
  42. }
  43. _renderer->render();
  44. _eventDispatcher->dispatchEvent(_eventAfterDraw);
  45. popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  46. _totalFrames++;
  47. // swap buffers
  48. if (_openGLView)
  49. {
  50. _openGLView->swapBuffers();
  51. }
  52. if (_displayStats)
  53. {
  54. calculateMPF();
  55. }
  56. }

从代码5中,我们看见visit()和render()函数的调用。其中visit()函数会调用draw()函数来向RenderQueue中添加RenderCommand,那么就继续追寻visit()的代码,如代码6。

代码6:

[cpp] view plaincopy

  1. void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
  2. {
  3. // quick return if not visible. children won‘t be drawn.
  4. if (!_visible)
  5. {
  6. return;
  7. }
  8. uint32_t flags = processParentFlags(parentTransform, parentFlags);
  9. // IMPORTANT:
  10. // To ease the migration to v3.0, we still support the Mat4 stack,
  11. // but it is deprecated and your code should not rely on it
  12. Director* director = Director::getInstance();
  13. director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  14. director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
  15. int i = 0;
  16. if(!_children.empty())
  17. {
  18. sortAllChildren();
  19. // draw children zOrder < 0
  20. for( ; i < _children.size(); i++ )
  21. {
  22. auto node = _children.at(i);
  23. if ( node && node->_localZOrder < 0 )
  24. node->visit(renderer, _modelViewTransform, flags);
  25. else
  26. break;
  27. }
  28. // self draw
  29. this->draw(renderer, _modelViewTransform, flags);
  30. for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
  31. (*it)->visit(renderer, _modelViewTransform, flags);
  32. }
  33. else
  34. {
  35. this->draw(renderer, _modelViewTransform, flags);
  36. }
  37. director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  38. // FIX ME: Why need to set _orderOfArrival to 0??
  39. // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920
  40. // reset for next frame
  41. // _orderOfArrival = 0;
  42. }

从代码6中,我们可以看到“ auto node = _children.at(i);和node->visit(renderer, _modelViewTransform, flags);”,这段代码的意思是先获取子节点,然后递归调用节点的visit()函数,到了没有子节点的节点,开始调用draw()函数。那么我们看看draw()函数代码,如代码7。

代码7:

[cpp] view plaincopy

  1. void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags)
  2. {
  3. }

好吧,从代码7中,我们看到Node的draw什么都没有做,是我们找错地方?原来draw()是虚函数,所以它执行时执行的是该字节类的draw()函数。确实是我们找错地方了。那么我们分别看DrawNode::draw()、Sprite::draw()。

代码8:

[cpp] view plaincopy

  1. void DrawNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
  2. {
  3. _customCommand.init(_globalZOrder);
  4. _customCommand.func = CC_CALLBACK_0(DrawNode::onDraw, this, transform, flags);
  5. renderer->addCommand(&_customCommand);
  6. }
  7. void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
  8. {
  9. // Don‘t do calculate the culling if the transform was not updated
  10. _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
  11. if(_insideBounds)
  12. {
  13. _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
  14. renderer->addCommand(&_quadCommand);
  15. #if CC_SPRITE_DEBUG_DRAW
  16. _customDebugDrawCommand.init(_globalZOrder);
  17. _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
  18. renderer->addCommand(&_customDebugDrawCommand);
  19. #endif //CC_SPRITE_DEBUG_DRAW
  20. }
  21. }

从代码8中,我们可以看到在draw()函数向RenderQueue中添加RenderCommand,当然有的类的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接进行渲染,或者做一些其他的事情。

那么当draw()都递归调用完了,我们来看看最后进行渲染的Renderer::render() 函数,如代码9。

代码9:

[cpp] view plaincopy

  1. void Renderer::render()
  2. {
  3. //Uncomment this once everything is rendered by new renderer
  4. //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  5. //TODO setup camera or MVP
  6. _isRendering = true;
  7. if (_glViewAssigned)
  8. {
  9. // cleanup
  10. _drawnBatches = _drawnVertices = 0;
  11. //Process render commands
  12. //1. Sort render commands based on ID
  13. for (auto &renderqueue : _renderGroups)
  14. {
  15. renderqueue.sort();
  16. }
  17. visitRenderQueue(_renderGroups[0]);
  18. flush();
  19. }
  20. clean();
  21. _isRendering = false;
  22. }

从代码9中,我们看到“renderqueue.sort()",这是之前所说的对命令先排序,然后才进行渲染,“visitRenderQueue( _renderGroups[0])”就是来进行渲染的。那么我们接着看看void Renderer::visitRenderQueue(const RenderQueue& queue)的代码,如代码10。

代码10:

[cpp] view plaincopy

  1. void Renderer::visitRenderQueue(const RenderQueue& queue)
  2. {
  3. ssize_t size = queue.size();
  4. for (ssize_t index = 0; index < size; ++index)
  5. {
  6. auto command = queue[index];
  7. auto commandType = command->getType();
  8. if(RenderCommand::Type::QUAD_COMMAND == commandType)
  9. {
  10. flush3D();
  11. auto cmd = static_cast<QuadCommand*>(command);
  12. //Batch quads
  13. if(_numQuads + cmd->getQuadCount() > VBO_SIZE)
  14. {
  15. CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() < VBO_SIZE, "VBO is not big enough for quad data, please break the quad data down or use customized render command");
  16. //Draw batched quads if VBO is full
  17. drawBatchedQuads();
  18. }
  19. _batchedQuadCommands.push_back(cmd);
  20. memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
  21. convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView());
  22. _numQuads += cmd->getQuadCount();
  23. }
  24. else if(RenderCommand::Type::GROUP_COMMAND == commandType)
  25. {
  26. flush();
  27. int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
  28. visitRenderQueue(_renderGroups[renderQueueID]);
  29. }
  30. else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
  31. {
  32. flush();
  33. auto cmd = static_cast<CustomCommand*>(command);
  34. cmd->execute();
  35. }
  36. else if(RenderCommand::Type::BATCH_COMMAND == commandType)
  37. {
  38. flush();
  39. auto cmd = static_cast<BatchCommand*>(command);
  40. cmd->execute();
  41. }
  42. else if (RenderCommand::Type::MESH_COMMAND == commandType)
  43. {
  44. flush2D();
  45. auto cmd = static_cast<MeshCommand*>(command);
  46. if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
  47. {
  48. flush3D();
  49. cmd->preBatchDraw();
  50. cmd->batchDraw();
  51. _lastBatchedMeshCommand = cmd;
  52. }
  53. else
  54. {
  55. cmd->batchDraw();
  56. }
  57. }
  58. else
  59. {
  60. CCLOGERROR("Unknown commands in renderQueue");
  61. }
  62. }
  63. }

从代码10中,我们看到RenderCommand类型有QUAD_COMMAND,CUSTOM_COMMAND,BATCH_COMMAND,

GROUP_COMMAND,MESH_COMMAND五种,这些类型的讲解在下一节。

从代码10中,好像没有与OpenGL相关的代码,有点囧。其实这OpenGL的API调用是在Renderer::drawBatched

Quads()、BatchCommand::execute()中。在代码10中,我们也看到在QUAD_COMMAND类型中调用了drawBatchedQuads(),如代码11。在CUSTOM_COMMAND中调用了CustomCommand::execute(),如代码12。在BATCH_COMMAND中调用了BatchCommand::execute(),如代码13。在MESH_COMMAND类型中调用了MeshCommand::preBatchDraw()和MeshCommand::batchDraw()。至于GROUP_COMMAND类型,就递归它组里的成员。

代码11:

[cpp] view plaincopy

  1. void Renderer::drawBatchedQuads()
  2. {
  3. //TODO we can improve the draw performance by insert material switching command before hand.
  4. int quadsToDraw = 0;
  5. int startQuad = 0;
  6. //Upload buffer to VBO
  7. if(_numQuads <= 0 || _batchedQuadCommands.empty())
  8. {
  9. return;
  10. }
  11. if (Configuration::getInstance()->supportsShareableVAO())
  12. {
  13. //Set VBO data
  14. glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
  15. // option 1: subdata
  16. //        glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] );
  17. // option 2: data
  18. //        glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW);
  19. // option 3: orphaning + glMapBuffer
  20. glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
  21. void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
  22. memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads));
  23. glUnmapBuffer(GL_ARRAY_BUFFER);
  24. glBindBuffer(GL_ARRAY_BUFFER, 0);
  25. //Bind VAO
  26. GL::bindVAO(_quadVAO);
  27. }
  28. else
  29. {
  30. #define kQuadSize sizeof(_quads[0].bl)
  31. glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
  32. glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, GL_DYNAMIC_DRAW);
  33. GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
  34. // vertices
  35. glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));
  36. // colors
  37. glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));
  38. // tex coords
  39. glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));
  40. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
  41. }
  42. //Start drawing verties in batch
  43. for(const auto& cmd : _batchedQuadCommands)
  44. {
  45. auto newMaterialID = cmd->getMaterialID();
  46. if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH)
  47. {
  48. //Draw quads
  49. if(quadsToDraw > 0)
  50. {
  51. glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
  52. _drawnBatches++;
  53. _drawnVertices += quadsToDraw*6;
  54. startQuad += quadsToDraw;
  55. quadsToDraw = 0;
  56. }
  57. //Use new material
  58. cmd->useMaterial();
  59. _lastMaterialID = newMaterialID;
  60. }
  61. quadsToDraw += cmd->getQuadCount();
  62. }
  63. //Draw any remaining quad
  64. if(quadsToDraw > 0)
  65. {
  66. glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
  67. _drawnBatches++;
  68. _drawnVertices += quadsToDraw*6;
  69. }
  70. if (Configuration::getInstance()->supportsShareableVAO())
  71. {
  72. //Unbind VAO
  73. GL::bindVAO(0);
  74. }
  75. else
  76. {
  77. glBindBuffer(GL_ARRAY_BUFFER, 0);
  78. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  79. }
  80. _batchedQuadCommands.clear();
  81. _numQuads = 0;

代码12:

[cpp] view plaincopy

  1. void CustomCommand::execute()
  2. {
  3. if(func)
  4. {
  5. func();
  6. }
  7. }

代码13:

[cpp] view plaincopy

  1. void BatchCommand::execute()
  2. {
  3. // Set material
  4. _shader->use();
  5. _shader->setUniformsForBuiltins(_mv);
  6. GL::bindTexture2D(_textureID);
  7. GL::blendFunc(_blendType.src, _blendType.dst);
  8. // Draw
  9. _textureAtlas->drawQuads();
  10. }

从代码11、代码12、代码13中,我们都看到了这些函数中对OpenGl的API调用来进行渲染。其中特别提醒一下,在CustomCommand::execute()中直接调用的函数是我们设置的回调函数。在这个函数中,我们可以自己使用OpenGL的API进行图形的渲染。这就在第三节中讲如何在Cocos2d-x中自己设置渲染功能中向_customCommand添加的函数。在这里我先给出简便的方式,_customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this)。

以上就是把一个完整的渲染的流程都梳理了一片,下面我给出了流程图,如图3。

图3:

第二,RenderCommand的类型。

这里的类型讲解主要参考这篇文章中关于RenderComman的类型讲解。

    QUAD_COMMAND:QuadCommand类绘制精灵等。
   所有绘制图片的命令都会调用到这里,处理这个类型命令的代码就是绘制贴图的openGL代码,下一篇文章会详细介绍这部分代码。 
  CUSTOM_COMMAND:CustomCommand类自定义绘制,自己定义绘制函数,在调用绘制时只需调用已经传进来的回调函数就可以,裁剪节点,绘制图形节点都采用这个绘制,把绘制函数定义在自己的类里。这种类型的绘制命令不会在处理命令的时候调用任何一句openGL代码,而是调用你写好并设置给func的绘制函数,后续文章会介绍引擎中的所有自定义绘制,并自己实现一个自定义的绘制。
  BATCH_COMMAND:BatchCommand类批处理绘制,批处理精灵和粒子
  其实它类似于自定义绘制,也不会再render函数中出现任何一句openGL函数,它调用一个固定的函数,这个函数会在下一篇文章中介绍。
  GROUP_COMMAND:GroupCommand类绘制组,一个节点包括两个以上绘制命令的时候,把这个绘制命令存储到另外一个_renderGroups中的元素中,并把这个元素的指针作为一个节点存储到_renderGroups[0]中。

第三,如何在Cocos2d-x中自己设置渲染功能。

1.第一种方法针对的是整个图层的渲染。

重写visit()函数,并且在visit()函数中直接向CommandQueue添加CustomCommand,设置好回调函数,这个比较直接,如代码14,代码14是子龙山人《基于Cocos2d-x学习OpenGL ES 2.0》第一篇中的部分代码。或者重写draw()函数,并且在draw()函数中向CommandQueue添加CustomCommand,设置好回调函数,这个就比较按照正规的流程走。

代码14:

[cpp] view plaincopy

  1. void HelloWorld::visit(cocos2d::Renderer *renderer, const Mat4 &transform, bool transformUpdated)
  2. {
  3. Layer::draw(renderer, transform, transformUpdated);
  4. //send custom command to tell the renderer to call opengl commands
  5. _customCommand.init(_globalZOrder);
  6. _customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this);
  7. renderer->addCommand(&_customCommand);
  8. }
  9. void HelloWorld::onDraw()
  10. {
  11. //question1: why the triangle goes to the up side
  12. //如果使用对等矩阵,则三角形绘制会在最前面
  13. Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  14. Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  15. Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  16. Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  17. auto glProgram = getGLProgram();
  18. glProgram->use();
  19. //set uniform values, the order of the line is very important
  20. glProgram->setUniformsForBuiltins();
  21. auto size = Director::getInstance()->getWinSize();
  22. //use vao
  23. glBindVertexArray(vao);
  24. GLuint uColorLocation = glGetUniformLocation(glProgram->getProgram(), "u_color");
  25. float uColor[] = {1.0, 1.0, 1.0, 1.0};
  26. glUniform4fv(uColorLocation,1, uColor);
  27. //  glDrawArrays(GL_TRIANGLES, 0, 6);
  28. glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE,(GLvoid*)0);
  29. glBindVertexArray(0);
  30. CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 6);
  31. CHECK_GL_ERROR_DEBUG();
  32. Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
  33. Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
  34. }

从代码14中,我们看到重写visit()函数,在visit()函数中直接向RenderQueue添加RenderCommand,即“renderer->addCommand(&_customCommand);”,由于此RenderCommand类型为CustomCommand,所以要添加处理图形渲染的回调函数,即“_customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this);”,这行代码就是添加回调函数的,onDraw()函数中调用OpengGL的API渲染图形。关于func是如何被调用,可以参考上面的代码12上下文的分析。

2.第二种方法针对个别精灵。

有时候,我们只要对个别精灵进行特效的处理,这个精灵需要使用我们自己编写的Shader,而图层其他的元素按默认处理就行了。这时候就需要第二种方法了。设置好Shader,向精灵添加Shader,最后在重写draw函数,在draw函数中进行特效的处理,如代码15,代码15是《捕鱼达人3》教程第二节的代码。

代码15:

[cpp] view plaincopy

  1. bool FishLayer::init()
  2. {
  3. ...省略了不相关的代码。
  4. // 将vsh与fsh装配成一个完整的Shader文件。
  5. auto glprogram = GLProgram::createWithFilenames("UVAnimation.vsh", "UVAnimation.fsh");
  6. // 由Shader文件创建这个Shader
  7. auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(glprogram);
  8. // 给精灵设置所用的Shader
  9. m_Sprite->setGLProgramState(glprogramstate);
  10. //创建海龟所用的贴图。
  11. auto textrue1 = Director::getInstance()->getTextureCache()->addImage("tortoise.png");
  12. //将贴图设置给Shader中的变量值u_texture1
  13. glprogramstate->setUniformTexture("u_texture1", textrue1);
  14. //创建波光贴图。
  15. auto textrue2 = Director::getInstance()->getTextureCache()->addImage("caustics.png");
  16. //将贴图设置给Shader中的变量值u_lightTexture
  17. glprogramstate->setUniformTexture("u_lightTexture", textrue2);
  18. //注意,对于波光贴图,我们希望它在进行UV动画时能产生四方连续效果,必须设置它的纹理UV寻址方式为GL_REPEAT。
  19. Texture2D::TexParams    tRepeatParams;
  20. tRepeatParams.magFilter = GL_LINEAR_MIPMAP_LINEAR;
  21. tRepeatParams.minFilter = GL_LINEAR;
  22. tRepeatParams.wrapS = GL_REPEAT;
  23. tRepeatParams.wrapT = GL_REPEAT;
  24. textrue2->setTexParameters(tRepeatParams);
  25. //在这里,我们设置一个波光的颜色,这里设置为白色。
  26. Vec4  tLightColor(1.0,1.0,1.0,1.0);
  27. glprogramstate->setUniformVec4("v_LightColor",tLightColor);
  28. //下面这一段,是为了将我们自定义的Shader与我们的模型顶点组织方式进行匹配。模型的顶点数据一般包括位置,法线,色彩,纹理,以及骨骼绑定信息。而Shader需要将内部相应的顶点属性通道与模型相应的顶点属性数据进行绑定才能正确显示出顶点。
  29. long offset = 0;
  30. auto attributeCount = m_Sprite->getMesh()->getMeshVertexAttribCount();
  31. for (auto k = 0; k < attributeCount; k++) {
  32. auto meshattribute = m_Sprite->getMesh()->getMeshVertexAttribute(k);
  33. glprogramstate->setVertexAttribPointer(s_attributeNames[meshattribute.vertexAttrib],
  34. meshattribute.size,
  35. meshattribute.type,
  36. GL_FALSE,
  37. m_Sprite->getMesh()->getVertexSizeInBytes(),
  38. (GLvoid*)offset);
  39. offset += meshattribute.attribSizeBytes;
  40. }
  41. //uv滚动初始值设为0
  42. m_LightAni.x = m_LightAni.y = 0;
  43. return true;
  44. }
  45. void FishLayer::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags)
  46. {
  47. if(m_Sprite)
  48. {
  49. //乌龟从右向左移动,移出屏幕后就回到最右边
  50. auto s = Director::getInstance()->getWinSize();
  51. m_Sprite->setPositionX(m_Sprite->getPositionX()-1);
  52. if(m_Sprite->getPositionX() < -100)
  53. {
  54. m_Sprite->setPositionX(s.width + 10);
  55. }
  56. auto glprogramstate = m_Sprite->getGLProgramState();
  57. if(glprogramstate)
  58. {
  59. m_LightAni.x += 0.01;
  60. if(m_LightAni.x > 1.0)
  61. {
  62. m_LightAni.x-= 1.0;
  63. }
  64. m_LightAni.y += 0.01;
  65. if(m_LightAni.y > 1.0)
  66. {
  67. m_LightAni.y-= 1.0;
  68. }
  69. glprogramstate->setUniformVec2("v_animLight",m_LightAni);
  70. }
  71. }
  72. Node::draw(renderer,transform,flags);
  73. }

从代码15中,我们可以看到先使用OpengGL的API创建自己的Shader,然后再把m_sprite的Shader设置为自己的Shader即“m_Sprite->setGLProgramState(glprogramstate);”,这是给精灵设置所用的Shader,这就是针对个别的精灵,而不是整个图层。接着在draw()中,如果精灵已生成,每次调用draw()函数都改变Shader中参数,以达到特别的效果。
    以上都是我通过阅读别人的代码总结的方法,不知道还有没有其他的在Cocos2d-x中自己设置渲染功能的方法,如果有的话,请告诉我,直接在我的博客留言就可以了。

参考资料:

1.http://cn.cocos2d-x.org/article/index?type=wiki&url=/doc/cocos-docs-master/manual/framework/native   /wiki/renderer/zh.md

2.http://cocos2d-x.org/wiki/Cocos2d_v30_renderer_pipeline_roadmap

3.http://cn.cocos2d-x.org/tutorial/show?id=1336

4.http://blog.csdn.net/bill_man/article/details/35839499

5.《Cocos2d-x高级开发教程》2.1.4节

6.Cocos2d-x3.2和Cocos2d-x2.0.4源码

如需转载,请标明出处:http://blog.csdn.net/cbbbc/article/details/39449945

时间: 2024-11-10 00:20:33

Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程的相关文章

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译)

cocos2d 2.x在opengl es 2.0 下自定义着色器来创建特别酷的特效(译) (2012-12-24 13:22:17) 转载▼ 标签: it cocos2d opengl 着色器 渲染 翻译:弹涂鱼 PS:欢迎加入开发群:285275050 本文翻译自:http://www.raywenderlich.com/10862/how-to-create-cool-effects-with-custom-shaders-in-opengl-es-2-0-and-cocos2d-2-x#

OpenGL学习日记-2015.3.13——多实例渲染

实例化(instancing)或者多实例渲染(instancd rendering)是一种连续执行多条相同渲染命令的方法.并且每个命令的所产生的渲染结果都会有轻微的差异.是一种非常有效的,实用少量api调用来渲染大量几何体的方法.OpenGL提供多种机制,允许着色器对不同渲染实例赋予不同的顶点属性. 几个简单的多实例渲染命令: 1.void glDrawArraysInstanced( GLenum mode, GLint first, GLsizei count, GLsizei primCo

关于OpenGL Framebuffer Object、glReadPixels与离屏渲染

最近写论文需要用到离屏渲染(主要是因为模型太大普通窗口绘制根本做不了),于是翻阅了红宝书查了下相关api和用法.中文版的红宝书可读性有点差,很多地方翻译地晦涩,但好歹读起来比较快,主要相关章节为第8章和第10章(可以连带把第9章读完以后写GLSL会顺利成章).貌似superbible可读性更强,但红宝书讲得也差不多了就没再继续看. 由于红宝书过于学术,想动手还是最好查查网上的资料,于是把一些还可以的资料列一下. 关于FBO: OpenGL中的FBO对象(含源码) OpenGL的帧缓冲对象和浮点纹

Ogre 渲染目标解析与多文本合并渲染

实现目标 因为需求,想找一个在Ogre中好用的文本显示,经过查找和一些比对.有三种方案 一利用Overlay的2D显示来达到效果. http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableTextOverlay 二重写Renderable与MovableObject,利用对应字体查找到每个字符元素纹理坐标. http://www.ogre3d.org/tikiwiki/tiki-index.php?page=MovableText 三利

浏览器渲染基本原理(五):优化渲染性能

渲染卡顿是怎么回事? 网页不仅应该被快速加载,同时还应该流畅运行,比如快速响应的交互,如丝般顺滑的动画等. 大多数设备的刷新频率是60次/秒,也就说是浏览器对每一帧画面的渲染工作要在16ms内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验. 为了保证页面的渲染效果,需要充分了解浏览器是如何处理HTML/JavaScript/CSS的. 渲染流程分为几步? JavaScript:JavaScript实现动画效果,DOM元素操作等. Style(计算样式):确定每个DOM元素应该应用什

iOS更改tabbar图片渲染 —不让tabbat有蓝色的渲染 并修改文字

方式一 代码实现 这种要写很多代码 ,每个控制器都要写 UIImage *image=[UIImage imageNamed:@"tabBar_friendTrends_click_icon"]; //    不让tabbar底部有渲染的关键代码 image=[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; vc01.tabBarItem.selectedImage=image; 更改tabbar下方的

七、vue中v-for有时候对页面不会重新渲染,数组变化后如何到渲染页面

v-for不能进行双向数据绑定,页面渲染完成后,再次更改v-for遍历的数据,js里面打印的数据看到数据值已经更改,但是页面的数据就是没有渲染,这是为什么呢? vue中v-for和angularjs中的ng-repeat不用 ,它对页面只进行一次渲染.后续如果需要更改数据且显示在页面上就需要想想其他办法啦~~~ 经过多次踩坑发现如下解决办法: 1.将vue引入当前页面,如下图所示: 2.使用Vue.set方法来对数据进行更改及渲染,如下图所示:

小程序echarts数据不改变,或者是一次渲染成功,第二次进入,渲染失败的解决办法

1.引入echarts插件: import * as echarts from '../../ec-canvas/echarts'; 2.data中定义: ecBar: { onInit: initChart }, 3.app.js中定义全局变量: globalData: { userInfo: null, all_date: [] }, 4.onload中,定义一个  all_date ,用来接收数据 5.循环出来的数据,赋值:app.globalData.all_date = all_dat

基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒子系统和其他纹理时会得不到想要的结果,渲染器还存在许多的不足: 1.当渲染许多透明图形时,没有对其进行排序,使得本应透明的图形没有透明. 2.不能对不同的顶点使用不同的状态进行渲染. 渲染器要做的东西很简单,就是 1.传递数据到 GPU 2.设置 OpenGL 状态信息(Alpha测试.模板测试.深

opengl 渲染方式在屏 离屏 CPU

一.概念理解 OpenGL中,GPU屏幕渲染有以下两种方式: On-Screen Rendering 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行. Off-Screen Rendering 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作. 二.离屏渲染的是是非非 相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体现在两个方面: 创建新缓冲区 要想进行离屏渲染,首先要创建一个新的缓冲区. 上下文切换 离屏渲染的整个过程,需要多次切换上下文