一:从openGL世界内到界面经历了那些坐标转换?
首先理解几个坐标系:
- 局部坐标系(Local Space:以自身为中心的坐标通常为Vec4(1);
- 世界坐标系(word space):以世界为原点的坐标系,将局部转化为世界;
- 变换坐标(model space):变换坐标是以世界坐标系为基础进行旋转,位移,缩放,等;
- 视窗坐标(View Space:以摄像机位置为坐标系进行转换;
- 裁剪空间(clip space):将屏幕以外的坐标裁剪,需要定义一种投影矩阵一种正视投影,另一种透视投影。
正视投影:
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
透视投影:
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
以上变化很复杂,本文只做了解,想了解请点击:OpenGL Projection Matrix (songho.ca)
最后组合到一起就是我们的屏幕坐标,就是我们看到的。
二:理解矩阵的相乘是什么含义!
贴一个链接:矩阵相乘的几何意义 - 知乎 (zhihu.com)
矩阵相乘的几何意义:将一个坐标系中所有的点映射到另一个坐标系中去。
当然,更为抽象的说法是,坐标系变换(即将一个坐标系变换成另一个坐标系,从一个空间转换到另一个空间)。
说白了经过一系列变换能将一个矩阵呈现到我们的屏幕中靠的就是矩阵的乘,但是这个乘是有顺序的 上面的公式从右往左读就是矩阵变换的先后顺序。
三:摄像机(view)矩阵形式怎么获得?
引用一下管方图,我们要做的就是根据原点坐标(视点、焦点),摄像机坐标,世界上向量就能求出摄像机的坐标系变换矩阵步骤如下:
首选求摄像机位置与原点连线的(z)向量:(方向由摄像机到原点)
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
我们求出了摄像机的一个轴向量,接下来我们求第二个,我们首先定义一个上向量代表zy平面,求出x轴的向量,我们定上向量为:
glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
我们根据向量的叉乘可以求出垂直于两个向量的那个向量(x)(右手坐标系);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));
同样我们求出剩下的那一个向量(y):
glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
这样我们就求出了视口矩阵
其中RR是右向量,UU是上向量,DD是方向向量PP是摄像机位置向量。
四:进行相机移动
在opengl中设置LookAt函数即可得到摄像机矩阵
view = glm::lookAt(cameraPos, cameraFront, cameraUp);
进行移动只是将焦点及位置同时向前移动即:
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
根据opengl自身的按键回调函数来变更相机输入。
五:相机旋转
涉及相机旋转我们要引入欧拉角。
欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:
其中第三种我们用的少,一般空战游戏会有,其他游戏都已第一二种为主。欧拉角定义了旋转,我们首先要知道矩阵旋转的变换公式。
通过旋转pitch、yaw、roll角度,并将相机移到指定位置eye,那么对应的视变换矩阵为
对相机进行pitch和yaw角度的旋转后,我们需要重新计算相机的forward向量,以及side向量用来完成相机的前后左右移动。这两个向量都是在世界坐标系下给定的。我们可以计算出相机的坐标系下的点经过旋转后,在世界坐标系下的值。计算得到:
通过矩阵R可以计算得到原始的forward=(0,0,-1,0)向量变换后的向量,计算结果为上述矩阵R第三列求反的结果,表示为:
Front.x = -cos(glm::radians(Pitch))*sin(glm::radians(Yaw));
Front.y = sin(glm::radians(Pitch));
Front.z = -cos(glm::radians(Pitch))*cos(glm::radians(Yaw));
Front = glm::normalize(Front);
最后结合回调函数即可获得鼠标移动的值。
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if(firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
float sensitivity = 0.05;
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
if(pitch > 89.0f)
pitch = 89.0f;
if(pitch < -89.0f)
pitch = -89.0f;
glm::vec3 front;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(front);
}
附录:摄像机类(转载官方脚本)
#ifndef CAMERA_H
#define CAMERA_H
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {
FORWARD,
BACKWARD,
LEFT,
RIGHT
};
// Default camera values
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;
// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:
// camera Attributes
glm::vec3 Position;
glm::vec3 Front;
glm::vec3 Up;
glm::vec3 Right;
glm::vec3 WorldUp;
// euler Angles
float Yaw;
float Pitch;
// camera options
float MovementSpeed;
float MouseSensitivity;
float Zoom;
// constructor with vectors
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;
Yaw = yaw;
Pitch = pitch;
updateCameraVectors();
}
// constructor with scalar values
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();
}
// returns the view matrix calculated using Euler Angles and the LookAt Matrix
glm::mat4 GetViewMatrix()
{
return glm::lookAt(Position, Position + Front, Up);
}
// processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)
void ProcessKeyboard(Camera_Movement direction, float deltaTime)
{
float velocity = MovementSpeed * 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;
}
// processes input received from a mouse input system. Expects the offset value in both the x and y direction.
void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true)
{
xoffset *= MouseSensitivity;
yoffset *= MouseSensitivity;
Yaw += xoffset;
Pitch += yoffset;
// make sure that when pitch is out of bounds, screen doesn't get flipped
if (constrainPitch)
{
if (Pitch > 89.0f)
Pitch = 89.0f;
if (Pitch < -89.0f)
Pitch = -89.0f;
}
// update Front, Right and Up Vectors using the updated Euler angles
updateCameraVectors();
}
// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axis
void ProcessMouseScroll(float yoffset)
{
Zoom -= (float)yoffset;
if (Zoom < 1.0f)
Zoom = 1.0f;
if (Zoom > 45.0f)
Zoom = 45.0f;
}
private:
// calculates the front vector from the Camera's (updated) Euler Angles
void updateCameraVectors()
{
// calculate the new Front vector
glm::vec3 front;
front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
front.y = sin(glm::radians(Pitch));
front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
Front = glm::normalize(front);
// also re-calculate the Right and Up vector
Right = glm::normalize(glm::cross(Front, WorldUp)); // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
Up = glm::normalize(glm::cross(Right, Front));
}
};
#endif