在OpenGL中,​​摄像机(Camera)​​是一个用于定义3D场景观察视角的抽象概念。它并不直接对应OpenGL的某个具体对象,而是通过​​视图矩阵(View Matrix)​​和​​投影矩阵(Projection Matrix)​​的组合来模拟现实世界中的相机行为,决定场景中哪些部分可见以及如何投影到2D屏幕。

摄像机位置

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

摄像机方向

摄像机方向包括前向向量(相机正前方指向的方向),上方向(通常固定为世界空间的Y轴)和右方向(normalize(cross(Front, WorldUp))).

确定摄像机方向:

(1)如果知道摄像机位置和目标点,那么我们可以直接用向量来计算摄像机的方向

得到摄像机指向负Z轴的方向:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
//normalize是指将向量转换为​​单位向量​​的过程,即保持向量的方向不变,但将其长度调整为1。

我们需要的另一个向量是一个右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

现在我们已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

lookat矩阵:

​LookAt矩阵(视图矩阵)​是一个将世界坐标系转换为的相机观察坐标系4x4变换矩阵。它通过定义相机的位置、观察目标和上方向,构建一个以相机为原点的坐标系,使得所有物体坐标都相对于相机重新计算。将世界空间中的顶点坐标转换为观察空间坐标。
lookat矩阵由三个部分组成,分别是相机位置,坐标点和上方向

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
           glm::vec3(0.0f, 0.0f, 0.0f), 
           glm::vec3(0.0f, 1.0f, 0.0f));

自由移动

在OpenGL中,摄像机自由移动​指的是通过用户输入(如键盘、鼠标)动态控制摄像机的位置和方向

核心:位置(通过WASD按键更新坐标),方向(通过鼠标移动计算偏航角(Yaw)和俯仰角(Pitch)),速度控制(使用 deltaTime(上一帧耗时)缩放移动速度),视角控制(将俯仰角(Pitch)限制在 [-89°, 89°] 范围内).

现在我们来实现移动代码:

void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
    {
        camera.speedX = 0.05f;
    }
    else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
    {
        camera.speedX = -0.05f;
    }
    else
    {
        camera.speedX = 0;
    }
    if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
    {
        camera.speedY = -0.05f;
    }
    else if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
    {
        camera.speedY = 0.05f;
    }
    else
    {
        camera.speedY = 0;
    }
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
    {
        camera.speedZ = 0.05f;
    }
    else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
    {
        camera.speedZ = -0.05f;
    }
    else
    {
        camera.speedZ = 0;
    }
}
//camera.cpp
void camera::UpdateCameraPos()
{
	Position += Forward * speedZ * 0.1f + Right *speedX * 0.1f + Up * speedY *0.1f;
}

视角移动:

为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量。

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//隐藏鼠标函数
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
//监听鼠标函数

这里的xpos和ypos代表当前鼠标的位置。在处理FPS风格摄像机的鼠标输入的时候,我们必须在最终获取方向向量之前做下面这几步:

  1. 计算鼠标距上一帧的偏移量。
  2. 把偏移量添加到摄像机的俯仰角和偏航角中。
  3. 对偏航角和俯仰角进行最大和最小值的限制。
  4. 计算方向向量.

 第一步是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置

void mouse_callback(GLFWwindow* window, double xPos, double yPos)
{
    if (firstMouse == true)
    {
        lastX = xPos;
        lastY = yPos;
        firstMouse = false;
    }
}

然后在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量

 float deltaX, deltaY;
 deltaX = xPos - lastX;//​​鼠标在X轴​​上从上一帧到当前帧的​​位移量​​
 deltaY = yPos - lastY;

 lastX = xPos;//更新鼠标位置
 lastY = yPos;

 camera.ProcessMouseMovement(deltaX,deltaY);

注意我们把偏移量乘以了sensitivity(灵敏度)值。如果我们忽略这个值,鼠标移动就会太大了

//camera.cpp
void camera::UpdateCameraVectors()
{
	Forward.x = glm::cos(Pitch) * glm::sin(Yaw);//就是通过俯仰角和偏航角来计算以得到真正的方向向量
	Forward.y = glm::sin(Pitch);
	Forward.z = glm::cos(Pitch) * glm::cos(Yaw);
	Right = glm::normalize(glm::cross(Forward, WorldUp));
	Up = glm::normalize(glm::cross(Forward, Right));
}

void camera::ProcessMouseMovement(float deltaX, float deltaY)
{
	Pitch -= deltaY * senseX;//把偏移量加到全局变量pitch和yaw上
	Yaw -= deltaX * senseY;
	UpdateCameraVectors();
}

第三步,我们需要给摄像机添加一些限制,这样摄像机就不会发生奇怪的移动了(这样也会避免一些奇怪的问题)。对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。

if(pitch > 89.0f)
  pitch =  89.0f;
if(pitch < -89.0f)
  pitch = -89.0f;

计算出来的方向向量就会包含根据鼠标移动计算出来的所有旋转了。由于cameraFront向量已经包含在GLM的lookAt函数中,我们这就没什么问题了。

缩放

在OpenGL中,缩放是一种通过变换矩阵改变物体大小的操作,它通过调整顶点坐标在X、Y、Z轴上的分量,实现模型的放大或缩小。

我们会使用鼠标的滚轮来放大。与鼠标移动、键盘输入一样,我们需要一个鼠标滚轮的回调函数

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
    fov += yoffset * 0.1f;
    fov = glm::clamp(fov, 0.1f, 5.0f);
    printf("当前缩放: %.2f\n", fov); // 调试输出
}

当滚动鼠标滚轮的时候,deltaY值代表我们竖直滚动的大小。当scroll_callback函数被调用后,我们改变全局变量fov变量的内容。因为45.0f是默认的视野值,我们将会把缩放级别限制在1.0f45.0f

我们现在在每一帧都必须把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野

 glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(fov));
 //set model matrix
 modelMat = (glm::translate(glm::mat4(1.0f), cubePositions[i])) * scale;

最后不要忘记注册鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

本文参考:Learn OpenGL, extensive tutorial resource for learning Modern OpenGL

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐