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

前端之家收集整理的这篇文章主要介绍了cocos2D-X源码分析之从cocos2D-X学习OpenGL(5)---绘制基本图形前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

几乎所有openGL教程都会从渲染过程调用着色器的渲染顺序讲起,由于我们这个系列是从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,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,colors));
    //贴图
    glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_TEX_COORD);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD,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,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,vertices));
        // color
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,colors));
        // texcood
        glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD,texCoords));
    }

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

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

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

猜你在找的Cocos2d-x相关文章