1.显示到屏幕
这里先简单介绍一下缓冲区,缓冲区存在于图形卡的显存中,OpenGL在绘制图元时,先是在一个缓冲区中完成渲染,然后再把渲染结果交换到屏幕上。
void display()
{
//指定OpenGL清理屏幕时将要使用的颜色,这里为黑色。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
//开始清理屏幕,GL_COLOR_BUFFER_BIT表示清理将影响颜色缓冲区,清理时使用上面指定的颜色。
glClear(GL_COLOR_BUFFER_BIT);
//告知OpenGL渲染的时候需要调用应用程序对象。
glUseProgram(theProgram);
//下面三行设置三角形的坐标,它们告知OpenGL三角形在缓冲区中的位置
//获取已经初始化的缓冲区对象。
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
//启用缓冲区对象中的数据,参数指定要修改的顶点属性的索引值
glEnableVertexAttribArray(0);
//尽管这个函数包含“Pointer”,但是它不处理指针,它用来对缓冲区对象中的顶点属性进行设定。
//参数1指定要修改的顶点属性的索引值;参数2指定每个顶点向量的维数;参数3指定每个顶点向量的数据类型;
//参数4指定是否归一化;参数5指定各顶点向量间是否有空隙,0表示紧密排列;参数6指定顶点数组起始位置与缓冲区
//对象起始位置的偏移量,0表示无偏移。后三个参数通常取默认值。
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
//渲染函数,从顶点数组索引0开始,读取3个顶点,然后将它们连接成一个三角形。
glDrawArrays(GL_TRIANGLES, 0, 3);
//下面两行是清理工作,释放为了实现渲染所做的一些设置。
glDisableVertexAttribArray(0);
glUseProgram(0);
}
2.顶点传递
以下是我们希望传递的数据,一个顶点数组。
const float vertexPositions[] = {
0.75f, 0.75f, 0.0f, 1.0f,
0.75f, -0.75f, 0.0f, 1.0f,
-0.75f, -0.75f, 0.0f, 1.0f,
};
每行表示一个顶点的四维向量,向量的前三个数字表示顶点的x、y、z坐标,第四个顶点w是缩放因子,通常为1,当w为1是,可以忽略它。尽管我们定义了这些数据,但是OpenGL不能直接使用。所以我们需要分配一些OpenGL可见的缓冲区,然后将我们的数据添加到这些缓冲区。这可以通过缓冲区对象(buffer object)来实现。缓冲区对象可以看成GPU内存中的线性数组,GPU可以快速访问这些数组。缓冲区对象在初始化时创建,代码如下所示。
void InitializeVertexBuffer()
{
//创建一个缓冲区对象,并将positionBufferObject指向该缓存区对象。
//此时缓存区对象已经创建,但并未给它分配存储空间。
glGenBuffers(1, &positionBufferObject);
//将缓冲区对象绑定到绑定目标GL_ARRAY_GUFFER,OpenGL中的所有对象在进行操作前都需要绑定到上下文,缓冲区对象也不例外。
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
//一旦绑定了一个缓冲区对象,就需要给该对象分配足够的GPU内存来存储顶点数据,分配的大小为 sizeof(vertexPositions)。
//然后该函数将我们定义的数据拷贝到缓冲区对象,第四个参数是数据的使用方式。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
//简单的清理。将0绑定到绑定目标GL_ARRAY_BUFFER后,之前的绑定自动解除。这里的0类似NULL指针。
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
3.顶点着色器
#version 330
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
第一行是着色器语言的版本声明,这里对应的是OpenGL 3.3版本。
着色器程序以main()函数开头,这个程序很简单,只是将输入的位置信息position拷贝到了变量gl_Position。以"gl_"开头的变量是着色器内置的变量,自定义的变量不要以"gl_"开头。gl_Position在内部的定义为out vec4 gl_Position。着色器包含输入和输出,就像函数包含输入的参数和输出的返回值。上面代码中 in 表示输入,输入到顶点着色器的是顶点属性,而顶点属性在缓冲区对象有一个索引位置(location)叫做属性索引,属性索引值必须大于或等于0。在代码中,当需要使用属性时,通常使用的是属性索引,函数glEnableVertexAttribArray,glDisableVertexAttribArray和glVertexAttribPointer都将第一个参数设置为属性索引。在第二行中,我们将属性索引值0指定给属性position,这样当调用glVertexAttribPointer等函数时,将第一个参数设置为0,可以访问属性position。下图是顶点着色器的数据流图。
4.片段着色器
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
片段做色器用于计算片段的输出颜色。
第二行定义了段着色器的输出变量outputColor。
main()函数简单的将输出变量赋值为一个四维向量,该向量表示白色。当片段着色器执行时,白色将被输出到图像中。需要注意的是在顶点着色器中,我们通过layout(location = #)语法来建立顶点着色器输入和顶点属性索引之间的联系,但是在片段着色器却没有采用类似的做法,将片段着色器输出和屏幕关联起来。这是因为片段着色器的输出目的地只有一个—当前要渲染的图像,即屏幕。因此当仅定义一个输出变量时,这个变量会被自动写入到当前图像。当然也可以定义多个片段着色器输出变量,分别输出了不同的目的图像,这样做会复杂一些,这里不做讨论。
5.开始着色
void InitializeProgram()
{
//创建着色器对象列表,这些着色器对象将通过链接生成应用程序对象
std::vector<GLuint> shaderList;
//下面两行分别编译顶点着色器和片段着色器,着色器的编译和链接与c语言类似,也是先编译后链接。更重要的是,编译还引入了出错检查。
shaderList.push_back(CreateShader(GL_VERTEX_SHADER, strVertexShader));
shaderList.push_back(CreateShader(GL_FRAGMENT_SHADER, strFragmentShader));
theProgram = CreateProgram(shaderList);
//删除着色器对象
std::vector<GLuint>::iterator it;
for(it=shaderList.begin();it!=shaderList.end();it++)
glDeleteShader(*it);
}
GLuint CreateShader(GLenum eShaderType, const std::string &strShaderFile)
{
//创建着色器对象
GLuint shader = glCreateShader(eShaderType);
const char *strFileData = strShaderFile.c_str();
//将着色器对象与着色器程序文件关联,参数2为着色器程序文件数量,当需要将多个文件编译到一个着色器对象,可以多个文件组成数组。
//最后一个参数指定文件的长度,这里用NULL,告知OpenGL假设该文件中不含null字符,除非需要在文件中使用null字 符,否则该参数
//都可以设置为 NULL
glShaderSource(shader, 1, &strFileData, NULL);
//编译着色器对象
glCompileShader(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;
}
接着可以把编译的着色器对象传给 CreateProgram函数
GLuint CreateProgram(const std::vector<GLuint> &shaderList)
{
//创建应用程序对象
GLuint program = glCreateProgram();
//将着色器对象附加到应用程序对象
for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
glAttachShader(program, shaderList[iLoop]);
//链接应用程序对象
glLinkProgram(program);
GLint status;
//获取链接状态
glGetProgramiv (program, GL_LINK_STATUS, &status);
//如果链接失败,打印日志信息
if (status == GL_FALSE)
{
GLint infoLogLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
GLchar *strInfoLog = new GLchar[infoLogLength + 1];
glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog);
fprintf(stderr, "Linker failure: %s\n", strInfoLog);
delete[] strInfoLog;
}
//取消着色器对象的附加
for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
glDetachShader(program, shaderList[iLoop]);
return program;
}
参考链接:http://alfonse.bitbucket.org/oldtut/Basics/Tut01%20Dissecting%20Display.html
源码需要用Qt5.6.0编译
源码链接:http://download.csdn.net/detail/caoshangpa/9471466