[转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND
原文:cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND
上一篇文章介绍了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(, &_quadVAO);
//绑定VAO
GL::bindVAO(_quadVAO);
//创建生成两个VBO
glGenBuffers(, &_buffersVBO[]);
//顶点Buffer
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * VBO_SIZE, _quads, GL_DYNAMIC_DRAW);
//这里就是VAO和VBO的区别,VAO把这些放到初始化中,无论后面绘制多少次,只要他不被改变,这段代码只会被调用一次,而VBO中,这个功能的代码会在每次被绘制时调用,这样就节约了效率
//位置
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_POSITION);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, , GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, vertices));
//颜色
glEnableVertexAttribArray(GLProgram::VERTEX_ATTRIB_COLOR);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, , 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, , GL_FLOAT, GL_FALSE, sizeof(V3F_C4B_T2F), (GLvoid*) offsetof( V3F_C4B_T2F, texCoords));
//索引Buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[]) * VBO_SIZE * , _indices, GL_STATIC_DRAW);
//取消VAO
GL::bindVAO();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, );
glBindBuffer(GL_ARRAY_BUFFER, ); CHECK_GL_ERROR_DEBUG();
}
void Renderer::setupVBO()
{
//创建生成两个VBO
glGenBuffers(, &_buffersVBO[]);
//调用函数绑定buffer
mapBuffers();
}
void Renderer::mapBuffers()
{
//GL_ARRAY_BUFFER 表示顶点数据
//GL_ELEMENT_ARRAY_BUFFER 表示索引数据
//避免改变buffer元素
GL::bindVAO();
//绑定id 顶点数据
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]);
//为改id制定一段内存区域
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * VBO_SIZE, _quads, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, );
//第二个VBO 索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(_indices[]) * VBO_SIZE * , _indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ); 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[]);
//向缓冲区申请空间并指定数据传输方式
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * (_numQuads), nullptr, GL_DYNAMIC_DRAW);
//提供缓冲区对象包含整个数据集合的更新
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[])* (_numQuads));
//缓冲区对象的更新完成
glUnmapBuffer(GL_ARRAY_BUFFER);
//为了禁用缓冲区对象,可以用0作为缓冲区对象的标识符来调用glBindBuffer()函数。这将把OpenGL切换为默认的不使用缓冲区对象的模式。
glBindBuffer(GL_ARRAY_BUFFER, );
//Bind VAO
GL::bindVAO(_quadVAO);
}
else
{
#define kQuadSize sizeof(_quads[0].bl)
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[]);
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[]) * _numQuads , _quads, GL_DYNAMIC_DRAW);
//激活顶点颜色纹理坐标的属性
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);
//顶点
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));
//颜色
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, , GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));
//纹理坐标
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, , GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords)); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _buffersVBO[]);
}
可以看到,这些设置属性的函数放在了绘制函数里,虽然看似是一样的,但是绘制函数会被调用的更频繁,所以把这些函数放到初始化函数中可以大幅提高程序的效率。
这里介绍VAO的两个函数:
glMapBuffer函数返回一个指针,指向与第一个参数相关联的当前绑定缓冲区对象的数据存储。第一个参数与glBufferData的第一个参数一致。第二个参数是GL_READ_ONLY、GL_WRITE_ONLY或GL_READ_WRITE之一,表示可以对数据进行的操作。
glUnmapBuffer表示对当前绑定缓冲区对象的更新已经完成,并且这个缓冲区可以释放。
enableVertexAttribs激活相关属性,激活的属性可以调用glVertexAttribPointer指定数据源,可选的有VERTEX_ATTRIB_FLAG_POSITION,VERTEX_ATTRIB_FLAG_COLOR和VERTEX_ATTRIB_FLAG_TEX_COORDS,这里这个参数是激活这三个。
glVertexAttribPointer指定了渲染时第一个参数代表的索引值的顶点属性数组的数据格式和位置。
第一个参数指定要修改的顶点属性的索引值,包括VERTEX_ATTRIB_POSITION(位置),VERTEX_ATTRIB_COLOR(颜色),VERTEX_ATTRIB_TEX_COORDS(纹理坐标)。
第二个参数指定每个属性值的组件数量且必须为1、2、3、4之一。
第三个参数指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT,GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
第四个参数指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE,意味着整数型的值会被映射至区间[-1,1](有符号整数),或者区间[0,1](无符号整数))或者直接转换为固定点值(GL_FALSE)。
第五个参数指定了一个属性到下一个属性之间的步长(这就允许属性值被存储在单一数组或者不同的数组中)。也就是连续顶点属性之间的偏移量。如果为0,那么它们是紧密排列在一起的。初始值为0。
第六个参数指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0。
最后需要调用绘制元素函数,绘制这些信息
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()>= && 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 = ;
}
这个处理主要是把命令存入_batchedQuadCommands中,如果如果Quad数据量超过VBO的大小,那么调用绘制,将缓存的命令全部绘制
如果一直没有超过VBO的大小,drawBatchedQuads绘制函数将在flush被调用时调用
如有错误,欢迎指出
下一篇介绍图形渲染和批处理
[转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(2)----QUAD_COMMAND的更多相关文章
- vlc源码分析(七) 调试学习HLS协议
HTTP Live Streaming(HLS)是苹果公司提出来的流媒体传输协议.与RTP协议不同的是,HLS可以穿透某些允许HTTP协议通过的防火墙. 一.HLS播放模式 (1) 点播模式(Vide ...
- jQuery源码分析_工具方法(学习笔记)
expando:生成唯一JQ字符串(内部使用) noConflict():防止冲突 isReady:DOM是否加载完成(内部) readyWait:等待多少文件的计数器(内部) holdReady() ...
- Django-restframework 之认证源码分析
Django-restframework 源码分析之认证 前言 最近学习了 django 的一个 restframework 框架,对于里面的执行流程产生了兴趣,经过昨天一晚上初步搞清楚了执行流程(部 ...
- NIO-SocketChannel源码分析
目录 NIO-SocketChannel源码分析 目录 前言 ServerSocketChannelImpl 创建ServerSocketChannel 绑定和监听 接收 SocketChannelI ...
- NIO-FileChannel源码分析
目录 NIO-FileChannel源码分析 目录 前言 RandomAccessFile 接口 创建实例 获取文件通道 FileChannelImpl 创建 写文件 读文件 修改起始位置 获取文件长 ...
- NIO-WindowsSelectorImpl源码分析
目录 NIO-WindowsSelectorImpl源码分析 目录 前言 初始化WindowsSelectorProvider 创建WindowsSelectorImpl WindowsSelecto ...
- NIO-EPollSelectorIpml源码分析
目录 NIO-EPollSelectorIpml源码分析 目录 前言 初始化EPollSelectorProvider 创建EPollSelectorImpl EPollSelectorImpl结构 ...
- 精尽 MyBatis 源码分析 - 整体架构
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 干货分享之spring框架源码分析02-(对象创建or生命周期)
记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 之前分析了Spring读取xml文件的所有信息封装成beanDef ...
- [转帖]cocos2D-X源码分析之从cocos2D-X学习OpenGL(3)----BATCH_COMMAND
原贴: cocos2D-X源码分析之从cocos2D-X学习OpenGL(3)----BATCH_COMMAND 上一篇介绍了QUAD_COMMAND渲染命令,顺带介绍了VAO和VBO,这一篇介绍批处 ...
随机推荐
- python线程详解
#线程状态 #线程同步(锁)#多线程的优势在于可以同时运行多个任务,至少感觉起来是这样,但是当线程需要共享数据时,可能存在数据不同步的问题. #threading模块#常用方法:'''threadin ...
- 17、JAVA流程控制
一.IF 1.第一种形式:if if(逻辑表达式){ 语句1; 语句2; ... } 当if中只有一条语句时,可以省略{} 2.第二种形式:if-else if(逻辑表达式){ 语句块1: } els ...
- Java 获取盘符及分类
import java.io.File; import javax.swing.filechooser.FileSystemView; public class ByteTest { public s ...
- 转:VC++线程同步-事件对象
这是整理孙鑫VC得到的关于线程同步方面的笔记. n 事件对象也属于内核对象,包含一个使用计数,一个用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于 ...
- Centos定时执行python脚本
其实就是linux的定时任务.老记不住参数,这次写下来,省着老百度.本文没有技术含量,请大家不要吐槽. ================================================ ...
- nyoj------------找球号(一)
找球号(一) 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 在某一国度里流行着一种游戏.游戏规则为:在一堆球中,每个球上都有一个整数编号i(0&l ...
- 《java设计模式》之责任链模式
在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...
- Linux 关机 休眠, 关闭移动设备自动挂载 命令
"+++++++++++++++++++++++++ Linux 关机.休眠命令 +++++++++++++++++++++++++++++++++++++++"indows7关机 ...
- GraphX实现N度关系
背景 本文给出了一个简单的计算图中每一个点的N度关系点集合的算法,也就是N跳关系. 之前通过官方文档学习和理解了一下GraphX的计算接口. N度关系 目标: 在N轮里.找到某一个点的N度关系的点集合 ...
- 使用Apktools反编译apk应用
使用Apktools反编译apk应用 1.获取APK的classes.dex文件: 得到你想要的应用的apk文件,用解压软件打开apk,从apk中复制出classes.dex文件. 2.classes ...