OpenGL ES2.0粒子系统(附有源码)

http://blog.csdn.net/cxy200927099/article/details/38584487



刚学OpenGL 2个多星期,也算是入门了吧
在看了老外写的书 OpenGL ES 2 for Android A Quick - Start Guide.pdf
后,由于这本书上给的代码不够全, 自己功力太浅,对于一些了解有点疑惑,
费了差不多两天才调出想要的结果,下面就分享一下我的经历,若有问题,希望大家多多交流

本代码中包含的内容还是比较多的,使用OpenGL ES2.0编写,包含有顶点 shader和片段shader,
还包含了VBO(vertex  buffer object),这里是我当时最疑惑的地方,因为对VBO的用法不熟悉,上网搜索了导致出不来结果, 后来还是在一个外国网站上找到答案的。

粒子系统是通过将时间作为一个变量绑定到绘制的点的位置信息中,由于时间是在流动的,因此粒子也会跟着在移动;从而就形成了粒子流,这里比较难理解
不废话了,直接上代码吧;

本程序代码结构:


先看MainActivity.java

[java]  view plain  copy
  1. package com.cxy.particles;  
  2. import android.app.Activity;  
  3. import android.opengl.GLSurfaceView;  
  4. import android.os.Bundle;  
  5. public class MainActivity extends Activity {  
  6.     GLSurfaceView glSurfaceView;  
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         glSurfaceView = new GLSurfaceView(this);  
  11.         // 设置OpenGLES版本为2.0  
  12.         glSurfaceView.setEGLContextClientVersion(2);  
  13.         // 设置渲染器 渲染模式  
  14.         MyRenderer mRenderer = new MyRenderer(this,glSurfaceView);  
  15.         glSurfaceView.setRenderer(mRenderer);  
  16.         glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);  
  17.         // 显示渲染内容  
  18.         setContentView(glSurfaceView);  
  19.     }  
  20. }    


最重要的渲染的文件MyRenderer.java


[java]  view plain  copy
  1. package com.cxy.particles;  
  2. import javax.microedition.khronos.egl.EGLConfig;  
  3. import javax.microedition.khronos.opengles.GL10;  
  4. import android.content.Context;  
  5. import android.graphics.Color;  
  6. import android.opengl.GLES20;  
  7. import android.opengl.GLSurfaceView;  
  8. import android.opengl.Matrix;  
  9. public class MyRenderer implements GLSurfaceView.Renderer {  
  10.       
  11.     GLSurfaceView glView;      
  12.     private Context context;  
  13.       
  14.     //视图矩阵  
  15.     private final float[] projectionMatrix=new float[16];  
  16.     private final float[] viewMatrix=new float[16];  
  17.     private final float[] viewProjectionMatrix=new float[16];  
  18.       
  19.     private ParticleShaderProgram particleProgram;  
  20.     private ParticleSystem particleSystem;  
  21.     private ParticleShooter redParticleShooter;  
  22.     private ParticleShooter greenParticleShooter;  
  23.     private ParticleShooter blueParticleShooter;  
  24.     private long globalStartTime;  
  25.     //private float globalStartTime;  
  26.       
  27.     public MyRenderer(Context context,GLSurfaceView view){  
  28.         //保存上下文,个GLSurfaceView;    
  29.         this.context=context;  
  30.         this.glView = view;  
  31.     }  
  32.         
  33.     @Override  
  34.     public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
  35.         //设置屏幕背景色        
  36.         GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);  
  37.         //获取GLSL脚本语言编译好后的programID  
  38.         particleProgram=new ParticleShaderProgram(context,glView);  
  39.         //获取粒子系统的实例,初始化粒子系统包含10000个粒子  
  40.         particleSystem=new ParticleSystem(10000);  
  41.         //获取系统时间  
  42.         globalStartTime=System.nanoTime();  
  43.         //globalStartTime = System.nanoTime()/1000000000f;  
  44.           
  45.         //设置粒子发射的方向,Y轴向上  
  46.         final Vector3 particleDirection=new Vector3(0f,0.5f,0f);  
  47.         //新建粒子发射器(红色粒子),获取实例  
  48.         redParticleShooter=new ParticleShooter(  
  49.                 new Point3(-1f,0f,0f),  
  50.                 particleDirection,  
  51.                 Color.rgb(255,50,5));  
  52.         //新建粒子发射器(绿色粒子),获取实例  
  53.         greenParticleShooter=new ParticleShooter(  
  54.                 new Point3(0f,0f,0f),  
  55.                 particleDirection,  
  56.                 Color.rgb(25,255,25));  
  57.         //新建粒子发射器(蓝色粒子),获取实例  
  58.         blueParticleShooter=new ParticleShooter(  
  59.                 new Point3(1f,0f,0f),  
  60.                 particleDirection,  
  61.                 Color.rgb(5,50,255));  
  62.     }  
  63.       
  64.     @Override  
  65.     public void onSurfaceChanged(GL10 gl, int width, int height) {  
  66.         //设置当前的视点适应新的尺寸  
  67.         GLES20.glViewport(0,0,width,height);  
  68.         Matrix.perspectiveM(  
  69.                 projectionMatrix,   
  70.                 0,   
  71.                 45,  
  72.                 (float)width/(float)height,  
  73.                 1f,  
  74.                 10f);  
  75.         Matrix.setIdentityM(viewMatrix,0);  
  76.         Matrix.translateM(viewMatrix,0,0f,-1.5f,-5f);  
  77.         //设置最终的视点  
  78.         Matrix.multiplyMM(  
  79.                 viewProjectionMatrix,  
  80.                 0,  
  81.                 projectionMatrix,  
  82.                 0,  
  83.                 viewMatrix,  
  84.                 0);  
  85.     }  
  86.       
  87.     @Override  
  88.     public void onDrawFrame(GL10 gl) {  
  89.         //清除位缓存  
  90.         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);  
  91.         float currentTime=(System.nanoTime()-globalStartTime)/1000000000f;  
  92.         //float currentTime=(System.nanoTime()/1000000000f);  
  93.         //Log.d("cxy", "CurrentTime = "+currentTime);  
  94.           
  95.         /*为粒子发射器添加粒子*/  
  96.         redParticleShooter.addParticles(particleSystem,currentTime,5);  
  97.         greenParticleShooter.addParticles(particleSystem,currentTime,5);  
  98.         blueParticleShooter.addParticles(particleSystem,currentTime,5);  
  99.           
  100.         /*redParticleShooter.addParticles(particleSystem,globalStartTime,5); 
  101.         greenParticleShooter.addParticles(particleSystem,globalStartTime,5); 
  102.         blueParticleShooter.addParticles(particleSystem,globalStartTime,5);*/  
  103.       
  104.         //使用着色器绘制粒子,  
  105.         particleProgram.useProgram();  
  106.         //这是  
  107.         particleProgram.setUniforms(viewProjectionMatrix,currentTime);  
  108.           
  109.         /**必须在USE progrem之后,才能进行数据绑定,即操作glVertexAttribPointer之类的函数 
  110.          * bindData()将所有的数据:粒子顶点数据、颜色数据、方向数据、时间捆绑在一个VBO中,然后 
  111.          * 通过这个VBO绘制的, 
  112.          **/  
  113.         particleSystem.bindData(particleProgram);  
  114.           
  115.         //绘制粒子  
  116.         particleSystem.draw(particleProgram);  
  117.     }  
  118. }  

在上面的OnSurfaceCreate中,新建的 ParticleShaderProgram实例,下面看看这个类是怎么实现的

ParticleShaderProgram.java


[java]  view plain  copy
  1. package com.cxy.particles;  
  2. import android.content.Context;  
  3. import android.opengl.GLES20;  
  4. import android.opengl.GLSurfaceView;  
  5. public class ParticleShaderProgram {  
  6.     protected static final String U_TIME = "u_Time";  
  7.     protected static final String U_MATRIX = "u_Matrix";  
  8.     protected static final String A_POSITION = "a_Position";  
  9.     protected static final String A_COLOR = "a_Color";  
  10.     protected static final String A_DIRECTION_VECTOR = "a_DirectionVector";  
  11.     protected static final String A_PARTICLE_START_TIME = "a_ParticleStartTime";  
  12.     // Uniformlocations  
  13.     private int uMatrixLocation;  
  14.     private int uTimeLocation;  
  15.     // Attributelocations  
  16.     private int aPositionLocation;  
  17.     private int aColorLocation;  
  18.     private int aDirectionVectorLocation;  
  19.     private int aParticleStartTimeLocation;  
  20.     // Shader着色器的代码  
  21.     private String mVertexShader;  
  22.     private String mFragmentShader;  
  23.     // program ID;  
  24.     int mProgram;  
  25.       
  26.     /** 
  27.      * 构造方法 
  28.      *  
  29.      * @param mv GLSurfaceView子类对象, 显示3D画面的载体 
  30.      */  
  31.     public ParticleShaderProgram(Context context,GLSurfaceView mv) {  
  32.         initShader(mv);  
  33.     }  
  34.     /** 
  35.      * 初始化着色器 
  36.      *  
  37.      * 流程 : ① 从资源中获取顶点 和 片元着色器脚本 ② 根据获取的顶点 片元着色器脚本创建着色程序 ③ 从着色程序中获取顶点位置引用 , 
  38.      * 顶点颜色引用, 总变换矩阵引用 
  39.      *  
  40.      * @param mv 
  41.      *            MyTDView对象, 是GLSurfaceView对象 
  42.      */  
  43.     public void initShader(GLSurfaceView mv) {  
  44.           
  45.         /*mVertextShader是顶点着色器脚本代码 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中 
  46.          * 传入的两个参数是 脚本名称 和 应用的资源 应用资源Resources就是res目录下的那写文件 
  47.          */  
  48.         mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh",  
  49.                 mv.getResources());  
  50.         mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh",  
  51.                 mv.getResources());  
  52.         /* 
  53.          * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错 
  54.          */  
  55.         mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);  
  56.         /* 
  57.          * 从着色程序中获取一致变量 
  58.          */  
  59.         uMatrixLocation = GLES20.glGetUniformLocation(mProgram, U_MATRIX);  
  60.         uTimeLocation = GLES20.glGetUniformLocation(mProgram, U_TIME);  
  61.         /* 
  62.          * 从着色程序中获取 属性变量 ,数据引用 其中的"aPosition"是顶点着色器中的顶点位置信息 
  63.          * 其中的"aColor"是顶点着色器的颜色信息等,返回一个ID,openGL程序就通过这个ID与GLSL 
  64.          * 进行数据交互 
  65.          */  
  66.         aPositionLocation = GLES20.glGetAttribLocation(mProgram, A_POSITION);  
  67.         aColorLocation = GLES20.glGetAttribLocation(mProgram, A_COLOR);  
  68.         aDirectionVectorLocation = GLES20.glGetAttribLocation(mProgram,  
  69.                 A_DIRECTION_VECTOR);  
  70.         aParticleStartTimeLocation = GLES20.glGetAttribLocation(mProgram,  
  71.                 A_PARTICLE_START_TIME);  
  72.     }  
  73.     //使用GLSL程序  
  74.     public void useProgram(){  
  75.         GLES20.glUseProgram(mProgram);  
  76.     }  
  77.       
  78.     public void setUniforms(float[] matrix, float elapsedTime) {  
  79.         //将ViewPort矩阵变量传递给顶点Shader中的u_Matrix  
  80.         GLES20.glUniformMatrix4fv(uMatrixLocation, 1false, matrix, 0);  
  81.         //将GlobalTime传递给顶点Shader的u_Time  
  82.         GLES20.glUniform1f(uTimeLocation, elapsedTime);  
  83.     }  
  84.     //获取与GLSL交互的点的位置变量的ID  
  85.     public int getPositionAttributeLocation() {  
  86.         return aPositionLocation;  
  87.     }  
  88.     //获取与GLSL交互的点的颜色变量的ID  
  89.     public int getColorAttributeLocation() {  
  90.         return aColorLocation;  
  91.     }  
  92.     //获取与GLSL交互的点的方向变量的ID  
  93.     public int getDirectionVectorAttributeLocation() {  
  94.         return aDirectionVectorLocation;  
  95.     }  
  96.     //获取与GLSL交互的点的时间变量的ID  
  97.     public int getParticleStartTimeAttributeLocation() {  
  98.         return aParticleStartTimeLocation;  
  99.     }  
  100. }    

ParticleShooter.java


[java]  view plain  copy
  1. package com.cxy.particles;  
  2. public class ParticleShooter {  
  3.       
  4.     private final Point3 position;  
  5.     private final Vector3 direction;  
  6.     private final int color;  
  7.     /** 
  8.      * 创建粒子流 
  9.      * @1:位置桌标 
  10.      * @2:粒子流的放向量坐标 
  11.      * @3:颜色 
  12.      **/      
  13.     public ParticleShooter(Point3 position,Vector3 direction,int color){  
  14.         this.position=position;  
  15.         this.direction=direction;  
  16.         this.color=color;  
  17.     }  
  18.       
  19.     public void addParticles(ParticleSystem particleSystem,float currentTime,  
  20.             int count){  
  21.         for(int i=0;i<count;i++){  
  22.             particleSystem.addParticle(position,color,direction,currentTime);  
  23.         }  
  24.     }  
  25. }  

ParticleSystem.java


[java]  view plain  copy
  1. package com.cxy.particles;  
  2.   
  3. import java.nio.ByteBuffer;  
  4. import java.nio.ByteOrder;  
  5. import java.nio.FloatBuffer;  
  6. import java.util.Vector;  
  7.   
  8. import android.annotation.SuppressLint;  
  9. import android.graphics.Color;  
  10. import android.graphics.Point;  
  11. import android.opengl.GLES20;  
  12. import android.util.Log;  
  13.   
  14. public class ParticleSystem {  
  15.   
  16.     private static final int BYTES_PER_FLOAT = 4;  
  17.     private static final int POSITION_COMPONENT_COUNT = 3;  
  18.     private static final int COLOR_COMPONENT_COUNT = 3;  
  19.     private static final int VECTOR_COMPONENT_COUNT = 3;  
  20.     private static final int PARTICLE_START_TIME_COMPONENT_COUNT = 1;  
  21.     private static final int TOTAL_COMPONENT_COUNT = POSITION_COMPONENT_COUNT  
  22.             + COLOR_COMPONENT_COUNT + VECTOR_COMPONENT_COUNT  
  23.             + PARTICLE_START_TIME_COMPONENT_COUNT;  
  24.     private static final int STRIDE = TOTAL_COMPONENT_COUNT * BYTES_PER_FLOAT;  
  25.   
  26.     private final float[] particles;  
  27.     // private final VertexArray vertexArray;  
  28.     private final int maxParticleCount;  
  29.     private int currentParticleCount;  
  30.     private int nextParticle = 0;  
  31.   
  32.     // Used for VBO  
  33.     private int[] vaoID;  
  34.     private int[] vboID = new int[1];  
  35.   
  36.     private FloatBuffer vertexArrayBuffer;  
  37.   
  38.     private int dataOffset = 0;  
  39.   
  40.     public ParticleSystem(int maxParticleCount) {  
  41.         particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];  
  42.         this.maxParticleCount = maxParticleCount;  
  43.           
  44.         /*包装particles成为FloatBuffer*/  
  45.         // 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节  
  46.         ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);  
  47.         // 数组排列用nativeOrder  
  48.         qbb.order(ByteOrder.nativeOrder());  
  49.         vertexArrayBuffer = qbb.asFloatBuffer();  
  50.           
  51.       
  52.     }  
  53.   
  54.     public void addParticle(Point3 position, int color, Vector3 direction,  
  55.             float particleStartTime) {  
  56.   
  57.         int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT;  
  58.         int currentOffset = particleOffset;  
  59.         nextParticle++;  
  60.           
  61.         //计算用于glDrawArrays的最后一个参数  
  62.         if (currentParticleCount < maxParticleCount) {  
  63.             currentParticleCount++;  
  64.         }  
  65.         if (nextParticle == maxParticleCount) {  
  66.             // Start over at the beginning,but keep current ParticleCount so  
  67.             // that all the other particles still get drawn.  
  68.             nextParticle = 0;  
  69.         }  
  70.   
  71.         particles[currentOffset++] = position.Px;  
  72.         particles[currentOffset++] = position.Py;  
  73.         particles[currentOffset++] = position.Pz;  
  74.         particles[currentOffset++] = Color.red(color) / 255f;  
  75.         particles[currentOffset++] = Color.green(color) / 255f;  
  76.         particles[currentOffset++] = Color.blue(color) / 255f;  
  77.         particles[currentOffset++] = direction.Vx;  
  78.         particles[currentOffset++] = direction.Vy;  
  79.         particles[currentOffset++] = direction.Vz;  
  80.         particles[currentOffset++] = particleStartTime;  
  81.           
  82.   
  83.         //Log.d("cxy", "currentOffset ("+currentOffset +")"+"maxParticleCount("+ maxParticleCount+")" );  
  84.         //更新vertexArrayBuffer的数据  
  85.         vertexArrayBuffer.position(particleOffset);  
  86.         //Log.d("cxy", "ramining ("+vertexArrayBuffer.remaining() +")");  
  87.           
  88.         vertexArrayBuffer.put(particles, particleOffset, TOTAL_COMPONENT_COUNT);  
  89.         vertexArrayBuffer.position(0);  
  90.           
  91.     }  
  92.   
  93.     public void bindData(ParticleShaderProgram particleProgram) {  
  94.         int dataOffset = 0;  
  95.   
  96.         // 将particles包含的package数组包装成FloatBuffer  
  97.         /*vertexArrayBuffer = floatBufferUtil(particles); 
  98.         vertexArrayBuffer.put(particles); 
  99.         vertexArrayBuffer.position(0);*/  
  100.   
  101.         // 创建VBO  
  102.         GLES20.glGenBuffers(1, vboID, 0);  
  103.         // 绑定VBO  
  104.         GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID[0]);  
  105.         // 绑定数据,最后一个参数要改为GL_DYNAMIC_DRAW,表示数据是在变化的  
  106.         //Log.d("cxy", "vertexArrayBuffer.capacity ( "+vertexArrayBuffer.capacity()+")");  
  107.         GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,  
  108.                 vertexArrayBuffer.capacity() * BYTES_PER_FLOAT,  
  109.                 vertexArrayBuffer, GLES20.GL_DYNAMIC_DRAW);  
  110.           
  111.                     //启用顶点位置数据的 属性数组  
  112.         GLES20.glEnableVertexAttribArray(particleProgram  
  113.                 .getPositionAttributeLocation());  
  114.         GLES20.glVertexAttribPointer(  
  115.                 particleProgram.getPositionAttributeLocation(),  
  116.                 POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,  
  117.                 dataOffset);  
  118.         dataOffset += POSITION_COMPONENT_COUNT * BYTES_PER_FLOAT;  
  119.   
  120.         GLES20.glEnableVertexAttribArray(particleProgram  
  121.                 .getColorAttributeLocation());  
  122.         GLES20.glVertexAttribPointer(  
  123.                 particleProgram.getColorAttributeLocation(),  
  124.                 COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,  
  125.                 dataOffset);  
  126.         dataOffset += COLOR_COMPONENT_COUNT * BYTES_PER_FLOAT;  
  127.   
  128.         GLES20.glEnableVertexAttribArray(particleProgram  
  129.                 .getDirectionVectorAttributeLocation());  
  130.         GLES20.glVertexAttribPointer(  
  131.                 particleProgram.getDirectionVectorAttributeLocation(),  
  132.                 VECTOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,  
  133.                 dataOffset);  
  134.         dataOffset += VECTOR_COMPONENT_COUNT * BYTES_PER_FLOAT;  
  135.   
  136.         GLES20.glEnableVertexAttribArray(particleProgram  
  137.                 .getParticleStartTimeAttributeLocation());  
  138.         GLES20.glVertexAttribPointer(  
  139.                 particleProgram.getParticleStartTimeAttributeLocation(),  
  140.                 PARTICLE_START_TIME_COMPONENT_COUNT, GLES20.GL_FLOAT, false,  
  141.                 STRIDE, dataOffset);  
  142.   
  143.         // 这里很重要,处理完后,需要解除数据绑定  
  144.         GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);  
  145.   
  146.   
  147.     }  
  148.   
  149.     public void draw(ParticleShaderProgram particleProgram) {  
  150.         //开始绘制这些粒子(点)  
  151.         GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);  
  152.   
  153.         /** 
  154.          * 这里非常非常的重要,不加上这句话,会照成内存泄露,程序运行几秒就蹦了 
  155.          * 猜测可能是GLES20.glBufferData()函数里有申请内存的语句,开始以为解除 
  156.          * 绑定就可以了即(GLES20.glBindBuffer),但是发现结果还是一样,后来上 
  157.          * 官网(http://www.khronos.org/opengles/sdk/docs/man/)查看发现如下: 
  158.          * "glBufferData creates a new data store for the buffer object currently bound to target" 
  159.          * 应该要使用GLES20.glDeleteBuffers才能释放内存 
  160.          */   
  161.         GLES20.glDeleteBuffers(1, vboID, 0);  
  162.           
  163.         GLES20.glDisableVertexAttribArray(particleProgram  
  164.                 .getPositionAttributeLocation());  
  165.         GLES20.glDisableVertexAttribArray(particleProgram  
  166.                 .getColorAttributeLocation());  
  167.         GLES20.glDisableVertexAttribArray(particleProgram  
  168.                 .getDirectionVectorAttributeLocation());  
  169.         GLES20.glDisableVertexAttribArray(particleProgram  
  170.                 .getParticleStartTimeAttributeLocation());  
  171.   
  172.     }  
  173.   
  174.     // 定义一个工具方法,将float[]buffer数据转换为OpenGL ES所需要的FloatBuffer  
  175.     public FloatBuffer floatBufferUtil(float[] arr) {  
  176.         FloatBuffer mbuffer;  
  177.         // 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节  
  178.         ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);  
  179.         // 数组排列用nativeOrder  
  180.         qbb.order(ByteOrder.nativeOrder());  
  181.   
  182.         mbuffer = qbb.asFloatBuffer();  
  183.         mbuffer.put(arr);  
  184.         mbuffer.position(0);  
  185.   
  186.         return mbuffer;  
  187.     }  
  188.   
  189. }  

VBO将数据打包:

这里用到了一个 VBO,并且使用了将所有数据打包在一个buffer中处理,刚开始在调试的时候,由于对VBO的使用不是很清楚,犯下了很多错误,而在网上找到的也大都是Windows版本的,和Android上的用法还是有区别的,最终在一个外国网站上找到了解决思路;
这个网站,讲解的很不错,分类讲解了
VBO处理单个数据和 VBO将数据打包在一起的使用

在了解这个内容之前,如果你对shader不熟悉,建议你看一本书,专门讲解OpenGL ES2.0 GLSL使用的,在这里附上下载地址: http://download.csdn.net/detail/cxy200927099/7733243 这个还是中文版的,讲的很详细
 
这里我就只说一下VBO将数据打包在一起的用法:
前提需要声明的变量:

[java]  view plain  copy
  1. private int[] vboID = new int[1];  
  2. private FloatBuffer vertexArrayBuffer;  

private int[] vboID = new int[1];
private FloatBuffer vertexArrayBuffer;

为FloatBuffer分配空间:
[java]  view plain  copy
  1. ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);  
  2.         // 数组排列用nativeOrder  
  3.         qbb.order(ByteOrder.nativeOrder());  
  4.         vertexArrayBuffer = qbb.asFloatBuffer();  

ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
// 数组排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
vertexArrayBuffer = qbb.asFloatBuffer();
关于怎么把  顶点位置坐标、方向坐标、颜色、时间等打包在一起到 vertexArrayBuffer  请直接参考代码

VBO用法流程:

1.创建VBO:
          GLES20.glGenBuffers(1, vboID, 0);
2.绑定VBO:
          GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID[0]);
3.绑定数据,最后一个参数要改为GL_DYNAMIC_DRAW,表示数据是在变化的
这里既是将 vertexArrayBuffer绑定了 GL_ARRAY_BUFFER,而 vertexArrayBuffer的内容既是我们在OpenGL程序里面添加包括,顶点位置坐标、方向坐标、颜色、时间等打包在一起的数据包
          GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
vertexArrayBuffer.capacity() * BYTES_PER_FLOAT,
vertexArrayBuffer, GLES20.GL_DYNAMIC_DRAW);
4.解除绑定:
          GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

将打包的VBO拆分传递给 shader: 

     具体怎么将这些数据从VBO Buffer中拆分出来传递给 GLSL shader,我们继续分析:
其实就是采用了 GLES20.glVertexAttribPointer()函数,但是这个函数有两个原形:
[java]  view plain  copy
  1. public static native void glVertexAttribPointer(  
  2.         int indx,   
  3.         int size,  
  4.         int type,  
  5.         boolean normalized,  
  6.         int stride,  
  7.         int offset  
  8.     );    
  9.   
  10.  public static void glVertexAttribPointer(  
  11.         int indx,  
  12.         int size,  
  13.         int type,  
  14.         boolean normalized,  
  15.         int stride,  
  16.         java.nio.Buffer ptr  
  17.     ) {  
  18.         glVertexAttribPointerBounds(  
  19.             indx,  
  20.             size,  
  21.             type,  
  22.             normalized,  
  23.             stride,  
  24.             ptr,  
  25.             ptr.remaining()  
  26.         );  
  27.     }                  
这两个函数就只有最后一个参数不同,
@1: 既是GLES20.glGetAttribLocation()的返回值,可理解为表示和shader中的变量交互的句柄
@2: 每一个顶点数据包含的 字节:只能是1,2,3,4;
@3: 数组的类型,如 GL_BYTEGL_UNSIGNED_BYTEGL_SHORT, GL_UNSIGNED_SHORTGL_FIXED, or  GL_FLOAT,默认的是GL_FLOAT;
@4: 是否将数据归一化处理,GL_TRUE表示归一化,GL_FALSE表示不归一化
@5: stride表示紧密联系的数组的字节偏移,0,表示最后数组数据时紧密联系在一起
像我们这里
传递顶点位置属性的时候,stride = 顶点位置大小(byte) 
传递顶点方向属性的时候, stride =   方向大小 (byte)
传递顶点颜色属性的时候, stride =   颜色大小 (byte)  
传递顶点时间属性的时候, stride =  时间大小 (byte)
@6:第二个函数比较好理解,最后一个参数就是上面定义的
vertexArrayBuffer;即理解为把这个相关联的数组传递给shader,
而在:第一个函数的,最后一个是偏移量;


刚开始不是很理解,仅仅传偏移量进去,他是怎么关联Buffer的,后来才慢慢理解GLES20.glBufferData()函数
关联了Buffer数组,因此这个glVertexAttribPointer()函数在使用的时候,必须搭配着VBO的整套流程使用,
即先创建VBO,然后绑定name,绑定数据,解除绑定
而以往我们使用glVertexAttribPointer()函数的时候,不使用VBO时,我们传递的最后一个参数都是直接传的Buffer实例,当时在网上收集资料,看到的大都是C++写的

绑定完成之后,就可在调用 GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);绘制点了
关于一些参数,定义请参见上面.java文件中较全的代码:


注意:这里会有个问题,当我们创建VBO的时候,在绑定数据时 GLES20.glBufferData()这个函数申请了内存空间,而我原以为解除绑定后,应该就会释放空间了,但事实上并非如此:
经过我亲自测试,发现此时会照成内存泄露
唯有:调用 GLES20.glDeleteBuffers(1, vboID, 0);内存空间才会释放,

因此,最后一定记得调用  glDeleteBuffers()函数,具体参见代码原文。


ShaderUtil shader使用小工具类


[java]  view plain  copy
  1. package com.cxy.particles;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.InputStream;  
  5.   
  6. import android.content.res.Resources;  
  7. import android.opengl.GLES20;  
  8. import android.util.Log;  
  9.   
  10. /* 
  11.  * 这个工具类用来加载定点着色器与片元着色器 
  12.  */  
  13. public class ShaderUtil {  
  14.       
  15.     /** 
  16.      * 加载着色器方法 
  17.      *  
  18.      * 流程 :  
  19.      *  
  20.      * ① 创建着色器 
  21.      * ② 加载着色器脚本 
  22.      * ③ 编译着色器 
  23.      * ④ 获取着色器编译结果 
  24.      *  
  25.      * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER) 
  26.      * @param source 着色脚本字符串 
  27.      * @return 返回的是着色器的引用, 返回值可以代表加载的着色器 
  28.      */  
  29.     public static int loadShader(int shaderType , String source){  
  30.         //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败  
  31.         int shader = GLES20.glCreateShader(shaderType);  
  32.         if(shader != 0){  
  33.             //2.如果着色器创建成功, 为创建的着色器加载脚本代码  
  34.             GLES20.glShaderSource(shader, source);  
  35.             //3.编译已经加载脚本代码的着色器  
  36.             GLES20.glCompileShader(shader);  
  37.             checkGLError("glCompileShader");  
  38.             int[] compiled = new int[1];  
  39.             //4.获取着色器的编译情况, 如果结果为0, 说明编译失败  
  40.             GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);  
  41.             if(compiled[0] == 0){  
  42.                  Log.e("ES20_ERROR""Could not compile shader " + shaderType + ":");  
  43.                  Log.e("ES20_ERROR""ERROR: "+GLES20.glGetShaderInfoLog(shader));  
  44.                  //编译失败的话, 删除着色器, 并显示log  
  45.                  GLES20.glDeleteShader(shader);  
  46.                  shader = 0;  
  47.             }  
  48.         }  
  49.         else{  
  50.             Log.e("ES20_ERROR""Could not Create shader " + shaderType + ":"+  
  51.                     "Error:"+ shader);  
  52.         }  
  53.         return shader;  
  54.     }  
  55.       
  56.     /** 
  57.      * 检查每一步的操作是否正确 
  58.      *  
  59.      * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误 
  60.      *  
  61.      * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器,  
  62.      *      使glAttachShader()方法, 那么这个参数就是"glAttachShader" 
  63.      */  
  64.     public static void checkGLError(String op){  
  65.         int error;  
  66.         //错误代码不为0, 就打印错误日志, 并抛出异常  
  67.         while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){  
  68.              Log.e("ES20_ERROR", op + ": glError " + error);  
  69.              throw new RuntimeException(op + ": glError " + error);  
  70.         }  
  71.     }  
  72.       
  73.     /** 
  74.      * 创建着色程序 
  75.      *  
  76.      * ① 加载顶点着色器 
  77.      * ② 加载片元着色器 
  78.      * ③ 创建着色程序 
  79.      * ④ 向着色程序中加入顶点着色器 
  80.      * ⑤ 向着色程序中加入片元着色器 
  81.      * ⑥ 链接程序 
  82.      * ⑦ 获取链接程序结果 
  83.      *  
  84.      * @param vertexSource      定点着色器脚本字符串 
  85.      * @param fragmentSource    片元着色器脚本字符串 
  86.      * @return 
  87.      */  
  88.     public static int createProgram(String vertexSource , String fragmentSource){  
  89.         //1. 加载顶点着色器, 返回0说明加载失败  
  90.         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);  
  91.         if(vertexShader == 0)  
  92.         {  
  93.             Log.e("ES20_ERROR""加载顶点着色器失败");             
  94.             return 0;  
  95.         }  
  96.         //2. 加载片元着色器, 返回0说明加载失败  
  97.         int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);  
  98.         if(fragShader == 0)  
  99.         {  
  100.             Log.e("ES20_ERROR""加载片元着色器失败");  
  101.             return 0;  
  102.         }  
  103.         //3. 创建着色程序, 返回0说明创建失败  
  104.         int program = GLES20.glCreateProgram();  
  105.         if(program != 0){  
  106.             //4. 向着色程序中加入顶点着色器  
  107.             GLES20.glAttachShader(program, vertexShader);  
  108.             //检查glAttachShader操作有没有失败  
  109.             checkGLError("glAttachShader");  
  110.             //5. 向着色程序中加入片元着色器  
  111.             GLES20.glAttachShader(program, fragShader);  
  112.             //检查glAttachShader操作有没有失败  
  113.             checkGLError("glAttachShader");  
  114.               
  115.             //6. 链接程序  
  116.             GLES20.glLinkProgram(program);  
  117.             int[] linkStatus = new int[1];  
  118.             //获取链接程序结果  
  119.             GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);  
  120.             if(linkStatus[0] != GLES20.GL_TRUE){  
  121.                 Log.e("ES20.ERROR""链接程序失败 : ");  
  122.                 Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));  
  123.                 //如果链接程序失败删除程序  
  124.                 GLES20.glDeleteProgram(program);  
  125.                 program = 0;  
  126.             }             
  127.         }  
  128.         else{  
  129.             Log.e("ES20_ERROR""glCreateProgram Failed: "+program);  
  130.         }  
  131.       
  132.         return program;  
  133.     }  
  134.       
  135.     /** 
  136.      * 从assets中加载着色脚本 
  137.      *  
  138.      * ① 打开assets目录中的文件输入流 
  139.      * ② 创建带缓冲区的输出流 
  140.      * ③ 逐个字节读取文件数据, 放入缓冲区 
  141.      * ④ 将缓冲区中的数据转为字符串 
  142.      *  
  143.      * @param fileName assets目录中的着色脚本文件名 
  144.      * @param resources 应用的资源 
  145.      * @return 
  146.      */  
  147.     public static String loadFromAssetsFile(String fileName, Resources resources){  
  148.         String result = null;  
  149.         try {  
  150.             //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流  
  151.             InputStream is = resources.getAssets().open(fileName);  
  152.             int ch = 0;  
  153.             //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型  
  154.             ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  155.             //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中  
  156.             while((ch = is.read()) != -1){  
  157.                 baos.write(ch);  
  158.             }  
  159.             //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串  
  160.             byte[] buffer = baos.toByteArray();  
  161.             baos.close();  
  162.             is.close();  
  163.             result = new String(buffer, "UTF-8");  
  164.             result = result.replaceAll("\\r\\n""\n");  
  165.         } catch (Exception e) {  
  166.             e.printStackTrace();  
  167.         }  
  168.         return result;  
  169.     }  
  170.       
  171. }  

Vector3向量


[java]  view plain  copy
  1. package com.cxy.particles;  
  2.       
  3. public class Vector3{  
  4.     protected float Vx;  
  5.     protected float Vy;  
  6.     protected float Vz;  
  7.           
  8.     public Vector3(float x,float y,float z){  
  9.             Vx = x;  
  10.             Vy = y;  
  11.             Vz = z;  
  12.     };  
  13.               
  14. }  

Point3向量

[java]  view plain  copy
  1. package com.cxy.particles;  
  2. public class Point3{  
  3.         protected float Px;  
  4.         protected float Py;  
  5.         protected float Pz;  
  6.           
  7.         public Point3(float x,float y,float z){  
  8.             Px = x;  
  9.             Py = y;  
  10.             Pz = z;  
  11.         };  
  12.           
  13. }  
  14.     

下面附上Shader的代码:

顶点Shader:

[java]  view plain  copy
  1. uniform mat4 u_Matrix;    //定义粒子接收openGL代码传过来的参数  
  2. uniform float u_Time;  
  3. attribute vec3 a_Position;  
  4. attribute vec3 a_Color;  
  5. attribute vec3 a_DirectionVector;  
  6. attribute float a_ParticleStartTime;  
  7. varying vec3 v_Color;  
  8. varying float v_ElapsedTime;    
  9. void main()  
  10. {  
  11.     v_Color=a_Color; //传递粒子颜色给FragShader  
  12.     v_ElapsedTime=u_Time-a_ParticleStartTime;//计算当前过了多少时间      
  13.     vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapsedTime);  
  14.     gl_Position=u_Matrix*vec4(currentPosition,1.0);  
  15.     gl_PointSize=10.0//set particles size is 10 pixels  
  16. }    

片段shader:

[java]  view plain  copy
  1. precision mediump float;  
  2. varying vec3 v_Color;  
  3. varying float v_ElapsedTime;  
  4. void main()  
  5. {  
  6.     gl_FragColor=vec4(v_Color/v_ElapsedTime,1.0);  
  7. }    

最让我困惑的就是这里,我们看到在顶点shader中 

v_ElapsedTime=u_Time-a_ParticleStartTime;
而u_Time和a_ParticleStartTime这两个值从OpenGL 程序传进来的都是同一个值;
代码如下:       
[java]  view plain  copy
  1. <span style="white-space:pre">    </span>/*为粒子发射器添加粒子,currentTime传递到shader中的a_ParticleStartTime变量*/  
  2.        redParticleShooter.addParticles(particleSystem,currentTime,5);  
  3.        greenParticleShooter.addParticles(particleSystem,currentTime,5);  
  4.        blueParticleShooter.addParticles(particleSystem,currentTime,5);  
  5.      
  6.        //使用着色器绘制粒子,  
  7.        particleProgram.useProgram();  
  8.        //这是传递到shader中的u_Time的变量  
  9.        particleProgram.setUniforms(viewProjectionMatrix,currentTime);  

这两个值既然一样了,在顶点shader中就相当于v_ElapsedTime等于0,currentPosition变量也应该等于零,因此可以,这样改一下shader:
    vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapsedTime);
改为:==> vec3 currentPosition=a_Position;

片段 shader中:
    gl_FragColor=vec4(v_Color/v_ElapsedTime,1.0);
改为:===> gl_FragColor=vec4(v_Color,1.0);

理论上应该是这样的,但实际结果得不到想要的结果,还希望大神指点指点?????

程序最终的运行结果:



代码下载地址:

Android OpenGL ES2.0粒子系统



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值