cocos2D-X源码讲解之从cocos2D-X学习OpenGL(1)----cocos2D-X渲染结构

 个人原创,欢迎转载,转载请注明原文地址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代码部分

cocos2D-X源码讲解之从cocos2D-X学习OpenGL(1)----cocos2D-X渲染结构,布布扣,bubuko.com

时间: 2024-10-07 06:17:18

cocos2D-X源码讲解之从cocos2D-X学习OpenGL(1)----cocos2D-X渲染结构的相关文章

源码讲解ActionBar的各种用法

1. Navigation Drawer 许多应用程序都使用了Navigation Drawer,如网易邮箱客户端.该控件位于 android.support.v4.widget.DrawerLayout ,用法如下,点击下载源码: <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/draw

深入Redis内部-Redis 源码讲解(转)

Redis作为 NoSQL 数据库的杰出代表,一直广受关注,其轻量级的敏捷架构,向来有存储中的瑞士军刀之称.下面推荐的一篇文章,从源码的角度讲解了Redis 的整个工作流程,是了解 Redis 流程的绝佳文章.英文的,想搞懂还是要花些时间的 原文链接:Redis: under the hood 目录: Startup Beginning global server state initialization Setting up command table Loading config file

游戏源码讲解

新建立了一个QQ群,用来讲解一些知名网络游戏源码,如游戏架构.网络通信.配置读写.逻辑管理.UI管理等,适合对游戏开发感兴趣的在校学生.刚从业的新手,玩服的勿加. 手游:暗黑战神.龙骑战歌 端游:江山美人(2.5D 回合制MMORPG) QQ:773495257 QQ群:557660018 付费加入:500RMB/人

Qt5.5.0使用mysql编写小软件源码讲解---顾客信息登记表

一个个人觉得比较简单小巧的软件. 下面就如何编写如何发布打包来介绍一下吧! 先下载mysql的库文件链接:http://files.cnblogs.com/files/xiaobo-Linux/mysql.zip 把两个文件放入 Qt目录\Qt5.5.0\5.5\mingw492_32\bin文件夹下直接粘贴(路径根据自己的设置) 也可以放到原来工程代码中,头文件用双引号引出即可. <!--小波Linux QQ463431476 cnblogs http://www.cnblogs.com/xi

laravel5源码讲解整理

来源:http://yuez.me/laravel-yuan-ma-jie-du/?utm_source=tuicool&utm_medium=referral 目录 入口文件 index.php Illuminate\Foundation\Application 类 注入所有基础 Service Provider 入口文件 index.php 一个基于Laravel的应用,当WEB服务器接受到来自外部的请求后,会将这个这个请求解析到 应用根目录的 public/index.php 中. Lar

源码讲解PyQt5的文本框与网格布局

网格布局其中的每一个按钮对应到一个网格,其实我们的窗口部件可以占据多个网格,我们可以据此做一个类似文本输入的窗口.下面我们就用源码来学习PyQt的文本框与网格布局. 先上源代码: import sysfrom PyQt5.QtWidgets import QApplication, QWidget, QLabel, QGridLayout, QTextEdit, QLineEdit class exp(QWidget): def __init__(self): super().__init__(

java里面的FutureTask简单使用(配合源码讲解)

最近无意间看到了关于AsyncTask的一篇分析文章AsyncTask源码分析,记得很早之前还看过郭神博客里面分析了AsyncTask源码.去查看AsyncTask源码会发现里面使用了FutureTask在它自己的构造函数里面,我的sdk是android-23里面查看的. /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */ public AsyncTask() {

jdk1.8 HashMap源码讲解

1. 开篇名义 jdk1.8中hashMap发生了一些改变,在之前的版本中hsahMap的组成是数组+链表的形式体现,而在1.8中则改为数组+链表+红黑树的形式实现,通过下面两张图来对比一下二者的不同.                   jdk1.8之前的hashMap结构图,基本对象为Entry<k,v>                               jdk1.8的hashMap结构图,基本对象改为了Node<k,v> 注意:无论Entry<key,valu

dyld方式遍历模块源码讲解

mac ios上遍历模块的有几种方式(其实不叫遍历模块,应该叫做遍历进程内所有的macho可执行文件,看完就知道为什么了).这里只看dyld方式遍历的,dyld大家都知道这个是水果支持动态链接启动macho文件用的,也就当你要依赖其它库时dyld会给也把这些坑填了,遍历模块代码是:https://blog.51cto.com/haidragon/2164203用到的函数有: int32_t nModNums= _dyld_image_count(); //获取所有image pModSlide