使用glm数学库
数学知识参考3d数学部分 glm::perspective 构造
正交投影
正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器:
上面的平截头体定义了可见的坐标,它由由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。
glm内置正交投影函数glm::ortho:glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);透视投影
正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上: glm内置透视投影函数glm::perspectivecpp // fov, 宽高比, 近裁剪面,远裁剪面 projection = glm::perspective(glm::radians(45.0f), (float)wWidth / (float)wHeight, 0.1f, 100.0f);\[ out = \left( x/w \\ y/w \\ z/w \right) \]
本地坐标转换到裁剪空间 \[ V_{clip} = M_{projection}⋅M_{view}⋅M_{model}⋅V_{local} \]
!!注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
顶点Shader,接受
MVPcpp #version 330 core layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0 layout (location = 1) in vec3 aColor; // 颜色变量的属性位置值为1 layout (location = 2) in vec2 aUv; // uv变量的属性位置值为2 out vec3 vertexColor; // 为片段着色器指定一个颜色输出 out vec2 uv; // 为片段着色器指定一个uv uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { //顶点乘以mvp, 转换到 projection空间, MVP相反顺序。 gl_Position = projection * view * model * vec4(aPos, 1.0); // 注意我们如何把一个vec3作为vec4的构造器的参数 vertexColor = aColor; // 把输出变量设置为暗红色 uv = aUv; // 把输出变量设置为暗红色 }构造mvp矩阵,并传递值
cpp //初始化矩阵变量 glm::mat4 model = glm::mat4(1.0f); glm::mat4 view = glm::mat4(1.0f); glm::mat4 projection = glm::mat4(1.0f); //使模型矩阵沿着x轴向倾斜`-55` model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); //观察矩阵 沿着 z平移 -3 view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); // 构造透视投影矩阵 projection = glm::perspective(glm::radians(45.0f), (float)wWidth / (float)wHeight, 0.1f, 100.0f); //传递model 给shader unsigned int modelLoc = glGetUniformLocation(shader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); //传递view 给shader unsigned int viewLoc = glGetUniformLocation(shader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]); //传递view 给shader unsigned int projectionLoc = glGetUniformLocation(shader.ID, "projection"); glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection)); // 配合Shader的矩阵运算,至此图形已经有了透视效果。完整代码
#include "glad/glad.h" #include "GLFW/glfw3.h" //图片库 #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" //glm OpenGL Math库 #include "glm/glm.hpp" #include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/type_ptr.hpp" #include "shader.h" #include <iostream> using namespace std; const int wWidth = 800, wHeight = 600; const char* wName = "LearnOpenGL"; float vertices[] = { //位置 // 基础颜色 // 题图uv 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left }; unsigned int indices[] = { 0, 1, 3, // 第一个三角形 1, 2, 3 // 第二个三角形 }; void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } int main() { //1. 初始化 if (glfwInit() == GLFW_FALSE) { cout << "init glfw fail"; return 1; } //2. 基本设置 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #if __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif //3. 创建窗口 GLFWwindow *window = glfwCreateWindow(wWidth, wHeight, wName, nullptr, nullptr); if (window == nullptr) { cout << "Failed to create GLFW window"; goto TERMINATE; //return 1; } //4. 当前主线程上下文窗口 glfwMakeContextCurrent(window); //5. 使用glad管理OpenGL指针。 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; goto TERMINATE; //return 1; } //6. 设置视口 glViewport(0, 0, wWidth, wHeight); //7. 设置resize回调,对应刷新视口 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); Shader shader("res/shaders/sample_vertex_mvp.shader", "res/shaders/sample_fragment_mvp.shader"); unsigned int VBO, VAO, EBO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //1. 指定顶点的顶点属性的结构,顶点 3个float,从0开始,整体每一组数据8个。 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //2. 指定颜色的顶点属性的结构,三色 3个float,从3开始,整体每一组数据8个。 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); //3. 指定uv的顶点属性的结构,uv 2个float,从6开始,整体每一组数据8个。 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); unsigned int texture1, texture2; //加载贴图1 glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); // 设置 wrapping 参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // 设置 filtering 参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 加载解析贴图文件 int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis. unsigned char* data = stbi_load("./res/textures/container.jpg", &width, &height, &nrChannels, 0); if (data) { //第一个 GL_RGB 将被转换的格式, 第二个GL_RGB原始文件的格式, 格式不匹配会报错。 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture" << std::endl; } stbi_image_free(data); //加载贴图2 glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); // 设置 wrapping 参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // 设置 filtering 参数 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // l加载解析贴图文件 data = stbi_load("./res/textures/awesomeface.png", &width, &height, &nrChannels, 0); if (data) { //第一个 GL_RGB 将被转换的格式, 第二个GL_RGB原始文件的格式, 格式不匹配会报错。 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture" << std::endl; } stbi_image_free(data); //指定采样器贴图储存的位置 shader.use(); shader.setInt("texture1", 0); shader.setInt("texture2", 1); while (!glfwWindowShouldClose(window)) { processInput(window); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // bind Texture1 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); // bind Texture2 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); shader.use(); glm::mat4 model = glm::mat4(1.0f); glm::mat4 view = glm::mat4(1.0f); glm::mat4 projection = glm::mat4(1.0f); model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); projection = glm::perspective(glm::radians(45.0f), (float)wWidth / (float)wHeight, 0.1f, 100.0f); unsigned int modelLoc = glGetUniformLocation(shader.ID, "model"); unsigned int viewLoc = glGetUniformLocation(shader.ID, "view"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]); shader.setMat4("projection", projection); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); TERMINATE: glfwTerminate(); return 0; }