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

jopen 8年前發布 | 26K 次閱讀 Android開發 移動開發

 英文原文: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 重寫這些混亂的邏輯將是非常有意思的事情。


在這里查看動畫:

推薦閱讀:


來自: http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1221/3793.html

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!