效果展示
这是《OpenGL ES应用开发实践指南》中的一个例子,写这篇blog简单总结下在Android上进行OpenGL ES开发的方法。
工作流程概述
定义顶点着色器、片段着色器。
##在哪里画图
在Activity中设置ContentView为GLSurfaceView,在该控件上设置自定义渲染器Renderer完成OpenGL绘图。
Renderer接口定义的方法:
onSurfaceCreated(GL10 gl10, EGLConfig eglConfig)
在Surface被创建时调用。
onSurfaceChanged(GL10 gl10, int width, int height)
每次Suface尺寸变化时被调用,包括第一次刚创建时。
onDrawFrame(GL10 gl10)
当绘制一帧时会被调用,比如一秒钟会被调用执行60次。
##如何告诉GPU绘制信息
把内存从java堆复制到本地堆
图形有顶点和颜色构成,将这些信息存放在一个数组中,并且需要将java数组转移到本地数组中,可以使用这个工具类VertexArray
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| * Created by felix on 15/5/19. * 负责将内存从java堆复制到本地堆。 * 关联属性与顶点数据,告诉OpenGL去哪里找属性对应的数据。 */ public class VertexArray { private final FloatBuffer floatBuffer; public VertexArray(float[] vertexData) { this.floatBuffer = ByteBuffer.allocateDirect(vertexData.length * BYTE_PER_FLOAT) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); floatBuffer.put(vertexData); } public void setVertexAttribPointer(int dataOffset, int attributeLocation, int componentCount, int stride) { floatBuffer.position(dataOffset); glVertexAttribPointer(attributeLocation, componentCount, GL_FLOAT, false, stride, floatBuffer); glEnableVertexAttribArray(attributeLocation); floatBuffer.position(0); } * 在原有数组的基础上更新指定范围的元素,如果全部复制的话速度太慢 * * @param vertexData * @param start * @param count */ public void updateBuffer(float[] vertexData, int start, int count) { floatBuffer.position(start); floatBuffer.put(vertexData, start, count); floatBuffer.position(0); } }
|
###还需要着色器
告诉GPU如何绘制数据,数据在着色器这一管道中传递。
着色器中变量的解释
uniform:会让每个顶点都使用同一个值,不需要对每个顶点设置,除非我们再次改变它。
attribute:把顶点属性放进着色器的手段,每个顶点都要设置一次
varying:不需要设置,共顶点着色器和片段着色器之间共享数据。
下面是一个顶点着色器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| uniform mat4 u_Matrix; uniform float u_Time; attribute vec3 a_Position; attribute vec3 a_Color; attribute vec3 a_DirectionVector; attribute float a_ParticleStartTime; varying float v_ElapseTime; varying vec3 v_Color; void main() { v_Color=a_Color; v_ElapseTime=u_Time-a_ParticleStartTime; vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapseTime); gl_Position=u_Matrix*vec4(currentPosition,1.0); gl_PointSize=10.0; }
|
下面是一个片段着色器:
告诉GPU每个片段最终颜色是什么,对于基本图元的每个片段都会被调用一次。
1 2 3 4 5 6 7 8 9 10 11
| precision mediump float; varying vec3 v_Color; varying float v_ElapseTime; void main() { gl_FragColor=vec4(v_Color/v_ElapseTime,1.0); }
|
###如何让OpenGL画图
当调用下面的方法时,OpenGL就会从缓冲区读数据,每读取完一组数据就会调用一次main方法,并把数据填到attribute对应的变量中。
1
| glDrawArrays(GL_POINTS, 0, currentParticleCount);
|
着色器main方法中的gl_Position和gl_PointSize是OpenGL中的变量,也就是最终给GPU的信息。
##编译着色器
glsl文件需要编译链接成OpenGL的一个程序才能使用。
需要使用这几个工具类。
##构建粒子系统
ParticlesRenderer
ParticlesShooter
ParticlsSystem
###向粒子系统中填充数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| * 向系统中添加粒子,每次添加一个 * * @param positionPoint 新加粒子的位置 */ public void addParticles(Point positionPoint, int color, Vector direction, float particleStrtTime) { final int particleOffset = nextParticleOffset * TOTAL_COMPONENT_COUNT; int currentOffset = particleOffset; nextParticleOffset++; if (currentParticlesCount < maxParticlesCount) { currentParticlesCount++; } if (nextParticleOffset == maxParticlesCount) { nextParticleOffset = 0; } particles[currentOffset++] = positionPoint.x; particles[currentOffset++] = positionPoint.y; particles[currentOffset++] = positionPoint.z; particles[currentOffset++] = Color.red(color) / 255f; particles[currentOffset++] = Color.green(color) / 255f; particles[currentOffset++] = Color.blue(color) / 255f; particles[currentOffset++] = direction.x; particles[currentOffset++] = direction.y; particles[currentOffset++] = direction.z; particles[currentOffset++] = particleStrtTime; vertexArray.updateBuffer(particles, particleOffset, TOTAL_COMPONENT_COUNT); }
|
通过发射器向粒子系统中添加数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ParticlesShooter { private final Point position; private final Vector direction; private final int color; public ParticlesShooter(Point position, Vector direction, int color) { this.position = position; this.direction = direction; this.color = color; } public void addParticles(ParticlsSystem particlsSystem, float currentTime, int count) { for (int i = 0; i < count; i++) { particlsSystem.addParticles(position,color,direction,currentTime); } } }
|
最后在ParticlesRenderer中加入一些调用统一管理这一切。
项目地址