本文主要整理自http://www.aiuxian.com/article/p-1704414.html,感谢作者wangdingqiaoit
传统的绘图方法
传统立即模式Immediate Mode绘图,性能低且设置复杂(新版ogl建议丢弃该绘制功能)
//包含头文件
# include <GL/glew.h>
# include <GL/freeglut.h>
#pragma comment(lib,"glew32d.lib")
//函数原型声明
void userInit();//自定义初始化函数
void display(void);//绘制回调函数
void keyboardAction(unsigned char key, int x, int y);//键盘按键回调函数
//程序入口函数
int main(int argc, char **argv)
{
glutInit(&argc, argv);//初始化GLUT
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);//设置显示模式
glutInitWindowPosition(100, 100);//设置窗口起始位置
glutInitWindowSize(512, 512);//设置绘图窗口的初始高度和宽
glutCreateWindow("Triangle demo");//在屏幕上创建一个窗口
glewInit();//使用GLEW时,使用该函数初始化GLEW
userInit();//自定义的初始化函数
glutDisplayFunc(display);//注册绘制窗口回调函数
glutKeyboardFunc(keyboardAction);//注册键盘事件回调函数
glutMainLoop();//开始事件循环
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);//设置清屏颜色
glColor4f(1.0, 1.0, 0.0, 0.0);//设置绘制颜色
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓存
glBegin(GL_TRIANGLES);
glVertex3f(-0.5, -0.5, 0.0);
glVertex3f(0.5, 0.0, 0.0);
glVertex3f(0.0, 0.5, 0.0);
glEnd();
glFlush();//强制刷新
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key) {
case 033: // Escape key
case 'q': case 'Q':
exit(EXIT_SUCCESS);//退出程序
break;
}
}
显示列表Display List绘图,数据一次性写入显存中,绘制时候不能动态修改
//依赖库glew32.lib freeglut.lib
//使用顶点列表绘制三角形(已过时,仅为学习目的)
# include <GL/glew.h>
# include <GL/freeglut.h>
#pragma comment(lib,"glew32d.lib")
void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);
//显示列表句柄
GLuint displayListId;
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(512, 512);
glutCreateWindow("Triangle demo");
glewInit();
userInit();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glColor4f(1.0, 1.0, 0.0, 0.0);
//创建显示列表,和向显存中写入顶点数据
displayListId = glGenLists(1);
glNewList(displayListId, GL_COMPILE);
glBegin(GL_TRIANGLES);
glVertex3f(-0.5, -0.5, 0.0);
glVertex3f(0.5, 0.0, 0.0);
glVertex3f(0.0, 0.5, 0.0);
glEnd();
glEndList();
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
//利用显示列表,绘制三角形
glPushMatrix();
glTranslatef(-0.5, 0, 0);
glCallList(displayListId); // 绘制一次
glPopMatrix();
glPushMatrix();
glTranslatef(0.5, 0, 0);
glCallList(displayListId); // 绘制二次
glPopMatrix();
glFlush();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
现代的绘图方法
顶点数组,类似D3D9顶点缓存和索引缓存绘图,每帧还是需要传递数据到xian
这里的VAO还是Vertex Array Object, 客户端的VAO(CPU RAM中的)。
使用顶点数组方式,需要利用glEnableClientState
开启一些特性,这里开启顶点数组特性使用glEnableClientState(GL_VERTEX_ARRAY)
。
使用顶点数组时,用户定义好存储顶点的数据,在调用glDrawArrays、glDrawElements之类的函数时,通过glVertexPointer
设定的指针,传送数据到GPU。当调用完glDrawArrays后,GPU中已经有了绘图所需数据,用户可以释放数据空间(其实是一帧结束后,每帧还是要向GPU传递数据的)。
一次draw call传递数据到显存中,绘制到后台缓存中,只是封装了glBegin/glEnd。
//依赖库glew32.lib freeglut.lib
//使用Vertex Arrays顶点数组绘制三角形(不推荐使用)
# include <GL/glew.h>
# include <GL/freeglut.h>
#pragma comment(lib, "glew32d.lib")
void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);
//定义一个包含3个float的结构体
//为了保持简单,暂时未引入c++类概念
struct vec3f {
GLfloat x, y, z;
};
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(512, 512);
glutCreateWindow("Triangle demo");
glewInit();
userInit();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glColor4f(1.0, 1.0, 0.0, 0.0);
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
//利用顶点数组,绘制三角形
const int num_indices = 3;
//创建保存顶点的结构体数组
vec3f *vertices = new vec3f[num_indices];
// 顶点1
vertices[0].x = -0.5f;
vertices[0].y = -0.5f;
vertices[0].z = 0.0f;
// 顶点2
vertices[1].x = 0.5f;
vertices[1].y = 0.0f;
vertices[1].z = 0.0f;
//顶点3
vertices[2].x = 0.0f;
vertices[2].y = 0.5f;
vertices[2].z = 0.0f;
// 启用vertex arrays
glEnableClientState(GL_VERTEX_ARRAY);
//定义顶点数组
glVertexPointer(
3, // 每个顶点的维度
GL_FLOAT, // 顶点数据类型
0, // 连续顶点之间的间隙,这里为0
vertices //指向第一个顶点的第一个坐标的指针
);
// 一次draw call传递数据到显存中,绘制到后台缓存中,只是封装了glBegin/glEnd
glDrawArrays(GL_TRIANGLES, 0, num_indices);
glDisableClientState(GL_VERTEX_ARRAY);
//释放内存空间
delete[] vertices;
// 将后台缓存提交到显示器,gluSwapBuffer翻转交换链提交到显示器
glFlush();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
单纯VBO 形式,渲染状态在客户端,需glEnableClientState和glVertexPointer指定数据
VBO即Vertex Buffer Object,是一个在高速视频卡中的内存缓冲,用来保存顶点数据,也可用于包含诸如归一化向量、纹理和索引等数据。
VBO存储了实际的数据,真正重要的不是它存储了数据,而是他将数据存储在GPU中。这意味着VBO它会很快,因为存在RAM中的数据需要被传送到GPU中,因此这个传送是有代价的。
但是VBO的相关状态,在没有启用Shader情况下,VBO相关状态还是在CPU中设置的。
初始化时,用glBufferData, glBufferDataX拷贝数据到GPU以后,就可以释放掉数据了, Shader中也一样。
//依赖库glew32.lib freeglut.lib
//使用VBO绘制三角形(现代OpenGL方式)
# include <GL/glew.h>
# include <GL/freeglut.h>
#pragma comment(lib, "glew32d.lib")
void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);
//VBO句柄
GLuint vboId;
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(512, 512);
glutCreateWindow("Triangle demo");
glewInit();
userInit();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glColor4f(1.0, 1.0, 0.0, 0.0);
//创建顶点数据
GLfloat *vertices = (GLfloat*)malloc(sizeof(GLfloat) * 9);
//memset(vertices, 0, sizeof(GLfloat)* 9);
GLfloat vertices2[]= {
-0.5, -0.5, 0.0,
0.5, 0.0, 0.0,
0.0, 0.5, 0.0
};
memcpy(vertices, vertices2, sizeof(GLfloat)* 9);
//分配vbo句柄
glGenBuffersARB(1, &vboId);
//GL_ARRAY_BUFFER_ARB表示作为顶点数组解析
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);
//拷贝数据
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)*9,
vertices, GL_STATIC_DRAW_ARB);
// 对CPU禁用VBO
glBindBufferARB(GL_VERTEX_ARRAY, 0);
free(vertices); // glBufferData,glBufferDataARB 函数会一次拷贝完数据到GPU中,后面清除即可。
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);//绑定vbo
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点数组属性
glVertexPointer(3, GL_FLOAT, 0, 0);
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);//绑定vbo
//glEnableClientState(GL_VERTEX_ARRAY);//启用顶点数组属性
// 如何解析vbo中数据, 用VBO传递NULL数据进入,GPU会取到当前激活的VBO中的顶点数据;
// 没有VAO则每帧需要glEnableClientState和glVertexPointer指定,当然绘制一个数据 可以不指定,绘制多个应该有问题。
//glVertexPointer(3, GL_FLOAT, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
//glDisableClientState(GL_VERTEX_ARRAY);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);//解除绑定
glFlush();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
VBO, VAO形式,渲染状态在客户端,需glEnableClientState和glVertexPointer指定数据
VAO代表的是一些描述存储在VBO中对象的属性。VAO可以被视为指向对象的高级内存指针,有点类似于C语言指针,但比地址多了跟多的跟踪作用。他们很复杂。
VAO很像是辅助对象,而不是实际的数据存储对象。他们记录了当前绘制过程中的属性,描述对象的属性,而不是已经存储在VBO中原始数据。
VAO并不与VBO直接相关,进过初看起来如此。VAOs节省了设置程序所需的状态的时间。如果没有VAO,你需要调用一堆类似gl*之类的命令(例如glVertexPointer 在绘制时候调用,设置时候真正关联VAO和VBO还是用glVertexPointer等函数的)。
VAO只是存储了顶点数组对象集合,每个顶点数组对象关联VBO,和关联绘制函数绘制状态,需要切换绘制物体,直接一句glBindVertexArray(VAO[i]);切换激活的顶点数组对象即可。
VBO将真实的顶点数据存储在GPU显卡中,提供绘图数据传递性能,绘制时候还是要用glDrawElements等绘图函数,这个glDrawElements在VBO下没有将数据从CPU传递到GPU,但是驱动了GPU中的顶点数据,进行渲染管道的转换和光照计算,像素融合计算,进行stencil depth test, 抖动融合逻辑操作等,提交到当前帧中的后台颜色缓存中,所以在VBO下也要靠减少Draw call来提高图形性能的。
//依赖库glew32.lib freeglut.lib
//使用VBO绘制三角形(现代OpenGL方式)
# include <GL/glew.h>
# include <GL/freeglut.h>
#pragma comment(lib, "glew32d.lib")
void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);
//VBO句柄
GLuint vboId[2];
GLuint vaoId[2];
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1024, 1024);
glutCreateWindow("Triangle demo");
glewInit();
userInit();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glGenVertexArrays(2, vaoId);
//分配vbo句柄
glGenBuffersARB(2, vboId);
//创建顶点数据
GLfloat *vertices = (GLfloat*)malloc(sizeof(GLfloat)* 9);
memset(vertices, 0, sizeof(GLfloat)* 9);
glBindVertexArray(vaoId[0]);
//GL_ARRAY_BUFFER_ARB表示作为顶点数组解析
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId[0]);//绑定vbo
glColor4f(1.0, 1.0, 0.0, 0.0);
GLfloat vertices1[] = {
-0.5, -0.5, 0.0,
0.0, 0.0, 0.0,
-0.5, 0.5, 0.0
};
memcpy(vertices, vertices1, sizeof(GLfloat)* 9);
//拷贝数据
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)* 9,
vertices, GL_STATIC_DRAW_ARB);
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点数组属性
glVertexPointer(3, GL_FLOAT, 0, 0);
glBindVertexArray(vaoId[1]);
glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId[1]);
GLfloat vertices2[] = {
0, 0, 0.0,
0.5, -0.5, 0.0,
0.5, 0.5, 0.0
};
memcpy(vertices, vertices2, sizeof(GLfloat)* 9);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, sizeof(GLfloat)* 9,
vertices, GL_STATIC_DRAW_ARB);
glEnableClientState(GL_VERTEX_ARRAY);//启用顶点数组属性
glVertexPointer(3, GL_FLOAT, 0, 0);
// 对CPU禁用VBO
glBindBufferARB(GL_VERTEX_ARRAY, 0);
free(vertices); // glBufferData,glBufferDataARB 函数会一次拷贝完数据到GPU中,后面清除即可。
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
for (int i = 0; i < 2; ++i)
{
float r = 0;
float g = 0;
if (i == 0)
{
r = 1.0f;
}
else
{
g = 1.0f;
}
glColor4f(r, g, 0.0, 0.0);
/*glPushMatrix();
glTranslatef(0.5 * i, 0.0f, 0.0f);*/
// vao是关联了VBO的索引结构,通过glBindVertexArray(vaoId);可以切换不同的VBO数据。
glBindVertexArray(vaoId[i]);
glDrawArrays(GL_TRIANGLES, 0, 3);
//glPopMatrix();
}
glFlush();
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
VBO,VAO,Shader形式
VBO,VAO,Shader基本认识
服务器端VAO(绘制索引为简单), VBO(顶点存放),Shader(可编程管线控制)绘制,每帧绘制只用glDraw
使用VBO,向GPU VBO中传递数据
这里的VAO是:Vertex Array Object和vertex attribute object
(1)VAO需要glBindVertexArray(vaoId);和glEnableVertexAttribArray(index); glVertexAttribPointer关联顶点属性数据和Shader输入属性索引,不能用客户端的的glVertexPointer关联VAO和VBO了,因为要考虑Shader的顶点属性的输入。
(2)无论是VBO数据,还是Shader程序都是载入一次到GPU中,后面每帧渲染时候激活和去激活即可。
(3) 数据不改变时候可以封装到Vertex Array Object中, 实现display时候简单切换;即可以在创建VBO时候,关联好VAO如何解析VBO(也就是glEnableVertexAttribArray(index); glVertexAttribPointer会根据绘制截断指定的数据,但不能glDisableVertexAttribArray(VBO的关闭却是可以的))的状态和索引集合,绘制时候glBindVertexArray(vaoId)切换绘制状态集合,给glUseProgram(programId)绘制即可。
但当Vertext Shader中的数据是改变的,那么在每帧display中都需要指定:glEnableVertexAttribArray,glVertexAttribPointer,而不能封装到Vertex Array Object中。
更多VAO,VBO,Shader使用的提示:
1.没有一个合适的地方给glDisableVertexAttribArray了,事实上调用glBindVertexArray(NULL)的时候里面所有状态都”关掉“了,也就没所谓针对顶点属性的location做其他什么;
2.glBindBuffer(GL_ARRAY_BUFFER, NULL)/glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, NULL)一定要在glBindVertexArray(NULL)后面(不然VAO就把它们也包含了,最后就渲染不出东西了);VAO只是记录了VBO的数据位置,解析VBO GL_ARRAY_BUFFER顶点中位置,uv, 法向量,颜色,雾,边缘的属性索引下标,GL_ELEMENT_ARRAY_BUFFER加载的索引数据的位置等VBO顶点索引数据,当glBindVertexArray(NULL)时候就会关闭索引的记录。
3.glDrawElements里面的东西(顶点索引的属性状态)VAO可没记录保存,因此需要绘制;
4.glVertexPointer那类函数理论上也可以,但是建议还是不要混用deprecated的函数进去了。
(4)顶点属性是并行输入给顶点Shader的,片元Shader是在光栅化插值位置后,也是并行输入处理逐个像素的。
(5)一次DrawCall就会进行一次图形渲染流水线过程(无论是固定管线,还是Shader的,顶点无论是VBO 显卡中,还是CPU RAM中的),进行顶点变换,光照计算,三角形背面剔除顶点Shader;硬件透视除法视口转换,光栅化;片元着色,计算纹理,抗锯齿,雾计算片元着色器计算;alpha检测,深度检测,stencil检测; alpha融合,抖动,逻辑操作,载入当前的后台FrameBuffer中。
所以减少DrawCall次数,单次提交的数据量,和启用的渲染状态计算量,才能正真提高GPU渲染的性能。
CPU的性能在CPU端的运算。
内存的压力在于GPU显存的使用,CPU计算的数据量和算法复杂度, CPU计算的频率。
IO的压力在于场景大小数据量的大小,和是否能够预加载和重用数据。
Shader基础
着色器就是运行在GPU上的一个程序而已。在绘制管线中有几个可能的着色器阶段,每个阶段都有它自己的输入输出。着色器的目的是将输入包括潜在的其他类型数据,转换到输出集中。
每个着色器都在输入集上执行。值得注意的是,在任何阶段,一个着色器都完全独立于那一阶段的其他着色器(最适合并发简单处理,而不是复杂的CPU逻辑)。独立执行的着色器之间不会有交叉。每个输入集的处理从着色器开始到结束阶段。着色器定义了它的输入输出,通常,没有完成输出数据任务的着色器是非法的。
着色器有着输入输出,就好比一个有参数和返回值的函数一样。
输入和输出来自和转到一些地方去了。因此,输入position 肯定在某处被填充了数据。那么这些数据来自哪里呢?顶点着色器的输入被称为顶点属性(vertex attributes)。
每个顶点着色器的输入有一个索引位置称作属性索引(attribute index.), 用于标识从顶点属性(顶点数组)那个位置开始取得顶点属性数据。
实例代码
#ifndef _SHADER_H_
#define _SHADER_H_
#include <vector>
#include <string>
#include <cstring>
#include <GL/glew.h>
class Shader {
public:
static GLuint createShader(GLenum eShaderType, const std::string &strShaderFile);
static GLuint createShader(GLenum eShaderType, const char* fileName);
static GLuint createProgram(const std::vector<GLuint> &shaderList);
};
#endif
Shader.cpp
#include <fstream>
#include <sstream>
#include "Shader.h"
//从字符串流构造着色器对象
GLuint Shader::createShader(GLenum eShaderType, const std::string &strShaderFile)
{
GLuint shader = glCreateShader(eShaderType);//根据类型创建shader
const char * strFileData = strShaderFile.c_str();
glShaderSource(shader, 1, &strFileData, NULL);//绑定shader字符串
glCompileShader(shader);//编译shader
//检查shader状态
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint infoLogLength;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
GLchar *strInfoLog = new GLchar[infoLogLength + 1];
glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);
const char * strShaderType = NULL;
switch (eShaderType)
{
case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
}
fprintf(stderr, "Compile failure in %s shader:\n%s\n", strShaderType, strInfoLog);
delete[] strInfoLog;
}
return shader;
}
//从文件构造着色器对象
GLuint Shader::createShader(GLenum eShaderType, const char* fileName)
{
std::ifstream infile(fileName);
if (!infile)
{
fprintf(stderr, "Could not open file : %s for reading.", fileName);
return 0;
}
std::stringstream buffer;
buffer << infile.rdbuf();
infile.close();
return Shader::createShader(eShaderType, buffer.str());
}
//构造着色器程序对象
GLuint Shader::createProgram(const std::vector<GLuint> &shaderList)
{
GLuint programId = glCreateProgram();//创建program
for (std::vector<GLuint>::size_type iLoop = 0; iLoop < shaderList.size(); iLoop++)
glAttachShader(programId, shaderList[iLoop]);//绑定shader
glLinkProgram(programId);//链接shader
//检查program状态
GLint status;
glGetProgramiv(programId, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint infoLogLength;
glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);
GLchar *strInfoLog = new GLchar[infoLogLength + 1];
glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);
fprintf(stderr, "Linker failure: %s\n", strInfoLog);
delete[] strInfoLog;
}
for (size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
glDetachShader(programId, shaderList[iLoop]);
return programId;
}
VAO_VBO_Shader.CPP
依赖库glew32.lib freeglut.lib
使用VAO VBO和着色器绘制三角形(现代OpenGL方式)
//#include <string>
//#include <vector>
//#include <GL/glew.h>
//#include <GL/freeglut.h>
//#pragma comment(lib, "glew32d.lib")
//#include "Shader.h"
//using namespace std;
//
//void userInit();
//void reshape(int w, int h);
//void display(void);
//void keyboardAction(unsigned char key, int x, int y);
//
//
//GLuint vboId;//vertex buffer object句柄
//GLuint vaoId;//vertext array object句柄
//GLuint programId;//shader program 句柄
//
//int main(int argc, char **argv)
//{
// glutInit(&argc, argv);
//
// glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
// glutInitWindowPosition(100, 100);
// glutInitWindowSize(512, 512);
// glutCreateWindow("Triangle demo");
//
// glewInit();
// userInit();
// glutReshapeFunc(reshape);
// glutDisplayFunc(display);
// glutKeyboardFunc(keyboardAction);
// glutMainLoop();
// return 0;
//}
自定义初始化函数
//void userInit()
//{
// glClearColor(0.0, 0.0, 0.0, 0.0);
// //创建顶点数据
// const GLfloat vertices[] = {
// -0.5f, -0.5f, 0.0f, 1.0f,
// 0.5f, 0.0f, 0.0f, 1.0f,
// 0.0f, 0.5f, 0.0f, 1.0f
// };
// //创建vertex array object对象
// glGenVertexArrays(1, &vaoId);
// glBindVertexArray(vaoId);
// //创建vertex buffer object对象
// glGenBuffers(1, &vboId);
// glBindBuffer(GL_ARRAY_BUFFER, vboId);
// glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// //glVertexPointer(4, GL_FLOAT, 0, 0);// 一定要VBO激活的时候才能使用,传递数据
// //glEnableClientState(GL_VERTEX_ARRAY);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// //创建着色器
// const std::string vertexStr(
// "#version 330\n"
// "in vec4 position;\n"// layout(location=0) 这个可以不使用,默认应该是该值
// "void main()\n"
// "{gl_Position = position;}\n"
// );
// const std::string fragmentStr(
// "#version 330\n"
// "out vec4 outputColor;\n"
// "void main()\n"
// "{outputColor = vec4(1.0f,1.0f,0.0f,1.0f);}\n"
// );
// std::vector<GLuint> idVector;
// idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, vertexStr)); // 指定Shader文件对应的着色器类型
// idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, fragmentStr));
// programId = Shader::createProgram(idVector);
//}
调整窗口大小回调函数
//void reshape(int w, int h)
//{
// glViewport(0, 0, (GLsizei)w, (GLsizei)h);
//}
绘制回调函数
//void display(void)
//{
// glClear(GL_COLOR_BUFFER_BIT);
// glUseProgram(programId);
// glBindBuffer(GL_ARRAY_BUFFER, vboId);
// //glBindVertexArray(vaoId);
// // 当然不用VAO的话也可以只用VBO,这里就是glEnableVertexAttribArray(0),glVertexAttribPointer指定
// glEnableVertexAttribArray(0);
// glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
// glDrawArrays(GL_TRIANGLES, 0, 3);
//
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// glUseProgram(0);
// glDisableVertexAttribArray(0);
// glutSwapBuffers();
//}
键盘按键回调函数
//void keyboardAction(unsigned char key, int x, int y)
//{
// switch (key)
// {
// case 033: // Escape key
// exit(EXIT_SUCCESS);
// break;
// }
//}
//依赖库glew32.lib freeglut.lib
//使用着色器颜色插值绘制三角形
#include <string>
#include <vector>
#include <GL/glew.h>
#include <GL/freeglut.h>
#pragma comment(lib, "glew32d.lib")
#include "shader.h"
using namespace std;
void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);
GLuint vboId;//vertex buffer object句柄
GLuint vaoId;//vertext array object句柄
GLuint programId;//shader program 句柄
GLuint offsetLocationId;
int main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(512, 512);
glutCreateWindow("Triangle demo");
glewInit();
userInit();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
glutKeyboardFunc(keyboardAction);
glutMainLoop();
return 0;
}
//自定义初始化函数
void userInit()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
//顶点位置和颜色数据
const GLfloat vertexData[] = {
-0.5f, 0.0f, 0.0f, 1.0f,
0.5f, 0.0f, 0.0f, 1.0f,
0.0f, 0.5f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
//创建vertex array object对象
glGenVertexArrays(1, &vaoId);
glBindVertexArray(vaoId);
//创建vertex buffer object对象
glGenBuffers(1, &vboId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
//启用顶点位置属性索引,要在display中指定的,因为需要开启顶点属性索引才能绘制,特别是绘制物体多的时候,需要切换才能正确绘制。
// 也可以封装在VAO中,只负责启用glEnableVertexAttribArray不关闭即可。
glEnableVertexAttribArray(0); // 激活顶点属性数组
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position顶点属性数据格式,大小会根据glDrawArrays截断。
//启用顶点颜色属性索引
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);// 指定Color顶点属性数据格式,大小会根据glDrawArrays截断。
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 这里不能关闭,否则Shader取不到数据
//glDisableVertexAttribArray(0); // 去激活VAO的顶点属性
//glDisableVertexAttribArray(1);
//从文件创建着色器
/*std::vector<GLuint> idVector;
string strPre = "E:\\OpenGL\\OpenGl7thEdition-master\\OpenGl7thEdition-master\\OpenGL_MyProject\\hello\\data";
idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, strPre + "\\vertex.glsl"));
idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, strPre + "\\fragment.glsl"));
programId = Shader::createProgram(idVector);*/
//从文件创建着色器
std::vector<GLuint> idVector;
const std::string vertexStr(
"#version 330\n"
"in vec4 pos;\n"
"in vec4 incolor;\n"
"uniform vec2 offset;\n"
"smooth out vec4 thecolor;\n"
"void main()\n"
"{\n"
"vec4 totalOffset = vec4(offset.x, offset.y, 0.0, 0.0);\n"
"gl_Position = pos + totalOffset;\n"
"thecolor = incolor;}\n"
);
const std::string fragmentStr(
"#version 330\n"
"smooth in vec4 thecolor;\n"
"out vec4 outputColor;\n"
"void main()\n"
"{outputColor = thecolor;}\n"
);
idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, vertexStr));// "data\\vertex.glsl"));
idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, fragmentStr));// "data\\fragment.glsl"));
programId = Shader::createProgram(idVector);
offsetLocationId = glGetUniformLocation(programId, "offset");
//int nStereoSupport = 0;
//glGetIntegerv(GL_STEREO, &nStereoSupport); // Win7 OGL 3.1不支持
//int nDoubleFrameBufferSupport = 0;
//glGetIntegerv(GL_DOUBLEBUFFER, &nDoubleFrameBufferSupport);// Win7 OGL 3.1支持
//int nAluColorBuffer = 0;
//glGetIntegerv(GL_AUX_BUFFERS, &nAluColorBuffer);// Win7 OGL 3.1不支持,只有0个颜色辅助缓存
}
//调整窗口大小回调函数
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
//根据时间计算偏移量
void ComputePositionOffsets(GLfloat &fXOffset, GLfloat &fYOffset)
{
const GLfloat fLoopDuration = 5.0f;
const GLfloat fScale = 3.14159f * 2.0f / fLoopDuration;
GLfloat fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
GLfloat fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);
fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
}
//绘制回调函数
//void display(void)
//{
//
// glClear(GL_COLOR_BUFFER_BIT);
// // 绑定到VAO状态,也就是封装了通过VAO 的glEnableVertexAttribArray,glVertexAttribPointer关联起来可以解释的VBO数据作为输入
// // 这样通过VAO的切换,就可以在轻松的切换VBO数据源,且正确的解释VBO数据源作为Shader的输入,能够方便的进行绘制切换。
//
//
// glBindVertexArray(vaoId);
// glUseProgram(programId);// 启用GPU中的Shader机器码程序
// GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
// ComputePositionOffsets(fXOffset, fYOffset);
// glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量发送到顶点着色器
//
//
// //绘制三角形,用glDrawElemenets不能正确绘制,因为这里需要连续的
// glDrawArrays(GL_TRIANGLES, 0, 3);
// glUseProgram(0);
// // 关闭GL_ARRAY_BUFFER,glDisableVertexAttribArray,也是可以正确绘制的,
// // 说明glBindVertexArray(vaoId)是正确封装了需要关联了启用状态和索引关系的集合,直接glBindVertexArray切换绘制即可。
// //glBindBuffer(GL_ARRAY_BUFFER, 0); // 去激活GPU中的该VBO
// //glDisableVertexAttribArray(0); // 去激活VAO的顶点属性
// //glDisableVertexAttribArray(1);
//
// glutSwapBuffers();
//}
//绘制回调函数
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
//计算偏移量
GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
ComputePositionOffsets(fXOffset, fYOffset);
glUseProgram(programId);
glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量发送到顶点着色器
glBindBuffer(GL_ARRAY_BUFFER, vboId);
//启用顶点位置属性索引
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
//绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUseProgram(0);
glDisableVertexAttribArray(0);
glutSwapBuffers();
glutPostRedisplay();//不断刷新
}
//键盘按键回调函数
void keyboardAction(unsigned char key, int x, int y)
{
switch (key)
{
case 033: // Escape key
exit(EXIT_SUCCESS);
break;
}
}
VBO中数据的更新
在CPU中计算顶点的偏移量并传递给着色器,让它来计算顶点数据
仅通过CPU提供基本参数,让着色器完成更多的任务
OGL绘图方法总结
- 使用立即模式的缺点很明显,数据量大一点的话,代码量增加,而且数据发送到服务端需要开销;
- 使用显示列表,显示列表是一个服务端函数,因此它免除了传送数据的额外开销。但是,显示列表一旦编译后,其中的数据无法修改。
- 使用顶点数组,可以减少函数调用和共享顶点数据的冗余。但是,使用顶点数组时,顶点数组相关函数是在客户端,因此数组中数据在每次被解引用时必须重新发送到服务端,额外开销不可忽视。
- 使用VBO在服务端创建缓存对象,并且提供了访问函数来解引用数组;例如在顶点数组中使用的函数如 glVertexPointer(), glNormalPointer(), glTexCoordPointer()。同时,VBO内存管理会根据用户提示,"target" 和"usage"模式,将缓存对象放在最佳地方。因此内存管理会通过在系统内存、AGP内存和视频卡内存(system, AGP and video memory)这3中内存见平衡来优化缓存。另外,不像显示列表,VBO中数据可以通过映射到客户端内存空间而被用户读取和更新。VBO的另外一个优势是它像显示列表和纹理一样,能和多个客户端共享缓存对象。可见使用VBO优势很明显。而VAO是方便绘制时候的切换,减少绘制设置函数。Shader是可编程管线的控制,绘图,且将顶点和着色运算都灵活的在GPU上控制,所以可以得到非常丰富的效果,和自行优化提高性能。因此VBO,结合VAO和Shader的绘制方式是当前最优秀的。