本系列来自 youtube 主播cherno的 openGL系列, b站双语版链接 https://www.bilibili.com/video/BV1Ni4y1o7Au?p=1&vd_source=0be7021510d651441db0edd576304997 这个视频教程很不错的,值得看。
其他资料:LearnOpenGL 、英语版
简介
OpenGL 是一个接口规范,定义了一系列接口,我们可以通过使用这些接口来一定程度的操作GPU。它不是一个库、一个框架、一种引擎之类的,它只是一个规范。有点类似于 C++ 中的函数的声明,具体的实现各个平台可能大相径庭。openGL 之所以能做到跨平台是因为各个平台的GPU提供商按照规范实现了它的接口。
OpenGL自身是一个巨大的状态机(State Machine):一系列的变量描述OpenGL此刻应当如何运行。OpenGL的状态通常被称为OpenGL上下文(Context)。我们通常使用如下途径去更改OpenGL状态:设置选项,操作缓冲。最后,我们使用当前OpenGL上下文来渲染。假设当我们想告诉OpenGL去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。一旦我们改变了OpenGL的状态为绘制线段,下一个绘制命令就会画出线段而不是三角形。当使用OpenGL的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前OpenGL的状态执行一些操作。只要你记住OpenGL本质上是个大状态机,就能更容易理解它的大部分特性。
OpenGL跨平台的特性是它的巨大优势,但并不能因此就认为它就是最好的图形API,实际上各个平台原生的图形API通常比这种跨平台的API要更强大。 比如你打算开发的游戏只支持window平台,Direct3D会更好一些。但是openGL 仍然值得一学,它简单易上手,而且稳定,对于大部分人来说,学会这个就可以做跨平台的游戏开发了。
环境
后续我们将在 Windows 平台用 visualStudio 及 c++语言 借助GLFW库和GLEW库 来进行开发。
GLFW(Graphics Library Framework)
GLFW是一个开源的、跨平台的c语言库,支持 OpenGL, OpenGL ES and Vulkan开发。借助于它提供的一些API,我们可以极为简单地创建窗口、上下文和界面、接收输入和事件。
GLEW (OpenGL Extension Wrangler)
因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询。所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中供以后使用。取得地址的方法因平台而异,在Windows上会是类似这样:
// 定义函数原型
typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
// 找到正确的函数并赋值给函数指针
GL_GENBUFFERS glGenBuffers = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
// 现在函数可以被正常调用了
GLuint buffer;
glGenBuffers(1, &buffer);
你可以看到代码非常复杂,而且很繁琐,我们需要对每个可能使用的函数都要重复这个过程。幸运的是,有些库能简化此过程,其中GLEW是其中之一。
PS: 在计算机领域,“wrangler” 也可以指数据工程师或数据科学家,负责处理和清洗数据。
开始
使用visualStudio 新建一个空白的c++项目,为了方便我们可以在直接引入GLFW和GLEW预编译的链接库。
具体方法这里不在赘述,上述视频链接里的第二集(引入GLFW)、第三集(引入GLEW)有详细步骤可以参考一下。
新建文件 Application.cpp 粘入glfw示例代码:
#include <GLFW/glfw3.h>
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}
编译,报错:
1>Application.obj : error LNK2019: 无法解析的外部符号 __imp__glClear@4,函数 _main中引用了该符号
搜一下 glClear,会发现它依赖 Opengl32:
windows内置了Opengl32.lib,直接引入即可编译通过。
对于源码中一些函数的说明:
glfwWindowShouldClose函数 在我们每次循环的开始前检查一次GLFW是否被要求退出,如果是的话,该函数返回true,渲染循环将停止运行,之后我们就可以关闭应用程序。
glClear(GL_COLOR_BUFFER_BIT) ,清空屏幕的颜色缓冲。在每个新的渲染迭代开始的时候我们总是希望清屏,否则我们仍能看见上一次迭代的渲染结果(这可能是你想要的效果,但通常这不是)。
glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
双缓冲(Double Buffer)–应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。
glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数(可以通过回调方法手动设置)。
glfwTerminate当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。
别忘了按照文档 调用 GLEW的初始化:
//glew.h 要求比 gl.h 先引入,故放在#include <GLFW/glfw3.h> 之前
#include<GL/glew.h>
//glewInit() 要求在context 创建之后调用, 故放在glfwMakeContextCurrent(window); 之后
glewInit();