learn-opengl入门
opengl是一个大状态机环境配置1.下载GLFW源代码2.创建librarel文件包含include目录和第三方库libs3.include直接从GLFW的源代码中拿,libs里需要编译生成的库glfw3.lib。4.通过CMake编译源代码拿到glfw3.lib。5.将我们的librarel加入编辑器的包含文件和库目录里解决方案处右击-属性-链接器6.GLAD库将不同显卡商的函数整合节省使用成本下载会得到include和glad.cinclude放到我们的include中glad.c添加到当前解决方案中入门创建窗口初始化版本和模式glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_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;}初始化gladglViewport(0, 0, 800, 600);初始化视口void framebuffer_size_callback(GLFWwindow* window, int width, int height){glViewport(0, 0, width, height);}glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);调整窗口时视口大小也跟着调整while(!glfwWindowShouldClose(window)){glfwSwapBuffers(window);//交换缓冲glfwPollEvents(); //检查有没有触发消息事件}死循环保证窗口不关闭void processInput(GLFWwindow *window){if(glfwGetKey(window, GLFW_KEY_ESCAPE) GLFW_PRESS)glfwSetWindowShouldClose(window, true);}输入函数可以加入主循环glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);清除颜色缓冲可以加入主循环创建三角形VBOunsigned int VBO;glGenBuffers(1, VBO);创建vbo对象glBindBuffer(GL_ARRAY_BUFFER, VBO);绑定到缓冲GL_ARRAY_BUFFERglBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);给缓冲传输数据(状态机每个缓冲对象的状态都会被绘制)glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数openl有很多缓冲对象所以我们创建一个对象绑定到缓冲上他就是什么缓冲类型。缓冲对象的id和类型需要不一样然后给缓冲类型传递数据数据就到缓冲对象上了顶点着色器#version 330 corelayout (location 0) in vec3 aPos;# 创建一个位置为0(在外面输入数据给变量的时候用),in输入变量vec3向量类型的 apos变量void main(){gl_Position vec4(aPos.x, aPos.y, aPos.z, 1.0);# gl_Position是写死的代表顶点位置}#创建着色器unsigned int vertexShader;vertexShader glCreateShader(GL_VERTEX_SHADER);#opengl中的对象都是无符号整型因为着色器只要传递代码就可以数量不需要太多也不需要太多方法所以无符号整型就够用只要将无符号整型绑定对象或者着色器即可上面就是将新创建的着色器对象所绑定的无符号整型赋值给vertexShader,后续只要传入就知道它对应的具体是什么对象glShaderSource(vertexShader, 1, vertexShaderSource, NULL);# 顶点着色器对象字符串数量着色器源码 NULLglCompileShader(vertexShader);#判断编译是否成功int success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, success);# opengl大部分函数没有返回值结果需要传入对象获取一切都是为了数据传递高速#不成功就获得信息if(!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout “ERROR::SHADER::VERTEX::COMPILATION_FAILED\n” infoLog std::endl;}片段着色器#version 330 coreout vec4 FragColor;# 输出变量void main(){FragColor vec4(1.0f, 0.5f, 0.2f, 1.0f);}创建片段着色器unsigned int fragmentShader;fragmentShader glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, fragmentShaderSource, NULL);glCompileShader(fragmentShader);创建程序对象unsigned int shaderProgram;shaderProgram glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);glGetProgramiv(shaderProgram, GL_LINK_STATUS, success);if(!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);…}glUseProgram(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// 0. 复制顶点数组到缓冲中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 1. 设置顶点属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// 2. 当我们渲染一个物体时要使用着色器程序glUseProgram(shaderProgram);// 3. 绘制物体someOpenGLFunctionThatDrawsOurTriangle();绑定VBO之后如果使用其他VBO那么使用原来的VBO又需要重新设置因为这是一个状态机状态变成另一个VBO对象了那么有没有办法记录这个状态呢于是有了VAO顶点数组对象VAO//创建VAOunsigned int VAO;glGenVertexArrays(1, VAO);// …:: 初始化代码只运行一次 (除非你的物体频繁改变) :: …// 1. 绑定VAO绑定VAO之后会记录接下来VBO的状态下次绑定VAO就可以还原这个状态glBindVertexArray(VAO);// 2. 把顶点数组复制到缓冲中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 3. 设置顶点属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);[…]// …:: 绘制代码渲染循环中 :: …// 4. 绘制物体glUseProgram(shaderProgram);glBindVertexArray(VAO);someOpenGLFunctionThatDrawsOurTriangle();顶点索引对象(EBO)绘制图形时存在多个顶点重合的情况那么没有必要传递重合的顶点提高效率所以我们可以只传递不同的顶点然后根据索引来知道绘制哪些顶点这就是EBO//创建EBOunsigned int EBO;glGenBuffers(1, EBO);//传递数据// …:: 初始化代码 :: …// 1. 绑定顶点数组对象glBindVertexArray(VAO);// 2. 把我们的顶点数组复制到一个顶点缓冲中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 3. 复制我们的索引数组到一个索引缓冲中供OpenGL使用glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 4. 设定顶点属性指针glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);[…]// …:: 绘制代码渲染循环中 :: …glUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)glBindVertexArray(0);着色器使用的GSLS语言1.固定名字的一些变量比如gl_Position这种内置代表固定含义的变量2.自己定义的输入输出变量in,out.可以用Layout(location…)来设置位置值以方便代码传值进来也可以通过glGetAttribLocation获取位置值进行数据传递3.同样的名字的输出变量会自动链接到下一个着色器相同名字的输入变量里4.uniform全局唯一变量每个着色器都一样且每个着色器都能访问这样就可以跳过顶点着色器给片段着色器传值了#version 330 coreout vec4 FragColor;uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量void main(){FragColor ourColor;}#获取uniform的location并且传递值float timeValue glfwGetTime();float greenValue (sin(timeValue) / 2.0f) 0.5f;int vertexColorLocation glGetUniformLocation(shaderProgram, “ourColor”);glUseProgram(shaderProgram);glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);着色器类(封装)封装着色器即可VAO,VBO环境构建好了之后设置使用项目和传递uniform的值即可纹理给绘制的图形贴图我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate)用来标明该从纹理图像的哪个部分采样译注采集片段颜色。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);设置s ,t ,r轴的不同纹理贴图方式float borderColor[] { 1.0f, 1.0f, 0.0f, 1.0f };glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);设置纹理贴图的边缘颜色纹理过滤纹理坐标的取样方式在坐标位于的像素块如何取颜色一般分为最近和混合两种最近:用最近的必然产生像素感显得很突兀混合比较自然但是容易感觉比比较糊多级渐远纹理如果一个物体在远处在图片中它非常小占有的像素块很小那我们进行纹理采样时如果依然对周围的像素块进行融合采样就会使颜色丢失显得不真实所以有了多级渐远纹理当距离达到一定的阈值时我们将采样范围缩小为原先的二分之一这样对于距离不远的物体来说也可以节省性能过滤方式 描述GL_NEAREST_MIPMAP_NEAREST 使用最邻近的多级渐远纹理来匹配像素大小并使用邻近插值进行纹理采样GL_LINEAR_MIPMAP_NEAREST 使用最邻近的多级渐远纹理级别并使用线性插值进行采样GL_NEAREST_MIPMAP_LINEAR 在两个最匹配像素大小的多级渐远纹理之间进行线性插值使用邻近插值进行采样GL_LINEAR_MIPMAP_LINEAR 在两个邻近的多级渐远纹理之间使用线性插值并使用线性插值进行采样1.使用当前采样块的当前块块像素颜色进行插值2.使用当前采样块的所有像素块进行混合3.使用所有采样块的当前像素块进行插值4.使用所有采样块的所有像素块进行混合加载与创建纹理stb_image.h直接复制过去就可以使用api加载图片数据int width, height, nrChannels;unsigned char *data stbi_load(“container.jpg”, width, height, nrChannels, 0);# 长宽通道(r,g,b,a等)0创建纹理对象unsigned int texture;glGenTextures(1, texture);绑定纹理对象到缓冲glBindTexture(GL_TEXTURE_2D, texture);把图片数据添加到绑定的对象glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);# 第二个参数是纹理等级glGenerateMipmap(GL_TEXTURE_2D);# 当调用glTexImage2D时当前绑定的纹理对象就会被附加上纹理图像。然而目前只有基本级别(Base-level)的纹理图像被加载了如果要使用多级渐远纹理我们必须手动设置所有不同的图像不断递增第二个参数。或者直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。释放图像数据内存stbi_image_free(data);#设置顶点属性指针float vertices[] {// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上};#设置顶点属性指针(如何读数据)glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));# 第一个参数是传到着色器里的位置值glEnableVertexAttribArray(2);#着色器#version 330 corelayout (location 0) in vec3 aPos;layout (location 1) in vec3 aColor;layout (location 2) in vec2 aTexCoord;out vec3 ourColor;out vec2 TexCoord;void main(){gl_Position vec4(aPos, 1.0);ourColor aColor;TexCoord aTexCoord;}#片段着色器#version 330 coreout vec4 FragColor;in vec3 ourColor;in vec2 TexCoord;uniform sampler2D ourTexture;# 采样器void main(){FragColor texture(ourTexture, TexCoord);#采样器 纹理坐标}#最后绑定纹理glBindTexture(GL_TEXTURE_2D, texture);glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);#激活纹理单元0单元默认被激活所以第一个纹路uniform不用传值glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元glBindTexture(GL_TEXTURE_2D, texture);#若要绑定第二个纹理那么就需要激活第二个纹理单元(最多16个)glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);#激活了第二个纹理单元那么就需要给着色器uniform传值告诉它是哪个纹理单元对应哪个uniformourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序glUniform1i(glGetUniformLocation(ourShader.ID, “texture1”), 0); // 手动设置ourShader.setInt(“texture2”, 1); // 或者使用着色器类设置while(…){[…]}变换向量加减、取反、点乘点乘:数学上a.b a.xb.xa.yb.y几何意义a的长度b的长度cosθ叉乘数学叉乘在数学上是a*bsinθ(向量所围成的四边形面积)几何意义上叉乘得到的是一个向量这个向量的方向是垂直与两条叉乘向量的第三条向量这个向量的模是向量所围成的四边形面积用矩阵求叉乘的方式只是在默认第三条向量为单位向量的情况下才能求结果就是求出第三条向量为1时另两条求行列式就是面积矩阵乘法用来做偏移为什么要用4*4矩阵1.一个2*2矩阵可以看作是坐标系里的两个基向量。那么如果需要旋转一个点只需要知道旋转后点的位置的坐标基就能知道旋转矩阵了比如(1,1)要变成(1,2)那么基向量需要从xy11变成1:2。那么y轴就需要进行倾斜才能成为1:2从而使得点在(1,2)的位置。那么倾斜后的基向量就是旋转矩阵2.在空间上的表现我们可以通过坐标基变换的计算方法可以算出坐标变换坐标基后的坐标那么我们数学上若要另点11变换成1,2也只需要求得对应的坐标基即可求得新点3.平移无法通过矩阵乘法实现当然如果对于特定的值专门做一个矩阵肯定是可以实现的但那不通用如果希望平移23找到一个矩阵可以另所有点都平移23是不可能的因为这取决于被平移这个向量本身所有肯定找不到这么一个通用矩阵。但是旋转和缩放可以找到一个矩阵可以使所有点都旋转30度和缩放两倍都是可行的。4.那么不能平移怎么办于是引入了w分量用来做映射通过增加一个维度来让每一个维都增加想要的位移。5.增加z轴的空间意义就是增加一个w的坐标轴对这条轴进行变换可以使任何一个点在w方向上倾斜致使其他三维都增加一个固定量这个量只与点在w方向上的分量以及w轴的倾斜程度有关。缩放矩阵乘法可以通过多个矩阵相乘变成一个信息A*B C矩阵从右向左看因为是左乘所以B最先乘。先做B矩阵的变换位移旋转万向节死锁:简单的如先向x旋转90度再z旋转90.此时y轴和旋转前的x轴重合那么此时如果再绕y旋转没有任何意义跟一开始绕z旋转的效果一致就丢失了一个维度的信息这就使万向节死锁行主序与列主序opengl是列主序如果要传入行主序需要矩阵转置运算GLM一个做矩阵变换的apiglm::rotate//旋转glm::scale//缩放glm::translate//平移glm::vecn()//创建向量glm::matn()//创建矩阵新版默认是0旧版默认是单位矩阵总结先旋转后平移和先平移后旋转是不一样的因为所有的旋转都是根据最开始的状态来的旋转会根据最开始的旋转点而平移不一样平移的方向会受旋转影响坐标系统局部空间世界空间观察空间摄像机裁剪空间如果是图元裁剪有点超出了就构不成形状了如何绘制呢会把它变成一个或多个在范围内的新的三角形然后在绘制(意思是这是重绘不是截图超出了就不是封闭图形了只能变成其他三角形来绘制)透视投影正射投影会不真实因为是直接投上去的没有根据距离进行进行改变投影点的w也没有变化也就是没有透视glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);前两个参数指定了平截头体的左右坐标第三和第四参数指定了平截头体的底部和顶部。通过这四个参数我们定义了近平面和远平面的大小透视投影加入了透视(远小近大)且w会随距离远变大使得图形变小glm::mat4 proj glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);//视角斜度,宽高比进截面远截面3D-2D组合到一起之后VclipMprojection⋅Mview⋅Mmodel⋅Vlocal//模型矩阵glm::mat4 model;model glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));//观察矩阵glm::mat4 view;// 注意我们将矩阵向我们要进行移动场景的反方向移动。view glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));//投影矩阵glm::mat4 projection;projection glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);模型矩阵旋转观察矩阵移动投影矩阵调节透视等创建多个立方体利用for循环和矩阵将一组顶点数据通过矩阵变换画多个几何体本应遮挡的面会被绘制出来覆盖在其他面上面就会很奇怪Z缓冲OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中也被称为深度缓冲(Depth Buffer)然而如果我们想要确定OpenGL真的执行了深度测试首先我们要告诉OpenGL我们想要启用深度测试它默认是关闭的。glEnable(GL_DEPTH_TEST);因为我们使用了深度测试我们也想要在每次渲染迭代之前清除深度缓冲否则前一帧的深度信息仍然保存在缓冲中。就像清除颜色缓冲一样我们可以通过在glClear函数中指定DEPTH_BUFFER_BIT位来清除深度缓冲glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);imgui一个图形api总结:1.fov角度会导致场景物体放大缩小视角变大物体就会缩小aspect-ratio宽高比导致物体压扁或拉长只运用比值2.相当于移动摄像机3.3的时候不加入时间控制模型矩阵摄像机摄像机位置摄像机方向让摄像机指向原点那么摄像机方向就是原点-摄像机位置右轴利用摄像机方向叉乘摄像机上向量则得到右轴上轴右轴和摄像机方向叉乘LOOK_AT前面的教程中我们讨论了观察矩阵以及如何使用观察矩阵移动场景我们向后移动了一点。OpenGL本身没有摄像机(Camera)的概念但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机产生一种我们在移动的感觉而不是场景在移动。本节我们将会讨论如何在OpenGL中配置一个摄像机并且将会讨论FPS风格的摄像机让你能够在3D场景中自由移动。我们也会讨论键盘和鼠标输入最终完成一个自定义的摄像机类ps:就是更改观察矩阵使所有点都投射到观察矩阵的坐标空间内就制造出了摄像机原理:矩阵可以把一个向量转换到自己的坐标空间下。前三条就是坐标空间基向量(需要线性无关)通过这种方式使目标点转换到自己的坐标空间下也就是观察矩阵内接口:只需要传入摄像机位置目标点以及空间中的上向量即可其他可以通过这三个计算得到示例自由移动glm::vec3 cameraPos glm::vec3(0.0f, 0.0f, 3.0f);glm::vec3 cameraFront glm::vec3(0.0f, 0.0f, -1.0f);glm::vec3 cameraUp glm::vec3(0.0f, 1.0f, 0.0f);view glm::lookAt(cameraPos, cameraPos cameraFront, cameraUp);void processInput(GLFWwindow *window){…float cameraSpeed 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) GLFW_PRESS)cameraPos cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) GLFW_PRESS)cameraPos - cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) GLFW_PRESS)cameraPos - glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) GLFW_PRESS)cameraPos glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;}视角移动把欧拉角转换为三维向量direction.y sin(glm::radians(pitch)); // 注意我们先把角度转为弧度direction.x cos(glm::radians(pitch));direction.z cos(glm::radians(pitch));在这里插入图片描述direction.x cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注direction代表摄像机的前轴(Front)这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的direction.y sin(glm::radians(pitch));direction.z cos(glm::radians(pitch)) * sin(glm::radians(yaw));这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。你可能会奇怪我们怎么得到俯仰角和偏航角鼠标输入隐藏光标并捕获鼠标输入数值glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);为了计算俯仰角和偏航角我们需要让GLFW监听鼠标移动事件。和键盘输入相似我们会用一个回调函数来完成函数的原型如下void mouse_callback(GLFWwindow* window, double xpos, double ypos);这里的xpos和ypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后鼠标一移动mouse_callback函数就会被调用glfwSetCursorPosCallback(window, mouse_callback);最后的代码应该是这样的void mouse_callback(GLFWwindow* window, double xpos, double ypos){if(firstMouse){lastX xpos;lastY ypos;firstMouse false;}float xoffset xpos - lastX; float yoffset lastY - ypos; lastX xpos; lastY ypos; float sensitivity 0.05; xoffset * sensitivity; yoffset * sensitivity; yaw xoffset; pitch yoffset; if(pitch 89.0f) pitch 89.0f; if(pitch -89.0f) pitch -89.0f; glm::vec3 front; front.x cos(glm::radians(yaw)) * cos(glm::radians(pitch)); front.y sin(glm::radians(pitch)); front.z sin(glm::radians(yaw)) * cos(glm::radians(pitch)); cameraFront glm::normalize(front);}输入控制相机视角:欧拉角缺点万向节死锁缩放通过透视矩阵调节缩放void scroll_callback(GLFWwindow* window, double xoffset, double yoffset){if(fov 1.0f fov 45.0f)fov - yoffset;if(fov 1.0f)fov 1.0f;if(fov 45.0f)fov 45.0f;}projection glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);glfwSetScrollCallback(window, scroll_callback);汇总OpenGL 一个定义了函数布局和输出的图形API的正式规范。GLAD 一个拓展加载库用来为我们加载并设定所有OpenGL函数指针从而让我们能够使用所有现代OpenGL函数。视口(Viewport) 我们需要渲染的窗口。图形管线(Graphics Pipeline) 一个顶点在呈现为像素之前经过的全部过程。着色器(Shader) 一个运行在显卡上的小型程序。很多阶段的图形管道都可以使用自定义的着色器来代替原有的功能。标准化设备坐标(Normalized Device Coordinates, NDC) 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。顶点缓冲对象(Vertex Buffer Object) 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。顶点数组对象(Vertex Array Object) 存储缓冲区和顶点属性状态。元素缓冲对象(Element Buffer ObjectEBO)也叫索引缓冲对象(Index Buffer ObjectIBO) 一个存储元素索引供索引化绘制使用的缓冲对象。Uniform 一个特殊类型的GLSL变量。它是全局的在一个着色器程序中每一个着色器都能够访问uniform变量并且只需要被设定一次。纹理(Texture) 一种包裹着物体的特殊类型图像给物体精细的视觉效果。纹理缠绕(Texture Wrapping) 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。纹理过滤(Texture Filtering) 定义了一种当有多种纹素选择时指定OpenGL如何采样纹理的模式。这通常在纹理被放大情况下发生。多级渐远纹理(Mipmaps) 被存储的材质的一些缩小版本根据距观察者的距离会使用材质的合适大小。stb_image.h 图像加载库。纹理单元(Texture Units) 通过绑定纹理到不同纹理单元从而允许多个纹理在同一对象上渲染。向量(Vector) 一个定义了在空间中方向和/或位置的数学实体。矩阵(Matrix) 一个矩形阵列的数学表达式。GLM 一个为OpenGL打造的数学库。局部空间(Local Space) 一个物体的初始空间。所有的坐标都是相对于物体的原点的。世界空间(World Space) 所有的坐标都相对于全局原点。观察空间(View Space) 所有的坐标都是从摄像机的视角观察的。裁剪空间(Clip Space) 所有的坐标都是从摄像机视角观察的但是该空间应用了投影。这个空间应该是一个顶点坐标最终的空间作为顶点着色器的输出。OpenGL负责处理剩下的事情裁剪/透视除法。屏幕空间(Screen Space) 所有的坐标都由屏幕视角来观察。坐标的范围是从0到屏幕的宽/高。LookAt矩阵 一种特殊类型的观察矩阵它创建了一个坐标系其中所有坐标都根据从一个位置正在观察目标的用户旋转或者平移。欧拉角(Euler Angles) 被定义为偏航角(Yaw)俯仰角(Pitch)和滚转角(Roll)从而允许我们通过这三个值构造任何3D方向。