1.初始化
glewExperimental = GL_TRUE;//让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术
if (glewInit() != GLEW_OK)//初始化
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
如果是以静态库调用glew,务必在开头加上预编译的头#define GLEW_STATIC
2.基本流程
首先创建一个对象,然后用一个id保存它的引用(实际数据被储存在后台)。然后将对象绑定至上下文的目标位置(例子中窗口对象目标的位置被定义成GL_WINDOW_TARGET
)。接下来设置窗口的选项。最后将目标位置的对象id设回0,解绑这个对象。设置的选项将被保存在objectId
所引用的对象中,一旦我们重新绑定这个对象到GL_WINDOW_TARGET
位置,这些选项就会重新生效。
// 创建对象
GLuint objectId = 0;
glGenObject(1, &objectId);
// 绑定对象至上下文
glBindObject(GL_WINDOW_TARGET, objectId);
// 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 将上下文对象设回默认
glBindObject(GL_WINDOW_TARGET, 0);
简单的理解就是像创建id,然后绑定至OpenGL中的对象,这两步也可以近似理解为实例化一个对象,然后就可以对这个对象进行操作。
3. FSM
OpenGL的系统是一个有限状态机,因此其函数有两种,状态设置函数(State-changing Function)和状态应用函数(State-using Function),前者改变OpenGL的状态,只有当进入相应状态,然后执行该状态下的应用函数才会起作用。
比如
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//rgb(范围0-1),alpha channel 状态设置函数
glClear(GL_COLOR_BUFFER_BIT);//状态应用函数,真正应用清除颜色
4. VBO
顶点缓冲对象为cpu->gpu的图形的顶点数据,简而言之,通过缓冲一次传一批顶点。
GLuint VBO;
glGenBuffers(1, &VBO); //获得VBO id
glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定顶点缓冲
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//绑定顶点数据,这一步才真正将顶点数据vertices与buffer绑定
介绍一下第四个参数
GL_STATIC_DRAW
:数据不会或几乎不会改变。GL_DYNAMIC_DRAW
:数据会被改变很多。GL_STREAM_DRAW
:数据每次绘制时都会改变。
5.Vertex Shader
#version 330 core //声明版本
layout (location = 0) in vec3 position;//in关键字声明输入变量的属性,layout(location=x)为输入变量的位置
void main()
{
gl_Position = vec4(position.x, position.y, position.z, 1.0);//输出给后面,因为输入的是三维向量,将其改成后面需要的齐次坐标形式
}
Shader 我理解为将一道完整的工序中独立出一个步骤,而这个步骤是由用户自定的,这个步骤有上一道步骤的输入,而我们可以在这里进行编程操作,只要保证输出是符合下一道步骤的输入即可。有点面向切面的味道。
5.1.编译Shader
GLuint vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建一个shader id
//绑定shader代码,参数一次为id,shader 数量,shader代码,第四个如果不为null,它会返回源码数组相应的长度数组
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);//编译
6.Fragment Shader
用于颜色
#version 330 core
out vec4 color;//out 关键字声明输出该变量
void main()
{
color = vec4(1.0f, 0.5f, 0.2f, 1.0f);//输出颜色
}
6.1 编译Shader
GLuint fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, null);
glCompileShader(fragmentShader);
7.Shader Program Object
要使用刚才编译的着色器我们必须把它们链接为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。当链接着色器至一个程序的时候,它会把每个着色器的输出链接到下个着色器的输入。当输出和输入不匹配的时候,你会得到一个连接错误。
GLuint shaderProgram;
shaderProgram = glCreateProgram();//创建一个程序对象
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);//将shader附加至Program
glLinkProgram(shaderProgram);//将gl与该program链接,即渲染时采用这个program
//激活使用该着色器对象,调用之后,每个着色器调用和渲染调用都会使用这个程序对象
glUseProgram(shaderProgram);
//链接shader到program后别忘记删除
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
8.读取顶点(解释自定义的顶点)
如果是自定义的顶点数据,该如何解释也是一个问题,可以使用下面的方法解决
// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();
glVertexAttribPointer
函数的参数:
- 第一个参数指定配置的顶点属性。比如在顶点着色器中使用
layout(location = 0)
定义了position顶点属性的位置值(Location)它可以把顶点属性的位置值设置为0
。因为我们希望把数据传递到这一个顶点属性中,所以这里传入0
。 - 第二个参数指定顶点属性的大小。顶点属性是一个
vec3
,它由3个值组成,所以大小是3。 - 第三个参数指定数据的类型,这里是
GL_FLOAT
(GLSL中vec*
都是由浮点数值组成的)。 - 第四个参数定义是否希望数据被标准化(Normalize)。如果我们设置为
GL_TRUE
,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。它设置为GL_FALSE。 - 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个
GLfloat
之后,我们把步长设置为3 * sizeof(GLfloat)
。 - 最后一个参数的类型是
GLvoid*
,所以需要进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。
每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)获取则是通过在调用glVetexAttribPointer
时绑定到GL_ARRAY_BUFFER
的VBO决定的。由于在调用glVetexAttribPointer
之前绑定的是先前定义的VBO对象,顶点属性0
现在会链接到它的顶点数据。
9.VAO
顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
GLuint VAO;
glGenVertexArrays(1, &VAO); //创建VAO
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
//4. 解绑VAO,相当于买完东西后离开,这个东西自己拿去用(比如在绘制东西时再绑定上去)
glBindVertexArray(0);
// 5. 绘制物体
glUseProgram(shaderProgram);//使用shader渲染
glBindVertexArray(VAO);//再绑定
glDrawArrays(GL_TRIANGLES, 0, 3);//形状,起始点,顶点数量
glBindVertexArray(0);//解绑
流程是:打算绘制多个物体时,首先要生成/配置所有的VAO(和必须的VBO及属性指针),然后储存它们供后面使用。当要绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。
10.EBO
索引缓冲对象,通过索引来重用顶点。
glDrawArrays(GL_TRIANGLES, 0, 3);GLuint EBO;
glGenBuffers(1, &EBO);//创建对象id
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW); //绑定索引
//此时,绘制方法改变,即不再使用glDrawArrays
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//绑定EBO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//通过索引方法来绘制,形状,顶点数,索引类型,EBO偏移量
11.线框模式
要想用线框模式绘制你的三角形,可以通过glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
函数配置OpenGL如何绘制图元。第一个参数表示将其应用到所有的三角形的正面和背面,第二个参数表示用线来绘制。之后的绘制调用会一直以线框模式绘制三角形,直到我们用glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
将其设置回默认模式。
12.总结
在绑定VAO时如果绑定了EBO,VAO也会存储索引。当目标是GL_ELEMENT_ARRAY_BUFFER
的时候,VAO会储存glBindBuffer
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
// ..:: 初始化代码 :: ..
// 1. 绑定顶点数组对象
glBindVertexArray(VAO);
// 2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 复制索引数组到一个索引缓冲中,供OpenGL使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 3. 设定顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 4. 解绑VAO(不是EBO!)
glBindVertexArray(0);
//...
// ..:: 绘制代码(游戏循环中) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!