参考官方文档:https://learnopengl-cn.github.io/
一直对OpenGL很感兴趣,但每次找到网上各种教程,跟着教程做总是不知道哪里出错了总是运行不出来,可能是自己有某一步出错了,也可能是这些教程都漏了什么细节没交代。这里终于根据官方教程结合自己的探究总算成功完成了第一步:创建一个窗口。为此,必须把整个步骤详细交代以下,方便作为以后的参考。
知识储备:这里是需要熟悉C++,虽然其他语言如C#也是可以通过安装OpenTK来进行OpenGL编程的,但OpenTK的相关资料太少了,所以感觉研究难度很大,要入门还是C++合适。然后是基本的数学知识:线性代数、几何等。不会这些的话,玩不来这些自然就被劝退了。
构建GLFW
-
首先是从官方网站下载最新版本的源代码包并解压。https://www.glfw.org/download.html。
-
然后是用CMake来构建。CMake最好是最新版的,它的版本是和VS的版本相关的。启动CMake,需要选择两个目录,源代码目录选定GLFW的源代码目录,新建一个
build
目录作为目标目录。 -
点击
Configure
按钮,选择对应的VS版本确定,然后点Generate
按钮。
-
在文件管理器中找到第二步创建的
build
文件夹,其中有个文件GLFW.sln
,双击用VS打开,在VS中的菜单栏的生成
栏的生成解决方案
生成成功后就行了。顺便其中包含了大量的Examples
和Tests
,其中有很多的小项目可以在后面出粗的时候作为一个参考。 -
上一步完成后,在
..\build\src\Debug\
下能找到文件glfw3.lib
。这个文件很重要。另外还有..\glfw-master\include
下的GLFW
文件夹也是很重要的。可以额外在任意的路径下新建一个文件夹比如命名为relation
,将GLFW
文件夹和glfw3.lib
文件都放进去。 -
配置GLAD。打开网址https://glad.dav1d.de/,如图选择
gl
栏和Profile
栏,最后点击GENERATE
。然后下载glad.zip
文件解压就行了。
-
接下来是容易出问题的。打开VS,新建一个C++的空白应用。首先确认
Debug
和x64
,通常默认的Debug
和x86
一定要改为x64
,正是这里总是导致出错。右击项目选择属性,要确认是Debug
和活动(x64)
。在VC++目录
下修改包含目录和库目录。
-
然后在
连接器
|输入
下的添加依赖项下添加两项:opengl32.lib
和glfw3.lib
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JX6wfPao-1590500907788)(C:\Users\xhh\Pictures\QQ浏览器截图\opentk_9.png)]
- 在解决方案资源管理下右击
源文件
添加现有项选择..\glad\src
下的glad.c
文件。然后新建C++源文件main.cpp
,输入代码:
#include<iostream>
#include<glad/glad.h>
#include<GLFW/glfw3.h>
int main(void) {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GlAD" << std::endl;
return -1;
}
glViewport(0, 0, 800, 600);
void framebuffer_size_callback(GLFWwindow * window, int width, int height);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
while (!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
如果上面的所有步骤都没问题的话最后运行会出现一个控制台窗口和一个额外的窗口。就说明成功运行了。如果失败的话再回过头看一下哪一步有问题。
必要的说明:
- 先是调用
glfwInit
函数来初始化GLFW,然后用glfwWindowHint
函数来配置GLFW。
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
}
- 接下来创建一个窗口对象,它存放了所有和窗口相关的数据,而且会被GLFW的其他函数频繁用到。
glfwCreateWindow
函数的前三个参数分别是宽、高和标题。这个函数会返回一个GLFWwindow
对象。创建完窗口我们就可以通知GLFW将我们的窗口的上下文设置为当前线程的主上下文。
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
- GLAD是用来管理OpenGL的函数指针的,所以我们需要先初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GlAD" << std::endl;
return -1;
}
- 开始渲染之前我们必须告诉OpenGL渲染窗口的尺寸大小。
glViewport(0, 0, 800, 600);
- 但当用户改变窗口的大小时,视口也需要被调整。我们可以对窗口注册应给回调函数,他会在每次窗口大小被调整时被调用。我们可以先在这里声明,在main函数后面再给出完整的定义。这样就不会有语法错误了。也可以在main函数之前给出函数的定义。
int main{
...;
void framebuffer_size_callback(GLFWwindow * window, int width, int height);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
...;
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
- 然后我们需要在程序中添加一个while循环,通常称为渲染循环,它可以在我们让GLFW退出前保持运行。
while (!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);//交换颜色缓冲,它在每一次迭代中被用来绘制,并作为输出显示在屏幕上
glfwPollEvents();//检测有没有触发什么事件、更新窗口状态,并调用对应的回调函数
}
- 最后,渲染循环结束后我们需要正确的释放和删除之前分配的所有资源。
glfwTerminate();
return 0;
- 我们可以添加一些输入控制,如让它响应键盘输入,在main函数前定义一个函数,然后在渲染循环的每一次迭代中调用这个函数:
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
int main(){
...;
while (!glfwWindowShouldClose(window))
{
processInput(window);
glfwSwapBuffers(window);//交换颜色缓冲,它在每一次迭代中被用来绘制,并作为输出显示在屏幕上
glfwPollEvents();//检测有没有触发什么事件、更新窗口状态,并调用对应的回调函数
}
glfwTerminate();
return 0;
}
- 我们可以在渲染循环中放入一些渲染操作。如用一个自定义的颜色清空屏幕。可以调用
glClear
函数来清空屏幕的颜色缓冲,它接受一个缓冲位来指定要清空的缓冲,可选的缓冲位有:GL_COLOR_BUFFER_BIT
、GL_DEPTH_BUFFER_BIT
和GL_STENCLL_BUFFER_BIT
。我们这里选择颜色缓冲。
while (!glfwWindowShouldClose(window))
{
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);//交换颜色缓冲,它在每一次迭代中被用来绘制,并作为输出显示在屏幕上
glfwPollEvents();//检测有没有触发什么事件、更新窗口状态,并调用对应的回调函数
}
这样我们运行以后窗口的屏幕变为我们设置的颜色