openGL 学习,Hello Triangle!
https://paroj.github.io/gltut/
Chapter 1 你好, 三角形
传统上来说,一种编程语言通常会以"Hello World"来作为入门教程,用最简单的代码来打印输出一个"Hello World"字符串
它会提供一个最简单的操作流程,告诉用户如何编译/执行代码
使用openGL来编写代码是相当复杂的, 我们将在屏幕上画一个三角形, 来作为我们"Hello World"的入门教程
Framework and FreeGLUT
用户可以在Tut1 Hello Triangle/Tut1.cpp 目录下面找到第一份参考代码, 这份代码相当简单
编译这个Tut1工程,除了Tut1.cpp以外, 还需要一个framework/framework.cpp代码
这个framework的代码会完成FreeGLUT实际上的初始化工作, 它也是程序的入口代码, 进而他会调用Tut1.cpp的函数来完成全部功能
FreeGLUT是一个简单的openGL使用框架,它可以简单的创建/管理一个窗口, 并且使用openGL命令来作用于这个窗口
由于各种GUI系统中的窗口需要完成某些记录,因此如何使用这些记录的用户界面受到严格控制。
framework需要TutX 实现5个接口, 分别是: defaults, init, display, reshape, and keyboard
FreeGLUT 初始化之前会先调用defaults函数, 它可以使我们sample code有机会调整窗口大小或者其他的初始化参数
初始化之后,会调用init函数, 它让sample code有机会在render之前加载一些需要的资源
窗口有大小变化时, 会调用reshape接口
有键盘操作的时候,会调用keyboard接口
最重要的是display接口, 当窗口需要绘制的时候FreeGLUT会调用这个接口
Dissecting Display
display函数看起来很简单, 然后具体的实现过程是相当复杂的, 它和init函数当中的初始化过程紧密的结合在一起
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(theProgram);
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(0);
glUseProgram(0);
glutSwapBuffers();
开始的两行负责清空屏幕
glClearColor是一个状态设置接口, 它设置窗口清空时的默认颜色, 这里我们设置为黑色.
glClear不是设置openGL的状态, 它会直接清空屏幕,
GL_COLOR_BUFFER_BIT参数意味着我们使用前面设置的默认颜色来清空窗口
下面一行设置了当前使用的着色器程序, 它是所有后续渲染命令的默认值, 我们将在之后详细讲解
下面三行全部是设置状态的, 这些命令设置三角形将被render的坐标, 他们告诉openGL应该从内存哪里读取三角形的位置,
glDrawArrays接口是一个渲染函数, 它使用当前状态来生成顶点流, 最终组成三角形
最后两行是清理工作, 它们是render之前设置的状态的清理工作
最后一行glutSwapBuffers, 它是FreeGLUT的一个命令, 不是OpenGL的命令
openGL 的framebuffer是双缓冲的, 这意味着屏幕上显示的图像和渲染的图像是不同的。
因此,渲染的图像不会被用户看到,直到它被显示给用户, 这样用户就不会看到一个渲染了一半的图像
glutSwapBuffers就是负责将渲染的图像显示给用户
Following the Data
前面我们描述了openGL流水线的一些函数
这里我们基于导读1的代码重新来看一下这些流水线, 这样我们可以更好的理解openGL是如何渲染数据的
- Vertex Transfer 顶点变换
光栅化流水线的第一阶段是将顶点转换为剪辑空间, openGL需要先接收到一系列的顶点坐标, 所以openGL流水线的第一步是将三角形的顶点坐标数据发生给openGL
下面就是我们需要发送的数据:
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,
};
每行的4个数字代表一个顶点坐标的4D位置, 为什么是4D? 因为clip-space也是4D的
我们需要openGL根据这些顶点坐标来绘制三角形, 既然每个顶点需要4D数据,那前面的数组代表3个顶点, 也就是可以绘制一个三角形
虽然我们已经有了这些数据, 但是openGL还不能直接使用他们, openGL使用上有一些限制, 它只能访问特定的内存
你可以分配所有你需要的顶点数据, 但是openGL无法直接访问到这些内存, 因此第一步就是要分配一些openGL能访问的内存
然后将我们的数据填充到这些内存里面, 这一步是使用buffer object来实现的
一个buffer object是一个由openGL分配和管理的的线性矩阵, 它的内容由用户控制, 但是用户之间间接的控制它
可以将buffer object考虑成一个GPU内存, GPU可以快速读取这个内存, 因此将数据存储在它里面有性能上的优势
在我们Tut1的例子中, buffer object是在init阶段创建出来的, 下面就是创建buffer object的代码:
void InitializeVertexBuffer()
{
glGenBuffers(1, &positionBufferObject);
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
第一行是创建buffer object, positionBufferObject将获得一个代表这个buffer object的句柄, 后续操作中需要使用这个句柄
这里只是创建了句柄,还未创建任何内存
glBindBuffer函数将新创建的句柄绑定到GL_ARRAY_BUFFER目标上面
像我们前面介绍过的, 在openGL当中的object通常需要绑定到上下文上面, 然后才可以操作他们做一些事情, buffer object也是一样的
后面操作GL_ARRAY_BUFFER的接口就会影响到绑定的句柄positionBufferObject, 直到一个新的object重新绑定到GL_ARRAY_BUFFER
glBufferData函数完成两个操作
一是它分配内存给buffer object, 数当中的sizeof通过计算CPU端分配的内存大小, 通过GPU分配足够的内存
二是它将数据拷贝到buffer object里面, vertexPositions参数是CPU端的内存地址, glBufferData将数据从CPU端复制到GPU端
当这个操作完成之后, GPU端就可以访问到buffer obeject里面的顶点坐标数据了
第4个参数我们后面再来看
下面一个glBindBuffer调用是一个简单的清理操作, 只是为了unbound前面绑定过的positionBufferObject对象
这里我们仅仅是将顶点数据复制到了GPU内存, 但是openGL并不知道buffer object里面的数据代表的实际意义
就openGL而言, 我们就是创建了一个buffer object, 填充了一些数据进去.
我们现在需要告诉openGL这些数据是什么格式
下面是实现这一部分的代码:
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
第一个函数我们之前见过, 在操作buffer object之前先绑定到GL_ARRAY_BUFFER, 后续的API才知道要操作的是这个buffer
第二个函数glEnableVertexAttribArray 我们下一节再来介绍, 如果没有这个函数, 后面的调用也不会起作用
第三个函数glVertexAttribPointer是最关键的一个, 它名字中尽管有一个“Pointer”的字样, 但是他并不操作指针
相反,它操作的是一个buffer object
当渲染开始的时候, openGL从buffer object当中读取顶点坐标数据。 我们仅仅需要告诉openGL这些顶点数据是什么格式的, 也就是告诉openGL如何使用这些数据
在我们这个例子当中, 我们的数据是这样的, 它通过glVertexAttribPointer参数告诉openGL
- 数据是32位浮点数 : GL_FLOAT
- 每一个位置由4个数字组成 : 4
- 4个数字是紧挨在一起的,中间没有填充其他数据:倒数第二个0
- 第一个数字是buffer object的开始数据:最后一个0
其他参数我们后续再来看
好像glVertexAttribPointer设置时并没有告诉openGL是哪个buffer object是这样的数据格式?
这就是前面glBindBuffer函数的作用, 设置的就是前面bind过的buffer object
一旦openGL知道从哪里获得顶点数据, 它现在就可以使用这些数据来作渲染
glDrawArrays(GL_TRIANGLES, 0, 3);
这个函数看起来很简单, 但是它做了一个很重要的事情
第二个参数代表顶点数据开始的index, 这里是从0开始
第三个参数代表定点数据的个数, 这里是3个顶点
这表示它将读取positionBufferObject里面从0开始读取3个顶点的数据
第一个参数告诉openGL它需要读取3个顶点作为一个独立的三角形
因此openGl将读3个顶点并连接他们组成一个三角形
后面的章节我们再来看里面的实现细节
Vertex Processing and Shaders 顶点处理和着色器
现在我们告诉了openGL顶点数据是什么, 那我们可以进入到下一个阶段, 如何处理这些顶点数据
这里会涉及到两个编程阶段, 这里我们开始引入着色器的使用
着色器就是一段运行在GPU上的程序, 在openGL的流水线上可以有多个着色器, 每一个都有他们自己的输入和输出
着色器负责按照程序的流程将输入的数据转换为输出的数据
每一个着色器都会作用在一系列的输入数据上, 着色器之间是互相独立的, 每个着色器只是负责处理好自己的输入和输出
顶点着色器,顾名思义,对顶点进行操作。具体来说,顶点着色器的每次调用都在单个顶点上操作。在任何其他用户定义的输出中,这些着色器必须输出该顶点的剪辑空间位置。
如何计算这个剪辑空间的位置完全取决于着色器。
OpenGL中的着色器是用OpenGL着色语言(GLSL)编写的。
这种语言看起来很像C,但它与C相差甚远。
它有太多的限制,不像C(例如,禁止递归)。
这是我们简单的顶点着色器的样子:
#version 330
layout(location = 0) in vec4 position;
void main()
{
gl_Position = position;
}
这看起来相当简单, 第一行代表GLSL语音的版本是3.30
第二行定义了着色器的输入, 输入是一个变量名为position, 类型是vec4 (一个四维的浮点数矩阵)
它还有一个location=0的layout,这个我们后面再说
像C语言一样,GLSL也是从main函数开始执行.
这个着色器非常简单, 只是将输入的position复制到一个输入的gl_Position上面
gl_Position并未在着色器当中定义,因为它是一个标准的变量定义在每一个顶点着色器中
如果你在着色器当中看到一个gl_开头的变量,那他就是一个buildin的变量标识符
你不能自己顶一个一个gl_开头的变量
gl_Position定义如下:
out vec4 gl_Position;
回想一下,顶点着色器必须做的最小事情是为顶点生成一个剪辑空间位置。这就是gl_Position:
顶点在剪辑空间的位置。因为我们的输入位置数据已经是一个剪辑空间的位置,这个着色器只是将它直接复制到输出中。
Vertex Attributes. 顶点属性
着色器有输入和输出。可以把它们看作函数参数和函数返回值。如果着色器是一个函数,那么它将被调用输入值,并且预计它将返回一些输出值。
着色器阶段的输入和输出来自某个地方,并到达某个地方。
因此,顶点着色器中的输入位置必须在某个地方填充数据。那么这些数据是从哪里来的呢?顶点着色器的输入称为顶点属性。
您可能认识一些类似于术语“顶点属性”的东西。例如,“glEnableVertexAttribArray”或“glVertexAttribPointer”。
这就是数据在OpenGL中的流动方式。
当渲染开始时,根据glVertexAttribPointer完成的设置工作读取缓冲对象中的顶点数据。
此函数描述属性的数据来自何处。对glVertexAttribPointer的特定调用和顶点着色器输入值的字符串名称之间的连接有些复杂。
每一个顶点着色器输入都有索引位置,称为属性索引。这个着色器输入被定义为这个语句:
layout(location = 0) in vec4 position;
布局位置部分将属性索引0赋给position。
属性索引必须大于或等于零,并且在任何时候可以使用的属性索引的数量有一个基于硬件的限制[2]。
在代码中,当引用属性时,它们总是通过属性索引来引用。
函数glEnableVertexAttribArray、glDisableVertexAttribArray和glVertexAttribPointer都将属性索引作为它们的第一个参数。
我们在顶点着色器中将位置属性的属性索引赋值为0,因此调用glEnableVertexAttribArray(0)将启用位置属性的属性索引。
如果不调用 glEnableVertexAttribArray, 那么在该属性索引上调用glVertexAttribPointer将没有多大意义。
不需要在顶点属性指针调用之前调用enable调用,但是需要在呈现之前调用它。如果未启用该属性,则在呈现期间不会使用它。
Rasterization 光栅化
到目前为止所发生的一切都是3个顶点已经给了OpenGL,
它已经用顶点着色器将它们转换为剪辑空间中的3个位置。
接下来,通过将顶点位置的3个XYZ分量除以W分量,将顶点位置转换为归一化的设备坐标。
在我们的例子中,我们的3个位置的W是1.0,所以这些位置已经有效地在标准化的设备坐标空间中。
在此之后,顶点位置将转换到窗口坐标中,这将要靠viewport转换来实现,使用接口glViewport
每一次窗口大小的变化,都会调用到这个函数
请记住framework每次窗口变化都会调用reshape,所以reshape的实现如下:
void reshape (int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}
glViewport这个告诉openGL那些区域是可视区域可以去渲染的
在我们这个例子当中, 我们渲染整个可视区域
此外,请注意我们没有努力保持长宽比恒定的事实;在一个方向上收缩或拉伸窗口将导致三角形收缩和拉伸以匹配。
回想一下,窗口坐标位于左下角的坐标系中。点(0,0)在窗口的左下方。这个函数以左下角的位置作为前两个坐标,视口矩形的宽度和高度作为另外两个坐标。
一旦进入窗口坐标,OpenGL现在可以将这3个顶点扫描转换为一系列片段。然而,为了做到这一点,OpenGL必须决定顶点列表代表什么。
OpenGL可以用各种不同的方式解释一个顶点列表。OpenGL解释顶点列表的方式由draw命令给出:
glDrawArrays(GL_TRIANGLES, 0, 3);
enum GL_TRIANGLES告诉OpenGL每3个顶点的列表应该被用来构建一个三角形。因为我们只传递了3个顶点,所以我们得到了一个三角形。
如果我们有6个顶点坐标, 就可以得到2个三角形
Fragment Processing 片段处理
片段着色器用于计算片段的输出颜色, 片段着色器的输入包括一个片段基于window的xyz坐标
一个片段着色器看起来像是这样:
#version 330
out vec4 outputColor;
void main()
{
outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
}
与顶点着色器一样,第一行声明着色器使用GLSL 3.30版本。
下一行指定片段着色器的输出,当前输出变量是一个vec4类型,变量名为outputColor
这几行代码主要的功能是简单的设置一个4D的输出颜色, 每一个都是1.0f,,分别代表R G B A, 全部是1.0时会产生一个白色
虽然所有的片段着色器都提供了片段的窗口空间位置,但这个不需要它。所以它根本不使用它。
在片段着色器执行完之后, 片段输出颜色将被写入到输出图像当中
Note
在顶点着色器部分,我们必须使用layout(location = #)语法来提供顶点着色器输入和顶点属性索引之间的连接。
这是为了让用户将顶点数组连接到顶点着色器输入所必需的。所以你可能想知道片段着色器输出和屏幕之间的连接在哪里。
OpenGL认识到,在许多渲染中,片段着色器输出只有一个逻辑位置:当前图像被渲染到(在我们的例子中,屏幕)。
正因为如此,如果你从一个片段着色器中只定义一个输出,那么这个输出值将自动写入当前目标图像。
有可能有多个片段着色器输出到多个不同的目标图像;这增加了一些复杂性,类似于属性索引。
Making Shaders
前面我们一直忽略了这些着色器文本字符串是如何被发送给openGL的, 现在我们来分析一些细节
着色器是一个like C的语言, 所以openGL使用了一个like C的编译模型
在c当中, 每一个独立的c文件被编译成一个obj文件,然后一个或者多个obj文件链接到一起生成一个单独的程序, openGL的行为也分成相似
一个着色器字符串被编译成一个着色器object, 这与object文件是类似的, 一个或者多个着色器object被链接到一个program object.
openGL的一个program object包括所有的着色器代码, 他们一起被用来做渲染工作
在本章节当中, 我们有一个顶点着色器,一个片段着色器, 这两个着色器被link到一个单独的program object里面
下面的代码负责编译这个program object
void InitializeProgram()
{
std::vector<GLuint> shaderList;
shaderList.push_back(CreateShader(GL_VERTEX_SHADER, strVertexShader));
shaderList.push_back(CreateShader(GL_FRAGMENT_SHADER, strFragmentShader));
theProgram = CreateProgram(shaderList);
std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);
}
第一行声明了一个着色器链表
下面两行编译了哦我们两个着色器字符串, CreateShader函数是我们自己实现的一个编译着色器的代码
将一个着色器编译到一个着色器object与编译源代码是非常相似的, 最重要的是,它涉及到错误检查。这是CreateShader的实现:
GLuint CreateShader(GLenum eShaderType, const std::string &strShaderFile)
{
GLuint shader = glCreateShader(eShaderType);
const char *strFileData = strShaderFile.c_str();
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;
}
OpenGL着色器对象,顾名思义,是一个对象.
第一步是使用glCreateShader来创建一个对象, 这个函数创建了一个特殊类型的着色器(顶点或者片段, 由参数来决定)
既然每一个着色器都有适当的语法规则和预定义的变量/常量, 我们必须要告诉编译器什么着色器正在被编译,
着色器/程序对象与openGL的其他对象有很多不同的地方.
下一步是将着色器字符串编译到对象当中, c风格的字符串通过glShaderSource接口填充进着色器对象当中
第一个参数是我们前面create出来的着色器对象
下一个参数是要加进去的字符串个数, 编译多个字符串到单个着色器对象也是可以的
下一个参数是是一个字符串指针数组, 指向每一个着色器字符串
下一个对象表示字符串的长度, 我们传NULL,告诉openGL字符串以null结尾, 通常来讲传NULL即可
一旦字符串被传入到object当中, 可以使用glCompileShader来编译着色器
编译之后, 我们需要查看一些编译是否成功,通过接口glGetShaderiv来实现这一点,通过参数GL_COMPILE_STATUS
如果编译错误, 我们增加一些错误打印
当两个着色器都创建成功之后,我们传入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;
}
这个函数相当简单, 它先使用glCreateProgram创建一个空的程序对象, 这个函数不需要参数
请记住程序对象是一系列着色器stages的组合
下一步, 将前面创建的着色器对象attach到这个程序对象上面glAttachShader,
当所有的着色器对象都attach之后, 通过glLinkProgram可以link整个程序对象, 同前面一样,我们也要查询一些这个link status是否成功
当程序对象成功链接之后,着色器对象通过glDetachShader从程序对象当中detach
程序对像的链接状态和功能不受影响
Using Programs.
我们使用glUseProgram来告诉openGL,后面的渲染将使用指定的程序对象,
在本例当中, 我们在display函数当中使用了两次程序对象
第一次是告诉openGL我们将使用名为theProgram的程序对象,第二次是告诉openGL后面不再使用程序对象
本章节分配了一系列的openGL资源,
包括一个buffer object,代表GPU的一片内存
创建了两个着色器对象和一个程序对象, 他们都分别存储在openGL的内存当中
但是我们只是删除了着色器对象,其他并没有做清理动作
这一部分是因为FreeGLUT的特性,一部分是由于openGL的特性
在这个简单的例子上, 没有必要删除任何东西, 当窗口销毁的时候, openGL会清理掉他的资源
自行删除之前创建的对象是好的一种模式,如果你确定知道自己在清理什么,那是没问题的,不过一般不强制要求这样做
In Review
In this tutorial, you have learned the following:
你从本章节里面学习到了以下内容:
Buffer objects are linear arrays of memory allocated by OpenGL. They can be used to store vertex data.
buffer objects是一个openGL分配的线性内存数组, 他们被用来存放顶点数据
GLSL shaders are compiled into shader objects that represent the code to be executed for a single shader stage. These shader objects can be linked together to produce a program object, which represent all of the shader code to be executed during rendering.
GLSL着色器被编译成着色器对象,他们分别代表一段要执行的着色器逻辑。这些着色器对象将被链接成一个程序对象,代表在渲染过程中要执行的着色器代码
The glDrawArrays function can be used to draw triangles, using particular buffer objects as sources for vertex data and the currently bound program object.
glDrawArrays函数能被用来画三角形, 它会使用我们前面将的buffer object和program额 object
Further Study
虽然本章节的例子比较简单, 但是仍然有很多事情需要去研究
Even with a simple tutorial like this, there are many things to play around with and investigate.
比如修改一下不同的颜色,看下能得到什么
Change the color value set by the fragment shader to different values. Use values in the range [0, 1], and then see what happens when you go outside that range.
修改一下顶点数据的位置,看下会发生什么
Change the positions of the vertex data. Keep position values in the [-1, 1] range, then see what happens when triangles go outside this range. Notice what happens when you change the Z value of the positions (note: nothing should happen while they’re within the range). Keep the W values at 1.0 for now.
修改一下清理颜色
Change the clear color, using values in the range [0, 1]. Notice how this interacts with changes to the viewport above.
增加3个顶点坐标到链表里面,修改一下glDrawArrays的参数
Add another 3 vertices to the list, and change the number of vertices sent in the glDrawArrays call from 3 to 6. Add more and play with them.
修改一下reshape/glViewport的窗口参数,调大或者调小看下会发生什么
Change the values that reshape gives to glViewport. Make them bigger or smaller than the window and see what happens. Shift them around to different quadrants within the window.
Change the reshape function so that changing the window size does not stretch the triangle. This means that the area rendered to, the viewport, may be smaller than the window area. Also, try to make it so that it always centers the area within the window.
OpenGL Functions of Note
glClearColor, glClear
These functions clear the current viewable area of the screen. glClearColor sets the color to clear, while glClear with the GL_COLOR_BUFFER_BIT value causes the image to be cleared with that color.
glGenBuffers, glBindBuffer, glBufferData
These functions are used to create and manipulate buffer objects. glGenBuffers creates one or more buffers, glBindBuffer attaches it to a location in the context, and glBufferData allocates memory and fills this memory with data from the user into the buffer object.
glEnableVertexAttribArray, glDisableVertexAttribArray, glVertexAttribPointer
These functions control vertex attribute arrays. glEnableVertexAttribArray activates the given attribute index, glDisableVertexAttribArray deactivates the given attribute index, and glVertexAttribPointer defines the format and source location (buffer object) of the vertex data.
glDrawArrays
This function initiates rendering, using the currently active vertex attributes and the current program object (among other state). It causes a number of vertices to be pulled from the attribute arrays in order.
glViewport
This function defines the current viewport transform. It defines as a region of the window, specified by the bottom-left position and a width/height.
glCreateShader, glShaderSource, glCompileShader, glDeleteShader
These functions create a working shader object. glCreateShader simply creates an empty shader object of a particular shader stage. glShaderSource sets strings into that object; multiple calls to this function simply overwrite the previously set strings. glCompileShader causes the shader object to be compiled with the previously set strings. glDeleteShader causes the shader object to be deleted.
glCreateProgram, glAttachShader, glLinkProgram, glDetachShader
These functions create a working program object. glCreateProgram creates an empty program object. glAttachShader attaches a shader object to that program. Multiple calls attach multiple shader objects. glLinkProgram links all of the previously attached shaders into a complete program. glDetachShader is used to remove a shader object from the program object; this does not affect the behavior of the program.
glUseProgram
This function causes the given program to become the current program. All rendering taking place after this call will use this program for the various shader stages. If the program 0 is given, then no program is current.
glGetAttribLocation
This function retrieves the attribute index of a named attribute. It takes the program to find the attribute in, and the name of the input variable of the vertex shader that the user is looking for the attribute index to.