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

看了opengles有一段时间了,算是了解了一下下。然后,就在基本要决定还是回归cocos2dx 3.2的,看了这篇好文章,欣喜转之~ 

推荐看原帖: Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程

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

 

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

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

代码1:

 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

      代码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。

    从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:

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

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

代码5:

 1 void Director::drawScene()
 2 {
 3    //计算间隔时间
 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)  {  } 

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

 

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

图3:

1411351453252003.png

第二、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:

 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    &nbsp;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中自己设置渲染功能的方法,可以一起沟通。

来源网址:http://blog.csdn.net/cbbbc/article/details/39449945

 

转载于:https://www.cnblogs.com/slysky/p/3988430.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259911.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组装与维护实训1,计算机组装与维护实训报告[1]

计算机组装与维护实训报告[1] (12页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;11.90 积分实习报告设计题目&#xff1a; 计算机组装与维护实习 专业班级&#xff1a; 计算机应用103班 学生姓名&a…

node.js-------使用路由模块

路由需要的信息&#xff0c;包括URL 及GET 或 POST参数。路由根据这些参数执行相应的js处理程序&#xff0c;因此&#xff0c;需要在HTTP请求中提取出URL以及GET或POST参数。这些请求参数在request对象中&#xff0c;这个对象是onRequest()回调函数的第一个参数。需要提取这些信…

计算机意外重启或遇错误无法继续,计算机意外地重新启动或遇到错误如何解决?...

电脑小白在重装系统后难免会遇到些问题&#xff0c;有的容易处理&#xff0c;有的会有些棘手。那么&#xff0c;计算机意外地重新启动或遇到错误如何解决?今天快启动小编为大家分享详细的计算机意外地重新启动或遇到错误的解决方法&#xff0c;献给对系统重装知识不太了解的小…

JMeter web 测试

2019独角兽企业重金招聘Python工程师标准>>> JMeter web 测试 http://jmeter.apache.org/usermanual/build-web-test-plan.html 转载于:https://my.oschina.net/276172622/blog/808957

计算机软件记不住设置,想知道电脑密码记不住了怎么办

635509517回答数&#xff1a;23216 | 被采纳数&#xff1a;32017-01-09 17:51:10方法一&#xff1a;(1)启动电脑&#xff0c;使用DOS启动盘(比如&#xff1a;Windows 98启动盘)进入纯DOS状态。(2)在DOS提示符下&#xff0c;根据下面步骤操作&#xff1a;cd\\ (切换到根目录)c…

120xa正反转参数_你知道变频器的“正反转死区时间”吗?它的“停机方式”有几种?...

若你我之间有缘&#xff0c;关注作者又何妨&#xff1f;两情若是久长时&#xff0c;又岂在朝朝暮暮。大家好&#xff01;我是江郎&#xff0c;一个踏踏实实的维修工。本期我们仍然探讨两个问题&#xff0c;如标题所述&#xff0c;#变频器#“死区时间”和“停机方式”&#xff0…

【转】游戏编程中的人工智能技术--神经网络

原文&#xff1a;http://blog.csdn.net/ecitnet/article/details/1799444 游戏编程中的人工智能技术.>. (连载之一)用平常语言介绍神经网络(Neural Networks in Plain English)因为我们没有很好了解大脑&#xff0c;我们经常试图用最新的技术作为一种模型来解释它。在我童年…

w8计算机配置要求,win8系统最低配置要求有哪些|win8系统是否有最低配置要求-系统城...

2013-10-17 17:08:08  浏览量&#xff1a;5753小编这里要为大家带来的是win8系统最低配置要求和部分安装截图&#xff0c;很多用户想要将自己的电脑装上win8&#xff0c;但也不是每一台电脑都可以安装win8系统的&#xff0c;为了避免一些低配置的用户安装了win8之后却无法运行…

财务管理专业应该报计算机二级哪个科目,我是应该报计算机二级还是三级呢

2008-12-01怎样学好财务管理&#xff1f;“五步”学好财务管理:学习这门课程前&#xff0c;首先就不要认为它“很难”&#xff0c;只要相信“难而不会&#xff0c;会而不难”&#xff0c;充满信心一定就能学好。我在学习过程中总结了几条经验&#xff0c;以供各位学友参考&…

java字符串排序_对字符串排序持一种宽容的心态

在Java中一涉及中文处理就会冒出很多问题来&#xff0c;其中排序也是一个让人头疼的课题&#xff0c;我们来看下面的代码&#xff1a;上面的代码定义一个数组&#xff0c;然后进行升序排序&#xff0c;我们期望的结果是按照拼音升序排列&#xff0c;即为李四、王五、张三&#…

nagios 监控配置介绍(二)

#配置服务端监控客户端[rootnagios etc]# cd objects/[rootnagios objects]# vi hosts.cfg# Define a host for the local machinedefine host{use linux-serverhost_name 1.3-sambaalias 1.3-sambaaddress …

spoj SUBLEX (Lexicographical Substring Search) RE的欢迎来看看

SPOJ.com - Problem SUBLEX 这么裸的一个SAM&#xff0c;放在了死破OJ上面就是个坑。 注意用SAM做的时候输出要用一个数组存下来&#xff0c;然后再puts&#xff0c;不然一个一个字符输出会更慢。 还有一个就是不要多数据输入&#xff0c;估计最后多了几个没用的数字&#xff0…

mt4双线macd_3年内从亏损90多万到获利近760万,我只坚持我的:60分钟MACD双回拉战法!附选股公式...

MACD指标被普遍认为是最经典实用的技术指标之一。其实并不是因为MACD有多么精妙的算法&#xff0c;而是MACD遵循了最基本的“均线指导原则”&#xff0c;形象的将经典双均线系统换了一种更加直观的表达方式。在MT4中&#xff0c;默认应用的是单线MACD指标&#xff0c;而在证券市…

计算机专业书籍速读方法,格式你玩的转?速读5分钟就懂

小编又接到了新问题&#xff0c;有小伙伴说自己64GB的U盘在电脑里格式化只能选ExFAT或者NTFS&#xff0c;不能选择FAT32&#xff0c;求小编解答&#xff0c;小编正好借着这个机会&#xff0c;说说现在电脑格式问题。如果你懒得读&#xff0c;↓↓↓最后一段有答案&#xff0c;如…

Thymeleaf 学习笔记 (4)~~~~

2019独角兽企业重金招聘Python工程师标准>>> 模板布局 模板布局主要用到的标记有这么几个&#xff1a; th:fragment &#xff0c;用来定义片段的&#xff0c;用法&#xff1a;th:fragment"fragmentName"&#xff0c;起一个名字方便被其他地方引用&#xf…

凭证 金蝶_金蝶软件账务处理流程之——凭证录入

金蝶是我们财务人非常熟悉的财务软件&#xff0c;但是我们很多财务人只在应用软件的时候还是会出现很多的问题&#xff0c;为了帮助大家更好地应用这个软件&#xff0c;小编今天就来和大家讲讲关于金蝶软件凭证查询环节的一些基本处理流程。点击主界面“凭证查询”→弹出凭证过…

计算机网申兴趣爱好怎么写,银行网申个人特长和兴趣爱好怎么写

银行网申个人特长和兴趣爱好怎么写银行网申中个人简历及兴趣爱好怎么写?下面jyj135小编为大家整理了银行网申中个人特长和兴趣爱好的写作技巧&#xff0c;希望能为大家提供帮助!银行网申特长及兴趣爱好怎么写?特长Strong Point(1)写强项。弱项一定不要写&#xff0c;面试人员…

人才管理是什么意思_上海托管仓库外包仓库管理什么意思

上海托管仓库外包仓库管理什么意思上海仓库托管外包。好的上海仓库托管是预估好自己的货物总计有多少个方。车子的体积有多少&#xff0c;然后估算出总计需要多少车需要多少钱&#xff0c;需要怎么装车、卸货码放方式是什么样的&#xff0c;算出总的费用然后包干给搬家公司。这…

Makefile学习(二)[第二版]

复杂实例#示例1:在上一个示例的基础上再增加一个可执行文件03test[修改之处已标红].PHONY: clean all CC gcc CFLAGS -Wall -gBIN 01test 02test 03testSOURCES $(BIN:.c)OBJECTS $(BIN:.o)all: $(BIN)01test: 01test.o02test: 02test.o03test: 03test.o.c.o:$(CC) $(CFLA…