6120 字
31 分钟
Camera

相机空间#

构建相机坐标系,需要求解向右、向上、朝向三个标准正交轴,所有向量最终必须进行归一化处理,保证后续计算精度。

核心参数#

  • 相机位置:相机在世界空间中的三维坐标点
  • 相机朝向(Direction):相机视线的负方向,由相机位置 - 观察目标位置计算得到
  • 初始向上方向:世界空间默认上向量,固定为 (0, 1, 0)

轴向量计算顺序#

  1. 向右向量(Right):由初始向上向量叉乘相机朝向向量得到,结果归一化
  2. 真实向上向量(Up):由已求出的向右向量叉乘相机朝向向量得到,结果归一化,修正初始上向量的倾斜误差
  3. 朝向向量(Direction):计算完成后统一归一化,作为相机-z视线方向

LookAt 视图矩阵(View矩阵)原理#

LookAt矩阵的核心作用:将世界空间坐标变换为相机空间坐标,由平移矩阵旋转矩阵组合而成。

矩阵变换步骤#

  1. 平移矩阵:将整个场景反向平移,把相机位置平移至世界空间原点
  2. 旋转矩阵:将相机的 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):竖直上下旋转,控制相机上下朝向

鼠标视角更新逻辑#

  1. 记录鼠标上一帧坐标与当前帧坐标,计算水平、竖直偏移量
  2. 水平偏移量叠加至偏航角,竖直偏移量叠加至俯仰角
  3. 通过更新后的 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);//相机永远看向-z
glm::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大,移动速度应该提高以抵消。

引入全局变量deltaTimelastFrame,在渲染循环中获得当前的时间,减去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);//相机永远看向-z
glm::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);//相机永远看向-z
glm::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;
}
Camera
https://fuwari.vercel.app/posts/notes/opengl/camera/
作者
Ruby
发布于
2026-06-27
许可协议
CC BY-NC-SA 4.0