個人原創,歡迎轉載,轉載請註明原文地址http://blog.csdn.net/bill_man
從本篇文章開始,將分析cocos2D-X 3.0源代碼,第一部分是從cocos2D-X學習OpenGL,也就是分析cocos2D-X 3.0的渲染代碼,本篇首先介紹cocos2D-X 3.0的渲染結構,使用的是3.0正式版。
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
//只有一種情況會調用到這裏來,就是導演類調用end函數
_purgeDirectorInNextLoop = false;
//清除導演類
purgeDirector();
}
else if (! _invalid)
{
//繪製
drawScene();
//清除內存
PoolManager::getInstance()->getCurrentPool()->clear();
}
}
分析的起點是mainLoop函數,這是在主線程裏面會調用的循環,其中drawScene函數進行繪製。那麼就進一步來看drawScene函數。
void Director::drawScene()
{
//計算間隔時間
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);
//切換下一場景,必須放在邏輯後繪製前,否則會出bug
if (_nextScene)
{
setNextScene();
}
kmGLPushMatrix();
//創建單位矩陣
kmMat4 identity;
kmMat4Identity(&identity);
//繪製場景
if (_runningScene)
{
_runningScene->visit(_renderer, identity, false);
_eventDispatcher->dispatchEvent(_eventAfterVisit);
}
//繪製觀察節點,如果你需要在場景中設立觀察節點,請調用攝像機的setNotificationNode函數
if (_notificationNode)
{
_notificationNode->visit(_renderer, identity, false);
}
//繪製屏幕左下角的狀態
if (_displayStats)
{
showStats();
}
//渲染
_renderer->render();
//渲染後
_eventDispatcher->dispatchEvent(_eventAfterDraw);
kmGLPopMatrix();
_totalFrames++;
if (_openGLView)
{
_openGLView->swapBuffers();
}
//計算繪製時間
if (_displayStats)
{
calculateMPF();
}
}
其中和繪製相關的是visit的調用和render的調用,其中visit函數會調用節點的draw函數,在3.0之前的版本中draw函數就會直接調用繪製代碼,3.0版本是在draw函數中將繪製命令存入到renderer中,然後renderer函數去進行真正的繪製,首先來看sprite的draw函數。
void Sprite::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
//檢查是否超出邊界,自動裁剪
_insideBounds = transformUpdated ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
if(_insideBounds)
{
//初始化
_quadCommand.init(_globalZOrder, _texture->getName(), _shaderProgram, _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
}
}
這裏面用了兩種不同的繪製命令quadCommand初始化後就可以加入到繪製命令中,customDebugDrawCommand傳入了一個回調函數,具體的命令種類會在後面介紹。其中自定義的customDebugDrawCommand命令在初始化的時候只傳入了全局z軸座標,因爲它的繪製函數全部都在傳入的回調函數裏面,_quadCommand則需要傳入全局z軸座標,貼圖名稱,shader,混合,座標點集合,座標點集個數,變換。
void Renderer::render()
{
_isRendering = true;
if (_glViewAssigned)
{
//清除
_drawnBatches = _drawnVertices = 0;
//排序
for (auto &renderqueue : _renderGroups)
{
renderqueue.sort();
}
//繪製
visitRenderQueue(_renderGroups[0]);
flush();
}
clean();
_isRendering = false;
}
Render類中的render函數進行真正的繪製,首先排序,再進行繪製,從列表中的第一個組開始繪製。在visitRenderQueue函數中可以看到五種不同類型的繪製命令類型,分別對應五個類,這五個類都繼承自RenderCommand。
QUAD_COMMAND:QuadCommand類繪製精靈等。
所有繪製圖片的命令都會調用到這裏,處理這個類型命令的代碼就是繪製貼圖的openGL代碼,下一篇文章會詳細介紹這部分代碼。
CUSTOM_COMMAND:CustomCommand類自定義繪製,自己定義繪製函數,在調用繪製時只需調用已經傳進來的回調函數就可以,裁剪節點,繪製圖形節點都採用這個繪製,把繪製函數定義在自己的類裏。
這種類型的繪製命令不會在處理命令的時候調用任何一句openGL代碼,而是調用你寫好並設置給func的繪製函數,後續文章會介紹引擎中的所有自定義繪製,並自己實現一個自定義的繪製。
BATCH_COMMAND:BatchCommand類批處理繪製,批處理精靈和粒子
其實它類似於自定義繪製,也不會再render函數中出現任何一句openGL函數,它調用一個固定的函數,這個函數會在下一篇文章中介紹。
GROUP_COMMAND:GroupCommand類繪製組,一個節點包括兩個以上繪製命令的時候,把這個繪製命令存儲到另外一個_renderGroups中的元素中,並把這個元素的指針作爲一個節點存儲到_renderGroups[0]中。
整個GROUP_COMMAND的原理需要從addCommand講起。
void Renderer::addCommand(RenderCommand* command)
{
//獲得棧頂的索引
int renderQueue =_commandGroupStack.top();
//調用真正的addCommand
addCommand(command, renderQueue);
}
void Renderer::addCommand(RenderCommand* command, int renderQueue)
{
CCASSERT(!_isRendering, "Cannot add command while rendering");
CCASSERT(renderQueue >=0, "Invalid render queue");
CCASSERT(command->getType() != RenderCommand::Type::UNKNOWN_COMMAND, "Invalid Command Type");
//將命令加入到數組中
_renderGroups[renderQueue].push_back(command);
}
addCommand有“真假”兩個,幾乎所有添加渲染命令的地方,調用的都是第一個“假” addCommand,它實際上不是真正的把命令添加到_renderGroups中,它是獲得需要把命令加入到_renderGroups位置中的索引,這個索引是從_commandGroupStack獲得的,_commandGroupStack是個棧,當我們創建一個GROUP_COMMAND時,需要調用pushGroup函數,它是把當前這個命令在_renderGroups的索引位置壓到棧頂,當addCommand時,調用top,獲得這個位置
_groupCommand.init(_globalZOrder);
renderer->addCommand(&_groupCommand);
renderer->pushGroup(_groupCommand.getRenderQueueID());
GROUP_COMMAND一般用於繪製的節點有一個以上的繪製命令,把這些命令組織在一起,無需排定它們之間的順序,他們作爲一個整體被調用,所以一定要記住,棧是push,pop對應的,關於這個節點的所有的繪製命令被添加完成後,請調用pop,將這個值從棧頂彈出,否則後面的命令也會被添加到這裏。
接下來就可以解釋爲什麼調用的起始只需調用
visitRenderQueue(_renderGroups[0]);,爲什麼只是0,其他的呢?
它們會在處理GROUP_COMMAND被調用
else if(RenderCommand::Type::GROUP_COMMAND == commandType) {
flush();
int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
visitRenderQueue(_renderGroups[renderQueueID]);
}
如有錯誤,歡迎指出
下一篇介紹貼圖和批處理的openGL代碼部分
同時推薦子龍山人的openGL相關博客:http://4gamers.cn/archives/category/opengl-es-2-0