最近幾天,我都在學習如何在Cocos2d-x3.2中使用OpenGL來實現對圖形的渲染。在網上也看到了不少好的文章,我在它們的基礎上作了此次的我我的認爲比較完整的總結。當你瞭解了Cocos2d-x3.2中對圖形渲染的流程,你就會以爲要學會寫本身的shader纔是最重要的。node
在2.x中,渲染過程是經過遞歸渲染樹(Rendering tree)這種圖關係來渲染關係圖。遞歸調用visit()函數,而且在visit()函數中調用該節點的draw函數渲染各個節點,此時draw函數的做用是直接調用OpenGL代碼進行圖形的渲染。因爲visit()和draw函數都是虛函數,因此要注意執行時的多態。那麼咱們來看看2.x版本中CCSprite的draw函數,如代碼1。git
代碼1:github
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
53 CC_INCREMENT_GL_DRAWS(1); 54
55 CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw"); 56 }
那麼咱們也看看3.x中Sprite的draw函數,如代碼2。ide
代碼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的對比中,咱們很容易就發現2.x版本中的draw函數直接調用OpengGL代碼進行圖形渲染,而3.x版本中draw的做用是把RenderCommand添加到CommandQueue中,至於這樣作的好處是,實際的渲染API進入其中一個與顯卡直接交流的有獨立線程的RenderQueue。oop
從Cocos2d-x3.0開始,Cocos2d-x引入了新的渲染流程,它不像2.x版本直接在每個node中的draw函數中直接調用OpenGL代碼進行圖形渲染,而是經過各類RenderCommand封裝起來,而後添加到一個CommandQueue隊列裏面去,而如今draw函數的做用就是在此函數中設置好相對應的RenderCommand參數,而後把此RenderCommand添加到CommandQueue中。最後在每一幀結束時調用renderer函數進行渲染,在renderer函數中會根據ID對RenderCommand進行排序,而後才進行渲染。源碼分析
下面咱們來看看圖一、圖2,這兩個圖形象地表現了Cocos2d-x3.x下RenderCommand的封裝與傳遞與及RenderCommand的排序。學習
圖1:動畫
圖2:ui
上面所說的各個方面都有點零碎,下面就對渲染的整個流程來一個從頭至尾的梳理吧。下面是針對3.2版本的,對於2.x版本的梳理不作梳理,由於我用的是3.2版本。
首先,咱們Cocos2d-x的執行是經過Application::run()來開始的,如代碼3,此代碼目錄中在xx\cocos2d\cocos\platform\對應平臺的目錄下,這是與多平臺實現有關的類,關於如何實現多平臺的編譯,你能夠參考《cocos2d-x3.2源碼分析(一)類FileUtils--實現把資源放在Resources文件目錄下達到多平臺的引用 》中我對平臺編譯的分析。以防篇幅過長,只截取了重要部分,如需詳解,能夠直接查看源碼。
代碼3:
1 int Application::run() 2 { 3 ... 4 director->mainLoop(); 5 ... 6 }
從代碼3中,它明顯的啓發着咱們要繼續追尋Director::mainLoop()函數。在Director中mainLoop()爲純函數,此子類DisplayLinkDirector纔有其實現,如代碼4。
代碼4:
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:
1 void Director::drawScene() 2 { 3 <span><span class="comment">//計算間隔時間</span><span> </span></span>
4 calculateDeltaTime(); 5
6 //忽略該幀若是時間間隔接近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()函數的調用。其中visit()函數會調用draw()函數來向RenderQueue中添加RenderCommand,那麼就繼續追尋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);」,這段代碼的意思是先獲取子節點,而後遞歸調用節點的visit()函數,到了沒有子節點的節點,開始調用draw()函數。那麼咱們看看draw()函數代碼,如代碼7。
代碼7:
1 void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags) 2 { 3 }
好吧,從代碼7中,咱們看到Node的draw什麼都沒有作,是咱們找錯地方?原來draw()是虛函數,因此它執行時執行的是該字節類的draw()函數。確實是咱們找錯地方了。那麼咱們分別看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()函數向RenderQueue中添加RenderCommand,固然有的類的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接進行渲染,或者作一些其餘的事情。
那麼當draw()都遞歸調用完了,咱們來看看最後進行渲染的Renderer::render() 函數,如代碼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()",這是以前所說的對命令先排序,而後才進行渲染,「visitRenderQueue( _renderGroups[0])」就是來進行渲染的。那麼咱們接着看看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五種,這些類型的講解在下一節。
從代碼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:
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 _batchedQuadCommands.clear(); 101 _numQuads = 0;
代碼12:
1 void CustomCommand::execute() 2 { 3 if(func) 4 { 5 func(); 6 } 7 }
代碼13:
void BatchCommand::execute() { // Set material
_shader->use(); _shader->setUniformsForBuiltins(_mv); GL::bindTexture2D(_textureID); GL::blendFunc(_blendType.src, _blendType.dst); // Draw
_textureAtlas->drawQuads(); }
從代碼十一、代碼十二、代碼13中,咱們都看到了這些函數中對OpenGl的API調用來進行渲染。其中特別提醒一下,在CustomCommand::execute()中直接調用的函數是咱們設置的回調函數。在這個函數中,咱們能夠本身使用OpenGL的API進行圖形的渲染。這就在第三節中講如何在Cocos2d-x中本身設置渲染功能中向_customCommand添加的函數。在這裏我先給出簡便的方式,_customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this)。
以上就是把一個完整的渲染的流程都梳理了一片,下面我給出了流程圖,如圖3。
圖3:
這裏的類型講解主要參考這篇文章中關於RenderComman的類型講解。
QUAD_COMMAND:QuadCommand類繪製精靈等。
全部繪製圖片的命令都會調用到這裏,處理這個類型命令的代碼就是繪製貼圖的openGL代碼,下一篇文章會詳細介紹這部分代碼。
CUSTOM_COMMAND:CustomCommand類自定義繪製,本身定義繪製函數,在調用繪製時只需調用已經傳進來的回調函數就能夠,裁剪節點,繪製圖形節點都採用這個繪製,把繪製函數定義在本身的類裏。這種類型的繪製命令不會在處理命令的時候調用任何一句openGL代碼,而是調用你寫好並設置給func的繪製函數,後續文章會介紹引擎中的全部自定義繪製,並本身實現一個自定義的繪製。
BATCH_COMMAND:BatchCommand類批處理繪製,批處理精靈和粒子
其實它相似於自定義繪製,也不會再render函數中出現任何一句openGL函數,它調用一個固定的函數,這個函數會在下一篇文章中介紹。
GROUP_COMMAND:GroupCommand類繪製組,一個節點包括兩個以上繪製命令的時候,把這個繪製命令存儲到另一個_renderGroups中的元素中,並把這個元素的指針做爲一個節點存儲到_renderGroups[0]中。
1.第一種方法針對的是整個圖層的渲染。
重寫visit()函數,而且在visit()函數中直接向CommandQueue添加CustomCommand,設置好回調函數,這個比較直接,如代碼14,代碼14是子龍山人《基於Cocos2d-x學習OpenGL ES 2.0》第一篇中的部分代碼。或者重寫draw()函數,而且在draw()函數中向CommandQueue添加CustomCommand,設置好回調函數,這個就比較按照正規的流程走。
代碼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 //若是使用對等矩陣,則三角形繪製會在最前面
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()函數,在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:
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 //建立海龜所用的貼圖。
12 auto textrue1 = Director::getInstance()->getTextureCache()->addImage("tortoise.png"); 13 //將貼圖設置給Shader中的變量值u_texture1
14 glprogramstate->setUniformTexture("u_texture1", textrue1); 15 //建立波光貼圖。
16 auto textrue2 = Director::getInstance()->getTextureCache()->addImage("caustics.png"); 17 //將貼圖設置給Shader中的變量值u_lightTexture
18 glprogramstate->setUniformTexture("u_lightTexture", textrue2); 19
20 //注意,對於波光貼圖,咱們但願它在進行UV動畫時能產生四方連續效果,必須設置它的紋理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 //在這裏,咱們設置一個波光的顏色,這裏設置爲白色。
28 Vec4 tLightColor(1.0,1.0,1.0,1.0); 29 glprogramstate->setUniformVec4("v_LightColor",tLightColor); 30 //下面這一段,是爲了將咱們自定義的Shader與咱們的模型頂點組織方式進行匹配。模型的頂點數據通常包括位置,法線,色彩,紋理,以及骨骼綁定信息。而Shader須要將內部相應的頂點屬性通道與模型相應的頂點屬性數據進行綁定才能正確顯示出頂點。
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滾動初始值設爲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 //烏龜從右向左移動,移出屏幕後就回到最右邊
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建立本身的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源碼