当前位置:   article > 正文

计算机图形学|南邮——画由键盘鼠、标控制的正方体_欧拉角绘制立方体

欧拉角绘制立方体

要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。

这里涉及到OpenGL的坐标系类型,OpenGL是右手坐标系,而DX是左手坐标系,简单讲讲右手坐标系:

在图上画右手坐标系,一条水平的直线表示X轴,从左往右为正方向;一条垂直的直线表示Y轴,从下到上位正方向;一条垂直纸面的轴为Z轴,从纸内到纸外为正方向。

右手坐标系

被称为右手坐标系是因为当你伸出右手,四指的方向指向X轴正方向,弯曲四指指向Y轴正方向,大拇指的方向为Z轴正方向。

摄像机初始位置在原点,朝向是-z轴方向。想要看到我们的物体,可以让摄像机往+z轴方向移动,也可以让整个场景往-z轴方向移动。我们选择让整个场景往-z轴方向移动。关于摄像机的更多东西我们会在下一节讨论

摄像机

(1)摄像机位置

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

不要忘记正z轴是从屏幕指向你的,如果我们希望摄像机向后移动,我们就沿着z轴的正方向移动。

(2)摄像机方向

Z轴:

摄像机的方向,这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)

方向向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向,我们就会获得一个指向摄像机正z轴方向的向量

glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

X轴:

右向量(Right Vector),它代表摄像机空间的x轴的正方向。为获取右向量我们需要先使用一个小技巧:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量

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

Y轴:

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

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

LOOKAT

     当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了。GLM已经提供了这些支持。我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。接着GLM就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵:

  1. glm::mat4 view;
  2. view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),
  3. glm::vec3(0.0f, 0.0f, 0.0f),
  4. glm::vec3(0.0f, 1.0f, 0.0f));

 

  1. glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
  2. glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
  3. glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

LookAt函数现在成了:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

我们首先将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上指向向量(与方向向量相反)。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。让我们摆弄一下这些向量,在按下某些按钮时更新cameraPos向量。

(3)由键盘控制移动(改变comerapos)

(1)移动速度

如果定义移动速度是个常量。但结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。因此设置一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

我们跟踪两个全局变量来计算出deltaTime值:

  1. float deltaTime = 0.0f; // 当前帧与上一帧的时间差
  2. float lastFrame = 0.0f; // 上一帧的时间

在每一帧中我们计算出新的deltaTime以备后用。

  1. float currentFrame = glfwGetTime();
  2. deltaTime = currentFrame - lastFrame;
  3. lastFrame = currentFrame;

现在我们有了deltaTime,在计算速度的时候可以将其考虑进去了:

  1. void processInput(GLFWwindow *window)
  2. {
  3. float cameraSpeed = 2.5f * deltaTime;
  4. ...
  5. }

(2)processInput函数

为GLFW的键盘输入定义过一个processInput函数了,我们来新添加几个需要检查的按键命令:

  1. void processInput(GLFWwindow *window)
  2. {
  3. ...
  4. float cameraSpeed = 0.05f; // adjust accordingly
  5. if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
  6. cameraPos += cameraSpeed * cameraFront;
  7. if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
  8. cameraPos -= cameraSpeed * cameraFront;
  9. if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
  10. cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
  11. if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
  12. cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
  13. }

当我们按下WASD键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动(向后就是向空间坐标系Z轴正向移动),我们就把位置向量加上或减去指向向量。如果我们希望向左右移动,我们使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。

注意,我们对右向量进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但如果进行了标准化移动就是匀速的。

(3)捕捉键盘响应

1、声明函数

  1. void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);

2、绑定

glfwSetKeyCallback(window, KeyCallback);

3、捕捉键盘操作,并调用

  1. glfwPollEvents(); //把所有事件系统都取过来:键盘/鼠标等操作
  2. DoMovement(); //获取完操作之后的额外参数

4、书写函数

  1. void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode)
  2. {
  3. if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
  4. glfwSetWindowShouldClose(window, GL_TRUE); //设定关闭窗口
  5. }
  6. if (key >= 0 && key < 1024) {
  7. if (action == GLFW_PRESS)
  8. {
  9. keys[key] = true; //键盘按下去了,就设置为 true,即为1
  10. }
  11. else if (action == GLFW_RELEASE)
  12. {
  13. keys[key] = false; //键盘松开,设为 false
  14. }
  15. }
  16. }
  1. void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime)
  2. {
  3. //乘以 deltaTime:消除电脑性能,控制时间一样长
  4. GLfloat velocity = this->movementSpeed * deltaTime;
  5. //处理键盘
  6. //向前:加上向前
  7. if (direction == FORWARD) {
  8. this->position += this->front * velocity;
  9. }
  10. //向后:减去向前
  11. if (direction == BACKWARD) {
  12. this->position -= this->front * velocity;
  13. }
  14. if (direction == LEFT) {
  15. this->position -= this->right * velocity;
  16. }
  17. if (direction == RIGHT) {
  18. this->position += this->right * velocity;
  19. }
  20. }

 

加入鼠标(修改cameraFront)

(1)欧拉角

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),下面的图片展示了它们的含义:

俯仰角是描述我们如何往上或往下看的角,可以在第一张图中看到。第二张图展示了偏航角,偏航角表示我们往左和往右看的程度。滚转角代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用。每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。

对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。

  1. direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
  2. direction.y = sin(glm::radians(pitch));
  3. direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

这样我们就有了一个可以把俯仰角和偏航角转化为用来自由旋转视角的摄像机的3维方向向量了。具体原理参考这里

(2)隐藏光标,并屏蔽第一次鼠标移动

1、隐藏光标

首先我们要告诉GLFW,它应该隐藏光标,并捕捉(Capture)它。捕捉光标表示的是,如果焦点在你的程序上(译注:即表示你正在操作这个程序,Windows中拥有焦点的程序标题栏通常是有颜色的那个,而失去焦点的程序标题栏则是灰色的),光标应该停留在窗口中(除非程序失去焦点或者退出)。我们可以用一个简单地配置调用来完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

在调用这个函数之后,无论我们怎么去移动鼠标,光标都不会显示了,它也不会离开窗口(按esc可退出)。

2、屏蔽鼠标第一次移动

我们可以简单的使用一个bool变量检验我们是否是第一次获取鼠标输入,如果是,那么我们先把鼠标的初始位置更新为xpos和ypos值,这样就能解决这个问题;接下来的鼠标移动就会使用刚进入的鼠标位置坐标来计算偏移量了:

  1. if(firstMouse) // 这个bool变量初始时是设定为true
  2. {
  3. lastX = xpos;
  4. lastY = ypos;
  5. firstMouse = false;
  6. }

 

(3)鼠标输入

     偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角,竖直的移动影响俯仰角。它的原理就是,储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。为了计算俯仰角和偏航角,我们需要让GLFW监听鼠标移动事件。(和键盘输入相似)我们会用一个回调函数来完成,函数的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

这里的xpos和ypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用:

glfwSetCursorPosCallback(window, mouse_callback);

在处理FPS风格摄像机的鼠标输入的时候,我们必须在最终获取方向向量之前做下面这几步:

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

第一步:

是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600):

float lastX = 400, lastY = 300;

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

  1. float xoffset = xpos - lastX;
  2. float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
  3. lastX = xpos;
  4. lastY = ypos;
  5. float sensitivity = 0.05f;
  6. xoffset *= sensitivity;
  7. yoffset *= sensitivity;

注意我们把偏移量乘以了sensitivity(灵敏度)值。如果我们忽略这个值,鼠标移动就会太大了;你可以自己实验一下,找到适合自己的灵敏度值。

第二步:

我们把偏移量加到全局变量pitch和yaw上:

  1. yaw += xoffset;
  2. pitch += yoffset;

第三步

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

  1. if(pitch > 89.0f)
  2. pitch = 89.0f;
  3. if(pitch < -89.0f)
  4. pitch = -89.0f;

注意我们没有给偏航角设置限制,这是因为我们不希望限制用户的水平旋转。当然,给偏航角设置限制也很容易,如果你愿意可以自己实现。

第四步

就是通过俯仰角和偏航角来计算以得到真正的方向向量:

  1. glm::vec3 front;
  2. front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
  3. front.y = sin(glm::radians(pitch));
  4. front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
  5. cameraFront = glm::normalize(front);

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

 

最后的代码应该是这样的:

  1. void mouse_callback(GLFWwindow* window, double xpos, double ypos)
  2. {
  3. if(firstMouse)//屏蔽第一次鼠标运动
  4. {
  5. lastX = xpos;
  6. lastY = ypos;
  7. firstMouse = false;
  8. }
  9. float xoffset = xpos - lastX;
  10. float yoffset = lastY - ypos;
  11. lastX = xpos;
  12. lastY = ypos;
  13. float sensitivity = 0.05;
  14. xoffset *= sensitivity;
  15. yoffset *= sensitivity;//计算偏移量
  16. yaw += xoffset;//修改欧拉角
  17. pitch += yoffset;
  18. if(pitch > 89.0f)//规定范围
  19. pitch = 89.0f;
  20. if(pitch < -89.0f)
  21. pitch = -89.0f;
  22. glm::vec3 front;//修改指向变量
  23. front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
  24. front.y = sin(glm::radians(pitch));
  25. front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
  26. cameraFront = glm::normalize(front);
  27. }

5、声明函数

  1. //监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
  2. void MouseCallback(GLFWwindow *window, double xPos, double yPos);

6、绑定

glfwSetCursorPosCallback(window, MouseCallback);

7、书写函数

  1. void MouseCallback(GLFWwindow *window, double xPos, double yPos)
  2. {
  3. if (firstMouse) { //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值
  4. lastX = xPos;
  5. lastY = yPos;
  6. firstMouse = false;
  7. }
  8. GLfloat xOffset = xPos - lastX; //当前位置 - 上一个x
  9. GLfloat yOffset = lastY - yPos; //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的
  10. lastX = xPos;
  11. lastY = yPos;
  12. camera.ProcessMouseMovement(xOffset, yOffset);
  13. }
  1. void ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPith = true)
  2. {
  3. xOffset *= this->mouseSensitivity;
  4. yOffset *= this->mouseSensitivity;
  5. this->yaw += xOffset;
  6. this->pitch += yOffset;
  7. if (constrainPith) { //保证用户只能看到天空或脚下,但是不能超越这个限制
  8. //设置界限 < 90.0f,超过 90 度就会失效,视角发生逆转,因为直角是 90
  9. if (this->pitch > 89.0f) {
  10. this->pitch = 89.0f;
  11. }
  12. if (this->pitch < -89.0f) {
  13. this->pitch = -89.0f;
  14. }
  15. }
  16. this->updateCameraVectors();
  17. }

鼠标控制缩放

实现一个缩放(Zoom)接口。在之前的教程中我们说视野(Field of View)或fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。我们会使用鼠标的滚轮来放大。

(1)定义鼠标滚轮的回调函数:

  1. void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
  2. {
  3. if(fov >= 1.0f && fov <= 45.0f)
  4. fov -= yoffset;
  5. if(fov <= 1.0f)
  6. fov = 1.0f;
  7. if(fov >= 45.0f)
  8. fov = 45.0f;
  9. }

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

(2)把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:

  1. glm::mat4 projection = glm::perspective(glm::radians((float)fov), static_cast<GLfloat>(screenWidth) / static_cast<
  2. GLfloat>(screenHeight), 0.1f, 1000.0f);

(3)记注册鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

哈,如果你一步步做下来,会发现问题,鼠标滑轮控制大小好像不管用耶,为什么呢,其实犯了一个很低级的错误(我也是摆弄好久搞不明白。。。。。。一度怀疑人生),问题就在

(2)把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野:

  1. glm::mat4 projection = glm::perspective(glm::radians((float)fov), static_cast<GLfloat>(screenWidth) / static_cast<
  2. GLfloat>(screenHeight), 0.1f, 1000.0f);

这一步,它的位置不对,应该放在主函数

//画图
    while (!glfwWindowShouldClose(window))
    {

的里面,不是放这个循环的外面。我们知道它是控制画这一过程的,随着时间、用户的操作,它随机响应各种变化,然后画出响应的画面。如果放在外面,就像相当于,projection一直是定值,它没有被修改。因为它在循环外就执行一次

 

主函数的修改

(1)由于顶点改变基本的渲染修改

定义所有顶点的工作非常复杂繁琐,我这里直接把定义好的36个顶点都列出来(6个面 * 每个面2个三角形 * 每个三角形3个顶点),每个面顶点都包含了纹理坐标。

  1. float vertices[] = {
  2. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  3. 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  4. 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  5. 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  6. -0.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.0f,
  7. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  8. -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  9. 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  10. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  11. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  12. -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  13. -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  14. -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  15. -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  16. -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  17. -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  18. -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  19. -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  20. 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  21. 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  22. 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  23. 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  24. 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  25. 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  26. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  27. 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  28. 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  29. 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  30. -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  31. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  32. -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
  33. 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
  34. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  35. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  36. -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  37. -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f
  38. };

修改顶点属性的设置

  1. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
  2. glEnableVertexAttribArray(0);
  3. glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
  4. glEnableVertexAttribArray(1);

修改顶点着色器中纹理坐标的位置

  1. #version 330 core
  2. layout (location = 0) in vec3 position;
  3. layout (location = 1) in vec3 color;
  4. out vec3 Color;
  5. //uniform mat4 transform;
  6. uniform mat4 model; //模型变化
  7. uniform mat4 view; //相机坐标系
  8. uniform mat4 projection; //投影变换
  9. void main(){
  10. //gl_Position = transform * vec4(position, 1.0f);
  11. //从右向左乘:先乘 model,最后乘 projection
  12. gl_Position = projection * view * model * vec4(position, 1.0f);
  13. Color = color;
  14. }

修改片段着色器

  1. #version 330 core
  2. in vec3 Color;
  3. out vec4 color;
  4. void main(){
  5. color = vec4(Color, 1.0f);
  6. }

再显示的方式就是我们之前讲过的glDrawArrays(GL_TRIANGLES, 0, 36);

(2)深度测试,为显示立方体

OpenGL内部本就保存了一份顶点的深度信息,这个信息的名字叫z缓存(z-buffer),也叫深度缓存。默认情况下,深度检测是关闭的,我们需要在某个位置把它打开。打开的方法是调用glEnable(GL_DEPTH_TEST);

然后,在清除屏幕的时候,也需要把深度缓存的数据清除掉!

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

(3)定义矩阵

1、定义模型变换矩阵:

  1. glm::mat4 model;
  2. model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));

模型会根据运行时间旋转一定的角度,看起来有动画的效果。

2、定义观察变换矩阵:

  1. glm::mat4 view;
  2. view = camera.GetViewMatrix(); //获得相机矩阵

3、定义投影变换矩阵:

  1. glm::mat4 projection;
  2. projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

glm::radians定义了FOV,角度为45度。第二个参数定义了屏幕宽高比(aspect ratio),这个值会影响显示到窗口中的物体是原样显示还是被拉伸。0.1f是近裁剪面,100.0f是远裁剪面。

 

(4)矩阵转给着色器

1、修改顶点着色器

将这些矩阵应用到顶点着色器中,顶点着色器需要3个变量来接收这些矩阵然后使用:

  1. uniform mat4 model;
  2. uniform mat4 view;
  3. uniform mat4 projection;
  4. void main()
  5. {
  6. gl_Position = projection * view * model * vec4 (aPos, 1.0f);
  7. TexCoord = vec2(aTexCoord.x, aTexCoord.y);
  8. }

主循环中也要每次给这三个变量赋值:

  1. shader.setMat4("model", glm::value_ptr(model));
  2. shader.setMat4("view", glm::value_ptr(view));
  3. shader.setMat4("projection", glm::value_ptr(projection));

2、将矩阵传入着色器

  1.  GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model");  //到 vs 找到那个 model 变量
  2. GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view");  //到 vs 找到那个 view 变量
  3. GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection");  //到 vs 找到那个 projection 变量
  4. glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
  5. glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
  6.  glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

 

源码在

1、core.vs

  1. #version 330 core
  2. layout (location = 0) in vec3 position;
  3. layout (location = 1) in vec3 color;
  4. out vec3 Color;
  5. //uniform mat4 transform;
  6. uniform mat4 model; //模型变化
  7. uniform mat4 view; //相机坐标系
  8. uniform mat4 projection; //投影变换
  9. void main(){
  10. //gl_Position = transform * vec4(position, 1.0f);
  11. //从右向左乘:先乘 model,最后乘 projection
  12. gl_Position = projection * view * model * vec4(position, 1.0f);
  13. Color = color;
  14. }

2、core.frag

  1. #version 330 core
  2. in vec3 Color;
  3. out vec4 color;
  4. void main(){
  5. color = vec4(Color, 1.0f);
  6. }

3、Shader.h

  1. #pragma once
  2. //#ifndef shader_hpp
  3. //#define shader_hpp
  4. //#endif /* shader_hpp */
  5. #include<string>
  6. #include<fstream> //可以打开文件
  7. #include<sstream>
  8. #include<iostream>
  9. #include<GL/glew.h>
  10. class Shader {
  11. GLuint vertex, fragment;
  12. public:
  13. GLuint Program;
  14. Shader(const GLchar * vertexPath, const GLchar * fragmentPath)
  15. {
  16. std::string vertexCode;
  17. std::string fragmentCode;
  18. std::ifstream vShaderFile;
  19. std::ifstream fShaderFile;
  20. vShaderFile.exceptions(std::ifstream::badbit);
  21. fShaderFile.exceptions(std::ifstream::badbit);
  22. try {
  23. vShaderFile.open(vertexPath);
  24. fShaderFile.open(fragmentPath);
  25. std::stringstream vShaderStream, fShaderStream;
  26. vShaderStream << vShaderFile.rdbuf();
  27. fShaderStream << fShaderFile.rdbuf();
  28. //文件关闭顺序,先 v 再 f
  29. vShaderFile.close();
  30. fShaderFile.close();
  31. vertexCode = vShaderStream.str();
  32. fragmentCode = fShaderStream.str();
  33. }
  34. catch (std::ifstream::failure a) {
  35. std::cout <<
  36. "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ"
  37. << std::endl;
  38. }
  39. //类型转换
  40. const GLchar *vShaderCode = vertexCode.c_str();
  41. const GLchar *fShaderCode = fragmentCode.c_str();
  42. //import and compile the shader
  43. vertex = glCreateShader(GL_VERTEX_SHADER); //不用重新定义
  44. glShaderSource(vertex, 1, &vShaderCode, NULL);
  45. glCompileShader(vertex); //编译
  46. GLint success;
  47. GLchar infoLog[512];
  48. glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); //编译是否完成的位置
  49. if (!success) {
  50. glGetShaderInfoLog(vertex, 512, NULL, infoLog);
  51. std::cout <<
  52. "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
  53. << infoLog << std::endl;
  54. }
  55. //边缘调色器
  56. fragment = glCreateShader(GL_FRAGMENT_SHADER);
  57. glShaderSource(fragment, 1, &fShaderCode, NULL);
  58. glCompileShader(fragment); //编译
  59. glGetShaderiv(fragment, GL_COMPILE_STATUS, &success); //编译是否完成的位置
  60. if (!success) {
  61. glGetShaderInfoLog(fragment, 512, NULL, infoLog);
  62. std::cout <<
  63. "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
  64. << infoLog << std::endl;
  65. }
  66. //create the program and link the program
  67. this->Program = glCreateProgram(); //创建着色器程序
  68. glAttachShader(this->Program, vertex);
  69. glAttachShader(this->Program, fragment);
  70. glLinkProgram(this->Program); //链接
  71. glValidateProgram(this->Program); //可省略
  72. glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
  73. if (!success) {
  74. glGetProgramInfoLog(this->Program, 512, NULL, infoLog); //获取链接情况
  75. std::cout <<
  76. "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" <<
  77. infoLog << std::endl;
  78. }
  79. }
  80. ~Shader() {
  81. glDetachShader(this->Program, vertex);
  82. glDetachShader(this->Program, fragment);
  83. glDeleteShader(vertex);
  84. glDeleteShader(fragment);
  85. glDeleteProgram(this->Program);
  86. }
  87. void Use() {
  88. glUseProgram(this->Program);
  89. }
  90. };

4、main

  1. #include <iostream>
  2. //GLEW
  3. #define GLEW_STATIC
  4. #include <GL/glew.h>
  5. //GLFW
  6. #include <GLFW/glfw3.h>
  7. //Shader
  8. #include "Shader.h"
  9. // SOIL2
  10. //Linux 用的是 \, 但是 / 都可以用
  11. //glm
  12. #include <glm/glm.hpp>
  13. #include <glm/gtc/matrix_transform.hpp> //需要什么变换,就导入什么文件,具体可以去官网看
  14. #include <glm/gtc/type_ptr.hpp>
  15. #include "Camera.h" //当前引用,所以用""
  16. const GLint WIDTH = 800, HEIGHT = 600; //新建窗口
  17. //键盘回应
  18. void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode);
  19. //监听鼠标移动事件:xPos 和 yPos 代表当前鼠标位置
  20. void MouseCallback(GLFWwindow *window, double xPos, double yPos);
  21. //键盘移动
  22. void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
  23. void DoMovement();
  24. //初始化一个相机
  25. Camera camera(glm::vec3(0.0f, 0.0f, 2.0f));
  26. //设置初始量
  27. GLfloat lastX = WIDTH / 2.0;
  28. GLfloat lastY = HEIGHT / 2.0;
  29. bool firstMouse = true;
  30. double fov = 45.0;
  31. //鼠标缩放
  32. //double fov = 45.0;
  33. bool keys[1024]; //存放获取的所有键盘操作,先存下来再进行操作
  34. GLfloat deltaTime = 0.0f; //两帧之间的间隔时间
  35. GLfloat lastTime = 0.0f; //上一帧绘制的时间
  36. int main()
  37. {
  38. glfwInit();
  39. //OpenGL 版本
  40. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  41. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  42. //窗口设置
  43. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //用的是新版的 OpenGL 3.3
  44. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // must for Mac
  45. glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); //改为 GL_TRUE,改变窗口,纵横比会变
  46. GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL B17070306", nullptr,
  47. nullptr); //窗口名字改成自己的学号
  48. if (nullptr == window)
  49. {
  50. std::cout << "Failed to create GLFW window" << std::endl;
  51. glfwTerminate();
  52. return -1;
  53. }
  54. // next two lines are for mac retina display
  55. int screenWidth, screenHeight;
  56. glfwGetFramebufferSize(window, &screenWidth, &screenHeight); //获取窗口大小
  57. glfwMakeContextCurrent(window); //可以新建很多 window
  58. // Set the required callback function
  59. //KeyCallback 是响应键盘消息的回调函数
  60. glfwSetKeyCallback(window, KeyCallback);
  61. //mouse_callback 是响应鼠标消息的回调函数,鼠标一移动 MouseCallback 函数就会被调用
  62. glfwSetCursorPosCallback(window, MouseCallback);
  63. //注册鼠标滚轮的回调函数
  64. glfwSetScrollCallback(window, ScrollCallback);
  65. glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //不允许光标出现
  66. //glewExperimental = GL_TRUE; //在 OpenGL 4.3 以上要加这一句:让 glewInit() 可以顺利完成所有的初始化
  67. if (GLEW_OK != glewInit())
  68. {
  69. std::cout << "Failed to initialise GLEW" << std::endl;
  70. return -1;
  71. }
  72. glViewport(0, 0, screenWidth, screenHeight); //从(0,0)开始画点,直到 WIDTH 和 HEIGHT
  73. glEnable(GL_DEPTH_TEST); //深度测试
  74. glDepthFunc(GL_LESS); //深度信息小于当期信息,就把进行测试
  75. /*
  76. //启动透明度混合,固定不能改,alpha 线性混合:设置当前为 α ,其他就为 1- α
  77. glEnable(GL_BLEND);
  78. //表示把渲染的图像融合到目标区域。也就是说源的每一个像素的alpha都等于自己的alpha,
  79. //目标的每一个像素的alpha等于1减去该位置源像素的alpha。因此不论叠加多少次,亮度是不变的。
  80. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  81. */
  82. //vs 是顶点调色器,frag 是边缘调色器
  83. Shader ourShader = Shader("core.vs", "core.frag"); //文件相对路径
  84. //now the verte information comes below
  85. float vertices[] = {
  86. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  87. 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  88. 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  89. 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  90. -0.5f, 0.5f, -0.5f, 1.0f, 0.0f,0.0f,
  91. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
  92. -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  93. 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  94. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  95. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  96. -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  97. -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f,
  98. -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  99. -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  100. -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  101. -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
  102. -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  103. -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f,
  104. 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  105. 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  106. 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  107. 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
  108. 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  109. 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f,
  110. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  111. 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  112. 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  113. 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  114. -0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 1.0f,
  115. -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f,
  116. -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
  117. 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f,
  118. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  119. 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  120. -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
  121. -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 1.0f
  122. };
  123. //the date should be transfered to the memory on the Graphics Card,传到显存
  124. GLuint VAO, VBO; //VAO:Vertex Array Object VBO:Vertex Buffer Object传数据
  125. glGenVertexArrays(1, &VAO); //创建 VAO
  126. glGenBuffers(1, &VBO);
  127. glBindVertexArray(VAO); //设当前直线
  128. glBindBuffer(GL_ARRAY_BUFFER, VBO); //VAO 和 VBO 成对出现
  129. // transfer the data:传数据
  130. glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //静态访问,几乎不修改
  131. //set the attribute
  132. glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
  133. 6 * sizeof(GLfloat), (GLvoid *)0); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
  134. glEnableVertexAttribArray(0);
  135. glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
  136. 6 * sizeof(GLfloat), (GLvoid *)(3 * sizeof(GLfloat))); //0:对应调色器里 location 的值;3:对应 vec3 三个量;GL_FLOAT:浮点型;GL_FALSE:;5*sizeof(GLfloat):对应 Buffer 里传的数据;(GLvoid*)0:从第 0 个位置开始
  137. glEnableVertexAttribArray(1);
  138. glBindBuffer(GL_ARRAY_BUFFER, 0);
  139. glBindVertexArray(0);
  140. glm::mat4 view = glm::mat4(1.0f); //初始化 4 * 4 单位矩阵
  141. //计算透视投影矩阵,设置几个参数就OK了
  142. //将视锥体的空间投影成正方体/将长方体变成立方体
  143. //第一个:正交投影:FoV/Field of View 视域。默认焦距是 1
  144. //第二个:长宽比
  145. //第三个、第四个:视平台里的上下底的距离,人的视觉空间。设置:近:0.1f。远:1000.0f。
  146. //画图
  147. while (!glfwWindowShouldClose(window))
  148. {
  149. GLfloat currentFrame = glfwGetTime(); //屏幕刚画出来的时间
  150. deltaTime = currentFrame - lastTime; //更新两帧之间的间隔时间
  151. lastTime = currentFrame; //更新上一帧绘制的时间
  152. glfwPollEvents(); //把所有事件系统都取过来:键盘/鼠标等操作
  153. DoMovement(); //获取完操作之后的额外参数
  154. glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //窗口背景颜色,RGB,最后一个是透明度
  155. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓冲,否则会保留之前的移动轨迹颜色
  156. //Bind the shader
  157. ourShader.Use();
  158. glm::mat4 model = glm::mat4(1.0f); //model
  159. model = glm::rotate(model, glm::radians(20.0f) * static_cast<GLfloat>
  160. (glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));
  161. view = camera.GetViewMatrix(); //获得相机矩阵
  162. //glm::mat4 projection = glm::perspective(glm::radians((float)fov),
  163. (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
  164. //glm 从 0.9.9 版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为 0
  165. //glm::mat4 transform = glm::mat4(1.0f); //初始化 4 * 4 单位矩阵
  166. //旋转
  167. //GLM 希望它的角度是弧度制,radians 将角度转化为弧度制
  168. //glfwGetTime():让图形一直变换,做一个类型转换,用 static_cast<GLfloat>,设为 GLfloat 型
  169. //glm::vec3(1.0f, 1.0f, 1.0f),分别绕 x 轴、y 轴、z 轴进行旋转,如果都为 1.0f,就是绕和向量 (1,1,1) 转
  170. //transform = glm::rotate(transform, glm::radians(20.0f) * static_cast<GLfloat>(glfwGetTime()), glm::vec3(1.0f, 1.0f, 1.0f));
  171. //缩放,x、y、z 都缩放到原来的 0.5
  172. //transform = glm::scale(transform, glm::vec3(0.5f, 0.5f, 0.5f));
  173. //平移
  174. //transform =
  175. //将矩阵传入着色器
  176. GLuint modelLoc = glGetUniformLocation(ourShader.Program, "model"); //到 vs 找到那个 model 变量
  177. GLuint viewLoc = glGetUniformLocation(ourShader.Program, "view"); //到 vs 找到那个 view 变量
  178. GLuint projectionLoc = glGetUniformLocation(ourShader.Program, "projection"); //到 vs 找到那个 projection 变量
  179. //GLuint transLoc = glGetUniformLocation(ourShader.Program, "transform"); //到 vs 找到那个 transform 变量
  180. //Matrix4fv:4维矩阵,fv:浮点类型
  181. //transLoc:变量 uniform 的位置
  182. //1:代表只传入一个矩阵
  183. //GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵
  184. //最后:直接给出 transform 矩阵数组,这里我们要把矩阵转换成数组的格式传递。
  185. //glUniformMatrix4fv(transLoc, 1, GL_FALSE, glm::value_ptr(transform)); //glUniformMatrix4fv:四个坐标 glUniform4fv:三个坐标
  186. //Matrix4fv:4维矩阵,fv:浮点类型
  187. //modelLoc:变量 uniform 的位置
  188. //1:代表只传入一个矩阵
  189. //GL_FALSE:不对矩阵进行置换,即不交换矩阵的行和列。GLM 的默认布局就是列主序,所以并不需要置换矩阵
  190. //最后:直接给出 model 矩阵数组,这里我们要把矩阵转换成数组的格式传递。
  191. glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
  192. glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
  193. glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
  194. //Draw the triangle
  195. glBindVertexArray(VAO); //使用 VAO,直接绑定
  196. glDrawArrays(GL_TRIANGLES, 0, 36); //画三角形,总共有 36 个顶点
  197. //glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  198. glBindVertexArray(0);
  199. glfwSwapBuffers(window); //调用双面进行画,显示一个,另一个在画,画面更流畅
  200. }
  201. glDeleteVertexArrays(1, &VAO);
  202. glDeleteBuffers(1, &VBO);
  203. //lDeleteBuffers(1, &EBO);
  204. glfwTerminate();
  205. return 0;
  206. }
  207. void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mode)
  208. {
  209. if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
  210. glfwSetWindowShouldClose(window, GL_TRUE); //设定关闭窗口
  211. }
  212. if (key >= 0 && key < 1024) {
  213. if (action == GLFW_PRESS)
  214. {
  215. keys[key] = true; //键盘按下去了,就设置为 true,即为1
  216. }
  217. else if (action == GLFW_RELEASE)
  218. {
  219. keys[key] = false; //键盘松开,设为 false
  220. }
  221. }
  222. }
  223. void MouseCallback(GLFWwindow *window, double xPos, double yPos)
  224. {
  225. if (firstMouse) { //只有第一次才把鼠标的初始位置更新为 xPos 和 yPos 值
  226. lastX = xPos;
  227. lastY = yPos;
  228. firstMouse = false;
  229. }
  230. GLfloat xOffset = xPos - lastX; //当前位置 - 上一个x
  231. GLfloat yOffset = lastY - yPos; //注意这里是相反的,因为 y 坐标是从底部往顶部依次增大的
  232. lastX = xPos;
  233. lastY = yPos;
  234. camera.ProcessMouseMovement(xOffset, yOffset);
  235. }
  236. void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
  237. {
  238. if (fov >= 1.0f && fov <= 45.0f)
  239. fov -= yoffset;
  240. if (fov <= 1.0f)
  241. fov = 1.0f;
  242. if (fov >= 45.0f)
  243. fov = 45.0f;
  244. }
  245. void DoMovement()
  246. {
  247. if (keys[GLFW_KEY_W] || keys[GLFW_KEY_UP]) { //W 或者 ↑
  248. camera.ProcessKeyboard(FORWARD, deltaTime);
  249. }
  250. if (keys[GLFW_KEY_S] || keys[GLFW_KEY_DOWN]) {
  251. camera.ProcessKeyboard(BACKWARD, deltaTime);
  252. }
  253. if (keys[GLFW_KEY_A] || keys[GLFW_KEY_LEFT]) {
  254. camera.ProcessKeyboard(LEFT, deltaTime);
  255. }
  256. if (keys[GLFW_KEY_D] || keys[GLFW_KEY_RIGHT]) {
  257. camera.ProcessKeyboard(RIGHT, deltaTime);
  258. }
  259. }

5、Camera.h

  1. //
  2. // Camera.h
  3. // Course 3
  4. //
  5. // Created by rui huang on 10/18/17.
  6. // Copyright © 2017 rui huang. All rights reserved.
  7. //
  8. #pragma once
  9. #include <vector>
  10. #define GLEW_STATIC
  11. #include <GL/glew.h>
  12. #include <glm/glm.hpp>
  13. #include <glm/gtc/matrix_transform.hpp>
  14. enum Camera_Movement
  15. {
  16. FORWARD,
  17. BACKWARD,
  18. LEFT,
  19. RIGHT
  20. };
  21. const GLfloat YAW = -90.0f;//上下
  22. const GLfloat PITCH = 0.0f;//左右
  23. const GLfloat SPEED = 6.0f;
  24. const GLfloat SENSITIVITY = 0.25f;
  25. const GLfloat ZOOM = 45.0f;//缩放
  26. // An abstract camera class that processes input and calculates the corresponding Eular Angles, Vectors and Matrices for OpenGL
  27. class Camera
  28. {
  29. public:
  30. // Constructor with vectors
  31. //设置摄像机位置、上向量、俯仰角
  32. Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM)
  33. {
  34. this->position = position; //相机的起点
  35. this->worldUp = up; //向前的量
  36. this->yaw = yaw; //仰角:飞机上下动
  37. this->pitch = pitch; //飞机左右动
  38. this->updateCameraVectors(); //更新:建立坐标系
  39. }
  40. // Constructor with scalar values
  41. Camera(GLfloat posX, GLfloat posY, GLfloat posZ, GLfloat upX, GLfloat upY, GLfloat upZ, GLfloat yaw = YAW, GLfloat pitch = PITCH) :front(glm::vec3(0.0f, 0.0f, -1.0f)), movementSpeed(SPEED), mouseSensitivity(SENSITIVITY), zoom(ZOOM)
  42. {
  43. this->position = glm::vec3(posX, posY, posZ);
  44. this->worldUp = glm::vec3(upX, upY, upZ);
  45. this->yaw = yaw;
  46. this->pitch = pitch;
  47. this->updateCameraVectors();
  48. }
  49. void ProcessKeyboard(Camera_Movement direction, GLfloat deltaTime)
  50. {
  51. //乘以 deltaTime:消除电脑性能,控制时间一样长
  52. GLfloat velocity = this->movementSpeed * deltaTime;
  53. //处理键盘
  54. //向前:加上向前
  55. if (direction == FORWARD) {
  56. this->position += this->front * velocity;
  57. }
  58. //向后:减去向前
  59. if (direction == BACKWARD) {
  60. this->position -= this->front * velocity;
  61. }
  62. if (direction == LEFT) {
  63. this->position -= this->right * velocity;
  64. }
  65. if (direction == RIGHT) {
  66. this->position += this->right * velocity;
  67. }
  68. }
  69. void ProcessMouseMovement(GLfloat xOffset, GLfloat yOffset, GLboolean constrainPith = true)
  70. {
  71. xOffset *= this->mouseSensitivity;
  72. yOffset *= this->mouseSensitivity;
  73. this->yaw += xOffset;
  74. this->pitch += yOffset;
  75. if (constrainPith) { //保证用户只能看到天空或脚下,但是不能超越这个限制
  76. //设置界限 < 90.0f,超过 90 度就会失效,视角发生逆转,因为直角是 90
  77. if (this->pitch > 89.0f) {
  78. this->pitch = 89.0f;
  79. }
  80. if (this->pitch < -89.0f) {
  81. this->pitch = -89.0f;
  82. }
  83. }
  84. this->updateCameraVectors();
  85. }
  86. glm::mat4 GetViewMatrix()
  87. {
  88. //lookAt:观察矩阵:摄像机位置;目标位置;上向量
  89. return glm::lookAt(this->position, this->position + this->front, this->up);
  90. }
  91. GLfloat GetZoom()
  92. {
  93. return this->zoom;
  94. }
  95. void ProcessMouseScroll(GLfloat yOffset)
  96. {
  97. }
  98. private://私用变量
  99. glm::vec3 position;
  100. glm::vec3 front;
  101. glm::vec3 up;
  102. glm::vec3 right;
  103. glm::vec3 worldUp;
  104. GLfloat yaw;
  105. GLfloat pitch;
  106. GLfloat movementSpeed;
  107. GLfloat mouseSensitivity;
  108. GLfloat zoom;
  109. void updateCameraVectors()
  110. {
  111. glm::vec3 front;
  112. //通过俯仰角和偏航角来计算以得到真正的方向向量
  113. //极坐标系下的:x、y、z轴
  114. front.x = cos(glm::radians(this->pitch)) * cos(glm::radians(this->yaw)); //x 轴向前
  115. front.y = sin(glm::radians(this->pitch)); //y 轴向上
  116. front.z = cos(glm::radians(this->pitch)) * sin(glm::radians(this->yaw)); //z 轴向右
  117. this->front = glm::normalize(front); //向前
  118. this->right = glm::normalize(glm::cross(this->front, this->worldUp)); //向右:向前 × 向上,normalize单位化
  119. this->up = glm::normalize(glm::cross(this->right, this->front)); //向上:向右 × 向前
  120. }
  121. };

 

参考链接:

https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera

https://www.jianshu.com/p/bc09f44e0856

https://blog.csdn.net/Wonz5130/article/details/83514217

 

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/922177
推荐阅读
相关标签
  

闽ICP备14008679号