一、VAO、VBO介绍
随着OpenGL状态和固定管线模式的移除,我们不在用任何glEnable函数调用,而且也不会有glVertex、glColor等函数调用。这就意味着我们需要一种新的方式来将数据传输到图形卡以渲染图形。我们可以采用VBO,或者是在OpenGL3以上版本引入的新的特性,叫做VAO。通过它,我们可以把顶点数据和颜色存储在不同的VBO中,但是在同一个VAO中。对于法线数据或者其他的顶点信息也是一样。
VAO,是这样一种方式:把对象信息直接存储在图形卡中,而不是在当我们需要的时候传输到图形卡。这就是Direct3D所采用得方式,而在OpenGL中只有OpenGL3.X以上的版本中采用。这就意味着我们的应用程序不用将数据传输到图形卡或者是从图形卡输出,这样也就获得了额外的性能提升。
使用VAO并不难。我们不需要大量的glVertex调用,而是把顶点数据存储在数组中,然后放进VBO,最后在VAO中存储相关的状态。记住:VAO中并没有存储顶点的相关属性数据。OpenGL会在后台为我们完成其他的功能。
http://blog.youkuaiyun.com/xiajun07061225/article/details/7628146
使用VAO和VBO更重要的原因在于提高绘制效率。在绘制线对象的时候,对现有的GDI和GLES的绘制效率做了对比,同样的4K条折线段绘制时间均在40~60ms。即相比GDI,GL并没有较大的绘制效率提升。
二、VAO、VBO使用心得
使用VBO和VAO之后,绘制时间基本为0。原因在于:坐标数据已经通过缓冲传入了GPU,通常看图的前提下,坐标数据改动的可能性很小;而原有的绘制在变换图形时,每次都对坐标数据做了转换(实际数据->屏幕数据),继而将转换完成的数据传到GPU,然后渲染。缺少了源源不断不断的坐标数据转换和切换,绘制效率也就提升了。
为了能够继续进行图形的变换(缩放、平移、旋转),引入附加矩阵(additionTransform),用来计算当前视图(currentViewContext)和原始视图(通常是全图时候的上下文:fullViewContext)比例关系。这样当进行缩放时,currentViewContext发生了变化,additionTransform = currentViewContext / fullViewContext,additionTransform传入第二篇中Program的开始函数即可实现变换。而原有的currenViewContext保留用于计算屏幕坐标和实际坐标转换。
坐标数据超过float能表达的范围怎么办?openGL和GLES中传递的数据通常都是float数据,而且有效数据仅6~7个,但是地理坐标数据通常都是需要十几位有效数据,如果在使用时直接将真实坐标数据传入VBO中,绘制出来的线段很可能是锯齿形状。所以在传递数据前,先以地图左下为参考原点做一次偏移再传入VBO,这样基本就能满足数据精度要求了。
三、代码
以下代码为几何VBO的管理类,负责创建VAO、VBO以及绘制。
struct GeoVOBuffer
{
std::vector<PointF> cos; // 坐标缓冲数据
std::vector<ColorF> cols; // 颜色缓冲数据
GLuint vao, coVBO, colVBO; // vao, vbo的Id
GLuint type; // 绘制类型:GL_LINES GL_TRIANGLES GL_POINTS
void init()
{
clear();
}
void clear()
{
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &coVBO);
glDeleteBuffers(1, &colVBO);
cos.clear();
cols.clear();
vao = coVBO = colVBO = 0;
}
GeoVOBuffer()
{
vao = coVBO = colVBO = 0;
type = 0;
}
~GeoVOBuffer()
{
clear();
}
virtual bool draw(ProgramId* progId)
{
int32_t count = cos.size();
if (count == 0)
return false;
if (vao == 0)
{
GLuint vboHandles[2];
glGenBuffers(2, vboHandles);
coVBO = vboHandles[0];
colVBO = vboHandles[1];
glBindBuffer(GL_ARRAY_BUFFER, coVBO);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(PointF), &cos[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, colVBO);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(ColorF), &cols[0], GL_STATIC_DRAW);
GLuint vaoHandle = 0;
glGenVertexArrays(1, &vaoHandle);
glBindVertexArray(vaoHandle);
glEnableVertexAttribArray(0);//顶点坐标
glEnableVertexAttribArray(1);//顶点颜色
//调用glVertexAttribPointer之前需要进行绑定操作
glBindBuffer(GL_ARRAY_BUFFER, coVBO);
glVertexAttribPointer(progId->position(), 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)NULL);
glBindBuffer(GL_ARRAY_BUFFER, colVBO);
glVertexAttribPointer(progId->color(), 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)NULL);
glBindVertexArray(0);
vao = vaoHandle;
}
glBindVertexArray(vao);
glDrawArrays(type, 0, count);
glBindVertexArray(0);
return true;
}
};
粗略解释为:我们把所有的线坐标存入了GPU,GPU返回了一个ID(VAO)。在绘制的时候我们绑定这个ID,然后进行绘制,这些线就会被一次性全部画出。
为了管理不同类型的VAO,编写了一个vo的管理器,里面同时记录了一些使用VAO时需要的数据,如左下角偏移、Z深度等。
class voMgr
{
public:
voMgr()
{
m_useVo = true;
m_width = 0.;
m_height = 0.;
m_vaoDrawed = false;
s_zIndex = 0;
dotBuffer.type = GL_POINTS;
lineBuffer.type = GL_LINES;
polyBuffer.type = GL_TRIANGLES;
}
~voMgr()
{
clear();
}
bool actived() const
{
return m_useVo;
}
void setExtent(LrDouble w, LrDouble h)
{
m_width = w;
m_height = h;
}
bool hasData()
{
return m_width > 0 || m_height > 0;
}
LrDouble width() const { return m_width; }
LrDouble height() const { return m_height; }
void begin()
{
m_vaoDrawed = false;
}
void clear()
{
dotBuffer.clear();
lineBuffer.clear();
polyBuffer.clear();
}
void init()
{
clear();
m_vaoDrawed = false;
}
void draw(ProgramId* progId, const DrawContext& dc, int32_t flag)
{
if (m_vaoDrawed)
return;
dotBuffer.draw(progId);
lineBuffer.draw(progId);
polyBuffer.draw(progId);
m_vaoDrawed = true;
}
protected:
bool m_useVo;// 是否使用vo
bool m_vaoDrawed; // 是否绘制过,只绘制一次
LrDouble m_width, m_height; // 全屏高宽
public:
GeoVOBuffer dotBuffer, lineBuffer, polyBuffer;
GLfloat s_zIndex;// 绘制顺序
GLdouble s_left, s_bottom; // 左下偏移
};
static voMgr m_vo;
下面是一个线类型坐标转换到VAO的代码:
ColorF colF(fr, fg, fb, fa);
for (int32_t i = 0; i < count; ++i)
{
PointF cc;
// 偏移
cc.x = pco[i].x - m_vo.s_left;
cc.y = pco[i].y - m_vo.s_bottom;
// 深度信息
cc.z = m_vo.s_zIndex;
m_vo.lineBuffer.cos.push_back(cc);
m_vo.lineBuffer.cols.push_back(colF);
// 补充点用GL_LINES
if (i > 0 && i < count - 1)
{
m_vo.lineBuffer.cos.push_back(cc);
m_vo.lineBuffer.cols.push_back(colF);
}
}
由于要求 独个VAO的一次性绘制,不得不使用GL_LINES(GL_LINE_STRIP无法使用)。