接触OpenGL有一段时间了,大多数时候都是使用OSG在进行开发,前几天同事问起OpenGL中VBO的相关内容,才发现很多OpenGL的知识生疏了。这两天花了点时间重新复习了一下与VBO相关的内容,记录在此以备忘。文中大部分内容来自网络。
OpenGL从OpenGL 3.0开始将API分成了两种类型:即旧式的OpenGL(Legacy OpenGL)和新式的OpenGL(Core Profile),OpenGL3.3 的官方API文档完整地描述了新式的OpenGL API,旧式的OpenGL API可以在OpenGL 2.1中查看。
对于OpenGL渲染的的第一站:也就是把顶点数据(包括顶点位置、顶点法线、顶点颜色、纹理坐标等)传入到OpenGL,旧式的OpenGL中有以下几种方式实现:
此外VBO在Legacy OpenGL中和Core Profile OpenGL中的使用也有着不同的方式。
(1) 开启和关闭VA
//开启和关闭客户端状态
void glEnableClientState(GLenum cap);
void glDisableClientState(GLenum cap);
//参数cap取值
GL_VERTEX_ARRAY //顶点位置
GL_COLOR_ARRAY //顶点颜色
GL_EDGE_FLAG_ARRAY //顶点边界线标识
GL_FOG_COORD_ARRAY //顶点雾坐标
GL_INDEX_ARRAY //顶点索引
GL_NORMAL_ARRAY //顶点法线
GL_SECONDARY_COLOR_ARRAY //顶点辅助颜色
GL_TEXTURE_COORD_ARRAY //顶点纹理坐标
(2) 设置数据到顶点
//////////////////////////////////////////////////////////////////////////
//以下是一系列设置顶点数据的API
//参数取值(默认形参代表OpenGL中默认值):
// size 描述数据的维度(2D\3D)
// type 描述每个数据的类型
// stride 描述每个顶点数据的跨度
//pointer 指向实际数据
//设置顶点位置数据
void glVertexPointer( GLint size=4, GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点颜色数据
void glColorPointer( GLint size=4, GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点边界线标识数据
void glEdgeFlagPointer( GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点雾坐标数据
void glFogCoordPointer( GLenum type=GL_FLOAT, GLsizei stride=0, GLvoid *pointer=0);
//设置顶点索引数据
void glIndexPointer( GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点法线数据
void glNormalPointer( GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点辅助颜色数据
void glSecondaryColorPointer( GLint size=3, GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
//设置顶点纹理坐标数据
void glTexCoordPointer( GLint size=4, GLenum type=GL_FLOAT, GLsizei stride=0, const GLvoid *pointer=0);
(3) VA绘制
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//通过设置好的顶点数据进行绘制:可以使用下列API进行绘制
void glDrawArrays( GLenum mode, GLint first, GLsizei count);
//参数描述
//mode 绘制几何图元类型(GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE等)
//first 顶点数据的开始索引位置(对应(2)中pointer)
//count 需要渲染的顶点数据数量
void glDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
//参数描述
//mode 绘制几何图元类型(GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE等)
//count 第四个参数indices中参与渲染的索引数量
//type 第四个参数indices的数据类型
//indices 需要额外传入的一个索引数组指针
void glDrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices);
//参数描述(与glDrawElements一致但多出两个参数)
//mode 绘制几何图元类型(GL_POINTS, GL_LINE_STRIP, GL_TRIANGLE等)
//count 第四个参数indices中参与渲染的索引数量
//type 第四个参数indices的数据类型
//indices 需要额外传入的一个索引数组指针
//start 索引数组indices中的最小值索引值
//end 索引数组indices中的最大值索引值
以上就是VA所涉及到的基本API,使用过程如下
首先开启客户端VertexArray状态,接着绑定数据到顶点状态,最后进行绘制:
static float vertices[][3] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
-1.0f, 0.0f, 0.0f
};
static float colors[][4] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
//绘制函数
void renderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -5.0f);
//////////////////////////////////////////////////////////////////////////
//开启VA状态
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
//绑定数据
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 3);
//关闭VA状态
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
//////////////////////////////////////////////////////////////////////////
}
前文描述了VBO在Legacy 和Core Profile中有两种不同的方式:
(1)创建(初始化)VBO对象
//创建VBO的API
//////////////////////////////////////////////////////////////////////////
void glGenBuffers( GLsizei n, GLuint * buffers);
//生成缓冲区ID
//参数描述
// n 生成ID数量
// buffers 存储缓冲区ID的数组
void glBindBuffer( GLenum target, GLuint buffer);
//设置缓冲区为当前操作的缓冲区,并且设置缓冲区的类型
//参数描述
//target 缓冲区类型(包括4种)
GL_ARRAY_BUFFER
GL_ELEMENT_ARRAY_BUFFER
GL_PIXEL_PACK_BUFFER
GL_PIXEL_UNPACK_BUFFER
//buffer 使用glGenBuffers生成的缓冲区ID
void glBufferData( GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);
//为缓冲区设置数据
//参数描述
//target 缓冲区类型(包括4种)
GL_ARRAY_BUFFER
GL_ELEMENT_ARRAY_BUFFER
GL_PIXEL_PACK_BUFFER
GL_PIXEL_UNPACK_BUFFER
//size 缓冲区大小(多少字节)
//data 实际指向数组的指针
//usage 当前缓冲区的使用模式[一种提示用来优化之用](取值如下9种)
GL_STREAM_DRAW
GL_STREAM_READ
GL_STREAM_COPY
GL_STATIC_DRAW
GL_STATIC_READ
GL_STATIC_COPY
GL_DYNAMIC_DRAW
GL_DYNAMIC_READ
GL_DYNAMIC_COPY
(2)开启/关闭VBO
当创建完VBO之后,我们并不知道VBO中存储的是顶点位置、还是顶点颜色或者是顶点法线,于是使用下面的API来描述某一个VBO中到底是顶点什么方面的数据
//////////////////////////////////////////////////////////////////////////
//开启关闭VBO状态
void glEnableClientState(GLenum cap);
void glDisableClientState(GLenum cap);
//参考VA中的描述
void glVertexPointer( GLint size, GLenum type, GLsizei stride, const GLvoid * pointer);
//参考VA中的描述:
//但是pointer参数有所区别:
//当使用了glBindBuffer将GL_ARRAY_BUFFER绑定到一个非0的VBO ID上之后,这时候的pointer
//代表的是一个VBO ID对象中实际数据(glBufferData中data参数)的偏移值,也就是说这时候pointer
//并不是一个指针,仅仅是一个偏移值。同时GL_ARRAY_BUFFER_BINDING这个状态会保存在客户端
//综合上面VA中的解释:可以知道这个glVertexPointer中的pointer两种含义:
//(1)当glBindBuffer绑定到非0的ID时:
//pointer代表偏移,此时客户端glClientState中会保存GL_ARRAY_BUFFER_BINDING暗示现在在用VBO
//(2)当glBindBuffer绑定到0的ID时:
//pointer代表一个指向数组的指针,此时客户端glClientState中不会保存GL_ARRAY_BUFFER_BINDING暗示现在在用VA
//同理,对于VA中用到的一系列函数都有同样的解释,不再赘述:
glColorPointer
glEdgeFlagPointer
glFogCoordPointer
glIndexPointer
glNormalPointer
glSecondaryColorPointer
glTexCoordPointer
(3)VBO绘制
//////////////////////////////////////////////////////////////////////////
//绘制VBO
void glDrawArrays( GLenum mode, GLint first, GLsizei count);
//参考VA中描述
//所不同的是数据现在在缓冲区之中,而不是VA所指向的pointer数组中了
void glDrawElements( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
//参考VA中描述
//所不同的是indices设置为NULL
//因为缓冲区类型有一种是GL_ELEMENT_ARRAY_BUFFER(查看glGenBuffers中类型描述)
//我们会把indices索引值存储在GL_ELEMENT_ARRAY_BUFFER类型的Buffer中,不需要额外的数组了
//直接用两个缓冲区即可
以上便是旧式OpenGL中实现VBO的API,使用过程如下:
首先创建缓冲区并设置缓冲区类型和填满缓冲区,接着指定缓冲里面是什么(到底存的是位置还是颜色或者是法线),最后绘制
static float vertices[][3] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
-1.0f, 0.0f, 0.0f
};
static float colors[][4] = {
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
void renderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -5.0f);
//////////////////////////////////////////////////////////////////////////
//创建VBO(一般放在初始化函数中)
GLuint vertexBufferID, colorBufferID;
glGenBuffers(1, &vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glGenBuffers(1, &colorBufferID);
glBindBuffer(GL_ARRAY_BUFFER, colorBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
//开启VBO模式并设置VBO该怎么解析
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glVertexPointer(3, GL_FLOAT, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, colorBufferID);
glColorPointer(4, GL_FLOAT, 0, 0);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 3);
//绘制完成之后关闭VBO状态
//将glBindBuffer设置为0,使得后续glVertexPointer类似函数起到VA的作用
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//////////////////////////////////////////////////////////////////////////
}
(1)创建VBO
参考Legacy VBO中内容,二者是一样的
(2)开启/关闭VBO
//////////////////////////////////////////////////////////////////////////
void glEnableVertexAttribArray( GLuint index);
void glDisableVertexAttribArray( GLuint index);
//开启和关闭一个通用的缓冲区对象
//参数描述
//index 缓冲区对象索引值
void glVertexAttribPointer( GLuint index,
GLint size,
GLenum type,
GLboolean normalized,
GLsizei stride,
const GLvoid * pointer
);
//设置如何解析缓冲区中的数据
//参数描述
//index 当前操作的缓冲区对象索引值
//size 数据的维度是2D/3D/4D
//type 数据类型 GL_INT /GL_FLOAT
//normalized 是否已经单位化
//stride 数据间距
//pointer 参考Legacy VBO中解释(在glBindBuffer未开启时是指针开启后是索引值)
(3)绘制
参考Legacy VBO中内容,二者是一样的
以上便是Core Profile 下的VBO,使用方式如下:
首先创建VBO对象,接着开启VBO并且用来说明VBO中数据是什么样组织的(但是并没有说明数据是顶点位置、顶点颜色还是法线),这一点与Legacy VBO不同,因为它的说明部分(哪个VBO存储着位置、哪个VBO存储着颜色、哪个VBO存储着法线等)是在着色语言Shader中指明的,最后还是使用同样的API绘制:
//////////////////////////////////////////////////////////////////////////
GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f };
GLfloat vColors [] = { 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f };
//////////////////////////////////////////////////////////////////////////
void RenderWidget::paintGL()
{
//////////////////////////////////////////////////////////////////////////
//生成VBO
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vVerts), vVerts, GL_STATIC_DRAW);
glGenBuffers(1, &_colorBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _colorBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vColors), vColors, GL_STATIC_DRAW);
setShaders();
//开启VBO并设置VBO里面存储的数据模式
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, _colorBuffer);
glVertexAttribPointer(
1,
4,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
//绘制VBO
glDrawArrays(GL_TRIANGLES, 0, 3);
//关闭VBO
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
Shader部分
:
顶点Shader
#version 330
layout(location = 0) in vec4 vertex;
layout(location = 1) in vec4 color;
out vec4 inFragColor;
void main( void )
{
gl_Position = vertex;
inFragColor = color;
}
片元Shader
#version 330
in vec4 inFragColor;
out vec4 outFragColor;
void main( void )
{
outFragColor = inFragColor;
}
VAO的介绍可以参考 <
(1)初始化
//////////////////////////////////////////////////////////////////////////
//初始化VAO
void glGenVertexArrays(GLsizei n, GLuint *arrays);
//创建VAO ID
//参数描述
//n 产生VAO ID的数量
//arrays 保存VAO ID的数组
void glBindVertexArray(GLuint array);
//设置当前操作的VAO对象
//参数描述
//array VAO的ID
VAO的开启当使用glBindVertexArray时自动开启,使用glBindVertexArray(0),传入一个0值可以视为将VAO关闭
设置VAO的过程就是调用VBO中(2)的过程
(3) 绘制
同VBO中绘制(绘制之前先启用VAO)
以上就是VAO涉及到的API,使用过程如下:
首先创建VAO,接着设置VAO(在调用VBO的数据设置过程中VAO自动完成了设置),最后开启VAO并绘制:
//////////////////////////////////////////////////////////////////////////
GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f };
GLfloat vColors [] = { 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f };
//////////////////////////////////////////////////////////////////////////
void RenderWidget::paintGL()
{
//////////////////////////////////////////////////////////////////////////
//生成VBO
glGenBuffers(1, &_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vVerts), vVerts, GL_STATIC_DRAW);
glGenBuffers(1, &_colorBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _colorBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vColors), vColors, GL_STATIC_DRAW);
setShaders();
//初始化VAO
GLuint vaoBuffer;
glGenVertexArrays(1, &vaoBuffer);
glBindVertexArray(vaoBuffer);
//通过设置VBO里面存储的数据模式完成VAO设置
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, _colorBuffer);
glVertexAttribPointer(
1,
4,
GL_FLOAT,
GL_FALSE,
0,
(void*)0
);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//////////////////////////////////////////////////////////////////////////
//上述所有的代码在初始化函数中调用
//只有绘制代码在渲染函数中调用
//使用VAO绘制
glBindVertexArray(vaoBuffer);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
}