iOS CoreAnimation专题——实战篇(四)基于拖动手势的视图3D旋转效果

今次我们来弄一个好玩的效果,纯粹是好玩,我觉得这个效果应该很少在实际项目中用得到吧(当然不排除一些变态项目)。

图1

当然这个效果运用的核心技术就是CoreAnimation的CATransform3D(还有我的DHVector2D!),那么CATransform3D是个啥玩意呢?不要着急,我们先来了解一点关于计算机图形学的,非常非常基础的东西:矩阵变换和齐次坐标。

计算机图形学是干啥的呢,简单来说的话,因为咱们的电脑(手机)屏幕是二维的,或者说像素点是二维的,但是我们往往要去显示三维的内容(就像上图那样的非常自然的3D变换),那么我应该如何处理这些像素点让它们显示出来的东西看起来是三维的呢?或者说,如何把3维的内容投射到二维平面上去?这就是计算机图形学研究的大致方向,通常一家游戏公司招聘的时候,最首要的要求就是掌握计算机图形学。而计算机图形学的基础是线性代数,怎么样,后悔大学没好好学线性代数了吧?

那么这篇博客我会以尽量简短的内容帮大家理解iOS中的CATransform3D是如何帮助系统让平面的图像(比如一个普通的CALayer)能够进行看起来是三维的变形的,如果你有线性代数的基础,那么在讲解具体代码之前的那部分关于计算机图形学的内容你读起来应该会比较轻松,如果你没有学过线性代数,那么希望你至少能明白一个二维向量是什么东西,那样也能尽量看明白前面的内容,否则的话,只能直接去代码实现的部分了。当然,您也可以参考我技巧篇的这篇博客来对二维向量有一个大致的理解。

二维图像的显示

在讲CAShapeLayer那一章的时候提到过位图和矢量图,而我们的显示器就是一张大大的位图。显示器通过密集地排布像素点来显示任意的图像。那么既然硬件上是这么玩的,我们的UIView和CALayer在显示的时候肯定也是通过排布像素点来绘制图像的。

绘制信息会通过CPU进行计算,在垂直同步信号(iOS默认是开启垂直同步的,这时屏幕刷新信号就等同于垂直同步信号)到来时再提交给GPU进行渲染,然后GPU渲染好了以后提交给帧缓冲区,最后由显示器从帧缓冲区中获得一帧图像显示到显示器上。所以GPU和屏幕都有各自的刷新率,比如你牛逼的1080,可能GPU每秒能渲染144帧图像,但是你的显示器有点辣鸡,每秒只能绘制60帧图像,那么这之间存在的差异就会导致画面撕裂,这时候就需要垂直同步技术来解决画面撕裂。具体的大家可以去网上搜索学习,这里就不做过多解释了。

总之我们要知道一个视图或者CALayer也是通过像素点来显示的,这些像素点的信息由CPU先计算好然后在屏幕刷新信号到来时提交给GPU,然后按照上面的流程显示到屏幕上。

矩阵变换

学习过线性变换的朋友知道,对一个向量进行线性变换(比如旋转、拉伸、斜切等)只需要乘以一个对应的变换矩阵即可。那么为什么乘以这个矩阵就可以得到变换后的向量?以及这个矩阵是怎么得来的?矩阵变换的这部分内容(基向量、线性变换、线性变换的复合、平移变换)主要解决这两个问题,如果感兴趣的话可以读一读,只需要你了解二维向量的姿势即可。如果不感兴趣当然可以直接跳到CATransform3D那里去。

既然我们看到的这些二维图像是通过像素点的排列显示出来的,那么计算机如何处理图像的变换(比如旋转、拉伸等)的呢?

图像的显示涉及到像素点,每个像素点有两个属性:坐标和颜色。在图像进行变形的时候,每个像素点对应的颜色是不会改变的,但是大家应该很轻易的能想到,它们的坐标在变化。那么坐标是如何变化的呢?

作为开发者,我们当然希望存在这样一个神奇函数:它接收一个点为输入参数,然后返回一个新的点,这个返回的点就是输入点变换后所在的点。这样我们把图像上所有的点都拿去调这个函数,就可以得到变换后的图像的所有点的坐标了,那么变换后的图像自然就能画出来了。

这个函数存在吗?实际上在线性代数中有一个和它极其相似的函数,其实在线性代数中它不叫函数,叫变换:线性变换。当然你完全可以把它当做函数来看,因为它也是接收参数,返回结果。线性变换处理的对象是向量,它接收一个向量作为输入参数,然后返回变换后的向量,向量和点之间又存在着那么一丝丝微妙的关系,我们当然可以用线性变换作为入口来考虑上面提到的神奇函数。

所以我们先来看点线代和图形学的基础姿势。

基向量

在平面系统中,我们可以定义一对基向量,用它们来表示平面中任意的向量。如何表示呢?通常在平面坐标系中我们取两个坐标轴上的单位向量为基向量(方向沿坐标轴正方向、长度为1):x轴上的基向量,记作 i=(1,0) i → = ( 1 , 0 ) ,y轴上的基向量,记作 j=(0,1) j → = ( 0 , 1 )

定义了基向量以后,该系统中任意的向量都能用它们的线性组合(加法和数量积)来表示。也就是考虑任意一个向量 v=(x,y) v → = ( x , y ) ,那么根据向量加法,我们可以构造两个新向量

a=(x,0),b=(0,y)v=a+b a → = ( x , 0 ) , b → = ( 0 , y ) , 有 v → = a → + b →
而根据向量数量积,有
a=xi,b=yj a → = x i → , b → = y j →
所以有

v=a+b=xi+yj v → = a → + b → = x i → + y j →

所以任意的向量都可以表示为基向量的线性组合,这是非常重要的思想,请牢牢记在脑海中。

实际上基向量我们可以任意去取,你可以在坐标系中任意找两个向量作为基向量,只要这一对基向量线性无关(不共线),那么它们就可以用来表示这个坐标系下所有存在的向量。

我们来简单证明一下:比如我们自己随意取的基向量分别是 i=(a,b),j=(c,d) i → = ( a , b ) , j → = ( c , d ) ,那么任意向量 v=(x,y) v → = ( x , y ) 如何表示呢?同样因为要用基向量的线性组合来表示,我们只考虑加法和数乘。则对于任意的向量 v=(x,y) v → = ( x , y ) ,一定存在两个实数 nm n , m ,使得

v=ni+mj=(na,nb)+(mc,md)=(na+mc,nb+md)=(x,y) v → = n i → + m j → = ( n a , n b ) + ( m c , m d ) = ( n a + m c , n b + m d ) = ( x , y )

那么就有方程组:

{ xy=na+mc=nb+md { x = n a + m c y = n b + m d

因为 ij i → j → 线性无关,即 abcd a b ≠ c d ,也就是 adbc a d ≠ b c ,那么方程就存在唯一解:

n=dxcyadbc,m=aybxadbc n = d x − c y a d − b c , m = a y − b x a d − b c

那么对于任意的向量 v=(x,y) v → = ( x , y ) ,和一对基向量 i=(a,b),j=(c,d) i → = ( a , b ) , j → = ( c , d ) 我们都能找到两个常数 n,m n , m 来表示这个向量

v=ni+mj=dxcyadbci+aybxadbcj v → = n i → + m j → = d x − c y a d − b c i → + a y − b x a d − b c j →

顺便一提,如果这对基向量线性相关(共线),那么它们就只能表示它们所在的那条直线上的所有向量;而如果它们都是零向量,则它们只能表示零向量。这应该比较好理解。

关于基向量,要记住两点,非常重要:

你可以取平面上任意一对向量作为基向量

如果这对基向量线性无关,则它们可以表示平面上所有向量;如果线性相关,则它们只能表示它们所在的直线上的所有向量;如果它们都为零向量,则它们只能表示零向量。

这对基向量能表示的所有的向量的集合,叫做这对基向量张成的空间。在平面中,如果这对基向量线性无关,则它们张成的空间就是整个平面;如果它们线性相关,则张成的空间就是它们所在的直线;如果它们是零向量,则它们张成的空间就是原点。

上面的结论同样适用于三维空间甚至更高维空间,大家可以尝试自己去推广一下。

线性变换

在线性代数中有一种变换叫做线性变换,CATransform3D中直接提供的三种变换,旋转(rotate)、拉伸(scale)都是线性变换,而平移(translation)则不是线性变换!后面会专门针对平移变换进行讨论。

线性变换是什么呢,当然线性代数书上给出了明确的定义,而我们作为IT人员,我觉得用一个programmer的思维方式来描述它更为合适。

所以现在我们来以programmer的思维来理解线性变换,考虑有一个函数 f f ,这个函数接收一个向量作为输入参数,然后产生一个新的向量作为返回值,我们先来写一段f的伪代码:

vector f(vector v) {
    vector x = v进行某些计算后的结果
    return x;
}

就相当于,我们有一个向量 v ,然后把 v v → 作为参数传入 f f ,然后通过其返回值得到一个新的向量 x

x=f(v) x → = f ( v → )

那么函数 f f 就是一个变换。所以变换就是把一个向量映射成另一个向量的过程(比如旋转,一个向量旋转后就变成另一个向量了)。因为这个映射的过程可以是任意的,如果这个映射的过程满足某些条件的话,嘿嘿,那么这个变换就可以叫做线性变换了。

ok,这“某些条件”是哪些条件啊?按照书上对线性的定义,函数需要满足:

f ( v + u ) = f ( v ) + f ( u )

f(cv)=cf(v) f ( c v → ) = c f ( v → )

则该函数所代表的变换就是线性的,即可加性和等比例(一阶齐次)。

注意上面两个式子中的向量指的任意向量,c指的任意常数。

而具体地,线性变换如何操作?举个例子,我要实现一个旋转的线性变换,应该对输入向量进行怎样的算法?

还记得我们的基向量吗,这里我不知道数学家们当时的脑回路是怎样的,我只能说这波操作极其风骚。

接下来会出现很多向量相关的等式,但是都是非常基础简单的,不要被吓到,勇敢的去读!(如果比较难理解,大家可以先把下面提到的所有“线性变换”暂时理解成“把一个向量沿原点进行旋转”)

我们知道,平面上任意一个向量都能用基向量的线性组合来表示。我们首先定义一对基向量:

i=(1,0),j=(0,1) i → = ( 1 , 0 ) , j → = ( 0 , 1 )

如果有任意线性变换 L L ,将它作用于任意向量 v = ( x , y ) ,即 L(v) L ( v → ) ,等同于 L(xi+yj) L ( x i → + y j → )

由线性变换的性质:

L(v)=L(xi+y
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值