OpenGL简介
OpenGL 全称Open Graphics Library,一种用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。由1992年成立的OpenGL架构评审委员会(ARB)维护。
OpenGL是使用“客户端—服务端”的形式实现的,我们编写的 应用程序 可以看作客户端,而计算机图形硬件厂商(如:英伟达)所提供的OpenGL实现可以看作服务端。
OpenGL渲染管线
OpenGL实现了我们通常所说的渲染管线(rendering pipeline),它是一系列数据处理过程,并且将应用程序 的 数据 转化到 最终渲染的 图像。
OpenGL渲染管线 如下图所示:
这里有一个OpenGL最本质的概念–着色器(Shader),它是图形硬件设备所执行的一类特殊函数。
理解 着色器 最好的方法是:把它看作专为 图形处理单元(GPU) 编译的一种小程序。
OpenGL在其内部包含了所有的 编译器工具,可以直接从着色器源代码创建GPU所需的编译代码,并执行。
着色器编译命令序列 如下图所示:
/*关于上图 “着色器编译命令序列” 举例
*/
#define GLSL_STR(a) (#a)
const char* ShaderSource = GLSL_STR(
#version 430 core\n
layout(location=0)in vec4 vPosition;
void main()
{
gl_Position = vPosition;
}
);
GLuint program = glCreateProgram();
GLuint shader = glCreateShader(GL_VERTEX_SHADER);//顶点着色器
glShaderSource(shader, 1, &ShaderSource, 0);
glCompileShader(shader);
GLint complied;
glGetShaderiv(shader, GL_COMPILE_STATUS, &complied);
if (!complied)
{
//do something when compile failed
}
glAttachShader(program, shader);
glLinkProgram(program);
GLint linked;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked)
{
//do something when link failed
}
glUseProgram(program);
第一个OpenGL程序
红宝书的第一个程序,两个蓝色三角形。
由于红宝书附带的源码牵扯一些其它文件,此处我稍做了修改,放到了同一个文件中,看起来更直观,可不用配置,直接编译运行。
#include <iostream>
#include <gl/glew.h>
#include <gl/glut.h>
#include <gl/freeglut_ext.h>
#pragma comment(lib, "glew32.lib")
namespace
{
//为了方便编写shader,定义一个宏
#define GLSL_STR(a) (#a)
GLuint VAO, VBO;
const GLint NumVertices = 6;//顶点数量
struct ShaderInfo
{
GLenum type;
const char* source;
};
//顶点着色器
const char* vShader = GLSL_STR(
#version 430 core\n
layout(location = 0) in vec4 vPos;
void main()
{
gl_Position = vPos;
}
);
//片元着色器
const char* fShader = GLSL_STR(
#version 430 core\n
out vec4 fColor;
void main()
{
fColor = vec4(0.0, 0.0, 1.0, 1.0);
}
);
// 着色器编译命令序列
GLuint createProgram(ShaderInfo* shaders)
{
if (!shaders) return 0;
GLuint program = glCreateProgram();
while (shaders->type != GL_NONE)
{
GLuint shader = glCreateShader(shaders->type);
glShaderSource(shader, 1, &shaders->source, 0);
glCompileShader(shader);
GLint compiled;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled)
{
return 0;
}
glAttachShader(program, shader);
++shaders;
}
glLinkProgram(program);
GLint linked;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked)
{
return 0;
}
return program;
}
void init()
{
ShaderInfo shaders[] =
{
{ GL_VERTEX_SHADER, vShader },
{ GL_FRAGMENT_SHADER, fShader },
{ GL_NONE, NULL }
};
GLfloat vertices[NumVertices][2] = {
{ -0.90, -0.90 }, // Triangle 1 左下部分
{ 0.85, -0.90 },
{ -0.90, 0.85 },
{ 0.90, -0.85 }, // Triangle 2 右上部分
{ 0.90, 0.90 },
{ -0.85, 0.90 }
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//将应用程序中的 数据 导入 GPU buffer
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
GLuint program = createProgram(shaders);
glUseProgram(program);
//将buffer中的数据 与 顶点属性 0(layout(location = 0) in vec4 vPos;)关联
//第一个参数0,对应location=0
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
//启用顶点属性数组,默认关闭,0同上
glEnableVertexAttribArray(0);
glClearColor(0.8, 0.2, 0.0, 1.0);//设置暗红色背景
}
void display()
{
//清除颜色缓存,缓存类型还有 深度缓存(GL_DEPTH_BUFFER_BIT)、模板缓存(GL_STENCIL_BUFFER_BIT)
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, NumVertices);//绘制三角形
glFlush();
}
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA); //显示模式
glutInitWindowSize(720, 576);//窗口宽 高
glutInitWindowPosition(100, 100);//窗口坐标
glutInitContextProfile(GLUT_CORE_PROFILE);//核心模式,还有一种“兼容模式GLUT_COMPATIBILITY_PROFILE"
glutInitContextVersion(4, 3);//OpenGL版本,对应shader程序#version 430 core
glutCreateWindow("Triangles");//创建窗口,名称Triangles
glewExperimental = true;
if (glewInit())//初始化GLEW
{
std::cout << "glew init failed..." << std::endl;
}
init();
glutDisplayFunc(display);//显示回调函数,每次更新窗口内容时,自动调用
glutMainLoop();//无限执行循环
return 0;
}
运行结果如下:
着色器程序说明
上述程序中使用了两个着色器程序:顶点着色器程序、片元着色器程序,下面我们就这两个程序,简单做个说明。
顶点着色器
const char* vShader = GLSL_STR(
#version 430 core\n
layout(location = 0) in vec4 vPos;
void main()
{
gl_Position = vPos;
}
);
这是一个传递着色器(pass-through shader)的例子,它只负责将数据拷贝到输出数据中。
第一行”#version 430 core“ ,指定OpenGL 4.3 版本对应的GLSL语言,core代表OpenGL核心模式。每个着色器程序第一行都应设置”#version …“, 否则系统会默认使用”110“版本,这与OpenGL核心模式不兼容。
第二行”layout(location = 0) in vec4 vPos;“,分配了一个着色器变量,着色器变量是着色器与外部世界的联系所在。
在着色器内部,直接获取输入类型着色器变量的数据,在外部(应用程序中),设置输入类型着色器变量的值。
此处,着色器变量名为vPos, 着色器流向为in,即输入类型, 数据类型为vec4,布局设定的位置属性为0。
第三行“void main()”,着色器在main()函数中去实现主体部分,无论哪种类型的着色器,都必须有一个空类型(void)的main()函数。
第五行:“gl_Position = vPos;”,将我们声明的输入着色器变量数据,传入OpenGL内置着色器变量中,OpenGL内置了很多着色器变量,均以 gl_ 作为前缀,如想了解,可翻阅红宝书附录 C。
片元着色器
const char* fShader = GLSL_STR(
#version 430 core\n
out vec4 fColor;
void main()
{
fColor = vec4(0.0, 0.0, 1.0, 1.0);
}
);
第二行“out vec4 fColor;”,声明一个输出类型的变量fColor,着色器将会把fColor对应的数值输出,而这也就是片元所对应的颜色值。
第五行“fColor = vec4(0.0, 0.0, 1.0, 1.0);”,片元的颜色值(蓝色)。OpenGL中的颜色是通过RGBA颜色空间来设定的,每个颜色分量的范围都是[0,1],如fColor( R=0.0, G=0.0, B=1.0, A=1.0),此处 A(alpha值) 代表透明度,1.0代表完全不透明。
一些名词
-
VA0(vertex-array object)顶点数组对象,用来管理vbo。
-
VBO(vertex buffer object)顶点缓冲对象,用来缓存用户传入的顶点数据。
-
EBO(element buffer object)索引缓冲对象,用来存放顶点索引数据。