- 坐标变换其实一直是一个比较让人着迷的内容,嘿嘿嘿
- 从中也可以看到矩阵的魅力
- 记得一篇文章讲,矩阵就是记录一个向量到另一个向量的运动,一个点可以用一个向量来表示,乘上一个矩阵就变成了另一个向量,对应着另一个点。所以说矩阵就是记录向量空间中向量的运动,记录向量之间的转换规则。
齐次坐标
只讲三维的情况啊
- 向量空间中,只有标量和向量
- 向量 + 向量 = 向量
- 标量 * 向量 = 向量
- 三维向量空间中,可以视任意一组线性无关的向量为基
- 基 V = [ v1 , v2 , v3 ]
- 其他向量可以用一个三维的元组来表示
- 向量 a = [ A1 , A2 , A3 ]
- 于是 向量 a = A1 * v2 + A2 * v2 + A3 * v3
- 仅有向量是无法表示几何的,至少要有点吧
- 一般认为点的表示也是一个三维的元组
- 点 b = [ B1 , B2 , B3 ]
- 这样就无法区分点和向量了,咋办?
- 一般认为点的表示也是一个三维的元组
- 仿射空间
- 仿射空间中,基称为标架,由三个向量和一个点组成
- 标架 V = [ v1 , v2 , v3 , p ]
- 仿射空间的运算
- 向量+ 向量 = 向量
- 标量 * 向量 = 向量
- 点 + 向量 = 点
- PS:可以看到,点与向量的和为另一个点,其实也可以认为向量记录的是点的运动,记录着一个点转换到另一个点的信息
- 于是
- 向量 a = [ a1 , a2 , a3 , 0 ]
- 点 b = [ b1 , b2 , b3 , 1 ]
- 即
- a = a1 * v1 + a2 * v2 + a3 * v3 , 确实是向量
- b = b1 * v1 + b2 * v2 + b3 * v3 + p
- b = 向量 + p = 点
- 勉强区分开了哈
- 仿射空间中,基称为标架,由三个向量和一个点组成
- 还有问题
- 点p和向量v1v2v3怎么确定?它们的值又是在那种标架或基下的?
- 所以讨论了再多,这个标架其实还是对应我们普通的三维坐标系才显得平易近人,即
- 点p为原点
- v1 v2 v3 分别对应三个坐标轴 [1,0,0]、[0,1,0]、[0,0,1]
- 只不过我还告诉你,对于任意一个点和任意三个线性无关的向量v1 v2 v3,其都可以当作一个标架。因为三个线性无关的向量就可以表示出任意一个向量,再有一个点为基础,就可以表示任意一个点
- 所以讨论了再多,这个标架其实还是对应我们普通的三维坐标系才显得平易近人,即
- 点p和向量v1v2v3怎么确定?它们的值又是在那种标架或基下的?
- 所以想OpenGL中就默认,其世界坐标系对应的仿射空间,p为原点,v1v2v3为坐标轴
- 但是相机坐标系就不是了,因为啥?因为相机可以移动啊,可以改变朝向和正向,这些都影响着相机坐标系。单就移动来说:
- 相机的默认位置是在世界坐标系的原点,移动了之后就不是了啊
- 那么你在相机的位置看世界坐标系上的点,那些点的坐标表示就应该转换到相机坐标系上来,才对应着我们真正应该看到的点的位置
- 还是写写坐标系比较实在
坐标系
- 世界坐标系
- 世界坐标系的标架
- p为原点(0,0,0)
- v1为x轴 [ 1 , 0 , 0 ]
- v2为y轴 [ 0 , 1 , 0 ]
- v3为z轴 [ 0 , 0 , 1 ]
- 对于输入的顶点数据,如点 a = ( a1 , a2 , a3 )
- 在OpenGL内用四维来表示
- a = [ a1 , a2 , a3 , 1 ]
- 理解起来的话就是
- a = a1 * v1 + a2 * v2 + a3 * v2 + p
- a = [ a1 , 0 , 0 ] + [ 0 , a2 , 0 ] + [ 0 , 0 , a3 ] + ( 0 , 0 , 0 )
- a = ( a1 , a2 , a3 )//也就是这个点嘛
- 世界坐标系的标架
- 相机坐标系
- 相机默认的位置为世界坐标系原点,朝向是Z轴的负方向,正向是Y轴的正方向
- 考虑相机
- 位置就是相机的位置,也就是从哪里去观察这个世界
- 朝向就是相机对准的方向
- 正向就是相机该放在这个位置、对准了方向后,该怎么摆。比如一般我们拍照,竖着摆拍出来的就是竖着的照片,横着摆就是横着的照片。
- 而这些参数其实也是在世界坐标系下表示的
- 默认的相机坐标系
- 移动相机之后,相机的位置、朝向、正向就改变了
- 但顶点的数据还是对应着的世界坐标系,而我们要生成图片是依赖于相机的,所以我们要把顶点转换到相机坐标系下才行,这就是视图的转换。
- 视图转换后顶点的表示就是相对于相机坐标系,但其标架在逻辑上依然是 原点+三个坐标轴
- 裁剪坐标系
- 这还只是相机的外部参数,我们还需要确定
- 那一块的三维区域内的点,我才会拍下来,才会用来生成图像,即我需要规定一块“视景体”来确定拍照的范围
- 是否需要透视效果等等其他
- 这还只是相机的外部参数,我们还需要确定
- 还是讲坐标变换比较实在
坐标变换
OpenGL主要有两个矩阵,模型视图矩阵和投影矩阵
- 模型视图矩阵
- 对世界坐标系下顶点的变化操作,如平移、旋转、缩放(模型)都是对顶点的变化,而相机的位置、朝向、正向的改变(视图)也需要顶点从相对于世界坐标系下转换到相对于相机坐标系,这两种虽然是不同的改变但都是对顶点的操作,所以可以用一个矩阵来记录
- 对于一个点
a=∣∣∣∣∣∣x1y1z11∣∣∣∣∣∣ - 平移,就是在原坐标的基础上加上某个值
- 有平移矩阵
T=∣∣∣∣∣∣100001000100dxdydz1∣∣∣∣∣∣ - 使得
T?a=∣∣∣∣∣∣x1+dxy1+dyz1+dz1∣∣∣∣∣∣ - 就是说只要在原先点的基础上左乘平移矩阵,就可以把点的坐标进行平移变换
- 记平移矩阵 T 为 T(dx,dy,dz),
- 有平移矩阵
- 旋转
- 先考虑绕Z轴旋转的情况,正向的旋转为右手大拇指朝向Z轴正向时,其余手指的朝向(逆时针)
- 绕Z轴旋转至改变X和Y坐标
- 有
x1=r?cos(a)y1=r?sin(a)x2=r?cos(a+b)y2=r?sin(a+b)
- 所以就有
x2=x1?cos(b)?y1?sin(b)y2=x1?sin(b)+y?cos(b)
- 所以绕Z轴旋转θ角度的旋转矩阵就是
Rz(θ)=∣∣∣∣∣∣cos(θ)sin(θ)00?sin(θ)cos(θ)0000100001∣∣∣∣∣∣
- 显然有
Rz(θ)?a=∣∣∣∣∣∣x1?cos(θ)?y1?sin(θ)x1?sin(θ)+y1?cos(θ)z11∣∣∣∣∣∣
- 同理
- 绕X轴旋转θ角度的矩阵
Rx(θ)=∣∣∣∣∣∣10000cos(θ)sin(θ)00?sin(θ)cos(θ)00001∣∣∣∣∣∣
- 绕Y轴旋转θ角度的矩阵
Ry(θ)=∣∣∣∣∣∣cos(θ)0?sin(θ)00100sin(θ)0cos(θ)00001∣∣∣∣∣∣
- 缩放,就是在原坐标的基础上乘以某个倍数
- 缩放矩阵
S(Sx,Sy,Sz)=∣∣∣∣∣∣Sx0000Sy0000Sz00001∣∣∣∣∣∣
- 缩放矩阵
- 视图矩阵,将以世界坐标系原点为(0,0,0)点的世界坐标系下的顶点转换到以相机位置为(0,0,0)的相机坐标系下。
- 顶点和相机之间真正相对位置是不变的,只不过我们原先顶点的坐标表示是在世界坐标系下,而我们需要的是相机坐标系下的坐标表示。所以这里只是做坐标变换,即同一个顶点在不同坐标系下其坐标表示是不同的。
- 从默认的相机位置可以看到,默认的相机坐标系和世界坐标系是重合的,即不需要转换,其实也就是相当于视图矩阵是单位矩阵而已
- 而改变了相机的位置、朝向和正向之后,视图矩阵才会真正的发挥作用,乘上的效果就是让世界坐标系下的顶点转换到相机坐标系下
- OnenGL通过gluLookAt函数来改变相机的外部参数
- gluLookAt( Tx , Ty , Tz , Ox , Oy , Oz , Vx , Vy , Vz )
- Tx Ty Tz 为相机的位置
- Ox Oy Oz 为相机朝向的点
- Vx Vy Vz 为相机的正向
- 考虑相机的三个向量
n=∣∣∣∣NxNyNz∣∣∣∣=∣∣∣∣Ox?TxOy?TyOz?Tz∣∣∣∣v=∣∣∣∣VxVyVz∣∣∣∣u=v×n=∣∣∣∣UxUyUz∣∣∣∣
- n为相机朝向的方向向量
- v为相机的正向向量
- u为二者叉乘的向量
- 要求:n和v为互相垂直,nuv三者均为单位向量(只表方向)
- 那么可以看出这三个向量和相机坐标系的关系
- 并且我们还知道相机的位置 Tx Ty Tz
- 对于顶点
a=∣∣∣∣∣∣x1y1z11∣∣∣∣∣∣ - 首先我们把相机移动到世界坐标系的原点,但因为我们要对顶点的数据进行修改,所以就要对顶点乘上 T( -Tx , -Ty , -Tz )这样一个平移矩阵。这样以来顶点移动到了就是以相机位置为原点的坐标系下
T(?Tx,?Ty,?Tz)=∣∣∣∣∣∣100001000010?Tx?Ty?Tz1∣∣∣∣∣∣
- 再求这个顶点在相机坐标系下的三个坐标的值。借助我们得出的 u,v,n三个向量,这三个向量分别对应了相机坐标系的三个坐标轴,所以要求坐标的值也就是看以这个顶点为终点、相机位置为起点的向量和nuv这三个单位向量的点积,相当于乘上这样一个矩阵
NUV=∣∣∣∣∣∣UxVxNx0UyVyNy0UzVzNz00001∣∣∣∣∣∣
- 然后我们得到了坐标的值吗?还没有。因为 向量n和坐标轴z的正向是相反的,向量u和坐标轴x的正向是相反的,所以我们还需要再乘上一个矩阵来调整
∣∣∣∣∣∣?1000010000?100001∣∣∣∣∣∣
- 于是我们最终用来把世界坐标系下的点转换到相机坐标系下的视图矩阵就是
∣∣∣∣∣∣?1000010000?100001∣∣∣∣∣∣?∣∣∣∣∣∣UxVxNx0UyVyNy0UzVzNz00001∣∣∣∣∣∣?∣∣∣∣∣∣100001000010?Tx?Ty?Tz1∣∣∣∣∣∣
- 投影矩阵
- 模型操作对顶点进行变换,其还是在世界坐标系下
- 视图操作将顶点转换到相机坐标系下
- 但这二者都是对顶点进行变换,所以只需要一个模型-视图矩阵来维护就可以了
- 那么投影矩阵就需要另外一个内存空间来维护
- 因为我们已经把顶点转换到了相机坐标系下,所以我们设置视景体就以相机坐标系来设置
- 只会glOrtho的矩阵,即正交投影。其视景体是一个方形的区域。
- glOrtho( left , right , bottom , top , near , far )
- left,right分别对应了x=left和x=right这两个平面
- bottom和top分别对应了y=bottom和y=top这两个平面
- near和far分别对应了z=-near和z=-far两个平面
- 这里取负,主要是因为相机的朝向是z的负方向,这样才会使视景体落在相机的前面
- glOrtho( left , right , bottom , top , near , far )
- 投影矩阵要做的事情,就是一个“规范化”
- 即根据视景体的范围,对顶点进行平移、放缩,使原先在视景体内的点都落在以原点为中心、边长为2的规范化正方体内
- 这样做的好处就是规范,之后的裁剪、光照等等操作都在一个统一的规范下进行
- 平移矩阵
T(?left+right2,?bottom+top2,near+far2)
- 放缩矩阵
S(2right?left,2top?bottom,?2far?near)
- 所以我们的投影矩阵就是
S?T=∣∣∣∣∣∣∣∣∣∣2right?left00002top?bottom00002far?near0?right+leftright?left?top+bottomtop?bottom?far+nearfar?near1∣∣∣∣∣∣∣∣∣∣
- 注意到我们放缩的时候,对于Z坐标的放缩多了一个负号,这也就意味着所有的点在规范化正方体内是Z坐标的负号是相反的
- 这也就意味着,我们要去“看”规范化正方体内的物体的时候,应该从Z轴的负无穷远看向原点,即方向应该是Z的正向
- 这一步得到的点的坐标,我们称其位于裁剪坐标系