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的使用,以及绘制流程,下一篇将介绍着色器

       

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


       





         

   








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

相关文章推荐

《游戏引擎架构》读书笔记-游戏引擎中的C++

《游戏引擎架构》在两年前就买了,而且自己也大体的看过一遍,因为自己想学习引擎的底层知识,所以重新又找出了这本书再读一遍,并把根据自己的理解写一些读书笔记在这里与大家交流。       闲话少续,开始本...

在 cocos2d-x 3.x中使用shader实现精灵色相(Hue)的修改

为cocos2d 的精灵修改色相(Hue)的问题困扰了我很久,网上一直没有找到比较靠谱的解决方案。 前段时间在https://github.com/alex314/CCSpriteWithHue发现国...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

不忘初心

今天终于有时间整理整理书柜,难得静下来想想自己的路,整理这一年的书,也是整理这一年的路,很遗憾,除了考研的复习书,没有完整的看完一本;如同读书一样,整个人也变得浮躁了,功利了,怨天尤人了..........

异或

任何值与0异或,值不变。任何值只有与其本身异或,异或结果才为0。

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

cocos2D-X 3.0渲染结构代码讲解,也是cocos2D-X源码讲解系列文章和从cocos2D-X学习OpenGL系列文章的开始

cocos2D-X源码分析之从cocos2D-X学习OpenGL(16)----基本光照

cocos引擎目前已经支持3d功能,之前在3d教程中介绍了cocos2d-x的3d功能中的光照,但是只是粗略的介绍了四种光源,因为光照的重要性和复杂性,这个系列文章会分两篇介绍光照,本篇介绍光照的基础...

Cocos2D-X shader(五) cocostudio导出界面置灰及复原lua实现版

利用cocostudio导出的界面,实现界面置灰与复原效果: 需要变色的有三种类型控件:普通图片(ImageView),九宫格切图(Scale9Sprite)以及文字(Text) ...

Unity学习笔记(1)-“Hello World”之Unity的调试和log

作为一个合格的程序员,持续保持学习的状态是十分必要,学习3d有一段时间了,期间接触过虚幻引擎,unity等3d引擎,个人的看法是虚幻引擎功能强大,但unity更适合开发手游,想来自己从2011年底开始...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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