cocos2d-x: 死磕"HelloWorld"(6)——场景渲染的实施

场景入栈后,下一步便可以开始渲染了。之前在第三篇中我们看到run()函数里调用了一个渲染主函数mainLoop(),该篇便是具体分析该函数。它的定义为:

CCDirector.cpp

void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop)
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
         drawScene();
     
         // release the objects
         CCPoolManager::sharedPoolManager()->pop();        
     }
}

该函数在第三篇讨论run()的时候简单介绍过。假如导演类数据成员m_bPurgeDirecotorInNextLoop为真,则将其设为假,并且调用purgeDirector(),停止并关闭渲染窗口。如果m_bPurgeDirecotorInNextLoop为假,并且m_bInvalid也为假(从CCDisplayLinkDirector构造中看出其初始值为假,并且在runWithScence()里再次将其设置为假)时,调用渲染场景函数drawScene()进行渲染,渲染完调用sharedPoolManager()->pop()释放内存。现在就来看一下场景渲染函数drawScene()

CCDirector.cpp

void CCDirector::drawScene(void)
{
    // calculate "global" dt
    calculateDeltaTime();//计算延迟时间m_fDeltaTime(网络或者计算机慢导致的延迟)。

    //tick before glClear: issue #533
    if (! m_bPaused)
    {
        m_pScheduler->update(m_fDeltaTime);//根据延迟时间调整动画速率,以达到连续协调播放的效果。
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清理openGL颜色缓冲和深度缓冲。

    /* to avoid flickr, nextScene MUST be here: after tick and before draw.
     XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
    if (m_pNextScene)//假如m_pNextScene为真,则设置下一场景。
    {
        setNextScene();//把场景指针m_pNextScene传递给m_pRuningScene。
    }

    kmGLPushMatrix();//调用kmGLPushMatrix()拷贝当前(栈顶)变换矩阵,并将拷贝入栈。如此便使得栈顶和第二个矩阵相同。当渲染完成后顶部矩阵已经被变换了(因为旋转、缩放、平移等动作是通过矩阵的仿射变换实现的,默认状态是会更新当前矩阵的,使得当前位置成为新的坐标原点,这对于使用节点坐标系的情况是方便的),所以需要调用kmGLPopMatrix()将顶部矩阵废弃,这样原先的矩阵又回到顶部成为当前矩阵。这种做法在使用世界坐标系的情况是必要的,因为我们要保证坐标原点永远在左下角。并且在visit()里还会递归调用这种方法。

    // draw the scene
    if (m_pRunningScene)
    {
        m_pRunningScene->visit();//渲染当前场景,即节点树的递归渲染。
    }

    // draw the notifications node
    if (m_pNotificationNode)
    {
        m_pNotificationNode->visit();//渲染通知节点。
    }
    
    if (m_bDisplayStats)
    {
        showStats();//显示渲染参数。
    }
    
    kmGLPopMatrix();//回复变换矩阵。

    m_uTotalFrames++;//将总帧数加一。

    // swap buffers
    if (m_pobOpenGLView)//如果m_pobOpenGLView为真,则交换缓冲区。
    {
        m_pobOpenGLView->swapBuffers();
    }
    
    if (m_bDisplayStats)
    {
        calculateMPF();//计算帧率。
    }
}

下面来分析一下其中的一个关键函数visit()

CCNode.cpp

void CCNode::visit()
{
    // quick return if not visible. children won't be drawn.
    if (!m_bVisible)//判断是否可见,如果不可见则不渲染(包括子节点)。
    {
        return;
    }
    kmGLPushMatrix();//和drawScene()一样,调用kmGLPushMatrix()来保护变换矩阵。

     if (m_pGrid && m_pGrid->isActive())//处理格点。
     {
         m_pGrid->beforeDraw();
     }

    this->transform();//openGL视图矩阵变换。

    CCNode* pNode = NULL;//创建一个CCNode指针,并将其置空
    unsigned int i = 0;//初始化子节点计数器

    if(m_pChildren && m_pChildren->count() > 0)//如果m_pChildren为真且子节点数大于零,则开始渲染子节点
    {
        sortAllChildren();//将子节点根据m_nZOrder大小排序,即按照Z轴排序。在二维游戏中,Z轴顺序表示遮挡关系,ZOrder越小表示越靠后,所以要先渲染。m_nZOrder<0的子节点在当前节点之后,而m_nZorder>0的子节点在当前节点之前。
        // draw children zOrder < 0
        ccArray *arrayData = m_pChildren->data;//将子节点数据指针传递给arrayData
        for( ; i < arrayData->num; i++ )//渲染m_nZOrder<0的子节点
        {
            pNode = (CCNode*) arrayData->arr[i];

            if ( pNode && pNode->m_nZOrder < 0 ) 
            {
                pNode->visit();
            }
            else
            {
                break;
            }
        }
        // self draw
        this->draw();//渲染当前节点

        for( ; i < arrayData->num; i++ )//渲染m_nZorder>0的子节点
        {
            pNode = (CCNode*) arrayData->arr[i];
            if (pNode)
            {
                pNode->visit();
            }
        }        
    }
    else//如果没有子节点,则仅渲染当前节点
    {
        this->draw();
    }

    // reset for next frame
    m_uOrderOfArrival = 0;//将节点的添加顺序置零,这使得所有节点在一次渲染之后添加顺序都为零,除了新添加进来的节点

     if (m_pGrid && m_pGrid->isActive())//处理格点
     {
         m_pGrid->afterDraw(this);
    }
 
    kmGLPopMatrix();//回复变换矩阵
}

注意渲染子节点的时候,调用的还是visit(),即递归。所以真正渲染节点函数是draw(),该函数在精灵类里被重载:

CCSprite.cpp

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");//断言m_pobBatchNode为假,否则说明该精灵属于精灵族,不应在此渲染。

    CC_NODE_DRAW_SETUP();//配制GL服务器状态,GL项目,以及模型视图投影矩阵。

    ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );//启用openGL混合功能,该函数作用是将某像素点上原有颜色和将要覆盖上去的颜色按照某个比例混合,达到透明效果。

    ccGLBindTexture2D( m_pobTexture->getName() );//绑定纹理。
    ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );//开启顶点属性设置,包括顶点位置、颜色和纹理坐标。

#define kQuadSize sizeof(m_sQuad.bl)
//将kQuadSize定义为“四顶点属性结构体”m_sQuad的左下顶点成员所占内存大小。m_sQuad结构体中有四个顶点成员,每个成员所占内存其实相同。每个顶点成员也是个结构体,它有三个成员,分别是顶点坐标、颜色和纹理坐标。这三个成员也都是结构体。请读者自行查找定义。
#ifdef EMSCRIPTEN
//宏开关,默认状态下EMSCRIPTEN无定义
    long offset = 0;
    setGLBufferData(&m_sQuad, 4 * kQuadSize, 0);
#else
    long offset = (long)&m_sQuad;//获取“四顶点属性结构体”地址。
#endif // EMSCRIPTEN

    // vertex
    int diff = offsetof( ccV3F_C4B_T2F, vertices);//获取顶点位置成员vertices在结构体ccV3F_C4B_T2F中的偏移量,即成员地址和结构体地址之差。
    glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));//指定渲染时:将要渲染的顶点属性(参1)、该属性的成员数(参2,有x,y,z坐标,所以为3)、该属性中成员的数据类型(参3)、是否将数据归一(参4)、属性之间的偏移量(参5)、以及该属性在内存中的地址(参6)。

    // texCoods
    diff = offsetof( ccV3F_C4B_T2F, texCoords);//获取纹理坐标成员texCoords在结构体ccV3F_C4B_T2F中的偏移量。
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));//纹理坐标只有u,v两个成员。

    // color
    diff = offsetof( ccV3F_C4B_T2F, colors);//获取颜色成员在结构体中的偏移量
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));//颜色有r,g,b,a四个成员,其中a为透明度。


    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//根据顶点数组中的数据进行渲染:渲染模式为三角形带(参1,该参可以选择GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、以及GL_TRIANGLE_FAN),从顶点数组的第0位开始渲染(参2),共要渲染的顶点数为4(参3)。

    CHECK_GL_ERROR_DEBUG();//检查OpenGL是否出错。


#if CC_SPRITE_DEBUG_DRAW == 1
    // draw bounding box
    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
    // draw texture box
    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");//默认状态下(未开启轮廓模式),此句什么都不做。
}

渲染工作到此基本分析完毕。下面来看看mainLoop()中调用的停止渲染函数purgeDirector():

CCDirector.cpp

void CCDirector::purgeDirector()
{
    // cleanup scheduler
    getScheduler()->unscheduleAll();//移除所有计时器。getScheduler()返回值为m_pScheduler(为什么不直接调用该值呢?)。
    
    // don't release the event handlers
    // They are needed in case the director is run again
    m_pTouchDispatcher->removeAllDelegates();//移除所有代理。

    if (m_pRunningScene)
    {
        m_pRunningScene->onExitTransitionDidStart();//开始退出。
        m_pRunningScene->onExit();//
        m_pRunningScene->cleanup();//清理
        m_pRunningScene->release();//释放
    }
    
    m_pRunningScene = NULL;//清空当前场景指针
    m_pNextScene = NULL;//清空下一场景指针

    // remove all objects, but don't release it.
    // runWithScene might be executed after 'end'.
    m_pobScenesStack->removeAllObjects();//移除所有对象。

    stopAnimation();//停止动画。

    CC_SAFE_RELEASE_NULL(m_pFPSLabel);
    CC_SAFE_RELEASE_NULL(m_pSPFLabel);
    CC_SAFE_RELEASE_NULL(m_pDrawsLabel);

    // purge bitmap cache
    CCLabelBMFont::purgeCachedData();

    // purge all managed caches
    ccDrawFree();
    CCAnimationCache::purgeSharedAnimationCache();
    CCSpriteFrameCache::purgeSharedSpriteFrameCache();
    CCTextureCache::purgeSharedTextureCache();
    CCShaderCache::purgeSharedShaderCache();
    CCFileUtils::purgeFileUtils();
    CCConfiguration::purgeConfiguration();

    // cocos2d-x specific data structures
    CCUserDefault::purgeSharedUserDefault();
    CCNotificationCenter::purgeNotificationCenter();

    ccGLInvalidateStateCache();//
    
    CHECK_GL_ERROR_DEBUG();//
    
    // OpenGL view
    m_pobOpenGLView->end();//
    m_pobOpenGLView = NULL;//清空openGL视窗指针

    // delete CCDirector
    release();//释放
}

从mainLoop()中我们看出渲染的实施和停止依赖于m_bPurgeDirecotorInNextLoop和m_bInvalid的取值。

我们将在下一篇,也就是最后一篇中作一总结。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值