Apr
8
界面扭曲,听起来就是高大上酷炫屌的的动效,说起界面扭曲特效,常见的系统中,iOS和OSX的动效中会稍有出现,印象比较深的当年ubuntu就有很多3d特效之类的涉及到界面扭曲,微软平台上有WindowsFX软件可以做到类似的事情。而回到安卓,大概系统动效里面完全没有扭曲效果吧。
扭曲效果往往被扣上好看不实用的标签,但是在一些比较注重界面交互与动效的app上,还是有存在的必要性的。那么扭曲效果与其它界面变化有什么不同呢?相对于移动旋转缩放,包括看起来高大上的3d旋转,它们最大的特点是,可以用矩阵来表示这一变化,在显示层也就是把矩阵叉乘到原本参数上的事儿。而扭曲,就是一个远比这个复杂的工作了。
在实际使用中,常见的app使用场景包括:图像弯曲贴图(静态),精确的大块图/界面回弹动画,翻页效果等。
1.实现原理
任何一个界面动效考虑实现原理,我们都会考虑平台CPU/GPU与具体实现性能(fps太低影响视觉效果)。所以排出canvas不可以完成的特效,摆在我们面前的方案就只有:canvas绘制与gl绘制。而实现界面扭曲,最方便的方式,也是canvas实现的唯一方式,就是方法:drawBitmapMesh。从实际效果上来看,这个方法性能可以满足1/2绘fps的需求。
2.drawBitmapMesh
这个api是从api1就引入,这么多年来并没有变过,但它在复杂的交互动效制作中的重要性是显而易见的。它最重要的东西,当然是mesh咯。接触过图形学或者3d建模的同学当然知道mesh是什么,mesh就是它的字面意思,网格。在3d建模中,再复杂的模型也是由无数的三角形构成的网格拼装起来的,而贴图就像一层布一样,以一顶投影方式紧贴在网格上(uv贴图)。显然,这个api就是让我们把一个位图绘制在一个网格上。那么看看参数:
它们分别是:
bitmap:当然,输入的位图源咯。
meshWidth,meshHeight:横纵坐标分段的段数,比如下面的图例一开始都是分的4段。
verts:3d民工的常见概念,点的数组咯。奇数位是x坐标偶数位是y坐标,所以这个数组的总长度应该大于等于(meshWidth+1) * (meshHeight+1) * 2才行。
vertOffset:坐标错位,如果讲动画中所有坐标都计算完成储存在一个verts里,动画是只需要移动offset就可以了。
colors:叠加颜色,可以没有。如果有,长度至少为 (meshWidth+1) * (meshHeight+1)也就是点的个数。
colorOffset:用法与意义和vertOffset彼此彼此。
paint:绘图api的日常。
那么,最简单的将一张图以4为分段的方式贴上去就是这样了:
当然,上面的红线是用其它代码绘制的。要注意到的是网格每一个四边形的切线都是左斜的,这是固定不变的且对后面的效果实现有非常巨大的影响。然后,既然网格的点是我们自己生成的,那么我们就可以改变其中的点的位置对图像进行拉伸了。比如我对右上角和右下角的点进行拉伸,如下图。
可以清晰的看到图像的拉伸,而且收到三角切线方向的影响,两个四边形内部拉伸出来的效果并不相同。不过我都把切线打出来了大家也都应该理解一下了。大家看到既然可以往图像外拉伸,那么往里拉伸是不是也可以呢,是不是可以覆盖到其它网格上实现3d效果呢(虽然数据中并没有z)?稍微测试了一下,如下图:
可以看到,依然受到切线影响,拉伸效果并不一致,右上角被拉到第二行网格的部分被第二行图像挡住了,最后一个点并没有被谁挡住,但是三角形两部分之间会互相遮挡。经过多次测试后,总结出规律:所有的四边形无论怎么拉伸怎么折叠,从左到右从上到下一次z轴上升,并且每一次四边形内右侧的三角高于左侧。话说这也可以猜出这个api的实现原理,并没有通过gl或者软件渲染中真正的3d部分去生成mesh去绘制,只是单纯的用mesh把图像拆开一块一块去画而已。
当然,本身,拉伸一个角或者一个点也不是这个api正确的用法,我们是要用一顶算法生成vert并用来扭曲图像的。比如下列图:
也许4分段的时候,图像边上一段一段的还可以看得很清楚,但是当分段增加到8段,16段的时候,已经就基本干净不出来了,要不是我绘制了红色的网格线的话,看起来就和曲面一样了。这就是传说中的曲面细分(才不是),就是3d建模中所谓的面越多,模型越精细,就是这个道理。通过不同大小的图调整不同的细分度,我们就可以调整出非常合适毫无违和感的曲面了。
然后再看看颜色,简单的做了两个颜色的实例:第一个分别从0xFF000000到0xFFFFFFFF到0x00FFFFFF;第二个是在第一,第三和第八个点设置了红色。
可以看到,第一幅图中黑色区域变黑了,但是白色区域并没有改变,这个效果类似于ps中叠加方式变暗,另外alpha通道的叠加方式看起来和一般的叠加方式不同,会直接将颜色的alpha值设置为图像的alpha通道值。另外,仔细看第二幅图中红色往外渐变的方式,可以注意到颜色的渐变方式依然受到网格切线的影响,站在切线方向的颜色渐变会影响到整个四边形,而另一个方向的颜色渐变会只占领当前三角形。考虑到网格的实际密度的允许范围,事实上这个api的颜色上色效果并不是很好,这也是上色功能(看起来设计之初是为了做阴影和立体效果的)并没有什么实际使用。
3.扭曲运算
当然,扭曲图像只是我们的第一步,一个静态的扭曲画面并没有什么好看的,往往出现扭曲的场景都是动效。当然,不同的动效实现的方法。本demo的目标是实现界面拖动是像旗帜一样的扭曲,在ubuntu和windowsfx中比较常见,效果如下图:
可以想想,这个动效的实现原理,是拖动的点是完全跟随手指一动到目标位置,而周围的点是随着第一个点的牵引带动,以一定的简谐运动的拉伸方式才开始运动——似乎计算很麻烦的样子,再简化一下,周围的点的运行,比牵引点慢,而且距离越远,起步速度越慢。
那么对于每一个点的计算算法,我们这样设定,每一个点拥有(x,y)的坐标与(vx,vy)的速度,速度初始值为0,那么对于每一帧在手指的牵引下一动(mx,my),速度的变化为v = v * (1-rate) + m * rate; pos += v;其中rate是一个(0,1]的常数,可以想想如果rate是1的时候,速度每次直接变化为了手指移动的变化,因此每次移动就直接跟随手指移动过去了——这就是中心点,而rate在0到1之间的话,移动会受到之前的速度影响,因此移动反应会慢很多;同理如果rate真为0的话,由于初始速度为0,那么速度永远为0,那这个点也就被固定下来了。
另外,当每次移动的m为0,即当手指停下来之后,每次v将随着rate立刻或者缓慢递减,直到趋近于0。可以方便的证明每次移动从V1向量到Vn向量。
如果直接移动过去的话,显然,将这些向量累加就可以了:
然而如果考虑rate,递推公式是这样的:
显然,当R=1的时候,就完全和上一公式相等了,当R=0的时候,这个合就为0。且可以证明得到,当R!=0时,Va与Vb就是相等的。至少直觉是正确的就可以了。
4.其它
那么通过这种计算方法在每一帧的移动后对每一个点进行运算,这个demo中,我们就可以模拟出比较逼真的物理效果了。当然,除了点的运算,对界面进行扭曲的动画考虑和优化的点还包括:
1.界面截取:是否要动态截取呢,这关乎到动画的性能。
2.动画帧率:动画帧率太大,运算时间少容易卡顿和跳帧得不偿失,帧率太小,当然动画就不流畅了,需要根据实际情况进行取舍。
3.mesh细分:到底细分多少段才可以保证动画中图像弯曲得流畅,要不要动态运算段数。
4.延迟:是否在移动停止后动画继续播放呢(毕竟可以到正无穷的运算),这关系到用户体验的取舍。
5.弹性交互:最好的交互动效莫过于带弹性反馈的东西了,那么在物理运算的过程中,是否考虑一下真实的简谐运动反馈效果呢?
同样,界面扭曲制作其他动效的同时,也要考虑类似的设计,还包括:
6.动画的点是否可以事前运算好直接用offset使用,还是事实运算。
7.mesh界面移动的过程中是否要加入参数z进行模拟3d运算来实现更逼真的3d效果。
8.实现太复杂drawBitmapMesh是否能满足需求与性能。
扭曲效果往往被扣上好看不实用的标签,但是在一些比较注重界面交互与动效的app上,还是有存在的必要性的。那么扭曲效果与其它界面变化有什么不同呢?相对于移动旋转缩放,包括看起来高大上的3d旋转,它们最大的特点是,可以用矩阵来表示这一变化,在显示层也就是把矩阵叉乘到原本参数上的事儿。而扭曲,就是一个远比这个复杂的工作了。
在实际使用中,常见的app使用场景包括:图像弯曲贴图(静态),精确的大块图/界面回弹动画,翻页效果等。
1.实现原理
任何一个界面动效考虑实现原理,我们都会考虑平台CPU/GPU与具体实现性能(fps太低影响视觉效果)。所以排出canvas不可以完成的特效,摆在我们面前的方案就只有:canvas绘制与gl绘制。而实现界面扭曲,最方便的方式,也是canvas实现的唯一方式,就是方法:drawBitmapMesh。从实际效果上来看,这个方法性能可以满足1/2绘fps的需求。
2.drawBitmapMesh
这个api是从api1就引入,这么多年来并没有变过,但它在复杂的交互动效制作中的重要性是显而易见的。它最重要的东西,当然是mesh咯。接触过图形学或者3d建模的同学当然知道mesh是什么,mesh就是它的字面意思,网格。在3d建模中,再复杂的模型也是由无数的三角形构成的网格拼装起来的,而贴图就像一层布一样,以一顶投影方式紧贴在网格上(uv贴图)。显然,这个api就是让我们把一个位图绘制在一个网格上。那么看看参数:
void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@Nullable Paint paint)
它们分别是:
bitmap:当然,输入的位图源咯。
meshWidth,meshHeight:横纵坐标分段的段数,比如下面的图例一开始都是分的4段。
verts:3d民工的常见概念,点的数组咯。奇数位是x坐标偶数位是y坐标,所以这个数组的总长度应该大于等于(meshWidth+1) * (meshHeight+1) * 2才行。
vertOffset:坐标错位,如果讲动画中所有坐标都计算完成储存在一个verts里,动画是只需要移动offset就可以了。
colors:叠加颜色,可以没有。如果有,长度至少为 (meshWidth+1) * (meshHeight+1)也就是点的个数。
colorOffset:用法与意义和vertOffset彼此彼此。
paint:绘图api的日常。
那么,最简单的将一张图以4为分段的方式贴上去就是这样了:
int count = 4;
int[] c = new int[(count + 1) * (count + 1)];
float[] v = new float[(count + 1) * (count + 1) * 2];
int k = 0;
for (int i = 0; i <= count; i++) {
for (int j = 0; j <= count; j++, k += 2) {
v[k] = j * 400 + 100;
v[k + 1] = i * 400 + 100;
}
}
canvas.drawBitmapMesh(BitmapFactory.decodeResource(getResources(), R.drawable.a), count, count, v, 0, null, 0, null);
当然,上面的红线是用其它代码绘制的。要注意到的是网格每一个四边形的切线都是左斜的,这是固定不变的且对后面的效果实现有非常巨大的影响。然后,既然网格的点是我们自己生成的,那么我们就可以改变其中的点的位置对图像进行拉伸了。比如我对右上角和右下角的点进行拉伸,如下图。
可以清晰的看到图像的拉伸,而且收到三角切线方向的影响,两个四边形内部拉伸出来的效果并不相同。不过我都把切线打出来了大家也都应该理解一下了。大家看到既然可以往图像外拉伸,那么往里拉伸是不是也可以呢,是不是可以覆盖到其它网格上实现3d效果呢(虽然数据中并没有z)?稍微测试了一下,如下图:
可以看到,依然受到切线影响,拉伸效果并不一致,右上角被拉到第二行网格的部分被第二行图像挡住了,最后一个点并没有被谁挡住,但是三角形两部分之间会互相遮挡。经过多次测试后,总结出规律:所有的四边形无论怎么拉伸怎么折叠,从左到右从上到下一次z轴上升,并且每一次四边形内右侧的三角高于左侧。话说这也可以猜出这个api的实现原理,并没有通过gl或者软件渲染中真正的3d部分去生成mesh去绘制,只是单纯的用mesh把图像拆开一块一块去画而已。
当然,本身,拉伸一个角或者一个点也不是这个api正确的用法,我们是要用一顶算法生成vert并用来扭曲图像的。比如下列图:
也许4分段的时候,图像边上一段一段的还可以看得很清楚,但是当分段增加到8段,16段的时候,已经就基本干净不出来了,要不是我绘制了红色的网格线的话,看起来就和曲面一样了。这就是传说中的曲面细分(才不是),就是3d建模中所谓的面越多,模型越精细,就是这个道理。通过不同大小的图调整不同的细分度,我们就可以调整出非常合适毫无违和感的曲面了。
然后再看看颜色,简单的做了两个颜色的实例:第一个分别从0xFF000000到0xFFFFFFFF到0x00FFFFFF;第二个是在第一,第三和第八个点设置了红色。
可以看到,第一幅图中黑色区域变黑了,但是白色区域并没有改变,这个效果类似于ps中叠加方式变暗,另外alpha通道的叠加方式看起来和一般的叠加方式不同,会直接将颜色的alpha值设置为图像的alpha通道值。另外,仔细看第二幅图中红色往外渐变的方式,可以注意到颜色的渐变方式依然受到网格切线的影响,站在切线方向的颜色渐变会影响到整个四边形,而另一个方向的颜色渐变会只占领当前三角形。考虑到网格的实际密度的允许范围,事实上这个api的颜色上色效果并不是很好,这也是上色功能(看起来设计之初是为了做阴影和立体效果的)并没有什么实际使用。
3.扭曲运算
当然,扭曲图像只是我们的第一步,一个静态的扭曲画面并没有什么好看的,往往出现扭曲的场景都是动效。当然,不同的动效实现的方法。本demo的目标是实现界面拖动是像旗帜一样的扭曲,在ubuntu和windowsfx中比较常见,效果如下图:
可以想想,这个动效的实现原理,是拖动的点是完全跟随手指一动到目标位置,而周围的点是随着第一个点的牵引带动,以一定的简谐运动的拉伸方式才开始运动——似乎计算很麻烦的样子,再简化一下,周围的点的运行,比牵引点慢,而且距离越远,起步速度越慢。
那么对于每一个点的计算算法,我们这样设定,每一个点拥有(x,y)的坐标与(vx,vy)的速度,速度初始值为0,那么对于每一帧在手指的牵引下一动(mx,my),速度的变化为v = v * (1-rate) + m * rate; pos += v;其中rate是一个(0,1]的常数,可以想想如果rate是1的时候,速度每次直接变化为了手指移动的变化,因此每次移动就直接跟随手指移动过去了——这就是中心点,而rate在0到1之间的话,移动会受到之前的速度影响,因此移动反应会慢很多;同理如果rate真为0的话,由于初始速度为0,那么速度永远为0,那这个点也就被固定下来了。
另外,当每次移动的m为0,即当手指停下来之后,每次v将随着rate立刻或者缓慢递减,直到趋近于0。可以方便的证明每次移动从V1向量到Vn向量。
如果直接移动过去的话,显然,将这些向量累加就可以了:
然而如果考虑rate,递推公式是这样的:
显然,当R=1的时候,就完全和上一公式相等了,当R=0的时候,这个合就为0。且可以证明得到,当R!=0时,Va与Vb就是相等的。至少直觉是正确的就可以了。
4.其它
那么通过这种计算方法在每一帧的移动后对每一个点进行运算,这个demo中,我们就可以模拟出比较逼真的物理效果了。当然,除了点的运算,对界面进行扭曲的动画考虑和优化的点还包括:
1.界面截取:是否要动态截取呢,这关乎到动画的性能。
2.动画帧率:动画帧率太大,运算时间少容易卡顿和跳帧得不偿失,帧率太小,当然动画就不流畅了,需要根据实际情况进行取舍。
3.mesh细分:到底细分多少段才可以保证动画中图像弯曲得流畅,要不要动态运算段数。
4.延迟:是否在移动停止后动画继续播放呢(毕竟可以到正无穷的运算),这关系到用户体验的取舍。
5.弹性交互:最好的交互动效莫过于带弹性反馈的东西了,那么在物理运算的过程中,是否考虑一下真实的简谐运动反馈效果呢?
同样,界面扭曲制作其他动效的同时,也要考虑类似的设计,还包括:
6.动画的点是否可以事前运算好直接用offset使用,还是事实运算。
7.mesh界面移动的过程中是否要加入参数z进行模拟3d运算来实现更逼真的3d效果。
8.实现太复杂drawBitmapMesh是否能满足需求与性能。
mn
2018/04/13 10:09
你好我是要动效拉伸的效果,能看下demo源码么,像是图 4、图5、图6那种效果
一颗金星
2018/01/16 14:14
你好,有demo源码吗?我现在遇到四个角控制拉伸扭曲图片这个需求,也想到用drawBitmapMesh方法实现,但是想不到怎么确定网格交点坐标,希望能看下你的demo,谢谢
上一頁 1 下一頁