星球大战:论在安卓上如何把view弄成碎片

 英文原文:Star Wars: The Force Awakens Or How to Crumble View Into Tiny Pieces on Android。 

上个月我们发布了ios上的史诗级动画效果星球大战,而你肯定也知道我们还会为安卓用户做一次同样的事情。备受瞩目的星球大战的现在有了安卓开发者版本 ,非常荣幸能跟你分享我们的开发机密。

首先,在我们的星球 大战动画中,有两个具有挑战性的地方:视图破碎成小块以及飞舞的星场。实现过程中有许多有趣的事情。

如何把view分裂成小的碎片

当我们星球大战动画中的view被外力点击之后,分裂成4000块碎片。这意味着两件事:1)外力确实非常大,2)在安卓上使用Canvas来创建如此复杂的图像应该会很慢,因为Canvas性能不佳。

但是OpenGL则完全相反,它要强大得多。而且在 iOS版的星球大战动画  中我们也是使用的OpenGL,UIDynamics和UIKit(Core Animation)无法应付起码的负荷。

鉴于动画的复杂性,我决定用OpenGL。

为了把安卓视图分裂成小块,我们需要截屏整个视图,把一个texture搬进OpenGL内存,并只渲染碎片的效果。让我们看看是如何做的:

1. 对view截图,这很简单:

Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888)
Canvas canvas = new Canvas(bitmap);
super.draw(canvas);

2.把一个texture搬进OpenGL内存。

3. 把bitmap分裂成碎片。

Android的扩展包,OpenGL ES 3.1的一个超集中有一个 tessellation shader就是完全做这个事情:它把一个平面分裂成许多三角形。而且跟OpenGL ES 2.0不一样的是,2.0里vertex data只能在cpu上产生,而有了扩展包我们可以在GPU上产生这些数据。

但不幸的是,OpenGL ES 3.1 和 AEP(即Android的扩展包的缩写)并不被大多数安卓设备支持。鉴于 56%的安卓设备上都是用的OpenGL ES 2.0,我们决定选择更为艰难的方式。

那么我们该如何把一个视图分裂成4000块呢?我们可以把一张图片一块块的切割!当然我是在开玩笑。如果我们创建了上千个新的texture,估计设备直接在手里化了。我们将使用一张大的 bitmap texture并 分配 UV (texture coordinates)到每个vertex :

final float stepX = 1f / mStarWarsRenderer.sizeX;
final float stepY = 1f / mStarWarsRenderer.sizeY;
  • SizeX - 横向碎片的数目

  • SizeY - 纵向碎片的数目

for (int x = 0; x < mStarWarsRenderer.sizeX; x++) {
        for (int y = 0; y < mStarWarsRenderer.sizeY; y++) {
            final float u0 = x * stepX;
            final float v0 = y * stepY;
            final float u1 = u0 + stepX;
            final float v1 = v0 + stepY;
            // push values to buffer
        }
}

我们希望尽可能把更多的计算移到GPU上,因为GPU擅长多个并行的计算。我对vertex shader中的所有位置(position)都做计算。用Android interpolator来动画它只需要一个变量:

// from 0 to plane height in OpenGL coordinates
animator = ValueAnimator.ofFloat(0, -Const.PLANE_HEIGHT * 2);
animator.setDuration(mAnimationDuration);
animator.setInterpolator(new DecelerateInterpolator(1.3f));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
   float value = (float) animation.getAnimatedValue();
   mDeltaPosX = value;
   mGlSurfaceView.requestRender();
 }
};
animator.start();

最后,在vertex shader中我们应该把这个值添加到碎片位置:

vec4 pos = a_Position;
pos.y += u_DeltaPos;
gl_Position = u_MVPMatrix * calcPos;

如何让星星飞舞

星球大战动画的另一个部分是飞舞的星场。对于星星的绘制,我考虑过使用Leonids 库。它使用的是标准的 Android Canva,使用起来很方便。但是让许多星星动画难免会导致性能问题,因此我们的动画就会显得不太负责任,尤其对于老的智能机来说。这就是我们用Leonids库测试动画时所得到的:

1450680247308666.png

[绿线代表 60 FPS (16ms).。为了避免动画不流畅,我们不能越过这条线]

考虑到性能问题,我决定采取一种类似对碎片的处理方法,在 vertex shader中执行星星运动的动画。

我发现一个星星的texture非常简单,可以用shader的技巧来替代- 使用一个公式来渲染星星对象:

// Render a star
float color = smoothstep(1.0, 0.0, length(v_TexCoordinate - vec2(0.5)) / v_Radius);
gl_FragColor = vec4(color);

就如你看到的,我们得到了和使用图片一样的效果。这让我们每秒多了30%的帧。这个方案虽然不是每次都比texture lookup (比如使用一个image) 快,但多数情况下是。唯一办法是去测量。

1450680296114735.png

左边是pngtexture

右边是产生的图片

当我在我用了三年的nexus4上测试这个方案的时候,我可以在60 FPS的情况下渲染100 000颗星星。

如何使用库

1.把fragment或者activity的主视图包裹在TilesFrameLayout中:

<com.yalantis.starwars.TilesFrameLayout
  android:id="@+id/tiles_frame_layout"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  app:sw_animationDuration="1500"
  app:sw_numberOfTilesX="35">
 
  <!-- Your views go here -->
 
</com.yalantis.starwars.TilesFrameLayout>

2.用这些属性调整动画:

  • app:sw_animationDuration – 持续时间 毫秒

  • app:sw_numberOfTilesX – the number of square tiles the plane is tessellated into broadwise

mTilesFrameLayout = (TilesFrameLayout) findViewById(R.id.tiles_frame);
mTilesFrameLayout.setOnAnimationFinishedListener(this);

3.在fragment或者activity的onPause() 和 onResume()中,记得调用相应的方法:

@Override
public void onResume() {
    super.onResume();
    mTilesFrameLayout.onResume();
}
 
@Override
public void onPause() {
    super.onPause();
    mTilesFrameLayout.onPause();
}

要开始动画,只需调用:

mTilesFrameLayout.startAnimation();

当动画结束的时候,将调用回调函数:

@Override
public void onAnimationFinished() {
   // Hide or remove your view/fragment/activity here
}

以上就是全部!

未来计划

如果能在安卓扩展包更普及的时候用tessellation shaders 重写这些混乱的逻辑将是非常有意思的事情。

在这里查看动画:

推荐阅读: