代码改变世界

摄像机与绕任意轴旋转

2014-04-02 18:03  truenight  阅读(1471)  评论(0编辑  收藏  举报

I 摄像机类

我们先用gluLookAt来实现摄像机,稍后会给出世界坐标到相机坐标的变换矩阵。

关于gluLookAt的描述

void gluLookAt(    GLdouble eyeX,
     GLdouble eyeY,
     GLdouble eyeZ,
     GLdouble centerX,
     GLdouble centerY,
     GLdouble centerZ,
     GLdouble upX,
     GLdouble upY,
     GLdouble upZ);
Parameters

eyeX, eyeY, eyeZ
Specifies the position of the eye point.

centerX, centerY, centerZ
Specifies the position of the reference point.

upX, upY, upZ
Specifies the direction of the up vector.

Description

gluLookAt creates a viewing matrix derived from an eye point, a reference point indicating the center of the scene, and an UP vector.

针对摄像机需要用三个向量来定义,视点坐标m_View,上向量m_Up决定摆动幅度,位置向量m_Position决定空间中的位置

class Camera 
{ 
      public: 
        Camera(void){}; 
        ~Camera(void){};

private:        
        VECTOR4D m_Position;            //位置 
        VECTOR4D m_View;        //视点 
        VECTOR4D m_Up;    //向上向量

};

要让摄像机能在游戏世界中漫游的话 需要支持移动和改变朝向。

移动拆解为左右移动和前后移动,我们把垂直于朝向向量和上向量形的向量叫作左向量,左右移动即是沿左向量正负方向的移动。前后移动则是沿朝向向量正负方向的移动。

//左右移动摄像机 
void Camera::yawCamera(float speed) 
{ 
        VECTOR4D dir = VECTOR4D_Sub(&m_View,&m_Position); 
        VECTOR4D cross = VECTOR4D_Cross(&dir,&m_Up);

        VECTOR4D_Normalize(&cross);

        //根据速度更新位置 
        m_Position.x+=cross.x*speed; 
        m_Position.z+=cross.z*speed;

        m_View.x+=cross.x*speed; 
        m_View.z+=cross.z*speed; 
}


//前后移动摄像机 
void Camera::moveCamera(float speed) 
{ 
        VECTOR4D dir = VECTOR4D_Sub(&m_View,&m_Position); 
        VECTOR4D_Normalize(&dir);

        m_Position.x+=dir.x*speed; 
        m_Position.y+=dir.y*speed; 
        m_Position.z+=dir.z*speed;

        m_View.x+=dir.x*speed; 
        m_View.y+=dir.y*speed; 
        m_View.z+=dir.z*speed; 
}

改变朝向的方法比较复杂,我们需要这样一个接口void rotateView(float angle,float x,float y,float z);来计算摄像机的视点绕任意轴旋转后的新位置

II 点绕任意轴旋转的公式推导

已知一点P坐标 以及轴A 求P绕轴A旋转后的新坐标P’

rotateView函数中的参数 x y z即为轴A向量 它的长度对我们没有意义 只需要知道方向就行了 也就是A的单位向量(在上面加^符号表示)

最后推导出来的公式应该只包含P向量 ,A的单位向量和旋转角度

image

III 旋转方法的实现

接下来的事情就简单了 只需要拿朝向向量计算旋转后的每个分量的值 再更新视点坐标就行了

void Camera::rotateView(float angle,float x,float y,float z) 
{ 
        VECTOR4D nV;

        VECTOR4D dir = VECTOR4D_Sub(&m_View,&m_Position);            //方向向量

        /** 计算 sin 和cos值 */ 
        float cosTheta = (float)cos(angle); 
        float sinTheta = (float)sin(angle);

        /** 计算旋转向量的x值 */ 
        nV.x  = (cosTheta + (1 - cosTheta) * x * x)        * dir.x; 
        nV.x += ((1 - cosTheta) * x * y - z * sinTheta)    * dir.y; 
        nV.x += ((1 - cosTheta) * x * z + y * sinTheta)    * dir.z;

        /** 计算旋转向量的y值 */ 
        nV.y  = ((1 - cosTheta) * x * y + z * sinTheta)    * dir.x; 
        nV.y += (cosTheta + (1 - cosTheta) * y * y)        * dir.y; 
        nV.y += ((1 - cosTheta) * y * z - x * sinTheta)    * dir.z;

        /** 计算旋转向量的z值 */ 
        nV.z  = ((1 - cosTheta) * x * z - y * sinTheta)    * dir.x; 
        nV.z += ((1 - cosTheta) * y * z + x * sinTheta)    * dir.y; 
        nV.z += (cosTheta + (1 - cosTheta) * z * z)        * dir.z; 
        
        //更新摄像机方向 
        VECTOR4D_Add(&m_Position,&nV,&m_View);        
}

image

III 世界坐标转换为相机坐标

gluLookAt可以用矩阵变换实现,只要推导出世界坐标转换为相机坐标的矩阵Tcaml。网上搜了下有用glTranslate和glRotate实现的,但是那是用的欧拉坐标系的方式,原理是根据绕XYZ绕转的角度来推导Tcam,比较麻烦。既然我们用的上向量,左向量,视点向量的方式表示摄像机,用UVN坐标系变换的方式顺理成章。

摄像机的上向量u,左向量v,方向向量n 相互正交,线性无关,可扩展成R3空间,即可做为三维空间的基。世界坐标到相机坐标的变换即是将世界坐标中的向量(a1,a2,a3) ,即关于 xyz坐标轴基向量的线性组合 a1X+a2Y+a3Z 变换为 基(u,v,n)的线性组合 c1U+c2V+c3N

image

注意推导过程是用的行向量右乘矩阵,与一般线性代数的教材列向量有点不同。u,v,n向量都是经过Normalize的单位向量。过程很简单,即用(u,v,n)向量表示出线性变换矩阵M(uvn坐标到世界坐标的变换),然后求M的逆矩阵。还有一种方法是对x向量的uvn线性组合,两边都点乘上u,由于u和v,u和n正交,所以dot(u,v),dot(u,n)都等于0,从而求出x向量与u向量的共线度,即为u坐标c1,同理可求c2,c3。

image

代码

void Camera::setLook()
{       
        /*gluLookAt(m_Position.x,m_Position.y,m_Position.z,
                    m_View.x,m_View.y,m_View.z,
                    m_Up.x,m_Up.y,m_Up.z);*/

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        buildMatrixUVN();
        glMultMatrixf((GLfloat*)m_Camera.M);
}


void Camera::buildMatrixUVN()
{       
        VECTOR4D u;
        VECTOR4D v;
        VECTOR4D n;

        VECTOR4D_Sub(&m_View,&m_Position,&n);       

//这里之所以要对方向向量反向,是因为opengl采用的右手坐标系
        n.x = -n.x;
        n.y = -n.y;
        n.z = -n.z;

        VECTOR4D_INITXYZ(&v,0,1,0);

        VECTOR4D_Cross(&v,&n,&u);
        VECTOR4D_Cross(&n,&u,&v);
       
        VECTOR4D_Normalize(&v);
        VECTOR4D_Normalize(&u);
        VECTOR4D_Normalize(&n);       

        //uvn变换
        Mat_Init_4X4(&m_Camera,u.x,v.x,n.x,0,
                                                    u.y,v.y,n.y,0,
                                                    u.z,v.z,n.z,0,
                                                    0,0,0,1);

//坐标变换,还需要叠加上相机位置的移动向量
        MATRIX4X4 mt_postion;
        Mat_Init_4X4(&mt_postion,1,0,0,0,
                                                          0,1,0,0,
                                                          0,0,1,0,
                                                          -m_Position.x,-m_Position.y,-m_Position.z,1);

        Mat_Mul_4X4(&mt_postion,&m_Camera,&m_Camera);
}