渲染立方体
数学知识参考3d数学部分 渲染立方体需要6个面,12个三角形,同样每个顶点需要对应一个uv。
基础代码同上一节,绘制立方体代码,这里调用绘制36个顶点的代码(6个面 = 12个三角形 = 36个顶点, 虽然重用顶点,但是对于每次绘制的三角形而言还是3个顶点一个三角形)
cpp glDrawArrays(GL_TRIANGLES, 0, 36);同一份顶点、uv数据、贴图数据绘制多次,用于绘制多个不同物体。 > 只需要重复调用绘制函数,比如
glDrawArrays(GL_TRIANGLES, 0, 36);. 因为数据完全没有变化,所以绘制是重复的。 > 此时可以传递不同的位置、旋转等值然后在绘制。Z缓冲& 深度缓冲 > OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。 > 深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。 > 这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。
- 开启深度测试
glEnable(GL_DEPTH_TEST);- 因为开启了深度测试,每次渲染之前深度缓冲区,都保留了之前的深度缓冲,所以需要先清除掉
GL_DEPTH_BUFFER_BIT。
glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);透视投影
正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的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; }