为什么我要学习OpenGL,首先学习 OpenGL 具有许多优势,尤其是在计算机图形学和图形编程领域,它是有开放性和跨平台这两个显著优点的,还有其他的优势比如强大的功能,广泛的支持和应用,在OpenGL之上有许多开发的比较完善的库,而且它是有社区众多维护者的,非常主流,当然最重要的一点就是学了它工作技能点又上去了,程序员当然是多多益善。
一、什么是OpenGL
百度解释:OpenGL是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)
自我理解:OpenGL是一套方便于用户使用的规范,而其本身包含了调用不同厂商直接在GPU中写好的程序接口,那些接口完成所有的功能实现,如完成2D、3D矢量图形渲染等功能。
二、OpenGL和DirectX的区别,如何选择使用
OpenGL(Open Graphics Library)和 DirectX 是两个用于图形渲染的开发接口。它们分别由不同的公司和组织开发,分别用于不同的操作系统和平台。以下是对这两者的简要比较:
OpenGL:
跨平台:
OpenGL 是一个跨平台的图形渲染 API,可以在多种操作系统上运行,包括 Windows、Linux 和 macOS。OpenGL 可以运行在各种图形硬件上,并且支持多种编程语言,如C++、Python等。
DirectX:
Windows 平台专用:
DirectX 是由 Microsoft 开发的,主要用于 Windows 平台。因此,它是 Windows 游戏开发的首选 API。由于其与 Windows 平台的深度集成,DirectX 在与 Windows 系统的协同工作和性能优化方面可能会更好。
DirectX 不仅包括图形渲染功能,还包括音频、输入、网络等功能,使其成为一个全面的多媒体开发套件。
如何选择:
一句话:如果你的应用程序需要在多个操作系统上运行,那么 OpenGL 可能是更好的选择。如果你专注于 Windows 游戏开发,DirectX 可能更适合。
在上面的选择条件下如果你的应用程序不仅仅涉及图形渲染,还包括音频、输入等方面,那么 DirectX 可能更合适。
三、学习的起始与流程
当我在决定学习OpenGL之后,首先要做的就是制定学习的计划,像这种主流的前沿的技术,一般是需要向国外学习的。最后选择跟着一位国外的大神学习,主要他讲的很基础,也很全,而且他不仅有OpenGL的教学,还有学习了OpenGL之后的,想要深入游戏引擎的教学,这就很全面了,不至于学了之后,感兴趣却不得不换一个人学习的尴尬之地。
由于作者本身是c++开发出身,所以c++基础勉强能跟上,如果c++不是很了解的话,建议的学习流程是
- C/C++基础
- Visual Studio的使用
- OpenGL
- OpenGL在游戏引擎的应用
话不多说,我把我看的视频链接贴出来,下面的笔记都是由视频学习而来,或者有些自己的补充吧。
跟着这个小哥的教学视频学的(YouTube原视频,科学上网AI字幕) ► http://bit.ly/2lt7ccM
这个是哔哩哔哩网站有人搬运的 ►https://www.bilibili.com/video/BV1MJ411u7Bc/?share_source=copy_web&vd_source=80ce9fa9cc5a33fdc2b9a467859dd047
四、环境准备与OpenGL第一次调用
环境:
Visual Studio 2019
Win11
1. 调试通过OpenGL最基础的窗口和画三角形(老方法)
首先VS创建一个空项目,然后用最简单的HellowWorld输出打印能通过
然后引入GLFW库,全称是(Graphics Library Framework)是一个专门用于创建窗口和处理用户输入的开源库。它提供了一个简单而强大的接口,使得创建图形应用程序变得更加容易。
就是它可以创建一个窗口,然后里面的图像可以利用OpenGL机制来渲染。
我们通过官网获取这个库
GLFW ► https://www.glfw.org/
这里提一下视频教学中提了,对于我们自己用或者是快速的使用这些库,可以下载已经编译好的二进制文件及现成的库,但是如果是要开发项目或者是良好习惯最好是能下载源码,嵌入到自己的项目中,然后跟着项目一起编译,这样在遇到问题的时候可以直接跟踪到源码里面去看,也可以更加深入的学习。
还有关于32bit 和 64bit 这个也是根据需求和自身需要来下载,因为32在32和64机器上都能跑,而64则不能再32机器上跑,所以这里就选择通用一点的32下载。
当链接库时,连接静态库.lib后缀的库的时候,有时候会有两个名字非常类似的库,在选择的时候,去连接那个比较大的,因为比较小的可能是用于连接动态库的静态库,这种有些库名字上不太会体现的很明显。如图
然后利用VS链接静态库,最后使用传统的画图方法,画出三角形如下图
2. 利用库GLEW使用缓冲区和着色器的概念来画三角形
之后我们就利用GLEW,全称(OpenGL Extension Wrangler)是一个用于管理OpenGL扩展的库。它的主要目的是简化OpenGL的扩展加载过程,允许你更轻松地访问OpenGL的所有功能,包括最新的扩展。
人话就是让我们几乎可以用这些封装好的接口和函数,方便简单的调到OpenGL的所有内容,我记得OpenGL本身的程序是在GPU中,如果我们一个一个去看原始的那些函数,将会很麻烦,所以看这些封装好的接口就比较简单和快速了,不过官方文档还是不能错过。
我们通过官网获取这个库
GLEW ► http://glew.sourceforge.net/
和GLFW一样的方式导入,链接静态库,然后这里有许多注意事项,也是视频教学中提到的,在引用头文件的时候,#include < GL/glew.h>要在#include <GLFW/glfw3.h>之前,不然会报错,而且GLEW官方文档第一句提到的,你所要初始化的位置一定是要有渲染上下文的,不然就会初始化失败。
所以是要放在这个位置初始化才能成功的。
然后就是OpenGL操作GPU的现代化方式了,这里就需要引出两个东西:缓冲区和着色器
因为c++写的程序都是在cpu上运行的,但是OpenGL的接口是在GPU上运行的,而且OpenGL并不能凭空做程序中的数据或者是取代一些程序上的事,它是一种状态机,程序从cpu发数据到缓冲区并且告诉GPU你从哪一块缓冲区取数据画什么,然后提前设计好的着色器开始根据数据画图最后显示在显示器上。
画图渲染的顺序如下
- 声明一个缓冲区
- 声明之后需要绑定,因为在GPU中的缓冲区都是有编号的,或者说是有管理的
- 现在要给一个缓冲区塞数据,每个接口函数都可以通过说明文档来查看参数的意义和使用
- 我们需要告诉着色器我们的数据是怎么样的, 或者说是怎么处理这些数据
这里有一个些比较重要概念和一个接口函数和void glVertexAttribPointer(
GLuint , indexGLint , sizeGLenum , typeGLboolean , normalizedGLsizei , strideconst GLvoid * pointer)
;
人话就是:
- index:我们从第几个顶点开始访问
- size:一个顶点属性值里面有几个数值
- type:每个值的数据类型
- normalized:是否要转化为统一的值
- stride:步幅 每个顶点属性值的大小,就是到下一个顶点的开始的字节偏移量。
- pointer:在开始访问到顶点属性值的时候开始的指针位置(注意和Index的区别)
其实你就是把顶点属性值想象成结构体就行了,然后多个结构体一起存,和网络传输一样,我发送给了另一边需要解析网络包,是不是需要找我结构体开始的位置,然后一个结构体的大小,然后结构体对齐里面有什么,分别解析,还有步幅指针,我还可以跳过结构体,是一样的道理。
如图:
最后加上 可用的顶点从第几个开始的这个 glEnableVertexAttribArray(0); 函数。就可以打印出来三角形了。
五、附上视频1-5的学习代码(但是会有崩溃,暂时写到这,之后会更改的)
#include <iostream>
#include < GL/glew.h>
#include <GLFW/glfw3.h>
int main()
{
//std::cout << "Hello OpenGL" << std::endl;
//std::cin.get();
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
//if (glewInit() != GLEW_OK)
// std::cout << "GLEWInit ERROR!" << std::endl;
/* 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);
if (glewInit() != GLEW_OK)
std::cout << "GLEWInit ERROR!" << std::endl;
std::cout << "OpenGL的版本是:" << glGetString(GL_VERSION) << std::endl;
//声明一个float数组,顶点属性列表
float positions[6] = {
-0.5f, -0.5f,
0.0f, 0.5f,
0.5f, -0.5f
};
//这里声明一个缓冲区
unsigned int buffer;
glGenBuffers(1, &buffer);
//声明之后需要绑定,因为在GPU中的缓冲区都是有编号的,或者说是有管理的
glBindBuffer(GL_ARRAY_BUFFER, buffer);
//现在要给一个缓冲区塞数据,每个接口函数都可以通过说明文档来查看参数的意义和使用
glBufferData(GL_ARRAY_BUFFER, 6 * sizeof(float), positions, GL_STATIC_DRAW);
//可用的顶点从第几个开始
glEnableVertexAttribArray(0);
//index:我们从第几个顶点开始访问
//size:一个顶点属性值里面有几个数值
//type:每个值的数据类型
//normalized:是否要转化为统一的值
//stride:步幅 每个顶点属性值的大小,就是到下一个顶点的开始的字节偏移量。
//pointer:在开始访问到顶点属性值的时候开始的指针位置(注意和Index的区别)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
//我们需要告诉着色器我们的数据是怎么样的, 或者说是怎么处理这些数据
glDrawArrays(GL_TRIANGLES, 0, 3);
传统OpenGL的调用和使用方式
//glBegin(GL_TRIANGLES); //三角形
这里坐标在三角形的正中心0,0
//glVertex2f(-0.5f, -0.5f); //左
//glVertex2f( 0.0f, 0.5f); //中
//glVertex2f( 0.5f, -0.5f); //右
//glEnd();
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}
最后就是很多的OpenGL方法都是需要自己熟悉的,这里要锻炼一下自己的看说明文档的能力。OpenGL API 说明文档,包含了大量的opengl的接口函数使用方法和说明等等。
OpenGL Documentation ► http://docs.gl