计算机图形学实验笔记
计算机视觉学累了就看看计算机图形学…反向消化…
理论看累了就敲敲代码
说不弃坑,就不弃坑|( ´・∧・`)
实验:搭建OpenGL环境
OpenGL版本:3.3
GLFW
GLFW是一个OpenGL的C语言库,是对之前固定管线常用的GLUT的一种改进。
官网下载源代码包(推荐),或者下载32位的预编译的二进制版本。
编译glfw3.lib
我下载的最新3.3版,下载后解压。并新建一个build文件夹。
下载CMake,安装Win32版本。
找到bin文件夹下cmake-gui.exe,
填入源代码路径和build文件夹路径:
Where is the source code: E:/glfw-3.3
Where to build the binaries: E:/glfw-3.3/build
点击Configure按钮,选择合适的生成器(我选择的是VS2015)。点Finish。
再次点击Configure按钮保存设置。
点击Generate按钮,生成工程文件。
在build文件夹打开GLFW.sln文件,并且生成解决方案。
在E:\glfw-3.3\build\src\Debug中所需要的glfw3.lib就有了。
配置
方便起见,我专门建立了一个文件夹OpenGLFiles用来存放相关的头文件和库文件。
在文件夹下新建include文件夹和lib文件夹。
将glfw-3.3\include下的文件复制到新建的include文件夹中,将glfw3.lib复制到新建的lib文件夹中。
新建项目所需工程文件,创建空项目。
打开工程属性页,选择VC++目录-包含目录中加上:
E:\OpenGLFiles\include
库目录加上:
E:\OpenGLFiles\lib
链接器-输入,附加依赖项加上:
glfw3.lib
GLAD
开发者需要在OpenGL运行时获取函数地址并保存在一个函数指针中,取地址方法因平台而异,而GLAD库能简化该过程。
通过GLAD在线服务,language选择C/C++,gl选择3.3,Profile选择Core,Options选中Generate a loader。
点击Generate,下载压缩包。解压后,将include文件夹下的文件复制到我新建的include文件夹里,并且将src下的glad.c添加到工程中。
环境搭好了!
实验:绘制一个窗口
包含头文件
1 |
初始化GLFW
1 | glfwInit(); // 初始化GLFW |
查看GLFW版本
1 | int Major, Minor, Rev; |
创建窗口
1 | GLFWwindow* window = glfwCreateWindow(screen_width, screen_height, "HelloWorld", nullptr, nullptr); |
初始化GLAD
加载OpenGL函数指针地址的函数1
2
3
4
5if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
指定视口
1 | glViewport(0, 0, screen_width, screen_height); |
glViewport函数前两个参数控制窗口左下角的位置,第三、第四个参数
控制渲染窗口的宽度和高度(像素)。
实际上也可以将视口的维度设置为比GLFW的维度小,这样所
有的OpenGL渲染将会在一个更小的窗口中显示,这样我们可以将一
些其它元素显示在OpenGL视口之外。
渲染
1 | while (!glfwWindowShouldClose(window)) |
双缓冲:
单缓冲使图像闪烁(图像是从左到右,从上到下逐像素绘制),不够真实。使用双缓冲规避该问题,前缓冲保存着最终输出的图像,显示在屏幕上。所有渲染指令在后缓冲上绘制,指令执行完毕后交换(swap)前缓冲和后缓冲,图像会立即呈现。
渲染结束,释放资源:1
glfwTerminate();
运行,此时会看到一个黑色的窗口。
修改窗口颜色
在渲染循环中加入:1
2glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
当调用 glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为
glClearColor里所设置的颜色。
运行效果
1 | GLFW 3.3.0 initialized |
完整代码
https://github.com/hubojing/ComputerGraphics/blob/master/CreateWindow.cpp
总结:
- 初始化:GLFW窗口,GLAD。
- 渲染:清空缓冲,交换缓冲区检查触发事件后释放资源。
实验:绘制三角形
初始化
1 | //初始化GLFW |
顶点输入
三角形顶点数据是标准化的设备坐标,即x,y,z轴坐标映射到[-1,1]之间。1
2
3
4
5
6const float triangle[]=
{
-0.5f,-0.5f,0.0f;//左下
0.5f, -0.5f, 0.0f;//右下
0.0f, 0.5f, 0.0f;//正上
};
数据处理
VBO
将顶点数据发送到GPU处理。生成一个顶点缓冲对象VBO,将其绑定到顶点缓冲对象上。
作用:不用将顶点数据逐个发送至显卡,可借助VBO一次性发送过去。
再使用glBufferData将顶点数据绑定到当前默认的缓冲上。1
2
3
4
5
6//生成并绑定VBO
GLuint vertex_buffer_object;
glGenBuffers(1, &vertex_buffer_object);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
//将顶点数据绑定到当前默认的缓冲中
glBufferData(GL_ARRAY_BUFFER, sizeof(triangle), triangle, GL_STATIC_DRAW);
VAO
作用:核心模式需要使用VAO,渲染时只需调用一次VAO即可,之前的数据对应存储在VAO中,不用再调用VBO。1
2
3GLuint vertex_array_object;
glGenVertexArrays(1, &vertex_array_object);
glBindVertexArray(vertex_array_object);
顶点属性
告诉OpenGL如何解释顶点数据。1
2
3//设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer函数参数含义:
1-顶点着色器位置值
2-顶点属性是一个三分量的向量
3-顶点类型
4-数据是否被标准化(映射到0-1之间)
5-步长(这里表示下组数据在3个float之后)
6-数据偏移量(此处位置属性在数组开头,因此为0)
glEnableVertexAttribArray表示开启0这个通道,默认状态是关闭的。
此时需要解绑VAO和VBO。
原因:
1. 防止继续绑定VAO时影响当前VAO。
2. 使代码更具灵活性,在渲染需要时会再次绑定VAO。1
2glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
顶点着色器和片段着色器
顶点着色器
GLSL语言,类似C语言。
源码:1
2
3
4
5
6
7const char *vertex_shader_source =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos, 1.0);\n"
"}\n\0";
第一行:使用OpenGL3.3核心模式
第二行:上面提到的位置值 in表示输入变量
main函数中将顶点数据直接输出到GLSL定义好的内建变量gl_Position中,这是顶点着色器的输出。
(即在顶点着色器这儿上面都没做,只是将顶点位置作为顶点着色器的输出。)
片段着色器
源码:1
2
3
4
5
6
7const char *fragment_shader_source =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
前两行类似上面,out表示输出变量。四分量是RGBA。
生成和编译
目的是为了得到着色器程序,所以首先生成和编译着色器,再链接到着色器程序中。
生成并编译顶点着色器1
2
3
4
5
6
7
8
9
10
11
12int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
glCompileShader(vertex_shader);
int success;
char info_log[512];
//是否成功编译
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << info_log << std::endl;
}
生成并编译片段着色器1
2
3
4
5
6
7
8
9
10int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
glCompileShader(fragment_shader);
//是否成功编译
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << info_log << std::endl;
}
链接顶点和片段着色器至一个着色器程序,并删除着色器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
//是否成功链接
glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(shader_program, 512, NULL, info_log);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info_log << std::endl;
}
//删除着色器
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
渲染
渲染时只需使用链接好的着色器程序就行,不再需要使用顶点和片段着色器。
窗口未关闭就一直进行渲染。1
2
3
4while (!glfwWindowShouldClose(window))
{
//渲染操作
}
这里使用蓝色背景色清空屏幕颜色缓冲。1
2glClearColor(0.0f, 0.34f, 0.57f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
接下来使用链接好的着色器和VAO来绘制三角形。1
2
3
4
5
6//使用着色器程序
glUseProgram(shader_program);
//绘制三角形
glBindVertexArray(vertex_array_object);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
其实绘制本身只是一个glDrawArrays函数,参数1表示三角形,参数2表示顶点数组起始索引值,参数3表示要绘制的顶点数量。 绘制结束后解除绑定。
双缓冲技术:1
2
3
4//交换缓冲
glfwSwapBuffers(window);
//检查是否有触发事件(键盘输入、鼠标移动等)
glfwPollEvents();
善后工作
1 | //删除VAO和VBO |
实验效果
完整代码
https://github.com/hubojing/ComputerGraphics/blob/master/Triangle
总结:
- 初始化。
- 数据处理:给定顶点数据,生成并绑定VAO和VBO,准备在GPU处理,设置顶点属性指针(告诉OpenGL怎么处理数据)。
- 着色器:生成并编译顶点和片段着色器,链接为着色器程序。
- 渲染绘制三角形。
下一个实验准备中…