用OpenGL構建粒子噴泉
效果展示
這是《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
/**
* 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:不需要設置,共頂點着色器和片段着色器之間共享數據。
下面是一個頂點着色器
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每個片段最終顏色是什麼,對於基本圖元的每個片段都會被調用一次。
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對應的變量中。
glDrawArrays(GL_POINTS, 0, currentParticleCount);
着色器main方法中的gl_Position和gl_PointSize是OpenGL中的變量,也就是最終給GPU的信息。
編譯着色器
glsl文件需要編譯鏈接成OpenGL的一個程序才能使用。
需要使用這幾個工具類。
構建粒子系統
向粒子系統中填充數據
/**
* 向系統中添加粒子,每次添加一個
*
* @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; //OpenGL需要[0,1)的顏色值
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);
}
通過發射器向粒子系統中添加數據
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中加入一些調用統一管理這一切。