前面的一篇文章笔者向大家介绍了模型变化矩阵,投影矩阵。现在只剩下最后一个观察矩阵没有和大家讲了。此片文章就为大家介绍OpenGL入门篇的最后一个内容。
观察矩阵
前面的篇章当中,我们看到了即使没有观察矩阵,我们也能对绘制出来的模型有一个很好的控制,那么观察矩阵存在的意义又是什么了?那么我们现在来想象一下这样一件事,我们的屏幕上绘制了特别多的方块,现在我们要把它们全部向屏幕的右边移动一定的距离,如果是前面的做法的话所有的模型都要再乘上一个向右平移的矩阵,这样做完全没有问题,只是这会让程序的效率变得很差,而且你的GPU除了计算一下像素颜色之外基本上就没有做其他任何事,把绝大部分的任务都交给了CPU,这样的任务分配是不合理的,所以我们需要上传一个观察矩阵ViewMatrix给GPU,让他把所有的顶点按照某一个方向进行平移,这样我们的任务就合理分配了。(什么样的任务应该交给CPU,什么样的任务又交给GPU这里也有不少东西需要研究)
看过资料的朋友们都知道,在OpenGL当中想用创建一个观察矩阵只需要使用glm::lookAt函数即可
glm::lookAt(m_Position, m_Forward, m_Up);
//glm::vec3 m_Position 照相机的位置
//glm::vec3 m_Forward 观察的位置
//glm::vec3 m_Up 相机屏幕的上方的方向
这样我们就得到了一个观察矩阵了,看起来也没什么好说的对不对。但是困难的往往是处理与之相关的数学问题,前面说我们要让屏幕上的所有的方块全部向右移动一段距离,那么我们应该怎么设置这个观察矩阵了?直接给答案
glm::vec3 m_Position(-0.3f,0.0f,3.0f)
glm::vec3 m_Forward(-0.3f,0.0f,0.0f);
glm::vec3 m_Up(0.0f,1.0f,0.0f);
glm::lookAt(m_Position, m_Forward, m_Up);
可能有的朋友们就要问了,相机为什么要往左边移动,并且看向左边了?要回答这个问题也很简单,大家现在把手机拿出来打开照相机,然后手机屏幕与电脑屏幕保持水平,现在把手机水平向左开始移动,你就会发现手机屏幕中的物体朝着反方向进行移动了,如果再把手机对准刚才位置,你就会发现所有的物体都行了旋转拉绳,这当然不是我们想要的结果,所以我们要让手机继续朝向正前方。观察矩阵和我们的照相机的工作原理一摸一样,所以观察矩阵也可以叫作相机矩阵。
制作一个可以观察物体的矩阵
观察矩阵其实介绍到这里也就算是结束了,我们现在来做一个可以实际使用的相机。比如说我现在制作了一个立方体,每一个面的颜色都不一样,它刚被绘制出来的时候我只能看见一两个面,但是我想观察其他的面,如果说我去修改模型让它自己转过来,这也有点太蠢了!所以我们要做一个进行鼠标控制的照相机。
我的打算是,让照相机沿着一个球面进行运动,鼠标坐标的变化来确定照相机在球体坐标上的夹角。
鼠标x方向上的偏移量决定角的大小,鼠标y方向上的偏移量决定角的大小,有了理论的基础过后我们看一下具体如何实现。
照相机类:
Camera.h
#pragma once
#include<glm/glm.hpp>
class Camera {
public:
Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yam = 0.0f, float pitch =90.0f);
Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch);
glm::mat4 GetViewMatrix();
void ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch = true);
void ProcessMouseScroll(float yoffset);
inline float GetCameraZoom() const { return m_Zoom; }
private:
void UpdateCameraVectors();
private:
glm::vec3 m_Position, m_Front, m_Up, m_Right, m_WorldUp;
float m_Yaw, m_Pitch;
float m_MouseSensitivity;
float m_Zoom;
};
Camera.cpp
#include<glm/gtc/matrix_transform.hpp>
#include<iostream>
#include"Camera.h"
Camera::Camera(glm::vec3 position ,glm::vec3 up,float yam,float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {
m_Position = position;
m_WorldUp = up;
m_Yaw = yam;
m_Pitch = pitch;
UpdateCameraVectors();
}
Camera::Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch):m_Front(glm::vec3(0.0f,0.0f,-1.0f)),m_MouseSensitivity(0.1f),m_Zoom(45.0f) {
m_Position = glm::vec3(posX, posY, posZ);
m_WorldUp = glm::vec3(upX, upY, upZ);
m_Yaw = yaw;
m_Pitch = pitch;
UpdateCameraVectors();
}
glm::mat4 Camera::GetViewMatrix() {
return glm::lookAt(m_Position, glm::vec3(0.0f, 0.0f, 0.0f), m_Up);
}
void Camera::ProcessMouseMovement(float xoffset, float yoffset, bool constrainPitch){
xoffset *= m_MouseSensitivity;
yoffset *= m_MouseSensitivity;
m_Yaw += xoffset;
m_Pitch += yoffset;
if (constrainPitch) {
m_Pitch = m_Pitch > 179.0f ? 179.0f : m_Pitch;
m_Pitch = m_Pitch < 0.1f ? 0.1f : m_Pitch;
}
UpdateCameraVectors();
}
void Camera::ProcessMouseScroll(float yoffset) {
m_Zoom -= yoffset;
m_Zoom = m_Zoom < 1.0f ? 1.0f : m_Zoom;
m_Zoom = m_Zoom > 45.0f ? 45.0f : m_Zoom;
}
void Camera::UpdateCameraVectors() {
glm::vec3 position(0.0f,0.0f,0.0f);
position.x = 5.0f * sinf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));
position.y = 5.0f * cosf(glm::radians(m_Pitch));
position.z = 5.0f * cosf(glm::radians(m_Yaw)) * sinf(glm::radians(m_Pitch));
m_Front = glm::normalize(-position);
m_Position = position;
m_Right = glm::normalize(glm::cross(m_Front, m_WorldUp));
m_Up = glm::normalize(glm::cross(m_Right, m_Front));
}
除此以外,我们还需要处理鼠标输入函数
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
static float lastX = 320.0f, lastY = 240.0f;
static bool firstMouse = true;
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos, lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {
camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
run = false;
}
好的主函数,贴在下面,我们就得到了一个可以绕着物体圆转的照相了,因为窗口隐藏了光标,想要推出的画按Esc键即可。
#include<glad/glad.h>
#include<GLFW/glfw3.h>
#include<iostream>
#include<glm/gtc/matrix_transform.hpp>
#include"Shader.h"
#include"Camera.h"
static Camera camera(glm::vec3(0.0f, 0.0f, 5.0f));
static bool run = true;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
int main() {
glfwInit();
GLFWwindow* window = glfwCreateWindow(640, 480, "Triangles", NULL, NULL);
glfwMakeContextCurrent(window);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetKeyCallback(window, key_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//需要初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
float vertexes[] = {
//front surface
-0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //0
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //1
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //2
-0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.588f,0.2784f, //3
//back surface
-0.5f, -0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //4
0.5f, -0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //5
0.5f, 0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //6
-0.5f, 0.5f, -0.5f, 1.0f, 0.933f,0.9098f,0.6666f, //7
//up surface
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.749f,1.0f, //8
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.749f,1.0f, //9
0.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.749f,1.0f, //10
-0.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.749f,1.0f, //11
//down surface
-0.5f, -0.5f, 0.5f, 1.0f, 0.498f,1.0f,0.0f, //12
0.5f, -0.5f, 0.5f, 1.0f, 0.498f,1.0f,0.0f, //13
0.5f, -0.5f, -0.5f, 1.0f, 0.498f,1.0f,0.0f, //14
-0.5f, -0.5f, -0.5f, 1.0f, 0.498f,1.0f,0.0f, //15
//left surface
-0.5f, -0.5f, -0.5f, 1.0f, 0.5f,0.5f,0.145f, //16
-0.5f, -0.5f, 0.5f, 1.0f, 0.5f,0.5f,0.145f, //17
-0.5f, 0.5f, 0.5f, 1.0f, 0.5f,0.5f,0.145f, //18
-0.5f, 0.5f, -0.5f, 1.0f, 0.5f,0.5f,0.145f, //19
//right surface
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //20
0.5f, -0.5f, 0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //21
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.8941f,0.7686f, //22
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.8941f,0.7686f //24
};
unsigned int indexes[] = {
//front surface
0,1,2,
2,3,0,
//back surface
4,5,6,
6,7,4,
//up surface
8,9,10,
10,11,8,
//down surface
12,13,14,
14,15,12,
//left surface
16,17,18,
18,19,16,
//right surface
20,21,22,
22,23,20
};
glEnable(GL_DEPTH_TEST);
unsigned int buffer = 0, vertexArray = 0, indexBuffer = 0;
glCreateVertexArrays(1, &vertexArray);
glBindVertexArray(vertexArray);
glCreateBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), NULL);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (const void*)(4 * sizeof(float)));
glCreateBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
Shader* pShader = new Shader("assets/shaders/TextureShader.glsl");
while (!glfwWindowShouldClose(window) && run) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 project = glm::perspective(glm::radians(camera.GetCameraZoom()), 640.0f / 480.0f, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 ViewProject = project * view;
pShader->Bind();
pShader->UploadUniformat4("u_ViewProject", ViewProject);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);
glfwSwapBuffers(window);
glfwPollEvents();
}
delete pShader;
glfwDestroyWindow(window);
glfwTerminate();
}
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) {
static float lastX = 320.0f, lastY = 240.0f;
static bool firstMouse = true;
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos, lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffsetIn, double yoffsetIn) {
camera.ProcessMouseScroll(static_cast<float>(yoffsetIn));
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
run = false;
}
注意
你可能注意到了,相机是不能绕过Y轴顶点的,但这里要声明的是并不是笔者的代码写错了,这是因为,如果绕道Y轴顶点,那么你看向的方向和世界向上的方向就平行了,这两个向量叉乘的结果是一个0向量,也就是根本没办法计算相机的右侧是那一边,相机的上边也同样没办法计算。这是纯数学问题导致的,纯数学问题也是最难解决的问题,现在多软件的相机使用都是四元数来解决这个问,有兴趣的朋友可以去查找相关资料。
总结
此片文章看完OpenGL入门基础篇已经写完了,相信你也可以做一个像样的游戏出来了,比如以前红白机上的一些游戏,可能觉得很Low,但凡事都要一步一个脚印,游戏也是一路发展过来的,没有多少人生来就特别牛。笔者在这里感谢大家的支持。