cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND

个人原创,欢迎转载,转载请注明原文地址http://blog.csdn.net/bill_man

上一篇文章介绍了cocos2d-x的基本渲染结构,这篇顺着之前的渲染结构介绍渲染命令QUAD_COMMAND命令的部分,通过这部分的函数,学习opengl处理图片渲染的方法,首先介绍这节需要涉及到的基本概念VAO和VBO。

VAO和VBO:

顶点数组对象(Vertex Array Object  即VAO)是一个包含一个或数个顶点缓冲区对象(Vertex Buffer Object, 即 VBO)的对象,一般存储一个可渲染物体的所有信息。顶点缓冲区对象(VertexBuffer Object VBO)是你显卡内存中的一块高速内存缓冲区,用来存储顶点的所有信息。

这些概念显得很晦涩,简而言之,一般我们绘制一些图形需要将所有顶点的信息存储在一个数组里,但是经常会出现一些点是被重复使用的,这样就会出现一个点的信息的存储空间被重复使用的问题,这样第一会造成存储控件的浪费,第二就是如果我们要修改这个点的信息,需要改多次。所以我们采用索引的方式来描述图形,这样可以用一个数组存储点的信息,另外一个数组存储点的索引,这样所有的点都是不同的,另外把顶点信息存储在显卡的内存中,减少了cpu向gpu传输数据的时间,提高了程序的渲染效率,这就是VBO,在OpenGL3.0中,出现了更进一步的VAO,VBO通过绘制上下文获得绘制状态,VAO可以拥有多个VBO,它记录所有绘制状态,它的代码更简洁,效率更高,在cocos2d-x的绘制中,我们会判断底层是否支持VAO,如果支持VAO,那么优先采用VAO绘制。二者的区别可以从初始化就可以看出来:

void Renderer::setupBuffer()
{
    if(Configuration::getInstance()->supportsShareableVAO())
    {
        //初始化VBO和VAO
        setupVBOAndVAO();
    }
    else
    {
        //不支持VAO,只初始化VBO
        setupVBO();
    }
}
void Renderer::setupVBOAndVAO()
{
    //一个VAO
    glGenVertexArrays(1, &_quadVAO);
    //绑定VAO
    GL::bindVAO(_quadVAO);
    //创建生成两个VBO
    glGenBuffers(2, &_buffersVBO[0]);
    //顶点Buffer
    glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * VBO_SIZE, _quads, GL_DYNAMIC_DRAW);
    //这里就是VAO和VBO的区别,VAO把这些放到初始化中,无论后面绘制多少次,只要他不被改变,这段代码只会被调用一次,而VBO中,这个功能的代码会在每次被绘制时调用,这样就节约了效率
    //位置
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, vertices));
    //颜色
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, colors));
    //纹理坐标数据
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORDS);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, texCoords));
    //索引Buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * VBO_SIZE * 6, _indices, GL_STATIC_DRAW);
    //取消VAO
    GL::bindVAO(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    CHECK_GL_ERROR_DEBUG();
}
void Renderer::setupVBO()
{
    //创建生成两个VBO
    glGenBuffers(2, &_buffersVBO[0]);
    //调用函数绑定buffer
    mapBuffers();
}
void Renderer::mapBuffers()
{
    //GL_ARRAY_BUFFER 表示顶点数据
    //GL_ELEMENT_ARRAY_BUFFER 表示索引数据
    //避免改变buffer元素
    GL::bindVAO(0);
    //绑定id 顶点数据
    glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
    //为改id制定一段内存区域
    glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * VBO_SIZE, _quads, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //第二个VBO 索引数据
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[0]) * VBO_SIZE * 6, _indices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    CHECK_GL_ERROR_DEBUG();
}

需要介绍的两个关键的函数

glBindBuffer:它绑定缓冲区对象表示选择未来的操作将影响哪个缓冲区对象。如果应用程序有多个缓冲区对象,就需要多次调用glBindBuffer()函数:一次用于初始化缓冲区对象以及它的数据,以后的调用要么选择用于渲染的缓冲区对象,要么对缓冲区对象的数据进行更新。

当传入的第二个参数第一次使用一个非零无符号整数时,创建一个新的缓冲区对象;当第二个参数是之前使用过的,这个缓冲区对象成为活动缓冲区对象;如果第二个参数值为0时,停止使用缓冲区对象

glBufferData:保留空间存储数据,他分配一定大小的(第二个参数)的openGL服务端内存,用于存储顶点数据或索引。这个被绑定的对象之前相关联的数据都会被清除。

glBufferData参数介绍

参数1,目标GL_ARRAY_BUFFER或者GL_ELEMENT_ARRAY_BUFFER

参数2,内存容量

参数3,用于初始化缓冲区对象,可以使一个指针,也可以是空

参数4,如何读写,可以选择如下几种

GL_DYNAMIC_DRAW:多次指定,多次作为绘图和图像指定函数的源数据,缓冲区对象的数据不仅常常需要进行更新,而且使用频率也非常高

GL_STATIC_DRAW:数据只指定一次,多次作为绘图和图像指定函数的源数据,缓冲区对象的数据只指定1次,但是这些数据被使用的频率很高

GL_STREAM_DRAW:数据只指定一次,最多只有几次作为绘图和图像指定函数的源数据,缓冲区对象中的数据常常需要更新,但是在绘图或其他操作中使用这些数据的次数较少

从初始化的代码上,为什么VAO反倒复杂了呢?因为他只是把绘制时需要做的一些事情提前放到初始化函数中,来看一下绘制流程。

    //当前的openGL是否支持VAO
    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);
        //为了禁用缓冲区对象,可以用0作为缓冲区对象的标识符来调用glBindBuffer()函数。这将把OpenGL切换为默认的不使用缓冲区对象的模式。
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        //Bind VAO
        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_COORDS, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[1]);
    }

可以看到,这些设置属性的函数放在了绘制函数里,虽然看似是一样的,但是绘制函数会被调用的更频繁,所以把这些函数放到初始化函数中可以大幅提高程序的效率。

这里介绍VAO的两个函数:

glMapBuffer函数返回一个指针,指向与第一个参数相关联的当前绑定缓冲区对象的数据存储。第一个参数与glBufferData的第一个参数一致。第二个参数是GL_READ_ONLY、GL_WRITE_ONLY或GL_READ_WRITE之一,表示可以对数据进行的操作。

glUnmapBuffer表示对当前绑定缓冲区对象的更新已经完成,并且这个缓冲区可以释放。

最后需要调用绘制元素函数,绘制这些信息

glDrawElements(GL_TRIANGLES, (GLsizei) quadsToDraw*6, GL_UNSIGNED_SHORT, (GLvoid*) (startQuad*6*sizeof(_indices[0])) );

它根据索引绘图(注意:顶点数据和索引各自使用不同的缓冲区)

需要注意的是在Renderer的析构函数中要调用glDeleteBuffers来释放它的资源,并使它的标识可以其他缓冲区对象使用。

上一篇中介绍的几种渲染命令中的QUAD_COMMAND(这里把它称作四边形绘制)命令回调用drawBatchedQuads调用绘制函数,处理这个逻辑的命令是这样的:

if(commandType == RenderCommand::Type::QUAD_COMMAND)
{
    auto cmd = static_cast<QuadCommand*>(command);
    CCASSERT(nullptr!= cmd, "Illegal command for RenderCommand Taged as QUAD_COMMAND");

    //如果Quad数据量超过VBO的大小,那么调用绘制,将缓存的命令全部绘制
    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();
}

void Renderer::flush()
{
    //绘制
    drawBatchedQuads();
    //清空
    _lastMaterialID = 0;
}

这个处理主要是把命令存入_batchedQuadCommands中,如果如果Quad数据量超过VBO的大小,那么调用绘制,将缓存的命令全部绘制

如果一直没有超过VBO的大小,drawBatchedQuads绘制函数将在flush被调用时调用

如有错误,欢迎指出

下一篇介绍图形渲染和批处理

cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND

时间: 2024-10-05 02:12:00

cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND的相关文章

vlc源码分析(七) 调试学习HLS协议

HTTP Live Streaming(HLS)是苹果公司提出来的流媒体传输协议.与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙. 一.HLS播放模式 (1) 点播模式(Video on demand, VOD) 点播模式是指当前时间点可以获取到所有index文件和ts文件,二级index文件中记录了所有ts文件的地址.这种模式允许客户端访问全部内容.上面的例子中就是一个点播模式下的m3u8的结构. (2) 直播模式(Live) 直播模式是指实时生成M3u8和ts文件.它的

Java源码分析——String的设计

Tip:笔者马上毕业了,准备开始Java的进阶学习计划.于是打算先从String类的源码分析入手,作为后面学习的案例.这篇文章寄托着今后进阶系列产出的愿望,希望能坚持下去,不忘初心,让自己保持那份对技术的热爱. 因为学习分析源码,所以借鉴了HollisChuang成神之路的大部分内容,并在此基础上对源码进行了学习,在此感谢. 问题的引入 关于String字符串,对于Java开发者而言,这无疑是一个非常熟悉的类.也正是因为经常使用,其内部代码的设计才值得被深究.所谓知其然,更得知其所以然. 举个例

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

【Cocos2d-x】源码分析之 2d/ui/UILayoutDefine.h

#ifndef __UILAYOUTDEFINE_H__ #define __UILAYOUTDEFINE_H__ #include "cocos2d.h" NS_CC_BEGIN namespace ui { /** *控件 距离四周的间隙 *设置间隙之后 相当于控件的大小扩大了 *不会和周围控件紧挨着 有一定间距 * */ class Margin { public: float left; float top; float right; float bottom; public:

【Cocos2d-x】源码分析之 2d/ui/Widget

从今天开始 咱也模仿 红孩儿这些大牛分析源码 ,由于水平有限 不对之处欢迎狂喷.哈哈. #ifndef __UIWIDGET_H__ #define __UIWIDGET_H__ #include "ui/CCProtectedNode.h" #include "ui/UILayoutDefine.h" #include "ui/UILayoutParameter.h" #include "ui/GUIDefine.h" NS

【Cocos2d-x】源码分析之 2d/ui/UILayout

#ifndef __LAYOUT_H__ #define __LAYOUT_H__ #include "ui/UIWidget.h" NS_CC_BEGIN namespace ui { typedef enum { LAYOUT_COLOR_NONE,//空 LAYOUT_COLOR_SOLID,//单一固定颜色的 LAYOUT_COLOR_GRADIENT//有梯度变化的 }LayoutBackGroundColorType;//容器背景颜色类型 typedef enum { LA

cocos2d-x 源码分析 : Ref (CCObject) 源码分析 cocos2d-x内存管理策略

源码版本来自3.x,转载请注明 cocos2d-x 源码分析总目录: http://blog.csdn.net/u011225840/article/details/31743129 1.Ref,AutoreleasePool,PoolManager Ref中包含了一个叫referenceCount的引用计数,当一个Ref类的变量被new的时候,其referenceCount的引用计数被置为1. 其中有三个重要的操作,retain,release,autorelease,下面源码分析时会详细说明

Cocos2dx-3.x 中CCCamera相机类详解及源码分析

Cocos2d-x 3.3版本中加入了相机这个类,该类在3D游戏中是必不可少的,在3D立体游戏中,往往需要视野角度的变化,通过相机的变换才能观察和体验整个游戏世界. CCCamera类基本使用 在游戏中一般有两种类型的相机:一种是透视相机,它在3D游戏中十分常见:另一种是正交相机,它没有透视相机的近大远小的效果而是相机内任何位置的物体大小比例都是一样的. 上图是透视相机的原理图,一般来说,我们通过以下代码创建: _camera = Camera::createPerspective(60, (G

Cocos2d-x 源码分析 : Scheduler(定时器) 源码分析

源码版本 3.1r,转载请注明 我也终于不out了,开始看3.x的源码了,此时此刻的心情只能是wtf!!!!!!!!!!不过也终于告别CC时代了. cocos2d-x 源码分析目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 没错,是两张图.(你没有老眼昏花..我脑子也没有秀逗..)Ref就是原来的CCObject,而Timer类是与Scheduler类密切相关的类,所以需要把他们放在一起说.Timer和Sche

CCTextureCache类源码分析 (1)

CCTextureCache类源码分析(1): 1. CCTextureCache类: 这个类跟纹理缓存有关,我们跟着代码分析下这个类是怎么对纹理进行缓存的. /** Returns the shared instance of the cache * 单例 */ static CCTextureCache * sharedTextureCache(); 2.我们在创建精灵的时候会调用 CCTextureCache::sharedTextureCache()->addImage(pszFilen