[转贴]Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程
看了opengles有一段時(shí)間了,算是了解了一下下。然后,就在基本要決定還是回歸cocos2dx 3.2的,看了這篇好文章,欣喜轉(zhuǎn)之~?
推薦看原帖:?Cocos2d-x3.2與OpenGL渲染總結(jié)(一)Cocos2d-x3.2的渲染流程
最近幾天,我都在學(xué)習(xí)如何在Cocos2d-x 3.2中使用OpenGL來實(shí)現(xiàn)對(duì)圖形的渲染。在網(wǎng)上也看到了很多好的文章,在這些文章基礎(chǔ)上做了這次的我個(gè)人認(rèn)為比較完整的總結(jié)。當(dāng)你了解了Cocos2d-x 3.2中對(duì)圖形渲染的流程,你就會(huì)覺得要學(xué)會(huì)寫自己的shader才是最重要的。
?
第一、渲染流程從2.x到3.x的變化
在2.x中,渲染過程是通過遞歸渲染樹(Rendering tree)這種圖關(guān)系來渲染關(guān)系圖。遞歸調(diào)用visit()函數(shù),并且在visit()函數(shù)中調(diào)用該節(jié)點(diǎn)的draw函數(shù)渲染各個(gè)節(jié)點(diǎn),此時(shí)draw函數(shù)的作用是直接調(diào)用OpenGL代碼進(jìn)行圖形的渲染。由于visit()和draw函數(shù)都是虛函數(shù),所以要注意執(zhí)行時(shí)的多態(tài)。那么我們來看看2.x版本中CCSprite的draw函數(shù),如代碼1。
代碼1:
1 //這是cocos2d-2.0-x-2.0.4版本的CCSprite的draw函數(shù) 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 53 CC_INCREMENT_GL_DRAWS(1); 54 55 CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw"); 56 }?那么我們也看看3.x中Sprite的draw函數(shù),如代碼2
? ? ? 代碼2:
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的對(duì)比中,我們很容易就發(fā)現(xiàn)2.x版本中的draw函數(shù)直接調(diào)用OpengGL代碼進(jìn)行圖形渲染,而3.x版本中draw的作用是把RenderCommand添加到CommandQueue中,至于這樣做的好處是,實(shí)際的渲染API進(jìn)入其中一個(gè)與顯卡直接交流的有獨(dú)立線程的RenderQueue。
??? 從Cocos2d-x3.0開始,Cocos2d-x引入了新的渲染流程,它不像2.x版本直接在每一個(gè)node中的draw函數(shù)中直接調(diào)用OpenGL代碼進(jìn)行圖形渲染,而是通過各種RenderCommand封裝起來,然后添加到一個(gè)CommandQueue隊(duì)列里面去,而現(xiàn)在draw函數(shù)的作用就是在此函數(shù)中設(shè)置好相對(duì)應(yīng)的RenderCommand參數(shù),然后把此RenderCommand添加到CommandQueue中。最后在每一幀結(jié)束時(shí)調(diào)用renderer函數(shù)進(jìn)行渲染,在renderer函數(shù)中會(huì)根據(jù)ID對(duì)RenderCommand進(jìn)行排序,然后才進(jìn)行渲染。
?? 下面我們來看看圖1、圖2,這兩個(gè)圖形象地表現(xiàn)了Cocos2d-x3.x下RenderCommand的封裝與傳遞與及RenderCommand的排序。
??? 圖1:
??????
?
? ? 圖2:
???????
?????上面所說的各個(gè)方面都有點(diǎn)零碎,下面就對(duì)渲染的整個(gè)流程來一個(gè)從頭到尾的梳理吧。下面是針對(duì)3.2版本的,對(duì)于2.x版本的梳理不做梳理,因?yàn)槲矣玫氖?.2版本。
首先,我們Cocos2d-x的執(zhí)行是通過Application::run()來開始的,如代碼3,此代碼目錄中在xx\cocos2d\cocos\platform\對(duì)應(yīng)平臺(tái)的目錄下,這是與多平臺(tái)實(shí)現(xiàn)有關(guān)的類,關(guān)于如何實(shí)現(xiàn)多平臺(tái)的編譯,你可以參考《Cocos2d-x3.2源碼分析(一)類FileUtils--實(shí)現(xiàn)把資源放在Resources文件目錄下達(dá)到多平臺(tái)的引用》中我對(duì)平臺(tái)編譯的分析。以防篇幅過長,只截取了重要部分,如需詳解,可以直接查看源碼。
代碼3:
1 int Application::run() 2 { 3 ... 4 director->mainLoop(); 5 ... 6 }從代碼3中,它明顯的啟發(fā)著我們要繼續(xù)追尋Director::mainLoop()函數(shù)。在Director中mainLoop()為純函數(shù),此子類DisplayLinkDirector才有其實(shí)現(xiàn),如代碼4。
代碼4:
1 void DisplayLinkDirector::mainLoop() 2 { 3 //只有一種情況會(huì)調(diào)用到這里來,就是導(dǎo)演類調(diào)用end函數(shù) 4 if (_purgeDirectorInNextLoop) 5 { 6 _purgeDirectorInNextLoop = false; 7 //清除導(dǎo)演類 8 purgeDirector(); 9 } 10 else if (! _invalid) 11 { //繪制 12 drawScene(); 13 //清除當(dāng)前內(nèi)存池中對(duì)象,即池中每一個(gè)對(duì)象--_referenceCount 14 PoolManager::getInstance()->getCurrentPool()->clear(); 15 } 16 }mainLoop是主線程調(diào)用的循環(huán),其中drawScene()是繪制函數(shù),接著我們繼續(xù)追尋它的代碼,如代碼5。
代碼5:
1 void Director::drawScene() 2 { 3 //計(jì)算間隔時(shí)間 4 calculateDeltaTime(); 5 6 //忽略該幀如果時(shí)間間隔接近0 7 if(_deltaTime < FLT_EPSILON) 8 { 9 return; 10 } 11 12 if (_openGLView) 13 { 14 _openGLView->pollInputEvents(); 15 } 16 17 //tick before glClear: issue #533 18 if (! _paused) 19 { 20 _scheduler->update(_deltaTime); 21 _eventDispatcher->dispatchEvent(_eventAfterUpdate); 22 } 23 24 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 25 26 /* to avoid flickr, nextScene MUST be here: after tick and before draw. 27 XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */ 28 if (_nextScene) 29 { 30 setNextScene(); 31 } 32 33 pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 34 35 // draw the scene 36 if (_runningScene) 37 { 38 _runningScene->visit(_renderer, Mat4::IDENTITY, false); 39 _eventDispatcher->dispatchEvent(_eventAfterVisit); 40 } 41 42 // draw the notifications node 43 if (_notificationNode) 44 { 45 _notificationNode->visit(_renderer, Mat4::IDENTITY, false); 46 } 47 48 if (_displayStats) 49 { 50 showStats(); 51 } 52 53 _renderer->render(); 54 _eventDispatcher->dispatchEvent(_eventAfterDraw); 55 56 popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 57 58 _totalFrames++; 59 60 // swap buffers 61 if (_openGLView) 62 { 63 _openGLView->swapBuffers(); 64 } 65 66 if (_displayStats) 67 { 68 calculateMPF(); 69 } 70 }從代碼5中,我們看見visit()和render()函數(shù)的調(diào)用。其中visit()函數(shù)會(huì)調(diào)用draw()函數(shù)來向RenderQueue中添加RenderCommand,那么就繼續(xù)追尋visit()的代碼,如代碼6。
代碼6:
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 9 uint32_t flags = processParentFlags(parentTransform, parentFlags); 10 11 // IMPORTANT: 12 // To ease the migration to v3.0, we still support the Mat4 stack, 13 // but it is deprecated and your code should not rely on it 14 Director* director = Director::getInstance(); 15 director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 16 director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform); 17 int i = 0; 18 if(!_children.empty()) 19 { 20 sortAllChildren(); 21 // draw children zOrder < 0 22 for( ; i < _children.size(); i++ ) 23 { 24 auto node = _children.at(i); 25 26 if ( node && node->_localZOrder < 0 ) 27 node->visit(renderer, _modelViewTransform, flags); 28 else 29 break; 30 } 31 // self draw 32 this->draw(renderer, _modelViewTransform, flags); 33 34 for(auto it=_children.cbegin()+i; it != _children.cend(); ++it) 35 (*it)->visit(renderer, _modelViewTransform, flags); 36 } 37 else 38 { 39 this->draw(renderer, _modelViewTransform, flags); 40 } 41 42 director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 43 44 // FIX ME: Why need to set _orderOfArrival to 0?? 45 // Please refer to https://github.com/cocos2d/cocos2d-x/pull/6920 46 // reset for next frame 47 // _orderOfArrival = 0; 48 }從代碼6中,我們可以看到“ auto node = _children.at(i);和node->visit(renderer, _modelViewTransform, flags);”,這段代碼的意思是先獲取子節(jié)點(diǎn),然后遞歸調(diào)用節(jié)點(diǎn)的visit()函數(shù),到了沒有子節(jié)點(diǎn)的節(jié)點(diǎn),開始調(diào)用draw()函數(shù)。那么我們看看draw()函數(shù)代碼,如代碼7。
代碼7:
?1 void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags) ?{ ?}?
好吧,從代碼7中,我們看到Node的draw什么都沒有做,是我們找錯(cuò)地方?原來draw()是虛函數(shù),所以它執(zhí)行時(shí)執(zhí)行的是該字節(jié)類的draw()函數(shù)。確實(shí)是我們找錯(cuò)地方了。那么我們分別看DrawNode::draw()、Sprite::draw()。
代碼8:
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 8 void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) 9 { 10 // Don't do calculate the culling if the transform was not updated 11 _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; 12 13 if(_insideBounds) 14 { 15 _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform); 16 renderer->addCommand(&_quadCommand); 17 #if CC_SPRITE_DEBUG_DRAW 18 _customDebugDrawCommand.init(_globalZOrder); 19 _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this); 20 renderer->addCommand(&_customDebugDrawCommand); 21 #endif //CC_SPRITE_DEBUG_DRAW 22 } 23 }?從代碼8中,我們可以看到在draw()函數(shù)向RenderQueue中添加RenderCommand,當(dāng)然有的類的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接進(jìn)行渲染,或者做一些其他的事情。
??? 那么當(dāng)draw()都遞歸調(diào)用完了,我們來看看最后進(jìn)行渲染的Renderer::render() 函數(shù),如代碼9。
??? 代碼9:
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 6 //TODO setup camera or MVP 7 _isRendering = true; 8 9 if (_glViewAssigned) 10 { 11 // cleanup 12 _drawnBatches = _drawnVertices = 0; 13 14 //Process render commands 15 //1. Sort render commands based on ID 16 for (auto &renderqueue : _renderGroups) 17 { 18 renderqueue.sort(); 19 } 20 visitRenderQueue(_renderGroups[0]); 21 flush(); 22 } 23 clean(); 24 _isRendering = false; 25 }從代碼9中,我們看到“renderqueue.sort()",這是之前所說的對(duì)命令先排序,然后才進(jìn)行渲染,“visitRenderQueue( _renderGroups[0])”就是來進(jìn)行渲染的。那么我們接著看看void Renderer::visitRenderQueue(const RenderQueue& queue)的代碼,如代碼10。
代碼10:
1 void Renderer::visitRenderQueue(const RenderQueue& queue) 2 { 3 ssize_t size = queue.size(); 4 5 for (ssize_t index = 0; index < size; ++index) 6 { 7 auto command = queue[index]; 8 auto commandType = command->getType(); 9 if(RenderCommand::Type::QUAD_COMMAND == commandType) 10 { 11 flush3D(); 12 auto cmd = static_cast<QuadCommand*>(command); 13 //Batch quads 14 if(_numQuads + cmd->getQuadCount() > VBO_SIZE) 15 { 16 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"); 17 18 //Draw batched quads if VBO is full 19 drawBatchedQuads(); 20 } 21 22 _batchedQuadCommands.push_back(cmd); 23 24 memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount()); 25 convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView()); 26 27 _numQuads += cmd->getQuadCount(); 28 29 } 30 else if(RenderCommand::Type::GROUP_COMMAND == commandType) 31 { 32 flush(); 33 int renderQueueID = ((GroupCommand*) command)->getRenderQueueID(); 34 visitRenderQueue(_renderGroups[renderQueueID]); 35 } 36 else if(RenderCommand::Type::CUSTOM_COMMAND == commandType) 37 { 38 flush(); 39 auto cmd = static_cast<CustomCommand*>(command); 40 cmd->execute(); 41 } 42 else if(RenderCommand::Type::BATCH_COMMAND == commandType) 43 { 44 flush(); 45 auto cmd = static_cast<BatchCommand*>(command); 46 cmd->execute(); 47 } 48 else if (RenderCommand::Type::MESH_COMMAND == commandType) 49 { 50 flush2D(); 51 auto cmd = static_cast<MeshCommand*>(command); 52 if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID()) 53 { 54 flush3D(); 55 cmd->preBatchDraw(); 56 cmd->batchDraw(); 57 _lastBatchedMeshCommand = cmd; 58 } 59 else 60 { 61 cmd->batchDraw(); 62 } 63 } 64 else 65 { 66 CCLOGERROR("Unknown commands in renderQueue"); 67 } 68 } 69 }從代碼10中,我們看到RenderCommand類型有QUAD_COMMAND,CUSTOM_COMMAND,BATCH_COMMAND,GROUP_COMMAND,MESH_COMMAND五種,這些類型的講解在下一節(jié)。
從代碼10中,好像沒有與OpenGL相關(guān)的代碼,有點(diǎn)囧。其實(shí)這OpenGL的API調(diào)用是在Renderer::drawBatchedQuads()、BatchCommand::execute()中。在代碼10中,我們也看到在QUAD_COMMAND類型中調(diào)用了drawBatchedQuads(),如代碼11。在CUSTOM_COMMAND中調(diào)用了CustomCommand::execute(),如代碼12。在BATCH_COMMAND中調(diào)用了BatchCommand::execute(),如代碼13。在MESH_COMMAND類型中調(diào)用了MeshCommand::preBatchDraw()和MeshCommand::batchDraw()。至于GROUP_COMMAND類型,就遞歸它組里的成員。
代碼11:
1 void Renderer::drawBatchedQuads() 2 { 3 //TODO we can improve the draw performance by insert material switching command before hand. 4 5 int quadsToDraw = 0; 6 int startQuad = 0; 7 8 //Upload buffer to VBO 9 if(_numQuads <= 0 || _batchedQuadCommands.empty()) 10 { 11 return; 12 } 13 14 if (Configuration::getInstance()->supportsShareableVAO()) 15 { 16 //Set VBO data 17 glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]); 18 19 // option 1: subdata 20 // glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] ); 21 22 // option 2: data 23 // glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW); 24 25 // option 3: orphaning + glMapBuffer 26 glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW); 27 void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); 28 memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads)); 29 glUnmapBuffer(GL_ARRAY_BUFFER); 30 31 glBindBuffer(GL_ARRAY_BUFFER, 0); 32 33 //Bind VAO 34 GL::bindVAO(_quadVAO); 35 } 36 else 37 { 38 #define kQuadSize sizeof(_quads[0].bl) 39 glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]); 40 41 glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, GL_DYNAMIC_DRAW); 42 43 GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX); 44 45 // vertices 46 glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices)); 47 48 // colors 49 glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors)); 50 51 // tex coords 52 glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords)); 53 54 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]); 55 } 56 57 //Start drawing verties in batch 58 for(const auto& cmd : _batchedQuadCommands) 59 { 60 auto newMaterialID = cmd->getMaterialID(); 61 if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH) 62 { 63 //Draw quads 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 startQuad += quadsToDraw; 71 quadsToDraw = 0; 72 } 73 74 //Use new material 75 cmd->useMaterial(); 76 _lastMaterialID = newMaterialID; 77 } 78 79 quadsToDraw += cmd->getQuadCount(); 80 } 81 82 //Draw any remaining quad 83 if(quadsToDraw > 0) 84 { 85 glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) ); 86 _drawnBatches++; 87 _drawnVertices += quadsToDraw*6; 88 } 89 90 if (Configuration::getInstance()->supportsShareableVAO()) 91 { 92 //Unbind VAO 93 GL::bindVAO(0); 94 } 95 else 96 { 97 glBindBuffer(GL_ARRAY_BUFFER, 0); 98 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); 99 } 100 101 _batchedQuadCommands.clear(); 102 _numQuads = 0; 103 }代碼12:
1 void CustomCommand::execute() 2 { 3 if(func) 4 { 5 func(); 6 } 7 }代碼13:
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 9 // Draw 10 _textureAtlas->drawQuads(); 11 }從代碼11、代碼12、代碼13中,我們都看到了這些函數(shù)中對(duì)OpenGl的API調(diào)用來進(jìn)行渲染。其中特別提醒一下,在CustomCommand::execute()中直接調(diào)用的函數(shù)是我們?cè)O(shè)置的回調(diào)函數(shù)。在這個(gè)函數(shù)中,我們可以自己使用OpenGL的API進(jìn)行圖形的渲染。這就在第三節(jié)中講如何在Cocos2d-x中自己設(shè)置渲染功能中向_customCommand添加的函數(shù)。在這里我先給出簡便的方式,_customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this)。
?
以上就是把一個(gè)完整的渲染的流程都梳理了一片,下面我給出了流程圖,如圖3。
圖3:
第二、RenderCommand的類型
這里的類型講解主要參考這篇文章中關(guān)于RenderComman的類型講解。
-
QUAD_COMMAND:QuadCommand類繪制精靈等。所有繪制圖片的命令都會(huì)調(diào)用到這里,處理這個(gè)類型命令的代碼就是繪制貼圖的openGL代碼,下一篇文章會(huì)詳細(xì)介紹這部分代碼。
-
CUSTOM_COMMAND:CustomCommand類自定義繪制,自己定義繪制函數(shù),在調(diào)用繪制時(shí)只需調(diào)用已經(jīng)傳進(jìn)來的回調(diào)函數(shù)就可以,裁剪節(jié)點(diǎn),繪制圖形節(jié)點(diǎn)都采用這個(gè)繪制,把繪制函數(shù)定義在自己的類里。這種類型的繪制命令不會(huì)在處理命令的時(shí)候調(diào)用任何一句openGL代碼,而是調(diào)用你寫好并設(shè)置給func的繪制函數(shù),后續(xù)文章會(huì)介紹引擎中的所有自定義繪制,并自己實(shí)現(xiàn)一個(gè)自定義的繪制。
-
BATCH_COMMAND:BatchCommand類批處理繪制,批處理精靈和粒子。其實(shí)它類似于自定義繪制,也不會(huì)再render函數(shù)中出現(xiàn)任何一句openGL函數(shù),它調(diào)用一個(gè)固定的函數(shù),這個(gè)函數(shù)會(huì)在下一篇文章中介紹。
-
GROUP_COMMAND:GroupCommand類繪制組,一個(gè)節(jié)點(diǎn)包括兩個(gè)以上繪制命令的時(shí)候,把這個(gè)繪制命令存儲(chǔ)到另外一個(gè)_renderGroups中的元素中,并把這個(gè)元素的指針作為一個(gè)節(jié)點(diǎn)存儲(chǔ)到_renderGroups[0]中。
?
第三、如何在Cocos2d-x中自己設(shè)置渲染功能
1.第一種方法針對(duì)的是整個(gè)圖層的渲染
重寫visit()函數(shù),并且在visit()函數(shù)中直接向CommandQueue添加CustomCommand,設(shè)置好回調(diào)函數(shù),這個(gè)比較直接,如代碼14,代碼14是子龍山人基于Cocos2d-x學(xué)習(xí)OpenGL ES 2.0系列文章的第一篇中的部分代碼。或者重寫draw()函數(shù),并且在draw()函數(shù)中向CommandQueue添加CustomCommand,設(shè)置好回調(diào)函數(shù),這個(gè)就比較按照正規(guī)的流程走。
代碼14:
1 void HelloWorld::visit(cocos2d::Renderer *renderer, const Mat4 &transform, bool transformUpdated) 2 { 3 Layer::draw(renderer, transform, transformUpdated); 4 5 //send custom command to tell the renderer to call opengl commands 6 _customCommand.init(_globalZOrder); 7 _customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this); 8 renderer->addCommand(&_customCommand); 9 10 11 } 12 void HelloWorld::onDraw() 13 { 14 //question1: why the triangle goes to the up side 15 //如果使用對(duì)等矩陣,則三角形繪制會(huì)在最前面 16 Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 17 Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 18 Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); 19 Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); 20 21 auto glProgram = getGLProgram(); 22 23 glProgram->use(); 24 25 //set uniform values, the order of the line is very important 26 glProgram->setUniformsForBuiltins(); 27 auto size = Director::getInstance()->getWinSize(); 28 29 //use vao 30 glBindVertexArray(vao); 31 32 GLuint uColorLocation = glGetUniformLocation(glProgram->getProgram(), "u_color"); 33 34 float uColor[] = {1.0, 1.0, 1.0, 1.0}; 35 glUniform4fv(uColorLocation,1, uColor); 36 // glDrawArrays(GL_TRIANGLES, 0, 6); 37 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE,(GLvoid*)0); 38 glBindVertexArray(0); 39 CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 6); 40 CHECK_GL_ERROR_DEBUG(); 41 Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); 42 Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); 43 44 }從代碼14中,我們看到重寫visit()函數(shù),在visit()函數(shù)中直接向RenderQueue添加RenderCommand,即“renderer->addCommand(&_customCommand);”,由于此RenderCommand類型為CustomCommand,所以要添加處理圖形渲染的回調(diào)函數(shù),即“_customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this);”,這行代碼就是添加回調(diào)函數(shù)的,onDraw()函數(shù)中調(diào)用OpengGL的API渲染圖形。關(guān)于func是如何被調(diào)用,可以參考上面的代碼12上下文的分析。
?
2.第二種方法針對(duì)個(gè)別精靈
有時(shí)候,我們只要對(duì)個(gè)別精靈進(jìn)行特效的處理,這個(gè)精靈需要使用我們自己編寫的Shader,而圖層其他的元素按默認(rèn)處理就行了。這時(shí)候就需要第二種方法了。設(shè)置好Shader,向精靈添加Shader,最后在重寫draw函數(shù),在draw函數(shù)中進(jìn)行特效的處理,如代碼15,代碼15是《捕魚達(dá)人3》教程第二節(jié)的代碼。
代碼15:
1 bool FishLayer::init() 2 { 3 ...省略了不相關(guān)的代碼。 4 // 將vsh與fsh裝配成一個(gè)完整的Shader文件。 5 auto glprogram = GLProgram::createWithFilenames("UVAnimation.vsh", "UVAnimation.fsh"); 6 // 由Shader文件創(chuàng)建這個(gè)Shader 7 auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(glprogram); 8 // 給精靈設(shè)置所用的Shader 9 m_Sprite->setGLProgramState(glprogramstate); 10 11 //創(chuàng)建海龜所用的貼圖。 12 auto textrue1 = Director::getInstance()->getTextureCache()->addImage("tortoise.png"); 13 //將貼圖設(shè)置給Shader中的變量值u_texture1 14 glprogramstate->setUniformTexture("u_texture1", textrue1); 15 //創(chuàng)建波光貼圖。 16 auto textrue2 = Director::getInstance()->getTextureCache()->addImage("caustics.png"); 17 //將貼圖設(shè)置給Shader中的變量值u_lightTexture 18 glprogramstate->setUniformTexture("u_lightTexture", textrue2); 19 20 //注意,對(duì)于波光貼圖,我們希望它在進(jìn)行UV動(dòng)畫時(shí)能產(chǎn)生四方連續(xù)效果,必須設(shè)置它的紋理UV尋址方式為GL_REPEAT。 21 Texture2D::TexParams tRepeatParams; 22 tRepeatParams.magFilter = GL_LINEAR_MIPMAP_LINEAR; 23 tRepeatParams.minFilter = GL_LINEAR; 24 tRepeatParams.wrapS = GL_REPEAT; 25 tRepeatParams.wrapT = GL_REPEAT; 26 textrue2->setTexParameters(tRepeatParams); 27 //在這里,我們?cè)O(shè)置一個(gè)波光的顏色,這里設(shè)置為白色。 28 Vec4 tLightColor(1.0,1.0,1.0,1.0); 29 glprogramstate->setUniformVec4("v_LightColor",tLightColor); 30 //下面這一段,是為了將我們自定義的Shader與我們的模型頂點(diǎn)組織方式進(jìn)行匹配。模型的頂點(diǎn)數(shù)據(jù)一般包括位置,法線,色彩,紋理,以及骨骼綁定信息。而Shader需要將內(nèi)部相應(yīng)的頂點(diǎn)屬性通道與模型相應(yīng)的頂點(diǎn)屬性數(shù)據(jù)進(jìn)行綁定才能正確顯示出頂點(diǎn)。 31 long offset = 0; 32 auto attributeCount = m_Sprite->getMesh()->getMeshVertexAttribCount(); 33 for (auto k = 0; k < attributeCount; k++) { 34 auto meshattribute = m_Sprite->getMesh()->getMeshVertexAttribute(k); 35 glprogramstate->setVertexAttribPointer(s_attributeNames[meshattribute.vertexAttrib], 36 meshattribute.size, 37 meshattribute.type, 38 GL_FALSE, 39 m_Sprite->getMesh()->getVertexSizeInBytes(), 40 (GLvoid*)offset); 41 offset += meshattribute.attribSizeBytes; 42 } 43 44 //uv滾動(dòng)初始值設(shè)為0 45 m_LightAni.x = m_LightAni.y = 0; 46 return true; 47 } 48 49 void FishLayer::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags) 50 { 51 if(m_Sprite) 52 { 53 //烏龜從右向左移動(dòng),移出屏幕后就回到最右邊 54 auto s = Director::getInstance()->getWinSize(); 55 m_Sprite->setPositionX(m_Sprite->getPositionX()-1); 56 if(m_Sprite->getPositionX() < -100) 57 { 58 m_Sprite->setPositionX(s.width + 10); 59 } 60 61 auto glprogramstate = m_Sprite->getGLProgramState(); 62 if(glprogramstate) 63 { 64 m_LightAni.x += 0.01; 65 if(m_LightAni.x > 1.0) 66 { 67 m_LightAni.x-= 1.0; 68 } 69 m_LightAni.y += 0.01; 70 if(m_LightAni.y > 1.0) 71 { 72 m_LightAni.y-= 1.0; 73 } 74 glprogramstate->setUniformVec2("v_animLight",m_LightAni); 75 } 76 } 77 Node::draw(renderer,transform,flags); 78 }從代碼15中,我們可以看到先使用OpengGL的API創(chuàng)建自己的Shader,然后再把m_sprite的Shader設(shè)置為自己的Shader即“m_Sprite->setGLProgramState(glprogramstate);”,這是給精靈設(shè)置所用的Shader,這就是針對(duì)個(gè)別的精靈,而不是整個(gè)圖層。接著在draw()中,如果精靈已生成,每次調(diào)用draw()函數(shù)都改變Shader中參數(shù),以達(dá)到特別的效果。
以上都是我通過閱讀別人的代碼總結(jié)的方法,有其他的在Cocos2d-x中自己設(shè)置渲染功能的方法,可以一起溝通。
來源網(wǎng)址:http://blog.csdn.net/cbbbc/article/details/39449945
?轉(zhuǎn)載于:https://www.cnblogs.com/slysky/p/3988430.html
總結(jié)
以上是生活随笔為你收集整理的[转贴]Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 信用卡还最低还款额会影响信用吗?
- 下一篇: node.js-------使用路由模块