0%

Qt+OpenGL 4. 摄像机设定旋转中心

许多CAD的软件中,都有设定摄像机旋转中心的功能,今天我就来实现这一功能。

基本思路

首先考虑一个简单的问题:要把摄像机的旋转中心设在点A处,应该怎么做?

思路很简单:

  1. 把点A移到摄像机原点处
  2. 再进行视角变换,旋转变换就都是绕点A的了。

转换成OpenGL摄像机矩阵的代码就是:

1
2
3
4
5
6
QMatrix4x4 CADCamera::getViewMatrix(){
QMatrix4x4 viewMat;
viewMat.lookAt(Position,Position+Front,Up);
viewMat.translate(-rotateCenter);
return viewMat;
}

首先获取摄像机原本的lookat矩阵,然后在其左边乘上一个平移矩阵,该矩阵将旋转中心的坐标移到原点处。用公式表达就是:
viewMatrix = lookAt * translateMatrix

这里再加上一个参数:translate向量。该向量是一个二维向量,之前用在摄像机的平移中。translate.x()为正代表画面向右平移,为负代表画面向左平移,y方向同理。加上translate的函数如下:

1
2
3
4
5
6
7
8
9
QMatrix4x4 CADCamera::getViewMatrix()
{
//摄像机平移后的坐标
QVector3D transPos=-translate.x()*Right - translate.y()*Up + Position;
QMatrix4x4 viewMat;
viewMat.lookAt(transPos,transPos+Front,Up);
viewMat.translate(-rotateCenter);
return viewMat;
}

在后面设定旋转中心的过程中,也要用到translate向量。

设定前后画面衔接

经过处理的摄像机矩阵已经能以设定点为中心旋转了。但还有个关键问题,我们进行了一个平移操作,这个操作会改变物体在摄像机中的位置,导致前后画面会出现一个突变。在我们的设想中,设定旋转中心不会对画面造成影响,因此需要进一步的衔接。

我们先不妨假设在设定前,摄像机的视野中心即为原先的旋转中心(称为O1)。而设定旋转中心后,新旋转中心(称为O2)被平移矩阵移到原点处,而O1也进行了相应的平移。我们要做的就是将点O1仍置于摄像机的视野中心。

一个很直观方法是将摄像机从原位置1直接平移到位置3处(如红线所示),即平移摄像机的位置变量Position,而不改变其他变量。这个方法确实可行,也比较简单,但我个人不大喜欢,因为这样就让摄像机在旋转时不是看向原点的,略有些怪异。

代码如下:

1
2
3
4
5
6
void CADCamera::setCenter(QVector3D pos)
{
QVector3D centerTrans=pos-rotateCenter;
rotateCenter=pos;
Position-=centerTrans;
}

第二种方法如蓝线所示,先延Front向量的方向平移摄像机(1-2),平移后,摄像机的中心仍看向原点;之后再调整translate参数,来使摄像机“看似”位于位置3处。这个相机位置+偏移量的形式可以让相机在旋转时总是看向原点,即相机的Front向量总是等于-Position。最后再通过加偏移量来平移画面。

实现过程如下:

  1. 我们称旋转中心的平移向量为V,首先将摄像机向前平移V点乘Front的距离
  2. 计算摄像机水平方向的偏移,将translate向量的x分量加上V点乘Right的值
  3. 计算摄像机垂直方向的偏移,将translate向量的y分量加上V点乘Up的值

代码如下:

1
2
3
4
5
6
7
8
void CADCamera::setCenter(QVector3D pos)
{
QVector3D centerTrans=pos-rotateCenter;
rotateCenter=pos;
Position-=QVector3D::dotProduct(centerTrans,Front)*Front;
translate[0]+=QVector3D::dotProduct(Right,centerTrans);
translate[1]+=QVector3D::dotProduct(Up,centerTrans);
}

这样就实现了旋转中心的设定,至于用哪个方法,看个人喜好吧。

效果展示

由于还没写点选的功能,所以只能手动输旋转中心啦。