http://blog.csdn.net/cbbbc/article/details/39449945
最近几天,我都在学习如何在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:
-
- void CCSprite::draw(void)
- {
- CC_PROFILER_START_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
- CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called");
- CC_NODE_DRAW_SETUP();
- ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );
- if (m_pobTexture != NULL)
- {
- ccGLBindTexture2D( m_pobTexture->getName() );
- }
- else
- {
- ccGLBindTexture2D(0);
- }
-
-
-
- ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );
- #define kQuadSize sizeof(m_sQuad.bl)
- long offset = (long)&m_sQuad;
-
- int diff = offsetof( ccV3F_C4B_T2F, vertices);
- glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));
-
- diff = offsetof( ccV3F_C4B_T2F, texCoords);
- glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
-
- diff = offsetof( ccV3F_C4B_T2F, colors);
- glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
- CHECK_GL_ERROR_DEBUG();
- #if CC_SPRITE_DEBUG_DRAW == 1
-
- CCPoint vertices[4]={
- ccp(m_sQuad.tl.vertices.x,m_sQuad.tl.vertices.y),
- ccp(m_sQuad.bl.vertices.x,m_sQuad.bl.vertices.y),
- ccp(m_sQuad.br.vertices.x,m_sQuad.br.vertices.y),
- ccp(m_sQuad.tr.vertices.x,m_sQuad.tr.vertices.y),
- };
- ccDrawPoly(vertices, 4, true);
- #elif CC_SPRITE_DEBUG_DRAW == 2
-
- CCSize s = this->getTextureRect().size;
- CCPoint offsetPix = this->getOffsetPosition();
- CCPoint vertices[4] = {
- ccp(offsetPix.x,offsetPix.y), ccp(offsetPix.x+s.width,offsetPix.y),
- ccp(offsetPix.x+s.width,offsetPix.y+s.height), ccp(offsetPix.x,offsetPix.y+s.height)
- };
- ccDrawPoly(vertices, 4, true);
- #endif // CC_SPRITE_DEBUG_DRAW
-
- CC_INCREMENT_GL_DRAWS(1);
-
- CC_PROFILER_STOP_CATEGORY(kCCProfilerCategorySprite, "CCSprite - draw");
- }
那么我们也看看3.x中Sprite的draw函数,如代码2。
代码2:
- void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
- {
-
- _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
- if(_insideBounds)
- {
- _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
- renderer->addCommand(&_quadCommand);
- #if CC_SPRITE_DEBUG_DRAW
- _customDebugDrawCommand.init(_globalZOrder);
- _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
- renderer->addCommand(&_customDebugDrawCommand);
- #endif //CC_SPRITE_DEBUG_DRAW
- }
- }
从代码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:
- int Application::run()
- {
- ...
- director->mainLoop();
- ...
- }
从代码3中,它明显的启发着我们要继续追寻Director::mainLoop()函数。在Director中mainLoop()为纯函数,此子类DisplayLinkDirector才有其实现,如代码4。
代码4:
- void DisplayLinkDirector::mainLoop()
- {
- class="comment">
- if (_purgeDirectorInNextLoop)
- {
- _purgeDirectorInNextLoop = false;
- class="comment">
- purgeDirector();
- }
- else if (! _invalid)
- { class="comment">
- drawScene();
-
- PoolManager::getInstance()->getCurrentPool()->clear();
- }
- }
mainLoop是主线程调用的循环,其中drawScene()是绘制函数,接着我们继续追寻它的代码,如代码5。
代码5:
- void Director::drawScene()
- {
- class="comment">
- calculateDeltaTime();
-
-
- if(_deltaTime < FLT_EPSILON)
- {
- return;
- }
-
- if (_openGLView)
- {
- _openGLView->pollInputEvents();
- }
-
-
- if (! _paused)
- {
- _scheduler->update(_deltaTime);
- _eventDispatcher->dispatchEvent(_eventAfterUpdate);
- }
-
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-
-
- if (_nextScene)
- {
- setNextScene();
- }
-
- pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
-
-
- if (_runningScene)
- {
- _runningScene->visit(_renderer, Mat4::IDENTITY, false);
- _eventDispatcher->dispatchEvent(_eventAfterVisit);
- }
-
-
- if (_notificationNode)
- {
- _notificationNode->visit(_renderer, Mat4::IDENTITY, false);
- }
-
- if (_displayStats)
- {
- showStats();
- }
-
- _renderer->render();
- _eventDispatcher->dispatchEvent(_eventAfterDraw);
-
- popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
-
- _totalFrames++;
-
-
- if (_openGLView)
- {
- _openGLView->swapBuffers();
- }
-
- if (_displayStats)
- {
- calculateMPF();
- }
- }
从代码5中,我们看见visit()和render()函数的调用。其中visit()函数会调用draw()函数来向RenderQueue中添加RenderCommand,那么就继续追寻visit()的代码,如代码6。
代码6:
- void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
- {
-
- if (!_visible)
- {
- return;
- }
-
- uint32_t flags = processParentFlags(parentTransform, parentFlags);
-
-
-
-
- Director* director = Director::getInstance();
- director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
- director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
- int i = 0;
- if(!_children.empty())
- {
- sortAllChildren();
-
- for( ; i < _children.size(); i++ )
- {
- auto node = _children.at(i);
-
- if ( node && node->_localZOrder < 0 )
- node->visit(renderer, _modelViewTransform, flags);
- else
- break;
- }
-
- this->draw(renderer, _modelViewTransform, flags);
-
- for(auto it=_children.cbegin()+i; it != _children.cend(); ++it)
- (*it)->visit(renderer, _modelViewTransform, flags);
- }
- else
- {
- this->draw(renderer, _modelViewTransform, flags);
- }
-
- director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
-
-
-
-
-
- }
从代码6中,我们可以看到“ auto node = _children.at(i);和node->visit(renderer, _modelViewTransform, flags);”,这段代码的意思是先获取子节点,然后递归调用节点的visit()函数,到了没有子节点的节点,开始调用draw()函数。那么我们看看draw()函数代码,如代码7。
代码7:
- void Node::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags)
- {
- }
好吧,从代码7中,我们看到Node的draw什么都没有做,是我们找错地方?原来draw()是虚函数,所以它执行时执行的是该字节类的draw()函数。确实是我们找错地方了。那么我们分别看DrawNode::draw()、Sprite::draw()。
代码8:
- void DrawNode::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
- {
- _customCommand.init(_globalZOrder);
- _customCommand.func = CC_CALLBACK_0(DrawNode::onDraw, this, transform, flags);
- renderer->addCommand(&_customCommand);
- }
-
- void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
- {
-
- _insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
-
- if(_insideBounds)
- {
- _quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
- renderer->addCommand(&_quadCommand);
- #if CC_SPRITE_DEBUG_DRAW
- _customDebugDrawCommand.init(_globalZOrder);
- _customDebugDrawCommand.func = CC_CALLBACK_0(Sprite::drawDebugData, this);
- renderer->addCommand(&_customDebugDrawCommand);
- #endif //CC_SPRITE_DEBUG_DRAW
- }
- }
从代码8中,我们可以看到在draw()函数 向RenderQueue中添加RenderCommand,当然有的类的draw()不是向RenderQueue中添加RenderCommand,而是直接使用OpenGL的API直接进行渲染,或者做一些其他的事情。
那么当draw()都递归调用完了,我们来看看最后进行渲染的Renderer::render() 函数,如代码9。
代码9:
- void Renderer::render()
- {
-
-
-
-
- _isRendering = true;
-
- if (_glViewAssigned)
- {
-
- _drawnBatches = _drawnVertices = 0;
-
-
-
- for (auto &renderqueue : _renderGroups)
- {
- renderqueue.sort();
- }
- visitRenderQueue(_renderGroups[0]);
- flush();
- }
- clean();
- _isRendering = false;
- }
从代码9中,我们看到“renderqueue.sort()",这是之前所说的对命令先排序,然后才进行渲染,“visitRenderQueue( _renderGroups[0])”就是来进行渲染的。那么我们接着看看void Renderer::visitRenderQueue(const RenderQueue& queue)的代码,如代码10。
代码10:
- void Renderer::visitRenderQueue(const RenderQueue& queue)
- {
- ssize_t size = queue.size();
-
- for (ssize_t index = 0; index < size; ++index)
- {
- auto command = queue[index];
- auto commandType = command->getType();
- if(RenderCommand::Type::QUAD_COMMAND == commandType)
- {
- flush3D();
- auto cmd = static_cast(command);
-
- if(_numQuads + cmd->getQuadCount() > VBO_SIZE)
- {
- 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");
-
-
- drawBatchedQuads();
- }
-
- _batchedQuadCommands.push_back(cmd);
-
- memcpy(_quads + _numQuads, cmd->getQuads(), sizeof(V3F_C4B_T2F_Quad) * cmd->getQuadCount());
- convertToWorldCoordinates(_quads + _numQuads, cmd->getQuadCount(), cmd->getModelView());
-
- _numQuads += cmd->getQuadCount();
-
- }
- else if(RenderCommand::Type::GROUP_COMMAND == commandType)
- {
- flush();
- int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
- visitRenderQueue(_renderGroups[renderQueueID]);
- }
- else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
- {
- flush();
- auto cmd = static_cast(command);
- cmd->execute();
- }
- else if(RenderCommand::Type::BATCH_COMMAND == commandType)
- {
- flush();
- auto cmd = static_cast(command);
- cmd->execute();
- }
- else if (RenderCommand::Type::MESH_COMMAND == commandType)
- {
- flush2D();
- auto cmd = static_cast(command);
- if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
- {
- flush3D();
- cmd->preBatchDraw();
- cmd->batchDraw();
- _lastBatchedMeshCommand = cmd;
- }
- else
- {
- cmd->batchDraw();
- }
- }
- else
- {
- CCLOGERROR("Unknown commands in renderQueue");
- }
- }
- }
从代码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:
- void Renderer::drawBatchedQuads()
- {
-
-
- int quadsToDraw = 0;
- int startQuad = 0;
-
-
- if(_numQuads <= 0 || _batchedQuadCommands.empty())
- {
- return;
- }
-
- if (Configuration::getInstance()->supportsShareableVAO())
- {
-
- glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
-
-
-
-
-
-
-
-
- glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
- void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
- memcpy(buf, _quads, sizeof(_quads[0])* (_numQuads));
- glUnmapBuffer(GL_ARRAY_BUFFER);
-
- glBindBuffer(GL_ARRAY_BUFFER, 0);
-
-
- GL::bindVAO(_quadVAO);
- }
- else
- {
- #define kQuadSize sizeof(_quads[0].bl)
- glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
-
- glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _numQuads , _quads, GL_DYNAMIC_DRAW);
-
- GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
-
-
- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));
-
-
- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));
-
-
- glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));
-
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
- }
-
-
- for(const auto& cmd : _batchedQuadCommands)
- {
- auto newMaterialID = cmd->getMaterialID();
- if(_lastMaterialID != newMaterialID || newMaterialID == QuadCommand::MATERIAL_ID_DO_NOT_BATCH)
- {
-
- if(quadsToDraw > 0)
- {
- glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
- _drawnBatches++;
- _drawnVertices += quadsToDraw*6;
-
- startQuad += quadsToDraw;
- quadsToDraw = 0;
- }
-
-
- cmd->useMaterial();
- _lastMaterialID = newMaterialID;
- }
-
- quadsToDraw += cmd->getQuadCount();
- }
-
-
- if(quadsToDraw > 0)
- {
- glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );
- _drawnBatches++;
- _drawnVertices += quadsToDraw*6;
- }
-
- if (Configuration::getInstance()->supportsShareableVAO())
- {
-
- GL::bindVAO(0);
- }
- else
- {
- glBindBuffer(GL_ARRAY_BUFFER, 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- }
- _batchedQuadCommands.clear();
- _numQuads = 0;
代码12:
- void CustomCommand::execute()
- {
- if(func)
- {
- func();
- }
- }
代码13:
- void BatchCommand::execute()
- {
-
- _shader->use();
- _shader->setUniformsForBuiltins(_mv);
- GL::bindTexture2D(_textureID);
- GL::blendFunc(_blendType.src, _blendType.dst);
-
-
- _textureAtlas->drawQuads();
- }
从代码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:
- void HelloWorld::visit(cocos2d::Renderer *renderer, const Mat4 &transform, bool transformUpdated)
- {
- Layer::draw(renderer, transform, transformUpdated);
-
-
- _customCommand.init(_globalZOrder);
- _customCommand.func = CC_CALLBACK_0(HelloWorld::onDraw, this);
- renderer->addCommand(&_customCommand);
-
-
- }
- void HelloWorld::onDraw()
- {
-
-
- Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
- Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
- Director::getInstance()->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
- Director::getInstance()->loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
-
- auto glProgram = getGLProgram();
-
- glProgram->use();
-
-
- glProgram->setUniformsForBuiltins();
- auto size = Director::getInstance()->getWinSize();
-
-
- glBindVertexArray(vao);
-
- GLuint uColorLocation = glGetUniformLocation(glProgram->getProgram(), "u_color");
-
- float uColor[] = {1.0, 1.0, 1.0, 1.0};
- glUniform4fv(uColorLocation,1, uColor);
-
- glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE,(GLvoid*)0);
- glBindVertexArray(0);
- CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 6);
- CHECK_GL_ERROR_DEBUG();
- Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
- Director::getInstance()->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
-
- }
从代码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:
- bool FishLayer::init()
- {
- ...省略了不相关的代码。
-
- auto glprogram = GLProgram::createWithFilenames("UVAnimation.vsh", "UVAnimation.fsh");
-
- auto glprogramstate = GLProgramState::getOrCreateWithGLProgram(glprogram);
-
- m_Sprite->setGLProgramState(glprogramstate);
-
-
- auto textrue1 = Director::getInstance()->getTextureCache()->addImage("tortoise.png");
-
- glprogramstate->setUniformTexture("u_texture1", textrue1);
-
- auto textrue2 = Director::getInstance()->getTextureCache()->addImage("caustics.png");
-
- glprogramstate->setUniformTexture("u_lightTexture", textrue2);
-
-
- Texture2D::TexParams tRepeatParams;
- tRepeatParams.magFilter = GL_LINEAR_MIPMAP_LINEAR;
- tRepeatParams.minFilter = GL_LINEAR;
- tRepeatParams.wrapS = GL_REPEAT;
- tRepeatParams.wrapT = GL_REPEAT;
- textrue2->setTexParameters(tRepeatParams);
-
- Vec4 tLightColor(1.0,1.0,1.0,1.0);
- glprogramstate->setUniformVec4("v_LightColor",tLightColor);
-
- long offset = 0;
- auto attributeCount = m_Sprite->getMesh()->getMeshVertexAttribCount();
- for (auto k = 0; k < attributeCount; k++) {
- auto meshattribute = m_Sprite->getMesh()->getMeshVertexAttribute(k);
- glprogramstate->setVertexAttribPointer(s_attributeNames[meshattribute.vertexAttrib],
- meshattribute.size,
- meshattribute.type,
- GL_FALSE,
- m_Sprite->getMesh()->getVertexSizeInBytes(),
- (GLvoid*)offset);
- offset += meshattribute.attribSizeBytes;
- }
-
-
- m_LightAni.x = m_LightAni.y = 0;
- return true;
- }
-
- void FishLayer::draw(Renderer* renderer, const Mat4 &transform, uint32_t flags)
- {
- if(m_Sprite)
- {
-
- auto s = Director::getInstance()->getWinSize();
- m_Sprite->setPositionX(m_Sprite->getPositionX()-1);
- if(m_Sprite->getPositionX() < -100)
- {
- m_Sprite->setPositionX(s.width + 10);
- }
-
- auto glprogramstate = m_Sprite->getGLProgramState();
- if(glprogramstate)
- {
- m_LightAni.x += 0.01;
- if(m_LightAni.x > 1.0)
- {
- m_LightAni.x-= 1.0;
- }
- m_LightAni.y += 0.01;
- if(m_LightAni.y > 1.0)
- {
- m_LightAni.y-= 1.0;
- }
- glprogramstate->setUniformVec2("v_animLight",m_LightAni);
- }
- }
- Node::draw(renderer,transform,flags);
- }
从代码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源码