OGL绘制相关函数总结2

本文主要整理自http://www.aiuxian.com/article/p-1704414.html,感谢作者wangdingqiaoit

传统的绘图方法

传统立即模式Immediate Mode绘图,性能低且设置复杂(新版ogl建议丢弃该绘制功能)

每次绘制CPU向GPU传递数据并绘制:
//包含头文件
#  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绘图,数据一次性写入显存中,绘制时候不能动态修改

一些2D UI系统中会使用该模式,例如android/ios ui绘图,拥有高性能,但是不灵活。
//依赖库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。

  glFlush()将后台缓存提交到显示器,gluSwapBuffer翻转交换链提交到显示器

//依赖库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绘制函数(有display list存储在GPU优点,也有顶点数组修改数据的优点, 没用Shader还是在客户端设置和维护渲染状态)

VBO即Vertex Buffer Object,是一个在高速视频卡中的内存缓冲,用来保存顶点数据,也可用于包含诸如归一化向量、纹理和索引等数据。

VBO存储了实际的数据,真正重要的不是它存储了数据,而是他将数据存储在GPU中。这意味着VBO它会很快,因为存在RAM中的数据需要被传送到GPU中,因此这个传送是有代价的。

但是VBO的相关状态,在没有启用Shader情况下,VBO相关状态还是在CPU中设置的。

初始化时,用glBufferData, glBufferDataX拷贝数据到GPU以后,就可以释放掉数据了, Shader中也一样。

单纯的VBO形式,非VAO绘制数据:

//依赖库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还是Vertex Array Object, 客户端的VAO(CPU RAM中的)。
VAO即Vertex Array Object ,是一个包含一个或多个VBO的对象,被设计用来存储一个完整被渲染对象所需的信息。

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.), 用于标识从顶点属性(顶点数组)那个位置开始取得顶点属性数据

Shader 着色器文件和字符串,要经过编译才会编译为Shader Object(着色器对象),一个或多个着色器对象要经过Linker(链接)变成一个Shader Program Object 也就是GPU可执行汇编指令程序。
所以OGL中使用Shader还需要,CPU对Shader文件进行处理:
GLuint shader = glCreateShader(eShaderType);//根据类型创建shader
const char * strFileData = strShaderFile.c_str();
glShaderSource(shader,1,&strFileData,NULL);//绑定shader字符串
glCompileShader(shader);//编译shader,得到Shader Object

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 Object, 将Shader程序装载到GPU中。

顶点着色器处理时候,会从 glVertexAttribPointer(0)里面取得一个顶点属性,从glVertexAttribPointer(1)里也获得一个顶点属性,对这些顶点属性会进行一次输入输出;像素着色器是在光栅化之后,所以顶点着色器中输出的颜色值,会是OGL光栅化后片元中对应的插值后颜色值,作为像素着色器的输入
实例代码
Shader.h
#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中数据的更新
更新的时候,顶点属性索引必须在display中设置:
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

在CPU中计算顶点数据并上传到缓存对象中  
在CPU中计算顶点的偏移量并传递给着色器,让它来计算顶点数据  
仅通过CPU提供基本参数,让着色器完成更多的任务

更新VBO中的数据,可以CPU计算结果传递给Shader, 也可以传递基本的参数给GPU,让GPU端在Shader中计算结果,具体看具体场景和数据量。
更新操作:
1)全部在CPU中,  glBufferSubData只拷贝不申请内存
 glBindBuffer(GL_ARRAY_BUFFER,vboId);
 glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(vertices),&vertexList[0]);
 glBindBuffer(GL_ARRAY_BUFFER,0);
2)CPU和GPU中的结合,结合时候有主要计算分配在CPU中,还是GPU中:

shader中:
uniform vec2 offset;

cpp中init:
GLuint offsetLocationId = glGetUniformLocation(programId, "offset");

cpp中display:
glUseProgram(programId);
glUniform2f(offsetLocationId, fXOffset, fYOffset);

glBindBuffer(GL_ARRAY_BUFFER, vboId);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

具体的更新见:

OGL绘图方法总结


  • 使用立即模式的缺点很明显,数据量大一点的话,代码量增加,而且数据发送到服务端需要开销;
  • 使用显示列表,显示列表是一个服务端函数,因此它免除了传送数据的额外开销。但是,显示列表一旦编译后,其中的数据无法修改。
  • 使用顶点数组,可以减少函数调用和共享顶点数据的冗余。但是,使用顶点数组时,顶点数组相关函数是在客户端,因此数组中数据在每次被解引用时必须重新发送到服务端,额外开销不可忽视。
  • 使用VBO在服务端创建缓存对象,并且提供了访问函数来解引用数组;例如在顶点数组中使用的函数如 glVertexPointer(), glNormalPointer(), glTexCoordPointer()。同时,VBO内存管理会根据用户提示,"target"  和"usage"模式,将缓存对象放在最佳地方。因此内存管理会通过在系统内存、AGP内存和视频卡内存(system, AGP and video memory)这3中内存见平衡来优化缓存。另外,不像显示列表,VBO中数据可以通过映射到客户端内存空间而被用户读取和更新。VBO的另外一个优势是它像显示列表和纹理一样,能和多个客户端共享缓存对象。可见使用VBO优势很明显。而VAO是方便绘制时候的切换,减少绘制设置函数。Shader是可编程管线的控制,绘图,且将顶点和着色运算都灵活的在GPU上控制,所以可以得到非常丰富的效果,和自行优化提高性能。因此VBO,结合VAO和Shader的绘制方式是当前最优秀的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值