on Modern OpenGL. -> 从graphics的programming开始讲的
Getting Start
OPENGL
- 是一个进行图像处理的工具
- 可以被认为是API,但是实际上是specification
- 明确说明了每个function应该的输入和输出,以及如何perform
- 用户在用这个说明来解决问题,因为没有给出明确的implement的过程,所以只要结果符合规则,怎么implement都可以
Core-profile vs Immediate mode
- 以前的版本用的是immediate mode
- 比较好用来画图
- 具体的是实现都在lib里面,developer不是很好的能看到如何计算
- 效率越来越低
- Core-profile
- 在3.2版本之后改成了这个
- 强制使用modern practices,如果想要用被分出去的function就会直接报错
- 效率高,更灵活,更难学
extensions
- 支持extensions,只要检查支不支持graphic card就可以知道能不能用
- 可以直接用比较新的东西,不用等着OPENGL更新新的功能
- 需要在用之前判断他是不是available的,如果不是需要用原来的方法搞
State Machine
- OpenGL自己就是一个State Machine:一个var的集合,来判断他现在应该如何操作
- state -> context
- 改变state:设定一些options,操作一些buffer,在现在的context来render
- 例子:
- 如果我想画三角形,而不是画线了,就改变draw的state
- 只要这个改变传达到了,下一条线就画的是三角形了
state-changing
用来改变context,state-using
在现在的state上面开始进行操作
Objects
- 一个集合来表现OpenGL的subset的state
- 比如可以用一个object来表示对window的设定,可以设置大小,设置支持的颜色等等
1
2
3
4
5
6// The State of OpenGL
struct OpenGL_Context {
...
object_name* object_Window_Target;
...
};
- 比如可以用一个object来表示对window的设定,可以设置大小,设置支持的颜色等等
1 | // create object |
- 流程
- 首先创建了一个object,里面存了一个ref是这个object的id
- 然后把这个object和context的目标位置bind在了一起
- 设置了这些window的参数
- 最后un-bind这两个东西,把window target改回原来的值
- 这样的话我们可以创建很多object,提前设置好里面的量,等到需要用的时候就直接bind就可以用了
- 比如我们有一堆object包含了小人,小马,小鹿
- 想画哪个就把哪个绑定到draw里面,就可以直接画出来了
Crateing a window
因为操作系统的问题,所有操作系统上面不是很一样。但是已经有一些提供这些功能的函数了,这里用的是GLFW
GLFW
- 一个lib,用C写的,主要目的是提供把东西渲染到屏幕的功能
- 可以创建一个context,定义窗口的params,处理用户的输入
已经一口气配置好了这些!
https://www.jianshu.com/p/25d5fbf792a2
记得在link lib里面把openGL的framework加进去!!!!!
GLAD
- 因为openGL还需要不同版本的driver的支持,需要有东西来处理这部分的内容
- 和其他的东西不同,GLAD用的是web service
- 在这个网页上选择好语言,版本号,确保profile是core,然后生成
- 直接下载下来对应的zip,然后把include放进include里面,.c文件放在project里面
- 莫名其妙并不需要这一步,神奇,可能是我在include里面已经搞进来了!!
Hello Window
初始化
1 | int main() |
- 首先进行了初始化
- 然后configure了GLFW,设置了a large enum of possible options prefixed with GLFW_. (第三行就是最小) -> 大概是设置要用GLFW的版本号
- 然后也告诉了他想用core
- 然后需要使用
glfwCreateWindow
这个函数,来创建这个GLFWwindow* window
的变量- 创建的函数需要窗口的长宽
- 窗口名
- 创建完之后就可以把这个窗口设置成
glfwMakeContextCurrent(window);
也就是说设置成了现在的thread里面1
2
3
4
5
6
7
8GLFWwindow* 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
- GLAD是为OpenGL来管理这些函数的,在使用这些函数之前需要初始化GLAD
1
2
3
4
5if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
viewpoint
- 在开始render之前我们还需要告诉GL渲染窗口的大小,用到了
glViewport
这个函数- 前面两个参数定义了这个窗口左下角的坐标
- 后面两个参数定义了需要render的窗口的大小
- 每次调整window的大小的时候viewport也需要被调整
engines
我们希望这个engine可以一直持续画图,直到最后我们告诉这个窗口要关闭,所以要建立一个循环
1
2
3
4
5while(!glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}在这个循环里面,pollevent是来检查是不是有trigger进来的事情(比如键盘输入),更新窗口的状态,并且call相应的函数
- swapbuffer,会交换color buffer(包括每个像素点颜色的buffer),然后show在窗口里面
last thing
glfwTerminate
退出这个循环之后,需要清除这些相关的资源,用这个函数放在最底下
input
- 需要一些键盘上的操作来调整的时候,写了一个processInput的函数
- 比如下面这个函数就是检测了有没有按下去esc,如果按了的话就关闭窗口
- 写完之后把这个函数在while循环里面调用
1
2
3
4
5void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
rendering
- 希望在一个loop里面放上去所有的rendering的命令,整个循环看起来应该是这个样子的
1
2
3
4
5
6
7
8
9
10
11
12
13// render loop
while(!glfwWindowShouldClose(window))
{
// input
processInput(window);
// rendering commands here
...
// check and call events and swap the buffers
glfwPollEvents();
glfwSwapBuffers(window);
}
hello triangle
opengl里面所有东西都是在3D的空间里的,但是屏幕上显示的东西是2D的。整个这个转换的过程叫做graphics pipeline,可以分成两个步骤:第一个是把物体的3D坐标转化成2D的坐标,第二个是把2D的坐标转化成pixel上面的具体值
pipeline
- 所有的转化步骤都可以parallel的进行,现在的显卡有很多小的core来进行 -> shaders
- 在最开始的时候pass进去了一个list的3D坐标 (Vertex Data)
- 第一步:vertex shader
- 把3D的坐标转化成不同的3D坐标(相当于把数据转化成点?)
- primitive assembly
- 从上一步得到的左右的点得到输入
- 然后形成一个基本的图形
- geometry shader
- 根据新给的点,形成新的不同的形状,比如在例子里面形成了新的一条线
- rasterization stage
- 把上面得到的primitives map到最后的屏幕上面的相应的pixel上面
- Clipping
- 这一步丢掉了所有在视线外面的fragments,提升性能
- fragment shader
- 计算这个pixel最后的颜色,会在这一步计算光影,以及光线的颜色等等东西
- 当每个像素的颜色决定了以后,这个object会被送到alpha test和blending
- 这一步会测试深度原因,判断fragment是在物体的前面还是后面
- 还会考虑透明度的问题
虽然上面的东西很复杂,但是在实际应用的时候只需要要考虑vertex和fragment shader
vertex input
- openGL是3D的东西,所有的点设置input的时候都需要设置三维的坐标 xyz
- 只有在坐标在 -1 到 1 中间的时候,才会处理这些坐标,这个范围里面的数字是根据屏幕的比例得出来的
normalized device coordinates
比如在这个例子里面,需要的渲染一个三角形,那么需要这个三角形的三个点的坐标。注意这个例子里面根本没有考虑深度,而是直接画在了平面上面
1
2
3
4
5float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};在定义好坐标之后需要把这个东西放进vertex shader里面,需要在GPU里面创建一部分内存来存储这个数据,并且需要在GPU里面存储大量的数据(这样不用每次都送了)
- 每个部分的object都会有一个自己的buffer id,可以通过下面的方法生成一个id。也可以把一串array绑到这个id上面
1
2unsigned int VBO;
glGenBuffers(1, &VBO);