最近在学习openGL,就找了几个相关的开源项目,一边理解,一边记录~ 这篇文章要介绍的项目来自久负盛名的yalantis
阅读此文需要一点OpenGL基础,比如纹理坐标。

项目giuhub地址

首先简要翻译一下官方原理介绍:

<星战: 原力觉醒> 如何在安卓中粉碎视图

首先,我们面临两个挑战:View粉碎和斗转星移的背景。我有好几个有趣的方案来实现它们。

如何粉碎View
当原力击中View时,View被粉碎成了4000块。这告诉我们两点:1. 原力很强大 2. 如果用Canvas来生成这些碎片,恐怕性能上不行。

所以我选择强大的OpenGL。首先,我需要对要击碎的View截屏,将其纹理传输到openGL的内存中。然后去渲染碎片效果。下面是具体的步骤:
1. 截屏。就是通俗的做法。

Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888)
Canvas canvas = new Canvas(bitmap);
super.draw(canvas);
  1. 将纹理传输到openGL内存。
  2. 将图片转化成碎片。
    虽然OpenGL3.1的Android Extension Pack有一个tessellation shader可以轻松实现我们的需求:将一个平面转化成大量的三角形图元。而且OpenGL3.1 Android Extension Pack 允许我们在GPU上产生顶点数据,而不是仅仅在CPU上。
    但是! 考虑到OpenGL2.0的市场占有率还不低,我还是选择hard 模式吧。
    如何把一个View碎成4000块?我们可以挨个切下每一块碎片!当然,我只是开个玩笑。如果我们生成了上千个纹理,大概手机都要融化了。相反,我们将使用一个大的BitMap纹理,并且对每一个顶点设置纹理坐标(UV坐标)。
final float stepX = 1f / mStarWarsRenderer.sizeX;
final float stepY = 1f / mStarWarsRenderer.sizeY;

sizeX指的是X轴上的碎片数目
stepY指的是Y轴上的碎片数目

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擅长异步计算. 所有的坐标计算我都放在顶点着色器里了. 我只需要一个变量来产生动画,这个变量通过 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() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float value = (float) animation.getAnimatedValue();mDeltaPosX = value;mGlSurfaceView.requestRender();}
};
animator.start();

最后, 在顶点着色器里,我们将这个值传给碎片:

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

如何斗转星移
可以通过粒子效果库 Leonids library画星星。此库用的是Canvas,上手也比较容易。然而,粒子数较多时,性能仍然是个问题,特别在旧手机上。
考虑到性能,我采用了跟碎片效果类似的方案,并且在顶点着色器中实现斗转星移的效果。
用纹理来画星星当然很容易,也可以用片段着色器附加一些使用技巧实现:使用公式来渲染星星。

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

后一种方法不仅能达成效果,还能增加30%的帧率。在大多数情况下,后一种方法渲染都比前一种方法快。
我采用了后一种方法,并且在我的旧手机Nexus 4上渲染100 000颗星星,仍有60 FPS(16ms)的帧率,很不错。

接下来回对OpenGL相关的点做一些解析。
分析开源项目,找准切入点很重要。我主要是想看怎么用OpenGl实现碎片效果,所以就先分析StarWarsRenderer这个类。
我写了些注释,来方便理解代码:

public class StarWarsRenderer implements
GLSurfaceView.Renderer {//略去成员变量声明//构造函数 声明配置和监听器public StarWarsRenderer(StarWarsTilesGLSurfaceView glSurfaceView,TilesFrameLayout TilesFrameLayout, int animationDuration, int numberOfTilesX) {mGlSurfaceView = glSurfaceView;mListener = TilesFrameLayout;mAnimationDuration = animationDuration;mNumberOfTilesX = numberOfTilesX;}@Overridepublic void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {// 常规的清屏操作GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);// Use culling to remove back faces.//开启剔除操作 提高渲染效率。默认剔除背面,如果要剔除正面:glCullFace(GL_FRONT)GLES20.glEnable(GLES20.GL_CULL_FACE);//顺时针表示正面 这样如果顶点数组是按顺时针排列的,就是告诉OpenGL是在绘制正面,反之就是背面。GLES20.glFrontFace(GLES20.GL_CW);// Enable depth testing//开启深度检测,这样后面被挡住的部分(按Z值区分前后)就不会被绘制,提高渲染效率。更多请参考glPolygonOffest函数GLES20.glEnable(GLES20.GL_DEPTH_TEST);//从这一行一直到Matrix.setLookAtM 都是在设置照相机(观察者)的位置和视角,默认的是照相机正立放在原点,// 相机顶部朝Y轴,可以发现下面的代码就是默认设置,所以删掉也不影响。//详细介绍:http://blog.csdn.net/kkae8643150/article/details/52805738//http://www.cnblogs.com/kesalin/archive/2012/12/06/3D_math.html// Position the eye in front of the origin.final float eyeX =  0.0f;final float eyeY =  0.0f;final float eyeZ =  0.0f;// We are looking toward the distancefinal float lookX =  0.0f;final float lookY =  0.0f;final float lookZ =  1.0f;// Set our up vector. This is where our head would be pointing were we holding the camera.final float upX = 0.0f;final float upY = 1.0f;final float upZ = 0.0f;Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);// 加载着色器和项目(program)final String vertexShader = RawResourceReader.readTextFileFromRawResource(mGlSurfaceView.getContext(), R.raw.tiles_vert);final String fragmentShader = RawResourceReader.readTextFileFromRawResource(mGlSurfaceView.getContext(), R.raw.tiles_frag);final int vertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader);final int fragmentShaderHandle = ShaderHelper.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader);programHandle = ShaderHelper.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle,new String[]{"a_Position", "a_Normal", "a_TexCoordinate"});// Initialize the accumulated rotation matrixMatrix.setIdentityM(mAccumulatedRotation, 0);}private void genTilesData() {//生成碎片的顶点坐标等值, 生成算法不是我们的重点,先略过Executors.newSingleThreadExecutor().submit(new GenerateVerticesData(this));}@Overridepublic void onSurfaceChanged(GL10 unused, int width, int height) {sizeX = mNumberOfTilesX;sizeY = height * sizeX /  width;// Set the OpenGL viewport to the same size as the surface.GLES20.glViewport(0, 0, width, height);// Create a new perspective projection matrix. The height will stay the same// while the width will vary as per aspect ratio.final float ratio = (float) width / height;final float left = -ratio;final float right = ratio;final float bottom = -1.0f;final float top = 1.0f;final float near = 1.0f;final float far = 10.0f;this.ratio = ratio;// 建议改成perspectiveM的方式,frustumM方法在某些情况下有bugMatrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);genTilesData();}@Overridepublic void onDrawFrame(GL10 gl10) {logFrame();drawGl();if (!requestedReveal && mAndroidDataHandle > 0) {requestedReveal = true;mListener.reveal();}}private void drawGl() {//常规操作,绑定变量地址,有些代码冗余GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);if (mAndroidDataHandle > 0) {GLES20.glUseProgram(programHandle);// Set program handlesmvpMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");mvMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVMatrix");textureUniformHandle = GLES20.glGetUniformLocation(programHandle, "u_Texture");deltaPosHandle = GLES20.glGetUniformLocation(programHandle, "u_DeltaPos");positionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");normalHandle = GLES20.glGetAttribLocation(programHandle, "a_Normal");textureCoordinateHandle = GLES20.glGetAttribLocation(programHandle, "a_TexCoordinate");tileXyHandle = GLES20.glGetAttribLocation(programHandle, "a_TileXY");Matrix.setIdentityM(mModelMatrix, 0);Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, PLANE_HEIGHT);// Set a matrix that contains the current rotation.Matrix.setIdentityM(mCurrentRotation, 0);Matrix.multiplyMM(mTemporaryMatrix, 0, mCurrentRotation, 0, mAccumulatedRotation, 0);System.arraycopy(mTemporaryMatrix, 0, mAccumulatedRotation, 0, 16);Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);// Pass in the modelview matrix.GLES20.glUniformMatrix4fv(mvMatrixHandle, 1, false, mMVPMatrix, 0);Matrix.multiplyMM(mTemporaryMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);System.arraycopy(mTemporaryMatrix, 0, mMVPMatrix, 0, 16);// Pass in the combined matrix.GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, mMVPMatrix, 0);// Pass in u_GravityGLES20.glUniform1f(deltaPosHandle, deltaPosX);// Pass in the texture informationGLES20.glActiveTexture(GLES20.GL_TEXTURE0);// Bind the texture to this unit.GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mAndroidDataHandle);GLES20.glUniform1i(textureUniformHandle, 0);if (mPlane != null) {mPlane.render();}GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);}}public int getTilesCount() {return sizeX * sizeY;}public void logFrame() {frames++;timePassed = (System.nanoTime() - startTime) / 1_000_000;if(timePassed >= 1000) {Timber.d("%d tiles @ %d fps", getTilesCount(), frames);frames = 0;startTime = System.nanoTime();}}public void startAnimation() {
//动画改变的是tiles_vert.glsl里的u_DeltaPos值
//动画结束时,u_DeltaPos = deltaPosX = -10。
//而tiles_vert.glsl里gl_Position.w = 5, 所以x y的可见取值范围是-5~5
//这也是GenerateVerticesData产生的坐标的取值范围。
//所以动画结束时,gl_Position的Y值最大为5-10 = -5,
//也就是最上方的点,被映射到屏幕底部了。
//有些人会问,为什么gl_Position.w = 5,这个是由
//Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, 5f);确定的。
// 因为这个5f会在后面的矩阵运算中参与gl_Position.w的生成。
//所以这个值要和PLANE_HEIGHT一致。
//作者为什么不直接用PLANE_HEIGHT代替哇,哭.jpg.
// ps:发现PLANE_HEIGHT设置为1.0 10.0都没问题,设置成20.0就有问题。
//然鹅没必要探究,设置成1,与归一化坐标范围一致,最简单。new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {animator = ValueAnimator.ofFloat(0, -PLANE_HEIGHT * 2); // plane heightanimator.setDuration(mAnimationDuration);animator.setInterpolator(new AccelerateDecelerateInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {float value = (float) animation.getAnimatedValue();deltaPosX = value;mGlSurfaceView.requestRender();}});animator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {mGlSurfaceView.requestRender();}@Overridepublic void onAnimationEnd(Animator animation) {mListener.onAnimationFinished();}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});animator.start();}});}public void updateTexture(final Bitmap bitmap) {mGlSurfaceView.queueEvent(new Runnable() {@Overridepublic void run() {requestedReveal = false;mAndroidDataHandle = TextureHelper.loadTexture(bitmap);mGlSurfaceView.requestRender();}});}public void cancelAnimation() {if (animator != null && animator.isRunning()) {animator.removeAllListeners();animator.cancel();}}
}

StarWars.Android 界面粉碎效果中的openGL操作解析相关推荐

  1. 【Android源代码下载】收集整理android界面UI效果源码

    在Android开发中,Android界面UI效果设计一直都是很多童鞋关注的问题,今天给大家分享下大神收集整理的多个android界面UI效果,都是源码,都是干货,贡献给各位网友! 话不多说,直接上效 ...

  2. 《Android 3D游戏开发技术宝典——OpenGL ES 2.0》——2.1节游戏中的音效

    本节书摘来自异步社区<Android 3D游戏开发技术宝典--OpenGL ES 2.0>一书中的第2章,第2.1节游戏中的音效,作者 吴亚峰,更多章节内容可以访问云栖社区"异步 ...

  3. Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)...

    在android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现..   例子相关博文:Androi ...

  4. Android 应用开发----7. ViewPager+Fragment一步步打造顶部导航界面滑动效果

    ViewPager+Fragment一步步打造顶部导航界面滑动效果 在许多应用中,我们常常用到这么一个效果: 可以看到,由于现在的应用数据经常需要涉及到多个模块,所以常常需要使用滑动标签在多个页面之间 ...

  5. Android移动应用开发之Viewpage2+fragment实现微信滑动界面的效果

    文章目录 布局 viewpager2 fragment adapter 实现的效果如下: 滑动界面能够实现界面的跳转. 点击下面按钮同样实现界面的跳转. 布局 最下面的导航栏,单独写了个布局文件: & ...

  6. Android 仿 窗帘效果 和 登录界面拖动效果 (Scroller类的应用) 附 2个DEMO及源码

    在Android学习中,动作交互是软件中重要的一部分,其中的Scroller就是提供了拖动效果的类,在网上,比如说一些Launcher实现滑屏都可以通过这个类去实现.下面要说的就是上次Scroller ...

  7. 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法

    一.前期基础储备 笔者之前的四篇文综述了Android中使用OpenGL ES绘制基本图形和实现了简单的相机预览,初次接触OpenGL ES开发的读者可能对其中新的概念比较迷惑,尤其是其中的顶点着色器 ...

  8. 在 Android 中使用 OpenGL

    Android 通过 OpenGL 包含了对高性能 2D 和 3D 图形的支持,特别是 OpenGL ES API.OpenGL 是一个跨平台的图形 API,它为 3D 图形处理硬件规定了一个标准的软 ...

  9. android开发界面 淡出,Android 界面淡出 淡入效果

    Android 界面淡出 淡入效果: 下面是一个工具类: AnimFadeUtil.java /**  * 处理界面的淡入和淡出的切换  * @author Bruce  *  */ public c ...

最新文章

  1. TensorRT PoolingLayer
  2. Log4J配置方式Java工程测试
  3. js function如何传入参数未字符串_JavaScript 学习之路- JS 小测验
  4. jetty设置双向ssl_在Jetty中设置SSL
  5. java中集合判空_Java中的类型安全的空集合
  6. Visual Studio .Net团队开发[转]
  7. python网络爬虫_Python爬虫实战之网络小说
  8. SPI单片机发送ARM接收
  9. putty远程linux系统时间修改,用putty怎么修改监控服务器时间?
  10. 在吗,支付宝土味情歌撩到你了吗?网友:撩到了,好酸
  11. IBM MQ - 连接远程队列管理器报AMQ4036错误
  12. HDU 4054 Number String
  13. 手把手教你Windows环境下配置Git环境
  14. 数字图像处理与机器视觉,机器视觉算法与应用 pdf电子版
  15. YUY与RGB格式区别
  16. Gmail终于对中文用户开放注册! update:2008.5.6
  17. 渗透测试-SQL注入之宽字节注入
  18. 1字符集 iso latin_ISO Latin-1字符集
  19. a pubhub service
  20. python multiprocessing_Python的multiprocessing模块详解

热门文章

  1. 行业“无人区”生存法则:看新潮传媒市场策略“广度”
  2. 多节点文件服务器群集,Win2008实战:配置双节点文件服务器故障转移群集
  3. 2018主流台式计算机跑分,2018主流台式机配置 八代i5-8400/B360/1066六核电脑组装机配置单(2)...
  4. 6月3号绝地求生服务器维护,绝地求生6月3日维护到几点_2020年6月3日绝地求生更新维护开服时间介绍_咖绿茵手游站...
  5. .NET Core 1.0学习(3)-做了个靠谱点的docker image)
  6. Fastjson使用:将json字符串转对象为空问题
  7. 《摆渡人》----读书笔记
  8. vue 判断页面加载完成_Vue实战040:nprogress页面加载进度条
  9. 必要回报率(Required Rate Of Return)
  10. 查找子串-字符串查找的简单函数