cocos2D-X源码分析之从cocos2D-X学习OpenGL(5)---绘制基本图形

原创 2016年06月01日 14:35:59

最近完整的学习了learnopenglhttp://www.learnopengl.com/),觉得非常有启发,从而又想起了这个长草许久的专题,正好趁这段时间,从本篇起完成这个专题,需要说明的是,从本系列的第五篇起将使用cocos2d-x3.10版本对应的代码,最早的三篇采用的是相对比较老的代码,但是我重新阅读后发现对理解有没有什么影响,所以暂时我不会升级之前的代码,但是为了保持和最新代码的一致性,从本篇起我们会采用最新的代码。

       cocos2d-x的底层调用的是openGL ESapi,因此所有的绘制命令追根到底都是调用openGLES,本系列的教程会“刨根问底”的从cocos2d-x的功能讲解到底层代码的调用,从而达到“从cocos2d-x学习openGL”的目的。

几乎所有openGL教程都会从渲染过程调用着色器的渲染顺序讲起,由于我们这个系列是从cocos2d-x学习openGL,所以,我们的顺序略有不同,我们会从cocos2d-x的应用层讲起,然后讲到调用openGL的代码,将这个过程讲清楚后,我们会在下一节从openGL的层面介绍渲染顺序。

下面的代码会被添加到我们的场景的上,从而我们的屏幕会显示一条直线:

auto drawNode = DrawNode::create();
    drawNode->drawLine(Vec2(100, 100), Vec2(200 ,200), Color4F::BLUE);
    this->addChild(drawNode);
        细节我想应该不用详细解释了,我们创建了一个DrawNode节点,然后让他绘制了一个从(100,100)到(200,200)的蓝色直线,显示的结果如图所示:

                

那么这个直线到底是如何绘制出来的呢?首先,我们先复习一下VAO和VBO的概念,VBO即顶点缓冲对象,他可以发送顶点数据到GPU上,VAO是顶点数组对象,可以绑定VBO,这样就不用每次都重新配置VBO了,任何对VBO数据层面的修改都可以直接映射,类似数据的指针和组织形式,但是并不是所有设备都支持VAO,所以,我们cocos2d-x通过一个函数来判断当前设备的openGL是否支持VAO:

Configuration::getInstance()->supportsShareableVAO()

         这个函数返回布尔型的返回值,如果是true说明设备支持VAO,我们首先看DrawNode类的init函数,我们从相对简单的VBO部分说起

//生成VBO
    glGenBuffers(1, &_vbo);
    //绑定VBO
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    //传入数据
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)* _bufferCapacity, _buffer, GL_STREAM_DRAW);
    //直线部分
    glGenBuffers(1, &_vboGLLine);
    glBindBuffer(GL_ARRAY_BUFFER, _vboGLLine);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacityGLLine, _bufferGLLine, GL_STREAM_DRAW);
    //点部分
    glGenBuffers(1, &_vboGLPoint);
    glBindBuffer(GL_ARRAY_BUFFER, _vboGLPoint);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacityGLPoint, _bufferGLPoint, GL_STREAM_DRAW);
    //解绑
    glBindBuffer(GL_ARRAY_BUFFER, 0);

        glGenBuffers返回n(第一个参数)个当前未使用的缓存对象名称,并保存到第二个参数的地址中。这里返回的名称只用于分配其他缓存对象,它们在绑定之后只会记录一个可用的状态。

       然后是调用glBindBuffer绑定缓冲,glBindBuffer完成三项工作,1)如果是第一次绑定,且它是一个非零的无符号整型,那么将创建一个与该名称相应得新缓存对象。2)如果绑定到一个已经创建的缓存对象,那么它将成为当前被激活的缓存对象。3)如果绑定的buffer值为0,那么openGL将不再对当前target应用任何缓存对象。要记住,openGL是个状态机,所有的设置都是设置当前的状态,如果进行下一次设置,那么当前的状态将会继续保留,就像一个工作台,我们把需要处理的零件放在工作台上才能处理,为了防止零件被做额外的处理,在处理完成后我们要把工作台清理干净,这里“glBindBuffer(GL_ARRAY_BUFFER, _vbo);”就是把零件放在加工台上,而“glBindBuffer(GL_ARRAY_BUFFER, 0);”就是将零件台清理干净。

       最后就是调用glBufferData装入数据,它主要有两个任务:分配顶点数据所需的存储空间,然后将数据从应用程序的数组中拷贝到openGL服务端的内存中。这个函数有五个参数,它们的意义如下:

        第一个参数:顶点属性数据类型,顶点数据是GL_ARRAY_BUFFER,索引数据是GL_ELEMENT_ARRAY_BUFFER,纹理缓冲数据GL_TEXTURE
        第二个参数:数据大小
        第三个参数:要么是个客户端内存指针,以便初始化缓存对象,要么是NULL。如果传入的指针非空,那么数据将从客户端拷贝到服务器端,如果传入的指针为空,那么将保留未初始化数据。
        第四个参数:分配数据的读取和写入方式,包括GL_STATIC_DRAW(几乎不会改变),GL_DYNAMIC_DRAW(数据易变),GL_STEAM_DRAW(每次都会被改变)等
        需要说明的是,这里把数据分为vbo,线vbo和点vbo,因为这里绘制的基本图形不是由线组成的,就是用点组成的(是的,画圆的时候要设置segments,其实就是设置直线的片段数)。
        下面是VAO,VAO也分为vao,线vao和点vao,这里节约空间,只介绍vao一个:

//VAO的处理
    glGenVertexArrays(1, &_vao);
    GL::bindVAO(_vao);
    //设置VBO数据
    glGenBuffers(1, &_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)* _bufferCapacity, _buffer, GL_STREAM_DRAW);
    //绑定VAO
    //顶点
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, vertices));
    //颜色
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, colors));
    //贴图
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, texCoords));
    //解绑
    GL::bindVAO(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
         整个过程和VBO类似,首先调用glGenVertexArrays生成一个VAO,然后调用GL::bindVAO(_vao);绑定VAO,最后调用GL::bindVAO(0);解绑

         中间是VAO和VBO的数据连接的过程

         




        如图就是VBO和VAO的关系,VAO索引到VBO数据上,像个指针,索引着VBO上的数据。

        接下来调用glEnableVertexAttribArray和glVertexAttribPointer连接顶点属性,glEnableVertexAttribArray设置是否启用与index(参数)相关联的顶点数据,默认是关闭的。glVertexAttribPointer是一个非常灵活的命令,我们手动指定我们的数据中的那部分对应相应的着色器上的那部分,参数的意义如下
        第一个参数:着色器位置索引,在GLProgram中我们定义了一个枚举,索引了所有的属性,这个参数和glEnableVertexAttribArray一致
        第二个参数:每个顶点的元素数目,第三个参数:数据类型
        第四个参数:是否对参数进行标准化,标准化即把参数转换成(-1,1)的范围,注意这项如果设为TRUE会进行额外的转化计算,会造成额外的开销,这时需要注意的

        第五个参数:步长,两段相同数据之间的距离,第六个参数:偏移量,为什么要设计偏移量呢,因为数据未必是连续的,有可能是位置和颜色混在一起,就如同cocos2d-x中的一样,我们来看V2F_C4B_T2F结构体的定义:

struct V2F_C4B_T2F
{
    /// vertices (2F)
    Vec2       vertices;
    /// colors (4B)
    Color4B        colors;
    /// tex coords (2F)
    Tex2F          texCoords;
};
        也就是首先是二维位置,然后是颜色和贴图,所以,偏移量就是我们需要的数据的起始位置,注意,我们2d图形只有x,y坐标,所以就是Vec2,DrawNode3D就是三维的。

       为什么在init中采用VAO的代码量反倒多了呢,因为VAO一劳永逸的把数据的组织方式都设置好了,后面不再修改,VBO则要在绘制的每一帧都设置一遍这些内容,代码如下:

if (_dirty)
    {
        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(V2F_C4B_T2F)*_bufferCapacity, _buffer, GL_STREAM_DRAW);
        
        _dirty = false;
    }
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        GL::bindVAO(_vao);
    }
    else
    {
        GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
        // vertex
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, vertices));
        // color
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, colors));
        // texcood
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, sizeof(V2F_C4B_T2F), (GLvoid *)offsetof(V2F_C4B_T2F, texCoords));
    }

    glDrawArrays(GL_LINES, 0, _bufferCount);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    if (Configuration::getInstance()->supportsShareableVAO())
    {
        GL::bindVAO(0);
    }

       这里只有一个新函数,就是glDrawArrays,它使用当前绑定的顶点数组元素(VAO)来建立一系列几何图元,第一个参数是绘制图形,第二个参数是起始位置,第三个参数是元素个数
       以上就是OpenGL的VAO和VBO的使用,以及绘制流程,下一篇将介绍着色器

       

       能力不足,水平有限,如有错误,多谢指出。


       





         

   








版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

cocos2D-X源码分析之从cocos2D-X学习OpenGL(11)----摄像机

本篇文章介绍一个在游戏中的重要概念,在MVP矩阵中,视图矩阵和投影矩阵都和摄像机有关,说句白话,摄像机其实就是生成投影矩阵和视图矩阵的方式和原因,cocos2d-x中使用GamePlay3D类的Mat...

Cocos2d-x 3.x 头像选择器,本地相册与拍照+头像编辑功能(Android、IOS双平台实现)

在应用甚至游戏当中会常常有用户上传头像的功能,在网上找了N多资料发现没有人具体介绍过该用cocos2d-x实现。这篇文章就来介绍一下如何在Android和IOS平台上实现该功能。 先传一张完成后的图片...

Cocos2d-x 绘制基本图形

在Cocos2d-x中,CCObject都重写了draw函数,我们如果想绘制一个基本类型的图形,可以在此类函数中实现。 在头文件中重写draw函数: virtual void draw()...

基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个立方体(5)

在上篇文章中,我们介绍了VBO索引的使用,使用VBO索引可以有效地减少顶点个数,优化内存,提高程序效率。 本教程将带领大家一起走进3D--绘制一个立方体。其实画立方体本质上和画三角形没什么区别,...

Cocos2d-x学习笔记(19)(TestCpp源码分析-3)

本章主要介绍

Cocos2d-x学习笔记(17)(TestCpp源码分析-1)

TestCpp源码基于Cocos2d-x2
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2D-X源码分析之从cocos2D-X学习OpenGL(5)---绘制基本图形
举报原因:
原因补充:

(最多只允许输入30个字)