前言
在Qt中使用OpenGL(一)
在Qt中使用OpenGL(二)
在Qt中使用OpenGL(三)
在Qt中使用OpenGL(四)
上一篇的文章中,我们成功的绘制出了一个旋转的色子。此时的你一定不再满足于只绘制一个色子了,肯定迫不及待的决定要绘制点别的东西出来。
但是先不要着急。
因为到目前为止,我们还有一些问题没有解决。
首先就是视角问题。
视角控制
还记得我们是怎么控制视角的吗?
通过数学的方法来构建出来一个视图矩阵,模拟摄像机。
但是你有没有想过,要如何控制它呢?
难道每次咱们都需要准确的去定义摄像机在哪里,又要看向哪里吗?我们又要怎么知道这些坐标的准确的值?我们要怎么输入这些值?给用户一个文本框,让用户自己输入坐标吗?这也太不方便了。
我要是想要实现在3D的FPS游戏中一样,用键盘自由地来回移动,用鼠标自由的来回观察怎么办呢?
没错,聪明的你一定想到了。我们需要定义并管理一些变量,例如人物的坐标和人物的视角朝向,然后监控用户的键盘和鼠标操作,根据用户的操作动态的修改这些变量,并且最终返回给OpenGL一个基于这些信息的视图矩阵。
现在我们就开始做这些事情。
摄像机类
首先让我们设计一个摄像机类。
我们怎么定义这个类的接口呢?换句话来说,一个摄像机可以被用户怎么操作呢?
首先我们应该可以想到,一个摄像机,一定是可以在x,y,z三个轴的方向上自由运动的。并且不是那种我们给你定一个位置,而是我们可以基于当前的位置,决定要前后,左右,上下的运动。
此时,我们就需要注意了,因为我们在说的不是x,y,z三个轴,而是前后,左右,上下。
什么意思呢?当你朝向x轴的正方向,所谓的前后,就是沿着x轴前后运动。但是当你朝向y轴的正方向,所谓的前后,就是沿着y轴的前后运动。
你的运动方向,实际上是和你的朝向相关的。
于是,为了可以正确的处理这个问题,我们首先需要定义一个叫做前方的向量。默认值就用z轴的负方向好了,正好是默认情况下我们面向屏幕时前方的方向,也就是(0,0,-1)。
然后,我们知道摄像机是可以倾斜着拍摄的,但是正常情况下,我们的视角都是头朝上的视角,也就是默认情况下的y轴正方向,于是我们就可以定义一个叫做上方的向量,并且默认值为(0,1,0),正好对应着默认情况下我们面向屏幕时,上方的方向。
还缺什么吗?
当然,我们还缺少一个叫做位置的向量,也就是摄像机的位置。那么,我们就定义它,然后默认值设置为(0,0,0)好了。
接下来要做什么呢?
我们还需要好好的定义一下前方这个向量允许怎么变化的情况。
一般情况下,当我们抬头看,我们肯定不会考虑仰头超过90°对吧,低下头也不会考虑低下头超过90°,但是当我们水平查看的时候,虽然我们的脖子限制了我们的视角,但是考虑到我们可以自己转身,所以实际上水平的视角角度可以认为是不存在任何限制的。
由于我们可以不用考虑歪着头或者倒立的情况,所以我们的上方可以认为永远不变。
那么,以上分析我们要怎么表示好呢?
实际上。现实中,正好有与之相对的概念,就是偏航(yaw),俯仰(pitch)与翻滚(roll)。
对应关系如下:
偏航(yaw)对应左右看,在OpenGL的默认坐标系,代表着视角沿着y轴旋转。
俯仰(pitch)对应上下看,在OpenGL的默认坐标系,代表着视角沿着x轴旋转。
翻滚(roll)对应着歪头看,在OpenGL的默认坐标系,代表着视角沿着z轴旋转。
于是,我们就可以定义出来三个变量,分别代表yaw,pitch,roll。
最后,我们需要有一个矩阵代表视图矩阵,于是我们可以定义一个QMatrix4x4的变量。
于是,我们在类中有了以下定义:
private:
QMatrix4x4 m_view;
QVector3D m_pos{
0,0,0 };
QVector3D m_front{
0,0,-1 };
QVector3D m_up{
0,1,0 };
float m_yaw = 0;
float m_pitch = 0;
float m_roll = 0;
那么,接口怎么写呢?不可能直接修改这些变量吧。
还记得一般情况下,我们在FPS游戏中都有哪些操作吗?
移动位置和调整视角(跳跃也是移动位置的一种)。
那么,我们就可以定义两个函数,分别代表移动位置和调整视角。
public:
void look(float yaw, float pitch, float roll);
void move(float x, float y, float z);
位置移动
其中,位置移动很容易理解,x就是指前后运动的距离,y就是左右运动的距离,z就是上下运动的距离。
由于我们已经有了表示前方和上方的向量,那么我们只需要借助向量叉乘,就可以计算出垂直于这两个方向的向量了。同时垂直于前方和上方的向量,正好就是代表着右侧的向量。
其次,我们还需要考虑一个问题,那就是一般情况下,我们的运动都是水平的,即我们都会沿着水平方向运动而不是真的沿着我们的视角朝向的方向运动。比如我可以看着下方或者上方,但是我的运动方向依旧是保持水平运动,而不是运动的时候会穿过地面或者飞到空中。
想要达到这个目标也很简单,我们只需要再次使用上方和刚刚计算出来的右侧向量进行叉乘,就可以得到一个水平方向的前方向量了。
于是,对于move函数的实现,就可以这么写:
void Camera::move(float x, float y, float z)
{
auto _right = QVector3D::crossProduct(m_front, m_up).normalized();
auto _front = QVector3D::crossProduct(m_up, _right).normalized();
m_pos += _front * x;
m_pos += _right * y;
m_pos += m_up * z;
}
我们充分借助了Qt提供的各种类中丰富的接口,计算出标准化的右侧向量与前方向量,然后让摄像机坐标朝向着这些方向运动。
视角变化
视角的变化则是需要使用之前提到的yaw,pitch与roll的概念了。
我们知道yaw是指左右看,没有什么限制,但是考虑到一周只有360°,那么我们还是将其限制在360°比较好。
而pitch是上下看,一旦我们的视角和表示这上的这个向量方向一致时,很显然我们将无法区分真正的上在哪里了,所以我们需要将pitch限制在±90°之内且不允许为±90°。那么我们就用±89°作为上下的极值好了,1°的差异不会有很大的问题。
而roll这个代表着歪头看的情况,我们目前可以完全不用考虑。
这么一来,剩下的就是怎么让前方这个向量,基于yaw和pitch进行变化了。
其实方法也很简单,依旧是利用3D数学,通过矩阵对向量进行旋转即可。
也就是让默认的前方向量(0,0,-1)先绕着x轴旋转,之后再绕着y轴旋转。
注意,pitch是上下看,代表绕x轴旋转。yaw是左右看,代表绕y轴旋转。
于是我们就可以将代码写成这样:
void