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



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

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

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

本程序代码结构:


先看MainActivity.java

package com.cxy.particles;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends Activity {
    GLSurfaceView glSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        glSurfaceView = new GLSurfaceView(this);
        // 设置OpenGLES版本为2.0
        glSurfaceView.setEGLContextClientVersion(2);
        // 设置渲染器 渲染模式
        MyRenderer mRenderer = new MyRenderer(this,glSurfaceView);
        glSurfaceView.setRenderer(mRenderer);
        glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        // 显示渲染内容
        setContentView(glSurfaceView);
    }
}  


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


package com.cxy.particles;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Color;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
public class MyRenderer implements GLSurfaceView.Renderer {
    
    GLSurfaceView glView;    
    private Context context;
    
    //视图矩阵
    private final float[] projectionMatrix=new float[16];
    private final float[] viewMatrix=new float[16];
    private final float[] viewProjectionMatrix=new float[16];
    
    private ParticleShaderProgram particleProgram;
    private ParticleSystem particleSystem;
    private ParticleShooter redParticleShooter;
    private ParticleShooter greenParticleShooter;
    private ParticleShooter blueParticleShooter;
    private long globalStartTime;
    //private float globalStartTime;
    
    public MyRenderer(Context context,GLSurfaceView view){
        //保存上下文,个GLSurfaceView;  
        this.context=context;
        this.glView = view;
    }
      
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //设置屏幕背景色      
        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
        //获取GLSL脚本语言编译好后的programID
        particleProgram=new ParticleShaderProgram(context,glView);
        //获取粒子系统的实例,初始化粒子系统包含10000个粒子
        particleSystem=new ParticleSystem(10000);
        //获取系统时间
        globalStartTime=System.nanoTime();
        //globalStartTime = System.nanoTime()/1000000000f;
        
        //设置粒子发射的方向,Y轴向上
        final Vector3 particleDirection=new Vector3(0f,0.5f,0f);
        //新建粒子发射器(红色粒子),获取实例
        redParticleShooter=new ParticleShooter(
                new Point3(-1f,0f,0f),
                particleDirection,
                Color.rgb(255,50,5));
        //新建粒子发射器(绿色粒子),获取实例
        greenParticleShooter=new ParticleShooter(
                new Point3(0f,0f,0f),
                particleDirection,
                Color.rgb(25,255,25));
        //新建粒子发射器(蓝色粒子),获取实例
        blueParticleShooter=new ParticleShooter(
                new Point3(1f,0f,0f),
                particleDirection,
                Color.rgb(5,50,255));
    }
    
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //设置当前的视点适应新的尺寸
        GLES20.glViewport(0,0,width,height);
        Matrix.perspectiveM(
                projectionMatrix, 
                0, 
                45,
                (float)width/(float)height,
                1f,
                10f);
        Matrix.setIdentityM(viewMatrix,0);
        Matrix.translateM(viewMatrix,0,0f,-1.5f,-5f);
        //设置最终的视点
        Matrix.multiplyMM(
                viewProjectionMatrix,
                0,
                projectionMatrix,
                0,
                viewMatrix,
                0);
    }
    
    @Override
    public void onDrawFrame(GL10 gl) {
        //清除位缓存
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        float currentTime=(System.nanoTime()-globalStartTime)/1000000000f;
        //float currentTime=(System.nanoTime()/1000000000f);
        //Log.d("cxy", "CurrentTime = "+currentTime);
        
        /*为粒子发射器添加粒子*/
        redParticleShooter.addParticles(particleSystem,currentTime,5);
        greenParticleShooter.addParticles(particleSystem,currentTime,5);
        blueParticleShooter.addParticles(particleSystem,currentTime,5);
        
        /*redParticleShooter.addParticles(particleSystem,globalStartTime,5);
        greenParticleShooter.addParticles(particleSystem,globalStartTime,5);
        blueParticleShooter.addParticles(particleSystem,globalStartTime,5);*/
    
        //使用着色器绘制粒子,
        particleProgram.useProgram();
        //这是
        particleProgram.setUniforms(viewProjectionMatrix,currentTime);
        
        /**必须在USE progrem之后,才能进行数据绑定,即操作glVertexAttribPointer之类的函数
         * bindData()将所有的数据:粒子顶点数据、颜色数据、方向数据、时间捆绑在一个VBO中,然后
         * 通过这个VBO绘制的,
         **/
        particleSystem.bindData(particleProgram);
        
        //绘制粒子
        particleSystem.draw(particleProgram);
    }
}

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

ParticleShaderProgram.java


package com.cxy.particles;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
public class ParticleShaderProgram {
    protected static final String U_TIME = "u_Time";
    protected static final String U_MATRIX = "u_Matrix";
    protected static final String A_POSITION = "a_Position";
    protected static final String A_COLOR = "a_Color";
    protected static final String A_DIRECTION_VECTOR = "a_DirectionVector";
    protected static final String A_PARTICLE_START_TIME = "a_ParticleStartTime";
    // Uniformlocations
    private int uMatrixLocation;
    private int uTimeLocation;
    // Attributelocations
    private int aPositionLocation;
    private int aColorLocation;
    private int aDirectionVectorLocation;
    private int aParticleStartTimeLocation;
    // Shader着色器的代码
    private String mVertexShader;
    private String mFragmentShader;
    // program ID;
    int mProgram;
    
    /**
     * 构造方法
     * 
     * @param mv GLSurfaceView子类对象, 显示3D画面的载体
     */
    public ParticleShaderProgram(Context context,GLSurfaceView mv) {
        initShader(mv);
    }
    /**
     * 初始化着色器
     * 
     * 流程 : ① 从资源中获取顶点 和 片元着色器脚本 ② 根据获取的顶点 片元着色器脚本创建着色程序 ③ 从着色程序中获取顶点位置引用 ,
     * 顶点颜色引用, 总变换矩阵引用
     * 
     * @param mv
     *            MyTDView对象, 是GLSurfaceView对象
     */
    public void initShader(GLSurfaceView mv) {
        
        /*mVertextShader是顶点着色器脚本代码 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中
         * 传入的两个参数是 脚本名称 和 应用的资源 应用资源Resources就是res目录下的那写文件
         */
        mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh",
                mv.getResources());
        mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh",
                mv.getResources());
        /*
         * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错
         */
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        /*
         * 从着色程序中获取一致变量
         */
        uMatrixLocation = GLES20.glGetUniformLocation(mProgram, U_MATRIX);
        uTimeLocation = GLES20.glGetUniformLocation(mProgram, U_TIME);
        /*
         * 从着色程序中获取 属性变量 ,数据引用 其中的"aPosition"是顶点着色器中的顶点位置信息
         * 其中的"aColor"是顶点着色器的颜色信息等,返回一个ID,openGL程序就通过这个ID与GLSL
         * 进行数据交互
         */
        aPositionLocation = GLES20.glGetAttribLocation(mProgram, A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(mProgram, A_COLOR);
        aDirectionVectorLocation = GLES20.glGetAttribLocation(mProgram,
                A_DIRECTION_VECTOR);
        aParticleStartTimeLocation = GLES20.glGetAttribLocation(mProgram,
                A_PARTICLE_START_TIME);
    }
    //使用GLSL程序
    public void useProgram(){
        GLES20.glUseProgram(mProgram);
    }
    
    public void setUniforms(float[] matrix, float elapsedTime) {
        //将ViewPort矩阵变量传递给顶点Shader中的u_Matrix
        GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);
        //将GlobalTime传递给顶点Shader的u_Time
        GLES20.glUniform1f(uTimeLocation, elapsedTime);
    }
    //获取与GLSL交互的点的位置变量的ID
    public int getPositionAttributeLocation() {
        return aPositionLocation;
    }
    //获取与GLSL交互的点的颜色变量的ID
    public int getColorAttributeLocation() {
        return aColorLocation;
    }
    //获取与GLSL交互的点的方向变量的ID
    public int getDirectionVectorAttributeLocation() {
        return aDirectionVectorLocation;
    }
    //获取与GLSL交互的点的时间变量的ID
    public int getParticleStartTimeAttributeLocation() {
        return aParticleStartTimeLocation;
    }
}  

ParticleShooter.java


package com.cxy.particles;
public class ParticleShooter {
    
    private final Point3 position;
    private final Vector3 direction;
    private final int color;
    /**
     * 创建粒子流
     * @1:位置桌标
     * @2:粒子流的放向量坐标
     * @3:颜色
     **/    
    public ParticleShooter(Point3 position,Vector3 direction,int color){
        this.position=position;
        this.direction=direction;
        this.color=color;
    }
    
    public void addParticles(ParticleSystem particleSystem,float currentTime,
            int count){
        for(int i=0;i

ParticleSystem.java


package com.cxy.particles;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Vector;

import android.annotation.SuppressLint;
import android.graphics.Color;
import android.graphics.Point;
import android.opengl.GLES20;
import android.util.Log;

public class ParticleSystem {

	private static final int BYTES_PER_FLOAT = 4;
	private static final int POSITION_COMPONENT_COUNT = 3;
	private static final int COLOR_COMPONENT_COUNT = 3;
	private static final int VECTOR_COMPONENT_COUNT = 3;
	private static final int PARTICLE_START_TIME_COMPONENT_COUNT = 1;
	private static final int TOTAL_COMPONENT_COUNT = POSITION_COMPONENT_COUNT
			+ COLOR_COMPONENT_COUNT + VECTOR_COMPONENT_COUNT
			+ PARTICLE_START_TIME_COMPONENT_COUNT;
	private static final int STRIDE = TOTAL_COMPONENT_COUNT * BYTES_PER_FLOAT;

	private final float[] particles;
	// private final VertexArray vertexArray;
	private final int maxParticleCount;
	private int currentParticleCount;
	private int nextParticle = 0;

	// Used for VBO
	private int[] vaoID;
	private int[] vboID = new int[1];

	private FloatBuffer vertexArrayBuffer;

	private int dataOffset = 0;

	public ParticleSystem(int maxParticleCount) {
		particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];
		this.maxParticleCount = maxParticleCount;
		
		/*包装particles成为FloatBuffer*/
		// 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
		ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
		// 数组排列用nativeOrder
		qbb.order(ByteOrder.nativeOrder());
		vertexArrayBuffer = qbb.asFloatBuffer();
		
	
	}

	public void addParticle(Point3 position, int color, Vector3 direction,
			float particleStartTime) {

		int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT;
		int currentOffset = particleOffset;
		nextParticle++;
		
		//计算用于glDrawArrays的最后一个参数
		if (currentParticleCount < maxParticleCount) {
			currentParticleCount++;
		}
		if (nextParticle == maxParticleCount) {
			// Start over at the beginning,but keep current ParticleCount so
			// that all the other particles still get drawn.
			nextParticle = 0;
		}

		particles[currentOffset++] = position.Px;
		particles[currentOffset++] = position.Py;
		particles[currentOffset++] = position.Pz;
		particles[currentOffset++] = Color.red(color) / 255f;
		particles[currentOffset++] = Color.green(color) / 255f;
		particles[currentOffset++] = Color.blue(color) / 255f;
		particles[currentOffset++] = direction.Vx;
		particles[currentOffset++] = direction.Vy;
		particles[currentOffset++] = direction.Vz;
		particles[currentOffset++] = particleStartTime;
		

		//Log.d("cxy", "currentOffset ("+currentOffset +")"+"maxParticleCount("+ maxParticleCount+")" );
		//更新vertexArrayBuffer的数据
		vertexArrayBuffer.position(particleOffset);
		//Log.d("cxy", "ramining ("+vertexArrayBuffer.remaining() +")");
		
		vertexArrayBuffer.put(particles, particleOffset, TOTAL_COMPONENT_COUNT);
		vertexArrayBuffer.position(0);
		
	}

	public void bindData(ParticleShaderProgram particleProgram) {
		int dataOffset = 0;

		// 将particles包含的package数组包装成FloatBuffer
		/*vertexArrayBuffer = floatBufferUtil(particles);
		vertexArrayBuffer.put(particles);
		vertexArrayBuffer.position(0);*/

		// 创建VBO
		GLES20.glGenBuffers(1, vboID, 0);
		// 绑定VBO
		GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID[0]);
		// 绑定数据,最后一个参数要改为GL_DYNAMIC_DRAW,表示数据是在变化的
		//Log.d("cxy", "vertexArrayBuffer.capacity ( "+vertexArrayBuffer.capacity()+")");
		GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
				vertexArrayBuffer.capacity() * BYTES_PER_FLOAT,
				vertexArrayBuffer, GLES20.GL_DYNAMIC_DRAW);
		
                    //启用顶点位置数据的 属性数组
		GLES20.glEnableVertexAttribArray(particleProgram
				.getPositionAttributeLocation());
		GLES20.glVertexAttribPointer(
				particleProgram.getPositionAttributeLocation(),
				POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
				dataOffset);
		dataOffset += POSITION_COMPONENT_COUNT * BYTES_PER_FLOAT;

		GLES20.glEnableVertexAttribArray(particleProgram
				.getColorAttributeLocation());
		GLES20.glVertexAttribPointer(
				particleProgram.getColorAttributeLocation(),
				COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
				dataOffset);
		dataOffset += COLOR_COMPONENT_COUNT * BYTES_PER_FLOAT;

		GLES20.glEnableVertexAttribArray(particleProgram
				.getDirectionVectorAttributeLocation());
		GLES20.glVertexAttribPointer(
				particleProgram.getDirectionVectorAttributeLocation(),
				VECTOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
				dataOffset);
		dataOffset += VECTOR_COMPONENT_COUNT * BYTES_PER_FLOAT;

		GLES20.glEnableVertexAttribArray(particleProgram
				.getParticleStartTimeAttributeLocation());
		GLES20.glVertexAttribPointer(
				particleProgram.getParticleStartTimeAttributeLocation(),
				PARTICLE_START_TIME_COMPONENT_COUNT, GLES20.GL_FLOAT, false,
				STRIDE, dataOffset);

		// 这里很重要,处理完后,需要解除数据绑定
		GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);


	}

	public void draw(ParticleShaderProgram particleProgram) {
		//开始绘制这些粒子(点)
		GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);

		/**
		 * 这里非常非常的重要,不加上这句话,会照成内存泄露,程序运行几秒就蹦了
		 * 猜测可能是GLES20.glBufferData()函数里有申请内存的语句,开始以为解除
		 * 绑定就可以了即(GLES20.glBindBuffer),但是发现结果还是一样,后来上
		 * 官网(http://www.khronos.org/opengles/sdk/docs/man/)查看发现如下:
		 * "glBufferData creates a new data store for the buffer object currently bound to target"
		 * 应该要使用GLES20.glDeleteBuffers才能释放内存
		 */	
		GLES20.glDeleteBuffers(1, vboID, 0);
		
		GLES20.glDisableVertexAttribArray(particleProgram
				.getPositionAttributeLocation());
		GLES20.glDisableVertexAttribArray(particleProgram
				.getColorAttributeLocation());
		GLES20.glDisableVertexAttribArray(particleProgram
				.getDirectionVectorAttributeLocation());
		GLES20.glDisableVertexAttribArray(particleProgram
				.getParticleStartTimeAttributeLocation());

	}

	// 定义一个工具方法,将float[]buffer数据转换为OpenGL ES所需要的FloatBuffer
	public FloatBuffer floatBufferUtil(float[] arr) {
		FloatBuffer mbuffer;
		// 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
		ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
		// 数组排列用nativeOrder
		qbb.order(ByteOrder.nativeOrder());

		mbuffer = qbb.asFloatBuffer();
		mbuffer.put(arr);
		mbuffer.position(0);

		return mbuffer;
	}

}

VBO将数据打包:

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

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

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

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

为FloatBuffer分配空间:
ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
		// 数组排列用nativeOrder
		qbb.order(ByteOrder.nativeOrder());
		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()函数,但是这个函数有两个原形:
public static native void glVertexAttribPointer(
        int indx, 
        int size,
        int type,
        boolean normalized,
        int stride,
        int offset
    );  

 public static void glVertexAttribPointer(
        int indx,
        int size,
        int type,
        boolean normalized,
        int stride,
        java.nio.Buffer ptr
    ) {
        glVertexAttribPointerBounds(
            indx,
            size,
            type,
            normalized,
            stride,
            ptr,
            ptr.remaining()
        );
    }                
这两个函数就只有最后一个参数不同,
@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()函数,具体参见代码原文。

相关的API的查询: http://www.khronos.org/opengles/sdk/docs/man/

ShaderUtil shader使用小工具类


package com.cxy.particles;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;

/*
 * 这个工具类用来加载定点着色器与片元着色器
 */
public class ShaderUtil {
	
	/**
	 * 加载着色器方法
	 * 
	 * 流程 : 
	 * 
	 * ① 创建着色器
	 * ② 加载着色器脚本
	 * ③ 编译着色器
	 * ④ 获取着色器编译结果
	 * 
	 * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
	 * @param source 着色脚本字符串
	 * @return 返回的是着色器的引用, 返回值可以代表加载的着色器
	 */
	public static int loadShader(int shaderType , String source){
		//1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
		int shader = GLES20.glCreateShader(shaderType);
		if(shader != 0){
			//2.如果着色器创建成功, 为创建的着色器加载脚本代码
			GLES20.glShaderSource(shader, source);
			//3.编译已经加载脚本代码的着色器
			GLES20.glCompileShader(shader);
			checkGLError("glCompileShader");
			int[] compiled = new int[1];
			//4.获取着色器的编译情况, 如果结果为0, 说明编译失败
			GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
			if(compiled[0] == 0){
				 Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
	             Log.e("ES20_ERROR", "ERROR: "+GLES20.glGetShaderInfoLog(shader));
	             //编译失败的话, 删除着色器, 并显示log
	             GLES20.glDeleteShader(shader);
	             shader = 0;
			}
		}
		else{
			Log.e("ES20_ERROR", "Could not Create shader " + shaderType + ":"+
					"Error:"+ shader);
		}
		return shader;
	}
	
	/**
	 * 检查每一步的操作是否正确
	 * 
	 * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
	 * 
	 * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器, 
	 * 		使glAttachShader()方法, 那么这个参数就是"glAttachShader"
	 */
	public static void checkGLError(String op){
		int error;
		//错误代码不为0, 就打印错误日志, 并抛出异常
		while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){
			 Log.e("ES20_ERROR", op + ": glError " + error);
	         throw new RuntimeException(op + ": glError " + error);
		}
	}
	
	/**
	 * 创建着色程序
	 * 
	 * ① 加载顶点着色器
	 * ② 加载片元着色器
	 * ③ 创建着色程序
	 * ④ 向着色程序中加入顶点着色器
	 * ⑤ 向着色程序中加入片元着色器
	 * ⑥ 链接程序
	 * ⑦ 获取链接程序结果
	 * 
	 * @param vertexSource		定点着色器脚本字符串
	 * @param fragmentSource	片元着色器脚本字符串
	 * @return
	 */
	public static int createProgram(String vertexSource , String fragmentSource){
		//1. 加载顶点着色器, 返回0说明加载失败
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		if(vertexShader == 0)
		{
			Log.e("ES20_ERROR", "加载顶点着色器失败");			
			return 0;
		}
		//2. 加载片元着色器, 返回0说明加载失败
		int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		if(fragShader == 0)
		{
			Log.e("ES20_ERROR", "加载片元着色器失败");
			return 0;
		}
		//3. 创建着色程序, 返回0说明创建失败
		int program = GLES20.glCreateProgram();
		if(program != 0){
			//4. 向着色程序中加入顶点着色器
			GLES20.glAttachShader(program, vertexShader);
			//检查glAttachShader操作有没有失败
			checkGLError("glAttachShader");
			//5. 向着色程序中加入片元着色器
			GLES20.glAttachShader(program, fragShader);
			//检查glAttachShader操作有没有失败
			checkGLError("glAttachShader");
			
			//6. 链接程序
			GLES20.glLinkProgram(program);
			int[] linkStatus = new int[1];
			//获取链接程序结果
			GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
			if(linkStatus[0] != GLES20.GL_TRUE){
				Log.e("ES20.ERROR", "链接程序失败 : ");
				Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));
				//如果链接程序失败删除程序
				GLES20.glDeleteProgram(program);
				program = 0;
			}			
		}
		else{
			Log.e("ES20_ERROR", "glCreateProgram Failed: "+program);
		}
	
		return program;
	}
	
	/**
	 * 从assets中加载着色脚本
	 * 
	 * ① 打开assets目录中的文件输入流
	 * ② 创建带缓冲区的输出流
	 * ③ 逐个字节读取文件数据, 放入缓冲区
	 * ④ 将缓冲区中的数据转为字符串
	 * 
	 * @param fileName assets目录中的着色脚本文件名
	 * @param resources	应用的资源
	 * @return
	 */
	public static String loadFromAssetsFile(String fileName, Resources resources){
		String result = null;
		try {
			//1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流
			InputStream is = resources.getAssets().open(fileName);
			int ch = 0;
			//2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			//3. 逐个字节读取数据, 并将读取的数据放入缓冲器中
			while((ch = is.read()) != -1){
				baos.write(ch);
			}
			//4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
			byte[] buffer = baos.toByteArray();
			baos.close();
			is.close();
			result = new String(buffer, "UTF-8");
			result = result.replaceAll("\\r\\n", "\n");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
}

Vector3向量


package com.cxy.particles;
    
public class Vector3{
    protected float Vx;
    protected float Vy;
    protected float Vz;
        
    public Vector3(float x,float y,float z){
            Vx = x;
            Vy = y;
            Vz = z;
    };
            
}

Point3向量

package com.cxy.particles;
public class Point3{
        protected float Px;
        protected float Py;
        protected float Pz;
        
        public Point3(float x,float y,float z){
            Px = x;
            Py = y;
            Pz = z;
        };
        
}
  

下面附上Shader的代码:

顶点Shader:

uniform mat4 u_Matrix;    //定义粒子接收openGL代码传过来的参数
uniform float u_Time;
attribute vec3 a_Position;
attribute vec3 a_Color;
attribute vec3 a_DirectionVector;
attribute float a_ParticleStartTime;
varying vec3 v_Color;
varying float v_ElapsedTime;  
void main()
{
    v_Color=a_Color; //传递粒子颜色给FragShader
    v_ElapsedTime=u_Time-a_ParticleStartTime;//计算当前过了多少时间    
    vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapsedTime);
    gl_Position=u_Matrix*vec4(currentPosition,1.0);
    gl_PointSize=10.0; //set particles size is 10 pixels
}  

片段shader:

precision mediump float;
varying vec3 v_Color;
varying float v_ElapsedTime;
void main()
{
    gl_FragColor=vec4(v_Color/v_ElapsedTime,1.0);
}  

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

v_ElapsedTime=u_Time-a_ParticleStartTime;
而u_Time和a_ParticleStartTime这两个值从OpenGL 程序传进来的都是同一个值;
代码如下:       
 	/*为粒子发射器添加粒子,currentTime传递到shader中的a_ParticleStartTime变量*/
        redParticleShooter.addParticles(particleSystem,currentTime,5);
        greenParticleShooter.addParticles(particleSystem,currentTime,5);
        blueParticleShooter.addParticles(particleSystem,currentTime,5);
    
        //使用着色器绘制粒子,
        particleProgram.useProgram();
        //这是传递到shader中的u_Time的变量
        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粒子系统

http://download.csdn.net/detail/cxy200927099/7759485



你可能感兴趣的:(openGL,ES)