投影变换与坐标流水线
完整坐标变换过程
投影矩阵的核心作用:将观察空间坐标变换为裁剪空间坐标,再通过GPU自动计算完成后续坐标转换,最终映射为屏幕坐标。完整流水线如下:
观察空间(4D坐标,w=1) → 投影变换 → 投影/裁剪空间(4D坐标,w=-z_view) → 透视除法(GPU自动执行) → 3D NDC标准设备坐标 → 视口/深度范围变换 → 屏幕坐标 + 深度缓冲值
关键说明:透视除法在每个顶点着色器执行后自动触发,将裁剪空间 x/y/z/w 全部除以w,使新的 w=1 并舍弃,得到归一化NDC坐标。
NDC坐标后续处理:
- x、y 分量:经
glViewport变换,映射为屏幕像素坐标(范围:x:0~宽度,y:0~高度) - z 分量:经
glDepthRange变换,映射为0~1区间,最终存入深度缓冲
两类投影矩阵
正射投影
观察空间视锥体为立方体平头截体,通过近平面、远平面的宽、高、距离定义可视范围。正射投影将视锥体内的顶点变换至裁剪空间,变换后所有顶点 w=1,无透视缩放效果,可直接将观察空间坐标映射为NDC坐标,物体大小不受距离影响。
OpenGL 使用(GLM):
// 参数:左、右、下、上、近平面距离、远平面距离glm::ortho(left, right, bottom, top, near, far);透视投影
观察空间视锥体为锥形平头截体,截体外部的顶点会被裁剪,不会参与渲染。透视投影不仅将视锥体映射到裁剪空间,还会修改顶点 w 分量:顶点距离观察者越远,w 值越大。经过透视除法后,远距离顶点坐标更小,实现近大远小的真实视觉效果。
OpenGL 使用(GLM):
// 参数:视角fov、屏幕宽高比、近平面距离、远平面距离glm::perspective(fov, aspectRatio, near, far);MVP变换整体逻辑
顶点依次经过 M模型变换、V视图变换、P投影变换 后,从模型空间转换到裁剪空间。将最终坐标赋值给 gl_Position,OpenGL 会自动完成透视除法、视锥体裁剪,无需手动干预。
深度缓冲和深度测试
核心原理
OpenGL 通过**深度缓冲(Z缓冲)**存储场景像素的深度信息,GLFW 会自动创建深度缓冲。每个渲染片段都携带独立深度值,用于标识像素距离观察者的远近。 深度测试(Depth Testing):片段输出颜色前,OpenGL 会自动对比当前片段深度值与深度缓冲中已存储的深度值。若当前片段处于后方,直接丢弃;若处于前方,则覆盖颜色与深度缓冲数据,解决物体遮挡错乱问题。
开关与配置代码
深度测试默认关闭,需手动开启,开启后对比、丢弃、保留逻辑均由GPU自动执行,无需手动编写判断逻辑。
// 开启深度测试glEnable(GL_DEPTH_TEST);// 关闭深度测试(仅透明物体等特殊场景使用,无需常规关闭)glDisable(GL_DEPTH_TEST);深度缓冲清空规则
开启深度测试后,GPU 会持续写入深度缓冲数据。为避免上一帧深度数据干扰当前帧渲染,每一帧渲染开始前必须清空深度缓冲,与清空颜色缓冲逻辑对应:
// 同时清空颜色缓冲与深度缓冲glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);example1: 使用MVP矩阵变换之前的平面,使之看起来像在3D空间
首先对于模型矩阵,这里施加一个旋转,让平面绕着x轴向后旋转,看起来像铺在地面上 然后对于视图矩阵,将物体沿着z轴负方向移动,则远离相机。等价于将相机沿着z轴正方向移动 最后对于投影矩阵,使用透视投影。
#define STB_IMAGE_IMPLEMENTATION#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>#include <myShader.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 600;
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() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { cout << "Failed to create window" << endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to load GLAD" << endl; glfwTerminate(); return -1; }
Shader ourShader("src/Shader/vertexShader.txt", "src/Shader/fragmentShader.txt");
float vertices[] = { 0.5f,0.5f,0.0f, 1.0f, 1.0f, 0.5f,-0.5f,0.0f, 1.0f,0.0f, -0.5f,-0.5f,0.0f, 0.0f,0.0f, -0.5f,0.5f,0.0f, 0.0f,1.0f };
unsigned int indices[] = { 0,1,3, 1,2,3 };
unsigned int VBO, EBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO);
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int texture1, texture2; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
ourShader.use(); ourShader.setInt("texture1", 0); ourShader.setInt("texture2", 1);
glm::mat4 project;//投影矩阵很少变化,在循环外设置 project = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); unsigned int projectLoc = glGetUniformLocation(ourShader.ID, "project"); glUniformMatrix4fv(projectLoc, 1, GL_FALSE, glm::value_ptr(project));
while (!glfwWindowShouldClose(window)) { processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use(); glBindVertexArray(VAO); glm::mat4 model; model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1, 0, 0)); glm::mat4 view; view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO);
glfwTerminate(); return 0;}顶点着色器中需要定义对应的uniform变量,并用这些变换矩阵的组合变换顶点。
#version 330 corelayout (location = 0) in vec3 aPos;layout (location = 1) in vec2 aTexcoord;
out vec2 ourTexcoord;
uniform mat4 model;uniform mat4 view;uniform mat4 project;void main(){ gl_Position = project*view*model*vec4(aPos,1.0f); ourTexcoord = aTexcoord;}example2: 将平面变为立方体,使之更加立体
扩充顶点数组,使之包含36个顶点(6个面,每个面2个三角形,每个三角形3个顶点);然后修改model矩阵,使之随着时间不断旋转。最后使用glDrawArrays绘制。
#define STB_IMAGE_IMPLEMENTATION#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>#include <myShader.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 600;
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() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { cout << "Failed to create window" << endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to load GLAD" << endl; glfwTerminate(); return -1; }
Shader ourShader("src/Shader/vertexShader.txt", "src/Shader/fragmentShader.txt");
float vertices[] = { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f };
unsigned int indices[] = { 0,1,3, 1,2,3 };
unsigned int VBO, EBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO);
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int texture1, texture2; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
ourShader.use(); ourShader.setInt("texture1", 0); ourShader.setInt("texture2", 1);
glm::mat4 project;//投影矩阵很少变化,在循环外设置 project = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); unsigned int projectLoc = glGetUniformLocation(ourShader.ID, "project"); glUniformMatrix4fv(projectLoc, 1, GL_FALSE, glm::value_ptr(project));
while (!glfwWindowShouldClose(window)) { processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use(); glBindVertexArray(VAO); glm::mat4 model; model = glm::rotate(model, (float)glfwGetTime()*glm::radians(50.0f), glm::vec3(0.5, 1.0, 0)); glm::mat4 view; view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO);
glfwTerminate(); return 0;}上面代码实现的效果有些奇怪,立方体的某些本应被遮挡住的面被绘制在了这个立方体其他面之上。这是因为当使用glDrawArrays时,OpenGL是根据你所指定的顺序绘制三角形的,后画的三角形会覆盖之前画的三角形。如果指定顺序符合从远至近,则绘制效果正常。但如果顺序反了,就不符合常理,看起来很奇怪。通过深度缓冲,可以让OpenGL先比较远近,然后只绘制近的,确保近的遮挡远的。
example3: 开启深度测试
使用glEnable开启深度测试,OpenGL就会写入深度缓冲并在每次绘制前进行深度比较。因此需要确保深度缓冲是干净的,则需要在渲染循环开头清除深度缓冲。
#define STB_IMAGE_IMPLEMENTATION#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>#include <myShader.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 600;
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() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { cout << "Failed to create window" << endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to load GLAD" << endl; glfwTerminate(); return -1; }
//开启深度测试 glEnable(GL_DEPTH_TEST);
Shader ourShader("src/Shader/vertexShader.txt", "src/Shader/fragmentShader.txt");
float vertices[] = { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f };
unsigned int indices[] = { 0,1,3, 1,2,3 };
unsigned int VBO, EBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO);
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int texture1, texture2; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
ourShader.use(); ourShader.setInt("texture1", 0); ourShader.setInt("texture2", 1);
glm::mat4 project;//投影矩阵很少变化,在循环外设置 project = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); unsigned int projectLoc = glGetUniformLocation(ourShader.ID, "project"); glUniformMatrix4fv(projectLoc, 1, GL_FALSE, glm::value_ptr(project));
while (!glfwWindowShouldClose(window)) { processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use(); glBindVertexArray(VAO); glm::mat4 model; model = glm::rotate(model, (float)glfwGetTime()*glm::radians(50.0f), glm::vec3(0.5, 1.0, 0)); glm::mat4 view; view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO);
glfwTerminate(); return 0;}example4: 画更多的箱子
因为这些箱子的外观、设置都是一样的,区别仅在于世界空间的位置和旋转,所以不用重复定义顶点数组,而是在渲染循环中调用十次glDrawArrays,每次调用前,计算新的模型变换矩阵并传递给着色器。考虑到重复代码较多,可以使用循环。定义十个箱子在世界空间的偏移,然后模型矩阵同时包含旋转和平移。

#define STB_IMAGE_IMPLEMENTATION#include <glad/glad.h>#include <GLFW/glfw3.h>#include <stb_image.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>#include <myShader.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 600;
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() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { cout << "Failed to create window" << endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { cout << "Failed to load GLAD" << endl; glfwTerminate(); return -1; }
//开启深度测试 glEnable(GL_DEPTH_TEST);
Shader ourShader("src/Shader/vertexShader.txt", "src/Shader/fragmentShader.txt");
float vertices[] = { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f };
glm::vec3 cubePositions[] = { glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(2.0f, 5.0f, -15.0f), glm::vec3(-1.5f, -2.2f, -2.5f), glm::vec3(-3.8f, -2.0f, -12.3f), glm::vec3(2.4f, -0.4f, -3.5f), glm::vec3(-1.7f, 3.0f, -7.5f), glm::vec3(1.3f, -2.0f, -2.5f), glm::vec3(1.5f, 2.0f, -2.5f), glm::vec3(1.5f, 0.2f, -1.5f), glm::vec3(-1.3f, 1.0f, -1.5f) };
unsigned int VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO);
glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1);
glBindVertexArray(0);
unsigned int texture1, texture2; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0); if (data) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else { cout << "Failed to load image" << endl; } stbi_image_free(data);
ourShader.use(); ourShader.setInt("texture1", 0); ourShader.setInt("texture2", 1);
glm::mat4 project;//投影矩阵很少变化,在循环外设置 project = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); unsigned int projectLoc = glGetUniformLocation(ourShader.ID, "project"); glUniformMatrix4fv(projectLoc, 1, GL_FALSE, glm::value_ptr(project));
while (!glfwWindowShouldClose(window)) { processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);
ourShader.use(); glBindVertexArray(VAO); glm::mat4 view; view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
for (unsigned int i = 0; i < 10; i++) { glm::mat4 model; model = glm::translate(model, cubePositions[i]); //model = glm::rotate(model, (float)glfwGetTime() * glm::radians(20.0f), glm::vec3(0.5, 1.0, 0.0)); float angle = 20.0f * i; model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glDrawArrays(GL_TRIANGLES, 0, 36); }
glfwSwapBuffers(window); glfwPollEvents(); } glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO);
glfwTerminate(); return 0;}homework1
对GLM的perspective函数中的FoV和aspect-ratio参数进行实验。看能否搞懂它们是如何影响透视平截头体的。
测试:
fov=45:
fov=20:
(float)(SCR_WIDTH*0.5)/(float)SCR_HEIGHT:
总结:fov变小,可视范围变窄,物体被裁剪,竖直高度变短,但仍需映射到[-1,1],所以竖直被拉伸,铺满屏幕,视觉放大;宽高比降低,即宽度降低(大于高度降低),视锥体左右收紧,近平面的宽度变小,但仍需映射到[-1,1],所以物体被横向拉伸。
homework2
将观察矩阵在各个方向上进行位移,来看看场景是如何改变的。注意把观察矩阵当成摄像机对象。 测试:
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));view = glm::translate(view, glm::vec3(0.0f, 0.0f, -10.0f));view = glm::translate(view, glm::vec3(0.0f, 0.0f, -0.1f));
总结:物体z越小,越向z的负半轴移动,则越远离相机,则物体越小;z越大,越向z的正半轴移动,则越靠近相机,则物体越大,且如果向z的正半轴移动距离过大,会跑到相机和视锥体近裁剪面中间或者相机背后,导致看不到物体。对于x和y分量,x减小则向左移动,y减小则向下移动
homework3
使用模型矩阵只让是3倍数的箱子旋转(以及第1个箱子),而让剩下的箱子保持静止。
思路:在循环内部加一个判断,只对i能整除3的那些箱子施加旋转矩阵
for (unsigned int i = 0; i < 10; i++) { glm::mat4 model; model = glm::translate(model, cubePositions[i]); float angle = 20.0f * i; if (i % 3 == 0) { angle = glfwGetTime() * 2.0f; model = glm::rotate(model, angle, glm::vec3(0.5f, 1.0f, 0.0f)); } unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glDrawArrays(GL_TRIANGLES, 0, 36);}