摄像机与绕任意轴旋转
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);
ParameterseyeX, 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的单位向量和旋转角度
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); }
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
注意推导过程是用的行向量右乘矩阵,与一般线性代数的教材列向量有点不同。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。
代码
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);
}