OpenGL学习脚印: 顶点数据传送和着色器处理1
写在前面
本节内容翻译和整理自《Learning Modern 3D Graphics Programming》Chapter1内容。作为学习目的,本文内容上不会完全遵从原文,有删节。另外原文示例代码有它独有的框架组织方式,为了保持自己的一贯风格,这里重写了示例程序代码,如果发现错误,请纠正我。转载需经过作者同意。
通过本节,你可以了解到:
- OpenGL中顶点数据传递方式
- Shader着色器的工作原理和过程
- VAO、VBO的使用
1.顶点数据传递方式
把握两点:
我们要在哪儿分配OpenGL可见的内存(给它数据buffer object)?
我们怎么告诉OpenGL如何解释分配的内存(给它附加描述信息glVertexAttribPointer)?
绘制管线的第一阶段是将顶点数据映射到裁剪空间。在OpenGL这样处理前,它必须接受一个顶点列表。因此,管线的最初阶段是发送三角形数据到OpenGL。这是我们要发送的数据:
- 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
- };
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
};
这些数据每行的4个值代表一个4D顶点坐标,因为裁剪坐标系是4维的。
这些坐标已经在裁剪坐标系范围内了。我们想要OpenGL做得就是根据这些数据绘制三角形。
尽管我们已经有了数据,OpenGL并不能直接使用它们。OpenGL对它能读取的内存有些限制。你可以按需分配你的顶点数据,但是这些内存对OpenGL并不直接可见。因此,第一步就是分配OpenGL可见的内存,并填充我们的数据。这是通过缓存对象(buffer object,以下简称BO)来实现的。
一个缓存对象,是一个线性数组形式的内存,由OpenGL根据用户请求管理和分配。这块内存的内容可由用户控制,但是用户也仅能间接地控制。可以把buffer object当做GPU内存中的数组。
GPU可以快速读取它,因此在它里面存储数据有性能优势。在本节中,缓存对象是这样来创建的:
代码片段:
- //创建vertex buffer object对象
- glGenBuffers(1,&vboId);
- glBindBuffer(GL_ARRAY_BUFFER,vboId);
- glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
- glBindBuffer(GL_ARRAY_BUFFER,0);
//创建vertex buffer object对象
glGenBuffers(1,&vboId);
glBindBuffer(GL_ARRAY_BUFFER,vboId);
glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
第一行,创建了buffer object,并将句柄存储在全局变量中。尽管对象已经存在,它没有任何空间。因为我们还未给他分配任何空间。glBindBuffer函数将新建的BO绑定到GL_ARRAY_BUFFER上下文中。
glBufferData 函数执行两个操作。它分配了当前绑定到glBufferData 的缓存的空间,这就是我们刚刚创建和绑定的BO。我们已经有了顶点数据,问题是它在我们的RAM中而不是OpenGL的内存中。sizeof(vertexPositions) 计算顶点数组的字节大小。我们向glBufferData 传递此值来表明分配空间的大小。这样在GPU内存中就有足够空间来保存顶点数据。
glBufferData 执行的另一个操作是从我们的数组内存RAM中拷贝数据到BO中。第三个参数控制了这个复制。如果这个参数不是NULL,正如此例,glBufferData 会将指针所指数据拷贝到BO中。当这个函数执行完后,BO中就有了顶点数据了。
第四个参数,稍后解释。
第二次调用glBufferData ,执行的是清理任务。通过绑定0值到GL_ARRAY_BUFFER,我们使之前绑定到这个目标的BO从该目标解除绑定。0在这里充当了NULL指针的作用。这并不是必须的,因为之后的绑定会自动解除已有的绑定。但是除非你严格的控制着你的渲染,通常解除你绑定的对象是个好的想法。
这样完成了发送顶点数据到GPU的任务。但是BO中的数据时未格式化的,但这是OpenGL关心的。我们只是分配了BO,并填充了些随机二进制数据。现在我们需要告诉OpenGL,BO中有顶点数据,并告诉他顶点数据的格式。我们通过下面这样的代码来完成这一任务:
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
第一个函数声明使用BO。第二个函数启动顶点属性数组,这个稍后解释。
第三个函数是关键的。glVertexAttribPointer,尽管,包含”Pointer”一词,但是实际上它处理的并不是指针,而是BO。
在渲染时,OpenGL从BO中提取顶点数据。我们要做的就是通告OpenGL存储在BO中的顶点数组中数据格式。也就是要告诉OpenGL如何解释BO中的数组。
在我们的案例中,数据格式如下
- 表示位置的单个数据值以32位浮点数据存储,使用C/C++ float类型。
- 每个位置由4个这样的值组成。
- 每4个值之间没有间隙,数据值在数组中紧密相连。
- 数组中第一个值在BO的开始处
glVertexAttribPointer 函数告诉了OpenGL所有这些情况。第三个参数制定了值得基本类型,即GL_FLOAT,对应32位浮点数据。第二个参数,指定多少个这样的值组成一个位置,即一个点。在这里,即4个值组成一个点。第5个参数指定数据间间隙,第6个参数指定BO中数据偏移量,0代表从BO的开始处算起。
第四个参数以后再做解释。
还有一件事好像没做,那就是指定从哪个BO中获取数据。这是一个隐含的而不是显示的关系。glVertexAttribPointer 总是指向它调用是绑定到GL_ARRAY_BUFFER 的缓存。因此,这里不需要传递BO的句柄。
关于这个函数,更多的解释,此处不再展开。
一旦OpenGL知道从哪里获取顶点数据,就可以渲染三角形了:
glDrawArrays(GL_TRIANGLES, 0, 3);
2.顶点处理和着色器
把握两点:
顶点和片元着色器的输入输出
顶点数据的流向
既然我们能够告诉OpenGL定点数据了,我们转到绘制管线的下一阶段:顶点处理。
在这个阶段中涉及到使用着色器(Shader)。
着色器就是运行在GPU上的一个程序而已。在绘制管线中有几个可能的着色器阶段,每个阶段都有它自己的输入输出。着色器的目的是将输入包括潜在的其他类型数据,转换到输出集中。
每个着色器都在输入集上执行。值得注意的是,在任何阶段,一个着色器都完全独立于那一阶段的其他着色器。独立执行的着色器之间不会有交叉。每个输入集的处理从着色器开始到结束阶段。着色器定义了它的输入输出,通常,没有完成输出数据任务的着色器是非法的。
顶点着色器,从名字上来看,操作的就是顶点。具体来说,每次触发顶点着色器都作用于单个顶点。这些着色器除了用户定义的输出外,必须输出顶点的裁剪坐标系中的位置。如何计算这个裁剪坐标系位置,完全取决于着色器。
在OpenGL中,着色器使用GLSL( OpenGL Shading Language )语言书写。看起来好像C语言,但实际上受到的限制很大,例如不能使用递归。我们的顶点着色器看起来是这样:
- #version 330
- layout(location = 0) in vec4 position;
- void main()
- {
- gl_Position = position;
- }
#version 330
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
看起来很简单。第一行定义版本3.30。所有GLSL着色器都必须声明版本。
下一行,定义了顶点着色器的输入。这个输入变量是position,类型是4维的浮点型的vec4。Layout location 0稍后解释。
就像C语言一样,以main函数开始。这个着色器很简单,它的任务,就是将输入position拷贝到输出gl_position。这个变量是着色器内置变量,如果你看到”gl_”开头的变量,那么一定是内置变量。”gl_”变量,不允许自己定义,你只能使用已经存在的。
gl_Position定义如下
out vec4 gl_Position;
刚刚说过,顶点着色器的基本任务是产生裁剪坐标系中顶点位置。这就是gl_Position,它就是裁剪坐标系的坐标。因为我们定义的顶点数据已经在裁剪坐标系下了,因此直接输出它即可。
顶点属性
着色器有着输入输出,就好比一个有参数和返回值的函数一样。
输入和输出来自和转到一些地方去了。因此,输入position 肯定在某处被填充了数据。那么这些数据来自哪里呢?顶点着色器的输入被称为顶点属性(vertex attributes)。
你可能认得一些类似的顶点属性术语,例如,“glEnableVertexAttribArray” 或者 “glVertexAttribPointer.”
这就是数据如何从管线中流动下来的。在渲染开始时,BO中的顶点数据,在glVertexAttribPointer初始化工作的基础上来读取。
这个函数描述了属性中数据的来源。glVertexAttribPointer和顶点着色器中某个字符串名字对应的输入之间的连接时有些复杂的。
每个顶点着色器的输入有一个索引位置称作属性索引(attribute index.)。在上例中输入定义为:
layout(location = 0) in vec4 position;
Layout location部分将属性索引0赋给position了。属性索引必须不小于0.并且受到硬件限制。
在代码中,当引用属性时,总是有属性索引来解引用。glEnableVertexAttribArray、glDisableVertexAttribArray和glVertexAttribPointer函数都将属性索引作为第一个参数。我们将属性索引0赋给positon,因此在代码中调用时也是如此。glEnableVertexAttribArray(0) 启用了指向position属性的索引。下图的图解释了数据时如何流到着色器中的:
如果没有调用glEnableVertexAttribArray, 在glVertexAttribPointer按索引调用就没什么意思。
这个启用属性的调用不一定非得在顶点属性指针之前调用,但是在渲染前必须调用。如果属性没有开启,在渲染阶段就不会被使用。
光栅化
现在所有完成的任务是,3个顶点被发送到OpenGL,并且由顶点着色器转换为裁剪坐标系中的3个位置。接下来,顶点位置将会通过把xyz三个分量除以W分量而转换为规格化设备坐标系。在我们的例子中,W都是1.0,因此我们的坐标已经都是有效地规则化设备坐标了。
在这之后,顶点位置被转换为屏幕坐标。这是通过视口转换(viewport transform.)来完成的。这样称呼是因为完成它的glViewport这个函数。本例中当窗口改变大小是,每次都调用这个函数。当窗口大小改变时总是调用reshape 函数,该函数如下:
- void reshape (int w, int h)
- {
- glViewport(0, 0, (GLsizei) w, (GLsizei) h);
- }
void reshape (int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}
这个告诉OpenGL那个可用区域将用来渲染。在本例中,我们使用全部的可用区域。如果没有这个函数调用,调整窗口大小将不会影响渲染。同时,记住我们没有保持宽高比为常量,这样会导致三角形变形。
回忆一下,屏幕坐标(0,0)在左下角。这个函数把左下角位置作为头两个坐标,而把视口的宽度和高度作为另外两个坐标。
一旦在屏幕坐标中了,OpenGL将会取这3个坐标,扫描转换为一些列的片元。要完成这项任务,OpenGL必须决定这个顶点列表代表什么。OpenGL解析一个定点列表的方式各有不同。使用命令:
glDrawArrays(GL_TRIANGLES, 0, 3);告诉它绘制三角形。
流向光栅器中数据如下图所示:
片元处理
片元着色器用于计算输出片元的颜色。它的输入包括屏幕坐标下片元的XYZ坐标,也可以包括用户定义数据,这里就不做介绍。
我们的片元着色器定义如下:
- <span style="font-size:14px;">#version 330
- out vec4 outputColor;
- void main()
- {
- outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
- }</span>
<span style="font-size:14px;">#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
}</span>
首行同样是版本声明。
下一行指定片元着色器的输出变量,类型为vec4。
主函数中仅仅使用了4维向量,来表达颜色。
尽管为片元着色器提供了屏幕坐标下的坐标,但是这里不需要它,因而也就没有使用它。在片元着色器执行完后,片元的输出颜色就被输出到图像中了。
注意:
在顶点着色器中使用layout(location = #)来表明属性索引,在顶点数组和顶点着色器之间建立关联。但是片元着色器的输出基本上都是到当前渲染的图像,在我们的例子中是屏幕,因此如果你在片元着色器中只定义了一个输出变量,那么这个变量将自动写入到当前的目标图像。
3.着色器的生成
着色器的生成可以参见下图:
一个着色器字符串被编译后成为着色器对象Shader Object.一个或者多个着色器对象链接成为着色器程序program Object.
注意在生成着色器对象和程序时,要访问他们被创建的状态,如果出错了,要进行出错处理。
这里面没什么需要细讲的东西,下面给出他们的一个实现版本:
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
#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;
- }
#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;
}
4.完整示例
本节的完整示例,利用VBO传送顶点数据,利用顶点着色器和片元着色器处理顶点,他们的字符串都以std::string形式写在代码中了(当然,也可以由文件读取)。
着色器shader.h和实现见上文代码,示例完整代码如下:
- //依赖库glew32.lib freeglut.lib
- //使用VAO VBO和着色器绘制三角形(现代OpenGL方式)
- #include <string>
- #include <vector>
- #include <GL/glew.h>
- #include <GL/freeglut.h>
- #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);
- glBindBuffer(GL_ARRAY_BUFFER,0);
- //创建着色器
- const std::string vertexStr (
- "#version 330\n"
- "layout(location=0) in vec4 position;\n"
- "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));
- 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);
- 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
//使用VAO VBO和着色器绘制三角形(现代OpenGL方式)
#include <string>
#include <vector>
#include <GL/glew.h>
#include <GL/freeglut.h>
#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);
glBindBuffer(GL_ARRAY_BUFFER,0);
//创建着色器
const std::string vertexStr (
"#version 330\n"
"layout(location=0) in vec4 position;\n"
"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));
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);
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;
}
}
运行效果如下图所示: