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(6)---cocos内置着色器

上一篇我们介绍了cocos2d-x绘制基本图形的基本流程,我们还留下了一个着色器的部分没有讲,本篇内容将从openGL的渲染流程讲起,介绍cocos2d-x中的着色器,openGL的渲染流程如图所示:...
  • bill_man
  • bill_man
  • 2016年06月02日 18:22
  • 6740

Cocos2dx下 基本的OpenGL ES 绘图

void GLFirstLayer::draw() { // CCLayer::draw(); //顶点数据 static GLfloat vertex[] ...
  • ym19860303
  • ym19860303
  • 2013年07月01日 00:36
  • 7360

cocos2d-x简单绘图(DrawNode)

DrawNode简单绘图示例如下:  DrawNode* drawNode=DrawNode::create();     //绘制空心4边形和直线         Vec2 point[4];  ...
  • windvally
  • windvally
  • 2015年08月22日 11:40
  • 1722

opengl实例源代码,全是精华!

  • 2010年02月26日 11:33
  • 16.53MB
  • 下载

OpenGL--代码解读

OpenGL代码解读
  • qiaogua
  • qiaogua
  • 2016年11月27日 11:40
  • 1898

cocos2d-x 源码剖析(1)

原文出处:http://www.tsiannian.com/book/archives/34 我认为在看这些文章的时候,最好有一些cocos2d-x的经验。起码能新建一个cocos2d-x的hel...
  • u012866513
  • u012866513
  • 2014年03月10日 12:46
  • 686

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

cocos2D-X 3.0渲染结构代码讲解,也是cocos2D-X源码讲解系列文章和从cocos2D-X学习OpenGL系列文章的开始...
  • bill_man
  • bill_man
  • 2014年06月29日 22:17
  • 12966

cocos2D-X源码分析之从cocos2D-X学习OpenGL(4)---混合

之前在项目中就使用过混合,但是研究的不深入,近期美术的一个需求让我下决心重新深入的研究了一下混合以及它在cocos2d-x中的使用,在这里分享给大家。...
  • bill_man
  • bill_man
  • 2015年12月30日 20:12
  • 4028

《Cocos2d-x手机游戏开发与项目实战详解》源代码

  • 2015年05月22日 15:18
  • 55.95MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2D-X源码分析之从cocos2D-X学习OpenGL(5)---绘制基本图形
举报原因:
原因补充:

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