Inside out: Camera
丁欧南
Keyword:[OpenGL Camera][FPS 场景][第一人称射击场景]
我是一名OpenGL初学者,刚刚弄懂Camera的实作方法,愿与初学者共享,与高手探讨.如需要此文完整代码,请发邮件给我.
A.
约定
1. sin cos 等的引数全用弧度制
2. 使用OpenGL 右手定则
3. 矩阵(Matrix)书写规范:
平移(Translation)部分位于最后一行,你可以不加修改地把矩阵写入自己的OpenGL 代码
例:
[A
11 A
12 A
13 A
14]
[A
21 A
22 A
23 A
24]
[A
31 A
32 A
33 A
34]
[A
41 A
42 A
43 A
44]
float matrix[]={A11,A12,A13,A14,A21,A22,A23,A24,A31,A32,A33,A34,A41,A42,A43,A44};
4. *:点积(Dot Product) ×:叉积(Cross Product)
B.
概念
大家都知道gluLookAt,它有三类(每类3个)共9个引数:
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,
GLdouble centerx,GLdouble centery,GLdouble centerz,
GLdouble upx,GLdouble upy,GLdouble upz);
你需要分别指定这三类引数,才能完成Camera变换.
1.
视线:你不用在gluLookAt中指定视线(从眼到被视物体的向量),但在变换视点(尤其是旋转时),
所作的许多操 作都是针对它进行的,
后面将给出如何计算视线向量的公式.
a.
视点:即gluLookAt中eye*的那三个引数,视点指的是一条从原点到眼位置的向量.
从你的座位上站起来,这时你改变的就是视点.
b.
视目标:即gluLookAt中center*的那三个引数,视目标指的是一条从原点到被视物体的向量.
水平转动你的脖子,这时你所作的就是改变视目标.
c.
仰视向量:即gluLookAt中up*的那三个引数,仰视向量指的是头顶的朝向.
竖直抬起头(‘向上看’),这时改变的就是仰视向量.
[一般来讲,仰视向量用(0,1,0)就可以了,不用再仔细考虑它]
2.很明显,由向量减法 视线 view,视点 pos,视目标 tar
view=tar-pos;
3.Camera的变换也就是改变视线位置(前进或后退),方向(转头)
4.Camera变换Pipeline:
Ⅰ 由pos,tar 计算出 view
Ⅱ 变换view
Ⅲ 将view的x,y,z分量传回pos和tar
Ⅳ 将pos,tar,up传给gluLookAt
C.
平移
平移是最简单的,它起的作用是游戏者的前进与后退,你会发现平移其实就是平行移动视线,方向不变.
如下图:
代码讲解:
void Move(float speed) { //speed:指定每按一下键前进多少,我取的是0.3
Vector view=tar-pos; //取得view,Vector不是std::vector
pos.x+=speed*view.x; pos.z+=speed*view.z;
tar.x+=speed*view.x; tar.z+=speed*view.z;
}
//我只希望在平面上移动,所以没有y分量的事.
D.
旋转
旋转就是转头操作,比较复杂,一点一点来.
D1.
绕任意轴旋转一条向量
V
up⊥V
perp |V
up|=|V
perp|=|V
aux| V
proj⊥V
perp n∥V
proj n为单位向量
绕n旋转V θ度到V’, 求V’
V
proj=|V
proj|/|n| n
n*V=|n||V|cosβ cosβ=|Vproj|/|V| |Vproj|=|V|cosβ
n*V=|n||V
proj| |V
proj|=(n*V)/|n|
Vproj=(n*V)/|n|2 n 因为 n 为单位向量
所以 Vproj=n*V n
Vperp=V-Vproj=V-n*V n
V
up=V
perp×n=(V-n*V n)×n=V×n-n*V(n×n)=V×n
V
aux=-sin(θ-90)V
perp+cos(θ-90)V
up=sin(90-θ)V
perp+cos(90-θ)V
up
=cosθV
perp+sinθV
up=cosθ(V-n*V n)+sinθ(V×n)
V’=Vperp+Vaux=n*v n+cosθ(V-n*V n)+sinθ(V×n)
D2.
水平转头
水平转头即在XZ平面上绕竖直方向旋转view向量
可看出pos无变化,y分量亦无变化.
XZ平面的旋转矩阵:
[ cosθ 0 -sinθ ]
[ 0 1 0 ]
[ sinθ 0 cosθ ]
取得view: view=tar-pos;
旋转view:
[cosθ 0 -sinθ ]
[view.x view.y view.z] [0 1 0 ]
[sinθ 0 cosθ]
=[view.x*cosθ+view.z*sinθ view.y -view.x*sinθ+view.z*cosθ]
传回tar值:
tar’=pos+view’;
D3.
竖直转头
竖直转头即在YZ平面上以垂直于view的直线为轴旋转view向量
侧视示意图:
鸟瞰示意图:
取得view: view=tar-pos;
取得 axis: 即旋转view (-π/2)
[0 0 1]
[view.x view.y view.z] [0 1 0]
[-1 0 0]
=[-view.z view.y view.x]
为了用上D1中求得的公式,把axis归一化(Normalize)
绕axis旋转view: (套用D1最后得出的公式)
view’=axis*view axis+cosθ(view-axis*view axis)+sinθ(view×axis);
更新tar值:
tar’=pos+view;
E.
鼠标滑动角度计算
我们注意到进行旋转Camera时,需要向程序提供转过的角度(2个)
一个是水平转动角度(转头),一个是竖直转动角度(抬头或低头)
这样计算:
鼠标滑过的距离其实是view向量终点滑过的距离
用滑过的距离(s),view向量的长度(l),求得水平角(a),竖直角(b)
a=arc tan s.x/l b=arc tan s.y/l