在11节我们说过,MVP矩阵中目前只应用了两个矩阵,World to View 矩阵被省略了,这就导致我们的画面没有办法转换视角。

本节我们将添加这一环节,让相机可以旋转。

为了实现这一目的,我们添加一个相机类, Camera类。

Camera.h:

  1. #pragma once
  2. #include <glm\glm.hpp>
  3.  
  4. class Camera
  5. {
  6. private:
  7. glm::vec3 position;
  8. glm::vec3 viewDirection;
  9. const glm::vec3 UP;
  10. glm::vec2 oldMousePosition;
  11.  
  12. public:
  13. Camera();
  14. glm::mat4 getWorldToViewMatrix() const;
  15. void mouseUpdate(const glm::vec2& newMousePosition);
  16. };

Camera.cpp:

  1. #include "Camera.h"
  2. #include "glm\gtx\transform.hpp"
  3.  
  4. Camera::Camera():
  5. viewDirection(0.0f,0.0f,-1.0f),
  6. UP(0.0f,1.0f,0.0f)
  7. {
  8.  
  9. }
  10.  
  11. glm::mat4 Camera::getWorldToViewMatrix() const
  12. {
  13. return glm::lookAt(position, position + viewDirection, UP);
  14. }
  15.  
  16. void Camera::mouseUpdate(const glm::vec2 & newMousePosition)
  17. {
  18. glm::vec2 mouseDelta = newMousePosition - oldMousePosition;
  19. if (glm::length(mouseDelta) > 10.0f)
  20. {
  21. oldMousePosition = newMousePosition;
  22. return;
  23. }
  24.  
  25. viewDirection = glm::mat3(glm::rotate(mouseDelta.x * 0.01f, UP)) * viewDirection;
  26.  
  27. oldMousePosition = newMousePosition;
  28. }

glm::lookAt

构建Camera类的最终目的是提供一个 World to view 转换矩阵,这个矩阵可以使用一个函数 glm::lookAt 来构造。

glm::lookAt需要三个参数:

  1. 相机在世界中的位置坐标
  2. 相机的观察目标
  3. 相机的“上”方向

Camera类

Camera类中定义了这些成员:

  • position- 表示相机的位置
  • viewDirection - 表示相机的视线方向
  • UP - 一个常量,用于表示世界的上方
  • oldMousePosition - 表示上一次鼠标的位置
  • getWorldToViewMatrix()函数 - 用于返回World to View转换矩阵
  • mouseUpdate() 函数,计算两次更新之间鼠标的位置变化,并根据此变化更新world to view矩阵

其中前三个成员正好可以提供给getWorldToViewmatrix用于返回world to view矩阵,唯一需要做点计算的是第二个参数,lookAt函数需要的是一个目标,而目标位置可以使用相机位置加上视线方向"模拟"出来,实际上只要朝向是我们需要的,我们并不用关心真正的“目标”是什么。

为什么不直接提供一个“目标位置”的成员呢?

原因是我们需要“旋转”相机,而旋转操作的结果是“方向”向量。这点在Camera.cpp的26行体现出来了。如果我们直接提供的是“目标位置”,这里的计算就无法进行了。

第26行之所以给mouseDelta.x乘以0.01,是对旋转的速度进行了细节的调整。

19-23行是为了避免鼠标离开屏幕后,再次进入时产生的跳跃。

MyGlWindow类的修改

MyGlWindow.h

  • 引入了Camera.h
  • 新增一个Camera类型的成员camera
  • override了一个 mouseMoveEvent函数,这个函数是QWidget类的虚函数,在鼠标按下以后会持续调用
  • 把transformMatrixBufferID提取到类成员中。

最终代码:

  1. #pragma once
  2. #include <QtOpenGL\qgl.h>
  3. #include <string>
  4. #include "Camera.h"
  5.  
  6. class MyGlWindow :public QGLWidget
  7. {
  8. protected:
  9. void sendDataToOpenGL();
  10. void installShaders();
  11. void initializeGL();
  12. void paintGL();
  13. GLuint transformMatrixBufferID;
  14. Camera camera;
  15. std::string ReadShaderCode(const char* fileName);
  16. void mouseMoveEvent(QMouseEvent*);
  17. };

MyGlWindow.cpp

  • 头文件包含 <Qt3DInput\qmouseevent.h>
  • 修改sendDataToOpenGL()函数
  • 修改paintGL()函数
  • 重新实现mouseMoveEvent()函数

修改后的代码:

  1. #include <gl\glew.h>
  2. #include "MyGlWindow.h"
  3. #include <iostream>
  4. #include <fstream>
  5. #include <glm\gtc\matrix_transform.hpp>
  6. #include <glm\gtx\transform.hpp>
  7. #include <ShapeGenerator.h>
  8. #include <Qt3DInput\qmouseevent.h>
  9.  
  10. GLuint programID;
  11. GLuint numIndices;
  12.  
  13. void MyGlWindow::sendDataToOpenGL()
  14. {
  15.  
  16. ShapeData shape = ShapeGenerator::makeCube();
  17.  
  18. GLuint vertexBufferID;
  19. glGenBuffers(, &vertexBufferID);
  20. glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
  21. glBufferData(GL_ARRAY_BUFFER, shape.vertexBufferSize(), shape.vertices, GL_STATIC_DRAW);
  22.  
  23. glEnableVertexAttribArray();
  24. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , );
  25.  
  26. GLuint indexBufferID;
  27. glGenBuffers(, &indexBufferID);
  28. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
  29. glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape.indexBufferSize(), shape.indices, GL_STATIC_DRAW);
  30.  
  31. glEnableVertexAttribArray();
  32. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , (char*)(sizeof(GLfloat) * ));
  33.  
  34. numIndices = shape.numIndices;
  35. shape.cleanUp();
  36.  
  37. //instancing
  38.  
  39. glGenBuffers(, &transformMatrixBufferID);
  40. glBindBuffer(GL_ARRAY_BUFFER, transformMatrixBufferID);
  41.  
  42. glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * , , GL_DYNAMIC_DRAW);
  43. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
  44. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
  45. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
  46. glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
  47. glEnableVertexAttribArray();
  48. glEnableVertexAttribArray();
  49. glEnableVertexAttribArray();
  50. glEnableVertexAttribArray();
  51. glVertexAttribDivisor(, );
  52. glVertexAttribDivisor(, );
  53. glVertexAttribDivisor(, );
  54. glVertexAttribDivisor(, );
  55. }
  56.  
  57. void MyGlWindow::installShaders()
  58. {
  59. //...
  60. }
  61.  
  62. void MyGlWindow::initializeGL()
  63. {
  64. //...
  65. }
  66.  
  67. void MyGlWindow::paintGL()
  68. {
  69.  
  70. glm::mat4 projectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f);
  71.  
  72. glm::mat4 fullTransforms[] =
  73. {
  74. projectionMatrix * camera.getWorldToViewMatrix() * glm::translate(glm::vec3(0.0f, 0.0f, -3.0f)) * glm::rotate(54.0f,glm::vec3(1.0f, 0.0f, 0.0f)),
  75. projectionMatrix * camera.getWorldToViewMatrix() * glm::translate(glm::vec3(2.0f, 0.0f, -4.0f)) * glm::rotate(126.0f, glm::vec3(0.0f, 1.0f, 0.0f))
  76. };
  77.  
  78. glBufferData(GL_ARRAY_BUFFER, sizeof(fullTransforms), fullTransforms, GL_DYNAMIC_DRAW);
  79.  
  80. glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
  81. glViewport(, , width(), height());
  82. glDrawElementsInstanced(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, , );
  83. }
  84.  
  85. std::string MyGlWindow::ReadShaderCode(const char* fileName)
  86. {
  87. //...
  88. }
  89.  
  90. void MyGlWindow::mouseMoveEvent(QMouseEvent * e)
  91. {
  92. camera.mouseUpdate(glm::vec2(e->x(), e->y()));
  93. repaint();
  94. }

72-80行是从sendDataToOpenGL中复制过来的,因为现在需要持续性修改和更新,所以要移动到paintGL函数中。

我们能看到在fullTransforms数组中,两个元素中都在中间增加了camera.getWorldToViewMatrix()。

80行和44行内容基本一样,除了44行没有提供任何数据,而80行提供了实际的数据。这里和之前将的glBufferSubData()的用法是一样的,之所以没有用glBufferSubData,原因是它的作用是改变buffer中的一部分数据,而在这里我们全部都要改变,所以就使用glBufferData是很合适的。

另外44行和80行的函数最后一个参数都改成了GL_DYANMIC_DRAW,原因是绘制的内容要频繁更新。

93行开始实现了mouseMoveEvent函数,该函数是声明在QWidget中的一个虚函数,只要鼠标按下并移动,就会触发这个函数,鼠标事件以参数的形式传入函数。在这里我们首先更新world to view矩阵,然后调用repaint()函数重新绘制画面。

最终的结果就是在画布上按下鼠标并左右移动的时候,我们的视角也左右旋转了。

加入上下旋转

根据左右旋转的方法,可以很方便的加入上下旋转,上下旋转和左右旋转的唯一区别是旋转轴不一样:左右旋转是绕“上”方向轴旋转,这个“上”很容易提供,就是世界坐标的上。而上下旋转是绕相机局部坐标的x轴旋转,这个坐标轴我们无法使用世界坐标,因为它不是固定的,随着相机在其他轴向上的旋转,这个轴会发生变化。

这里我们可以使用一个数学方法来计算这个轴,我们的“上”方向是固定的,而“前”方向也是计算出来了的(就是viewDirection),这样我们可以使用“向量的叉乘”来得到一个垂直于“上和前方向构成的平面”的向量,也就是“朝向相机左或者右方向的向量”,也就是相机的局部坐标的x方向或者-x方向。

叉乘可以使用glm::cross()函数来实现。因此,上下旋转的矩阵可以这样构建:

  1. glm::vec3 pitchAxis = glm::cross(viewDirection, UP);
  2. glm::vec3 pitchMatrix = glm::rotate(mouseDelta.y * 0.01f, pitchAxis)

第一行定义的是旋转轴,第二行就是绕这个旋转轴进行的变化,注意这里用的是mouseDelta的y分量。

我们看一下最终修改的代码:

Camera.cpp的 mouseUpdate函数:

  1. void Camera::mouseUpdate(const glm::vec2 & newMousePosition)
  2. {
  3. glm::vec2 mouseDelta = newMousePosition - oldMousePosition;
  4. if (glm::length(mouseDelta) > 50.0f)
  5. {
  6. oldMousePosition = newMousePosition;
  7. return;
  8. }
  9.  
  10. glm::vec3 pitchAxis = glm::cross(viewDirection, UP);
  11.  
  12. viewDirection = glm::mat3(
  13. glm::rotate(mouseDelta.x * 0.01f, UP) *
  14. glm::rotate(mouseDelta.y * 0.01f, pitchAxis)
  15. ) * viewDirection;
  16.  
  17. oldMousePosition = newMousePosition;
  18. }

注意我们把绕两个轴向旋转的变换结合到一个表达式里了。

最终效果就是相机可以在上下左右方向自由移动了。

潜在编译错误:

如果编译时出现"cannot open source file "QObject" 等错误提示,需要定位到#include <QObject>等相关语句,在路径前增加QtCore路径:

例如:#include <QtCore/QObject>

这段代码出现在qt的头文件中qmouseevent.h和qkeyevent.h中。尚不清楚这是源码中的错误还是我的设置问题。

后面章节出现类似问题的话解决方法一样。

3D Computer Grapihcs Using OpenGL - 17 添加相机(旋转)的更多相关文章

  1. 3D Computer Grapihcs Using OpenGL - 18 相机移动

    移动相机需要用到键盘按键,按键事件的引入需要包含头文件 #include <Qt3DInput\qkeyevent.h> 并实现QWidget中定义的虚函数keyPressEvent 我们 ...

  2. 3D Computer Grapihcs Using OpenGL - 06 Vertex and Fragment Shaders

    从这里就接触到了可编程图形渲染管线. 下面介绍使用Vertex Shader (顶点着色器)和 Fragment Shader(像素着色器)的方法. 我们的目标是使用这两个着色器给三角形填充绿色. 添 ...

  3. 3D Computer Grapihcs Using OpenGL - 04 First Triangle

    本节将绘制一个三角形 先看最终代码: MyGlWindow.cpp: #include <gl\glew.h> #include "MyGlWindow.h" void ...

  4. 3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)

    大部分OpenGL教程都会在一开始就讲解VAO,但是该教程的作者认为这是很不合理的,因为要理解它的作用需要建立在我们此前学过的知识基础上.因此直到教程已经进行了一大半,作者才引入VAO这个概念.在我看 ...

  5. 3D Computer Grapihcs Using OpenGL - 11 Model View Projection Matrices

    本节我们将绘制一个3维物体,立方体. 如果要渲染3D物体,我们需要了解MVP(Model View Projection),它表示三个转换矩阵.实际上这个名字不够明确,更加确切的释义如下: Model ...

  6. 3D Computer Grapihcs Using OpenGL - 10 Color Buffer

    本节我们将尝试利用三角形制作一个“走马灯”效果. 一个三角形如图示方式,从左向右依次移动. 先看一下代码: MyGlWindow.cpp #include <gl\glew.h> #inc ...

  7. 3D Computer Grapihcs Using OpenGL - 09 Enable Depth Test

    启用Depth Test OpenGL是个3D绘图API,也就是说不只有xy坐标轴,还有第三个坐标轴z,z轴的方向是垂直于屏幕,指向屏幕内. 靠近人眼的方向是负方向,标准化设备坐标的最小值是-1, 最 ...

  8. 3D Computer Grapihcs Using OpenGL - 02 QGLWidget

    用红色来填充GLWidget窗口 修改MyGlWindow.h,添加两个函数,一个用来初始化OpengGL,一个用来绘制OpenGL #pragma once #include <QtOpenG ...

  9. 3D Computer Grapihcs Using OpenGL - 01 环境设置

    这系列文章是我学习Youtube上一套OpenGL教程的笔记,自己对教程的案例重新制作并且做了一定程度的修改(更有条理,且修正了一些问题).后续将持续更新. Visual Studio 2017工程 ...

随机推荐

  1. [Python3] 031 常用模块 shutil & zipfile

    目录 shutil 1. shutil.copy() 2. shutil.copy2() 3. shutil.copyfile() 4. shutil.move() 5. 归档 5.1 shutil. ...

  2. 【转】iptables命令、规则、参数详解

    表    (table)包含4个表:4个表的优先级由高到低:raw-->mangle-->nat-->filterraw---RAW表只使用在PREROUTING链和OUTPUT链上 ...

  3. GPIB、USB、PCI、PCI Express和以太网/LAN/LXI

    GPIB 我们研究的第一个总线是IEEE 488总线,较为熟悉的称谓是GPIB(通用接口总线).GPIB是一种在业界已经得到证明的专为仪器控制应用设计的总线.GPIB在过去30年来一直是鲁棒的.可靠的 ...

  4. __main__ 变量

    1. 摘要 通俗的理解__name__ == '__main__':假如你叫小明.py,在朋友眼中,你是小明(__name__ == '小明'):在你自己眼中,你是你自己(__name__ == '_ ...

  5. js 判断图片和视频是否加载成功

    图片: 失败: // 方法 1:更换图片地址 $('img').error(function(){ $(this).attr('src', '加载失败.png'); }); // 方法 2:隐藏它 $ ...

  6. Ubuntu分区方案(菜鸟方案、常用方案和进阶方案)

    菜鸟方案 “/”与swap两个分区就可以应付绝大多数的应用 常用方案 分为3个区 1. 挂载点/:主分区:安装系统和软件:大小为30G:分区格式为ext4: 2. 挂载点/home:逻辑分区:相当于“ ...

  7. nginx的rewrite

    nginx服务的rewrite nginx后端服务的指令 1)upstream指令 :设置后端服务器组的主要指令 Upstream name {} 2)server指令:用于设定组内的服务器 3)Ip ...

  8. C++ 6小时刷完面向对象

    **本篇博文参考视频见我上一篇博文的第一行**### 类和对象的声明- 类的声明```class People{ int a; void fun(){ cout<<"fun&qu ...

  9. python中对RSA的加密和解密

    首先,生成一对密钥,并保存 def create_keys(): # 生成公钥和私钥 (pubkey, privkey) = rsa.newkeys(1024) pub = pubkey.save_p ...

  10. xavier_uniform/xavier_normal

    import math from torch.autograd import Variable import torch import torch.nn as nn import warnings w ...