相机空间
构建相机坐标系,需要求解向右、向上、朝向三个标准正交轴,所有向量最终必须进行归一化处理,保证后续计算精度。
核心参数
- 相机位置:相机在世界空间中的三维坐标点
- 相机朝向(Direction):相机视线的负方向,由相机位置 - 观察目标位置计算得到
- 初始向上方向:世界空间默认上向量,固定为
(0, 1, 0)
轴向量计算顺序
- 向右向量(Right):由初始向上向量叉乘相机朝向向量得到,结果归一化
- 真实向上向量(Up):由已求出的向右向量叉乘相机朝向向量得到,结果归一化,修正初始上向量的倾斜误差
- 朝向向量(Direction):计算完成后统一归一化,作为相机
-z视线方向
LookAt 视图矩阵(View矩阵)原理
LookAt矩阵的核心作用:将世界空间坐标变换为相机空间坐标,由平移矩阵和旋转矩阵组合而成。
矩阵变换步骤
- 平移矩阵:将整个场景反向平移,把相机位置平移至世界空间原点
- 旋转矩阵:将相机的 Right、Up、Direction 三个轴,对齐到世界空间的 X、Y、Z 标准轴 变换完成后:相机位于坐标原点、视线朝向 -Z 方向,所有物体坐标转换为相机空间坐标,因此 LookAt 矩阵可直接作为 OpenGL 的 View 矩阵参与 MVP 变换。
GLM快速构建View矩阵
GLM 封装了 glm::lookAt 函数,自动计算并组合平移、旋转矩阵,无需手动推导。
// 参数:相机位置、观察目标位置、世界向上向量glm::lookAt(cameraPos, targetPos, glm::vec3(0.0f, 1.0f, 0.0f));DeltaTime帧时间适配
问题根源
输入监听、移动更新函数为每帧执行:高帧率设备每秒钟执行更多次移动逻辑,物体移动距离更长、速度更快;低帧率设备速度更慢,导致不同硬件下相机移动速度不统一。
解决方案:DeltaTime
定义 deltaTime:上一帧渲染所耗费的时间。
- 计算方式:
deltaTime = 当前帧时间 - 上一帧时间 - 更新逻辑:每帧计算完成后,将「上一帧时间」更新为当前帧时间,用于下一帧计算
- 速度适配:用
移动速度 * deltaTime计算本帧移动距离,帧率越高 deltaTime 越小、单帧移动距离越短,帧率越低 deltaTime 越大、单帧移动距离越长,保证所有硬件每秒移动距离完全一致。
相机欧拉角旋转
欧拉角基础
三维物体旋转由三个欧拉角组成:偏航角(Yaw)、俯仰角(Pitch)、翻滚角(Roll)。相机视角控制仅使用偏航角、俯仰角,忽略翻滚角。
- 偏航角(Yaw):水平左右旋转,控制相机左右朝向
- 俯仰角(Pitch):竖直上下旋转,控制相机上下朝向
鼠标视角更新逻辑
- 记录鼠标上一帧坐标与当前帧坐标,计算水平、竖直偏移量
- 水平偏移量叠加至偏航角,竖直偏移量叠加至俯仰角
- 通过更新后的 Yaw、Pitch 欧拉角,重新计算相机视线朝向向量,完成视角旋转
GLM 矩阵索引规则
GLM 矩阵遵循:先列索引,后行索引。 示例:
glm::mat4 trans;// trans[列索引][行索引]trans[3][0]; // 访问:第4列、第1行元素(第一行第四列)example1: 让相机在场景中旋转
使相机在XOZ平面内绕着Y轴旋转,旋转轨迹是一个圆。所以需要不断地更改相机的x坐标和z坐标,并且借助三角函数使它的运动是一个圆。使用相机的新位置构建lookAt矩阵,将其作为view矩阵并作用于物体的顶点,将物体从世界空间变换到相机空间。
代码中radius控制相机绕圈的大小。现在camX=sin,camZ=cos,从上往下看,相机是顺时针旋转,则物体是逆时针旋转。现在相机位置是(camX,0.0,camZ),和看向的目标(0.0,0.0,0.0)处于同一位置,是正视物体,如果改成(camX,2.0,camZ),则相机是俯视物体。向上方向确定画面是否歪斜。
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; float radius = 5.0f; float camX = static_cast<float>(sin(glfwGetTime()) * radius); float camZ = static_cast<float>(cos(glfwGetTime()) * radius); view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); 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]); float angle = 20.0f * i; unsigned int modelLoc = glGetUniformLocation(ourShader.ID, "model"); glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); glDrawArrays(GL_TRIANGLES, 0, 36); } glfwSwapBuffers(window); glfwPollEvents();}example2: 自由移动相机
使用wasd按键控制相机的移动。实则是监听按键事件,转化为相机坐标,根据新的坐标构建新的lookAt矩阵。因此把构建lookAt矩阵的三个参数:相机位置、目标位置和向上方向抽离成变量,同时,为了保证相机始终看向正前方的某个位置,目标位置不是固定的,而是由当前相机位置加上一个向前方向的向量得到。
#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;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);//相机永远看向-zglm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
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); } float cameraSpeed = 0.01f;//每次移动的幅度 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { cameraPos += cameraSpeed * cameraFront;//ws控制的是相机靠近或远离,而非相机向上或向下,要实现后者得是cameraUp } 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; }}
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::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); 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]); float angle = 20.0f * i; 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;}example3: 引入deltaTime均衡不同帧率的系统
如果camerSpeed是固定的,那么帧率高的系统调用processInput的次数多,移动多,速度快。而引入deltaTime,是系统渲染一帧所花的时间,当deltaTime大,移动速度应该提高以抵消。
引入全局变量deltaTime和lastFrame,在渲染循环中获得当前的时间,减去lastFrame即上一帧的时间,得到deltaTime,然后更新lastFrame=当前的时间。在processInput中使用deltaTime决定cameraSpeed。
#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;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);//相机永远看向-zglm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float deltaTime = 0.0f;float lastFrame = 0.0f;
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); } float cameraSpeed = 0.25f * deltaTime;//每次移动的幅度 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { cameraPos += cameraSpeed * cameraFront;//ws控制的是相机靠近或远离,而非相机向上或向下,要实现后者得是cameraUp } 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; }}
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)) { float currentFrame = static_cast<float>(glfwGetTime()); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame;
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::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); 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]); float angle = 20.0f * i; 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;}example4: 鼠标控制相机移动
鼠标移动的offset转变为相机的俯仰角和偏航角。利用这两个角度重新计算相机的向前方向,再计算相机的lookAt矩阵。
加入缩放控制,鼠标滚轮的上下滑动修改fov,进而修改投影矩阵。
#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;
glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);//相机永远看向-zglm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
float deltaTime = 0.0f;float lastFrame = 0.0f;
bool firstMouse = true;float yaw = -90.0f;//初始偏航角为-90,是因为初始时相机看向-z,即front=(0,0,-1),根据picth和yaw和front的关系计算得到float pitch = 0.0f;//初始俯仰角为零,表示相机看向水平面float lastX = 400, lastY = 300;//鼠标的初始位置设在屏幕中心float fov = 45.0f;
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); }}void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos); firstMouse = false; } float offsetX = static_cast<float>(xpos - lastX); float offsetY = static_cast<float>(lastY - ypos);//对于GLFW窗口而言,坐标原点是窗口左上角,向下为Y轴增大方向,鼠标坐标也是在这个GLFW窗口内的,也就是向下移动鼠标,鼠标坐标的Y分量是增大的。但是相机坐标系中,向上是Y轴增大方向,所以需要翻转,以符合相机坐标系 lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos);//用当前的鼠标位置更新上一帧鼠标位置
float sensitivity = 0.05f; offsetX *= sensitivity; offsetY *= sensitivity;//用sensitivity控制鼠标移动->相机移动的距离关系,sensitivity=1时,鼠标移动多少->相机移动多少,比较灵敏
yaw += offsetX; pitch += offsetY;
if (pitch > 89.0f) { pitch = 89.0f; } if (pitch < -89.0f) { pitch = -89.0f; } //对pitch的角度范围进行限制,当picth=90或-90时,相机看向天空或者地面,这时front和up平行,导致right计算错误(right计算需要借助front和up的叉乘) glm::vec3 front; front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); front.y = sin(glm::radians(pitch)); front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw)); cameraFront = glm::normalize(front);}void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { if (fov >= 1.0f && fov <= 45.0f) { fov -= static_cast<float>(yoffset);//鼠标滚轮向上滚,yoffset是正数,画面应该放大,则fov应该缩小;故fov和yoffset成反比 } if (fov < 1.0f) { fov = 1.0f; } if (fov > 45.0f) { fov = 45.0f; }}
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); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//隐藏光标并限制光变停留在程序窗口中 glfwSetCursorPosCallback(window, mouse_callback);//一旦鼠标移动,则调用回调函数,并将鼠标的位置传递给回调函数 glfwSetScrollCallback(window, scroll_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);
while (!glfwWindowShouldClose(window)) { float currentFrame = static_cast<float>(glfwGetTime()); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame;
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::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glm::mat4 project;//滚轮缩放需要每帧读取用户的输入,所以应该写在循环内 project = glm::perspective(glm::radians(fov), (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));
for (unsigned int i = 0; i < 10; i++) { glm::mat4 model; model = glm::translate(model, cubePositions[i]); float angle = 20.0f * i; 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;}example5: 将相机的相关变量和方法抽离成相机类
#pragma once#ifndef CAMERA_H#define CAMERA_H
#include <glad/glad.h>#include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp>
enum Camera_Movement { FORWARD, BACKWARD, LEFT, RIGHT};
const float YAW = -90.0f;const float PITCH = 0.0f;const float SPEED = 2.5f;const float SENSITIVITY = 0.1f;const float ZOOM = 45.0f;//这些常量是默认值/初始值,支持创建相机时不传入参数时用这些常量初始化class Camera {public: glm::vec3 Position; glm::vec3 Front; glm::vec3 Up;//相机的向上方向,会根据相机抬头或低头而发生变化 glm::vec3 Right; glm::vec3 WorldUp;//世界向上方向,永远不变。用于初始化相机向上方向而计算向右方向
float Yaw; float Pitch;
float MovementSpeed; float MouseSensitivity; float Zoom;
Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) :Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { Position = position; WorldUp = up;//初始化时给的向上方向是世界向上方向,仍需要传入的原因时特殊情况下WorldUp!=(0,1,0) Yaw = yaw; Pitch = pitch; updateCameraVectors(); }//这四个变量是经常要改的,所以允许外部传入,并修改默认值。但同样允许扩充或者减少外部传入参数的数量
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) :Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { Position = glm::vec3(posX, posY, posZ); WorldUp = glm::vec3(upX, upY, upZ); Yaw = yaw; Pitch = pitch; updateCameraVectors(); }
glm::mat4 GetViewMatrix() { return glm::lookAt(Position, Position + Front, Up);//计算lookAt矩阵时需要的是相机自己的向上方向 }
//相机可支持键盘移动(前后左右) void processKeyboard(Camera_Movement direction, float deltaTime) { float velocity = deltaTime * MovementSpeed; if (direction == FORWARD) { Position += Front * velocity; } if (direction == BACKWARD) { Position -= Front * velocity; } if (direction == LEFT) { Position -= Right * velocity; } if (direction == RIGHT) { Position += Right * velocity; } }
//相机可支持鼠标更改看向方向 void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true) {//为什么contrainPitch的类型不是bool?GLFW/OpenGL习惯,本质和bool一样,只是类型名不同 xoffset *= MouseSensitivity; yoffset *= MouseSensitivity;
Yaw += xoffset; Pitch += yoffset;
if (constrainPitch) {//contrainPitch默认情况下是true,表示需要限制相机的俯仰角,但是有些特殊效果(如飞行相机)需要360°旋转,不需要限制 if (Pitch > 89.0f) { Pitch = 89.0f; } if (Pitch < -89.0f) { Pitch = -89.0f; } }
updateCameraVectors(); }
//相机可支持鼠标滚轮空间画面缩放 void ProcessMouseScroll(float yoffset) { Zoom -= yoffset; if (Zoom < 1.0f) { Zoom = 1.0f; } if (Zoom > 45.0f) { Zoom = 45.0f; } }
private: //更新相机的三个轴向 void updateCameraVectors() { glm::vec3 front; front.x = cos(glm::radians(Pitch)) * cos(glm::radians(Yaw));//glm::cos/glm::sin都必须接收弧度制的输入 front.y = sin(glm::radians(Pitch)); front.z = cos(glm::radians(Pitch)) * sin(glm::radians(Yaw));
Front = glm::normalize(front);
Right = glm::normalize(glm::cross(Front, WorldUp));
Up = glm::normalize(glm::cross(Right, Front));//叉乘的顺序不能换,否则画面上下颠倒 }};
#endif#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 <myCamera.h>
#include <iostream>
const unsigned int SCR_WIDTH = 800;const unsigned int SCR_HEIGHT = 600;
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));float lastX = SCR_WIDTH / 2.0f;float lastY = SCR_HEIGHT / 2.0f;bool firstMouse = true;
float deltaTime = 0.0f;float lastFrame = 0.0f;
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); } if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { camera.processKeyboard(FORWARD, deltaTime); } if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { camera.processKeyboard(BACKWARD, deltaTime); } if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { camera.processKeyboard(LEFT, deltaTime); } if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { camera.processKeyboard(RIGHT, deltaTime); }}void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos); firstMouse = false; } float offsetX = static_cast<float>(xpos - lastX); float offsetY = static_cast<float>(lastY - ypos);//对于GLFW窗口而言,坐标原点是窗口左上角,向下为Y轴增大方向,鼠标坐标也是在这个GLFW窗口内的,也就是向下移动鼠标,鼠标坐标的Y分量是增大的。但是相机坐标系中,向上是Y轴增大方向,所以需要翻转,以符合相机坐标系 lastX = static_cast<float>(xpos); lastY = static_cast<float>(ypos);//用当前的鼠标位置更新上一帧鼠标位置
camera.ProcessMouseMovement(offsetX, offsetY, true);}void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(static_cast<float>(yoffset));}
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); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//隐藏光标并限制光变停留在程序窗口中 glfwSetCursorPosCallback(window, mouse_callback);//一旦鼠标移动,则调用回调函数,并将鼠标的位置传递给回调函数 glfwSetScrollCallback(window, scroll_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);
while (!glfwWindowShouldClose(window)) { float currentFrame = static_cast<float>(glfwGetTime()); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame;
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 = camera.GetViewMatrix(); unsigned int viewLoc = glGetUniformLocation(ourShader.ID, "view"); glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glm::mat4 project;//滚轮缩放需要每帧读取用户的输入,所以应该写在循环内 project = glm::perspective(glm::radians(camera.Zoom), (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));
for (unsigned int i = 0; i < 10; i++) { glm::mat4 model; model = glm::translate(model, cubePositions[i]); float angle = 20.0f * i; 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
看看你是否能够修改摄像机类,使得其能够变成一个真正的FPS摄像机(也就是说不能够随意飞行);你只能够呆在xz平面上。
思路:当相机俯仰角不等于0时,方向向量的y分量就不为0,那么相机向前移动之后,就可能跑出xoz平面。所以如果要把相机限制在xoz平面上,那么相机的向前移动方向的y分量就不能为0.不限制俯仰角必须为0,而是限制相机移动方向的y分量为0,这样相机还是可以到处看,只是移动限制在水平面了。
void ProcessKeyboard(Camera_Movement direction, float deltaTime) { float velocity = CameraMovementSpeed * deltaTime; if (direction == FORWARD) { Position += Front * velocity; } if (direction == BACKWARD) { Position -= Front * velocity; } if (direction == LEFT) { Position -= Right * velocity; } if (direction == RIGHT) { Position += Right * velocity; } Position.y = 0;}homework2
试着创建你自己的LookAt函数,其中你需要手动创建一个我们在一开始讨论的观察矩阵。用你的函数实现来替换GLM的LookAt函数,看看它是否还能一样地工作。
思路:手动构建LookAt矩阵,包含平移和缩放两部分。主要注意的点是,OpenGL的矩阵索引是列索引在前,行索引在后。
glm::mat4 calculate_lookAt_matrix(glm::vec3 position, glm::vec3 target, glm::vec3 worldUp) { glm::mat4 translateMatrix; translateMatrix[3][0] = -position.x; translateMatrix[3][1] = -position.y; translateMatrix[3][2] = -position.z; glm::mat4 rotateMatrix; glm::vec3 zBasis = glm::normalize(position-target);//D glm::vec3 xBasis = glm::normalize(glm::cross(zBasis, worldUp));//R glm::vec3 yBasis = glm::normalize(glm::cross(xBasis, zBasis));//U rotateMatrix[0][0] = xBasis.x; rotateMatrix[1][0] = xBasis.y; rotateMatrix[2][0] = xBasis.z; rotateMatrix[0][1] = yBasis.x; rotateMatrix[1][1] = yBasis.y; rotateMatrix[2][1] = yBasis.z; rotateMatrix[0][2] = zBasis.x; rotateMatrix[1][2] = zBasis.y; rotateMatrix[2][2] = zBasis.z; return rotateMatrix * translateMatrix;}