Android OpenGL粒子

Android OpenGL粒子

首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。

粒子就是被画作一组点的简单物体。

1、着色器文件

(1) 顶点着色器,particle_vertex_shader.glsl文件
其中u_Time代表程序运行时间,a_ParticleStartTime代表粒子运行时间。

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 vec3 v_Color;
varying float v_ElapsedTime;

void main()
{
    v_Color = a_Color;
    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;
}
(2) 片段着色器,particle_fragment_shader.glsl文件
片段颜色随着时间变得暗淡

precision mediump float;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main()
{                    	
    gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0);
}

2、ParticleProgram类

ParticleProgram类封装了着色器,继承Program类。setCurrentTime方法设置程序开始时间。
public class ParticleProgram extends Program {
    private final static String U_MATRIX = "u_Matrix";
    private final static String U_TIME = "u_Time";

    private final static String A_POSITION = "a_Position";
    private final static String A_COLOR = "a_Color";
    private final static String A_DIRECTION_VECTOR = "a_DirectionVector";
    private final static String A_PARTICLE_START_TIME = "a_ParticleStartTime";

    private int uMatrixLocation, uTimeLocation;
    private int aPositionLocation, aColorLocation, aDirectionVectorLocation, aParticleStartTimeLocation;

    public ParticleProgram(Context context) {
        super(context, R.raw.particle_vertex_shader, R.raw.particle_fragment_shader);

        uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
        uTimeLocation = GLES20.glGetUniformLocation(mProgramId, U_TIME);

        aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(mProgramId, A_COLOR);
        aDirectionVectorLocation = GLES20.glGetAttribLocation(mProgramId, A_DIRECTION_VECTOR);
        aParticleStartTimeLocation = GLES20.glGetAttribLocation(mProgramId, A_PARTICLE_START_TIME);
    }

    @Override
    public void setUniform(float[] projectionMatrix) {
        GLES20.glUseProgram(mProgramId);

        GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
    }

    public void setCurrentTime(float elapsedTime) {
        GLES20.glUniform1f(uTimeLocation, elapsedTime);
    }

    public int getPositionLocation() {
        return aPositionLocation;
    }

    public int getColorLocation() {
        return aColorLocation;
    }

    public int getDirectionVectorLocation() {
        return aDirectionVectorLocation;
    }

    public int getParticleStartTimeLocation() {
        return aParticleStartTimeLocation;
    }
}

3、Particle类

Particle类封装了顶点数据,包括顶点的xyz数据、颜色的rgb数据、移动的xyz数据和粒子开始时间。
public class Particle extends Resource {
    private final static int POSITION_COMPONENT_COUNT = 3;	
    private final static int COLOR_COMPONENT_COUNT = 3;
    private final static int VECTOR_COMPONENT_COUNT = 3;
    private final static int PARTICLE_START_TIME_COMPONENT_COUNT = 1;

    private final static 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 * VertexArray.BYTES_PER_FLOAT;

    private int maxParticleCount, currentParticleCount, nextParticle;
    private float[] particles;

    public Particle(int maxParticleCount) {
        super(new float[maxParticleCount * TOTAL_COMPONENT_COUNT]);
        this.particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];
        this.maxParticleCount = maxParticleCount;
    }

    public void addParticle(Geometry.Point position, int color, Geometry.Vector direction,
                            float particleStartTime) {
        final int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT;
        int currentOffset = particleOffset;
        nextParticle++;

        if (currentParticleCount < maxParticleCount) {
            currentParticleCount++;
        }

        // 如果到队尾,从0开始
        if (nextParticle == maxParticleCount) {
            nextParticle = 0;
        }

        // 开始位置
        particles[currentOffset++] = position.x;
        particles[currentOffset++] = position.y;
        particles[currentOffset++] = position.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++] = particleStartTime;

	// 修改顶点数据
        mVertextArray.updateBuffer(particles, particleOffset, TOTAL_COMPONENT_COUNT);
    }

    public void bindData(ParticleProgram program) {
        int dataOffset = 0;
        bindData(program.getPositionLocation(), dataOffset, POSITION_COMPONENT_COUNT, STRIDE);

        dataOffset += POSITION_COMPONENT_COUNT;
        bindData(program.getColorLocation(), dataOffset, COLOR_COMPONENT_COUNT, STRIDE);

        dataOffset += COLOR_COMPONENT_COUNT;
        bindData(program.getDirectionVectorLocation(), dataOffset, VECTOR_COMPONENT_COUNT, STRIDE);

        dataOffset += VECTOR_COMPONENT_COUNT;
        bindData(program.getParticleStartTimeLocation(), dataOffset,
                PARTICLE_START_TIME_COMPONENT_COUNT, STRIDE);
    }

    @Override
    public void draw() {
        super.draw();
        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);
    }
}
VertexArray类中updateBuffer方法
public void updateBuffer(float[] vertexData, int start, int count) {
	floatBuffer.position(start);
	floatBuffer.put(vertexData, start, count);
	floatBuffer.position(0);
}

4、绘制着色器

(1) ParticleShooter类,粒子喷泉
public class ParticleShooter {
    private final Geometry.Point position;
    private final Geometry.Vector direction;
    private final int color;

    ParticleShooter(Geometry.Point position, Geometry.Vector direction, int color) {
        this.position = position;
        this.direction = direction;
        this.color = color;
    }

    public void addParticles(Particle particle, float currentTime, int count) {
        for (int i = 0; i < count; i++) {
            particle.addParticle(position, color, direction, currentTime);
        }
    }

}
(2) OpenGLParticleShaderRender类
private class OpenGLParticleShaderRender implements GLSurfaceView.Renderer {
	private Particle mParticle;
	private ParticleProgram mParticleProgram;
	private ParticleShooter mRedParticleShooter, mGreenParticleShooter, mBlueParticleShooter;
	private long mGlobalStartTime;

	private float[] projectionMatrix = new float[16];
	private float[] viewlMatrix = new float[16];
	private float[] viewProjectionMatrix = new float[16];

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.1f);

		mParticleProgram = new ParticleProgram(OpenGLParticleShaderActivity.this);
		mParticle = new Particle(10000);

		mGlobalStartTime = System.nanoTime();

		final Geometry.Vector particleDirection = new Geometry.Vector(0f, 0.5f, 0f);

		mRedParticleShooter = new ParticleShooter(new Geometry.Point(-1, 0, 0),
				particleDirection, Color.rgb(250, 50, 5));

		mGreenParticleShooter = new ParticleShooter(new Geometry.Point(0, 0, 0),
				particleDirection, Color.rgb(25, 255, 25));

		mBlueParticleShooter = new ParticleShooter(new Geometry.Point(1, 0, 0),
				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(viewlMatrix, 0);
		Matrix.translateM(viewlMatrix, 0, 0f, -1.5f, -5f);

		Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewlMatrix, 0);
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		// 清空屏幕
		GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT);

		float currentTime = (System.nanoTime() - mGlobalStartTime) / 1000000000f;
		mRedParticleShooter.addParticles(mParticle, currentTime, 5);
		mGreenParticleShooter.addParticles(mParticle, currentTime, 5);
		mBlueParticleShooter.addParticles(mParticle, currentTime, 5);

		mParticleProgram.setUniform(viewProjectionMatrix);
		mParticleProgram.setCurrentTime(currentTime);
		mParticle.bindData(mParticleProgram);
		mParticle.draw();
	}

}
显示如下

5、扩散粒子

我们将变化每个粒子的速度,让每个粒子喷泉有更多的变化。
(1) ParticleShooter类中添加角度和速度变量

private final float angleVariance;
private final float speendVariance;

private final Random random = new Random();
private float[] rotationMatrix = new float[16];
private float[] directionVector = new float[4];
private float[] resultVector = new float[4];
Matrix.setRotateEulerM方法创建一个旋转矩阵,用angleVariance的一个随机量改变发射角度。
Matrix.setRotateEulerM(rotationMatrix, 0,
		(random.nextFloat() - 0.5f) * angleVariance,
		(random.nextFloat() - 0.5f) * angleVariance,
		(random.nextFloat() - 0.5f) * angleVariance);
调整方向向量,每个分量都与speendVariance相乘。
float speedAdjustment = 1f + random.nextFloat() * speendVariance;
Geometry.Vector thisDirection = new Geometry.Vector(
		resultVector[0] * speedAdjustment,
		resultVector[1] * speedAdjustment,
		resultVector[2] * speedAdjustment);
ParticleShooter类
public class ParticleShooter {
    private final Geometry.Point position;
    private final Geometry.Vector direction;
    private final int color;

    private final float angleVariance;
    private final float speendVariance;

    private final Random random = new Random();
    private float[] rotationMatrix = new float[16];
    private float[] directionVector = new float[4];
    private float[] resultVector = new float[4];

    ParticleShooter(Geometry.Point position, Geometry.Vector direction, int color) {
        this.position = position;
        this.direction = direction;
        this.color = color;

        angleVariance = 0;
        speendVariance = 0;
    }

    ParticleShooter(Geometry.Point position, Geometry.Vector direction, int color,
                    float angleVariance, float speendVariance) {
        this.position = position;
        this.direction = direction;
        this.color = color;

        this.angleVariance = angleVariance;
        this.speendVariance = speendVariance;

        directionVector[0] = direction.x;
        directionVector[1] = direction.y;
        directionVector[2] = direction.z;
    }

    public void addParticles(Particle particle, float currentTime, int count) {
        for (int i = 0; i < count; i++) {
//            particle.addParticle(position, color, direction, currentTime);

            // 创建一个旋转矩阵,用angleVariance的一个随机量改变发射角度
            Matrix.setRotateEulerM(rotationMatrix, 0,
                    (random.nextFloat() - 0.5f) * angleVariance,
                    (random.nextFloat() - 0.5f) * angleVariance,
                    (random.nextFloat() - 0.5f) * angleVariance);
            Matrix.multiplyMV(resultVector, 0, rotationMatrix, 0, directionVector, 0);

            // 调整发射速度
            float speedAdjustment = 1f + random.nextFloat() * speendVariance;
            Geometry.Vector thisDirection = new Geometry.Vector(
                    resultVector[0] * speedAdjustment,
                    resultVector[1] * speedAdjustment,
                    resultVector[2] * speedAdjustment);
            particle.addParticle(position, color, thisDirection, currentTime);
        }
    }
}
(2) OpenGLParticleShaderRender类,替代ParticleShooter
//    mRedParticleShooter = new ParticleShooter(new Geometry.Point(-1, 0, 0),
//    			particleDirection, Color.rgb(250, 50, 5));
//
//    mGreenParticleShooter = new ParticleShooter(new Geometry.Point(0, 0, 0),
//             	particleDirection, Color.rgb(25, 255, 25));
//
//    mBlueParticleShooter = new ParticleShooter(new Geometry.Point(1, 0, 0),
//              particleDirection, Color.rgb(5, 50, 255));

final float angleVarianceInDegrees = 5f;
final float speedVariance = 1f;

mRedParticleShooter = new ParticleShooter(new Geometry.Point(-1, 0, 0),
		particleDirection, Color.rgb(250, 50, 5), angleVarianceInDegrees, speedVariance);

mGreenParticleShooter = new ParticleShooter(new Geometry.Point(0, 0, 0),
		particleDirection, Color.rgb(25, 255, 25), angleVarianceInDegrees, speedVariance);

mBlueParticleShooter = new ParticleShooter(new Geometry.Point(1, 0, 0),
		particleDirection, Color.rgb(5, 50, 255), angleVarianceInDegrees, speedVariance);
显示如下

6、添加重力

通过给顶点着色器加入一个重力调整因子,来给粒子加入重力效应。
顶点着色器,particle_vertex_shader.glsl文件

void main()
{
    v_Color = a_Color;
    v_ElapsedTime = u_Time - a_ParticleStartTime;

    vec3 currentPosition = a_Position + (a_DirectionVector * v_ElapsedTime);

    float gravityFactor = v_ElapsedTime * v_ElapsedTime / 9.8;
    currentPosition.y -= gravityFactor;

    gl_Position = u_Matrix * vec4(currentPosition, 1.0);
    gl_PointSize = 10.0;
}
显示如下

7、混合粒子

就向我们看烟花表演,粒子越多,它们就应该越亮。模仿这个效果的方法之一是使用累加混合技术。
在onSurfaceCreated里添加如下代码

GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);		
显示如下

8、自定义点的外形

粒子被渲染为小的四方形,其每边的像素都等于gl_PointSize的值。我们可以使用另一个特殊的OpenGL变量gl_PointCoord自定义这些点的外形。
对gl_PointCoord来说,其片段的位置范围都是[0, 1],因此,把点的圆心放在[0.5,0.5],只要距离这个点小于0.5,就是需要渲染的部分。
片段着色器,particle_fragment_shader.xml文件

void main()
{                    	
    /*
        gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0);
    */
    float xDistance = 0.5 - gl_PointCoord.x;
    float yDistance = 0.5 - gl_PointCoord.y;
    float distanceFromCenter = sqrt(xDistance * xDistance + yDistance * yDistance);

    if (distanceFromCenter > 0.5) {
        discard;
    } else {
        gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0);
    }
}
显示如下

9、纹理粒子

(1) 首先修改片段着色器
precision mediump float;

uniform sampler2D u_TextUnit;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main()
{
    gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0)
            * texture2D(u_TextUnit, gl_PointCoord);
}
(2) ParticleProgram类,
获取u_TextUnit

private final static String U_TEXT_UNIT = "u_TextUnit";
private int uTextUnitLocation;
uTextUnitLocation = GLES20.glGetUniformLocation(mProgramId, U_TEXT_UNIT);
获取textureID,可参考前面

private int mTextureId;

mTextureId = loadTexture(context, R.drawable.particle_texture);
最后在setMatrix方法中加载纹理
public void setMatrix(float[] projectionMatrix) {
	GLES20.glUseProgram(mProgramId);

	GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);

	// 把活动的纹理单元设置为纹理单元0
	GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
	// 把纹理绑定到这个单元
	GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
	// 把被选择的纹理单元传递给片段着色器中的
	GLES20.glUniform1i(uTextUnitLocation, 0);
}
显示如下
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值