Android 使用OpenGLES 的粒子系统

前面我们说到了天空盒,下面我们来看看粒子Particle是怎样实现的。我们在《Android 使用OpenGLES制作天空盒》(地址:
http://www.jianshu.com/p/820581046d3c)的基础上实现粒子系统:
1、在SkyBoxRenderer类中添加ParticleFilter:

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

        // 创建天空盒
        mSkyBoxFilter = new SkyBoxFilter(mContext);
        mSkyBoxFilter.createProgram();

        // 创建粒子系统
        mParticleFilter = new ParticleFilter(mContext);
        mParticleFilter.createProgram();
    }

    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {                
        GLES20.glViewport(0, 0, width, height);
        mViewWidth = width;
        mViewHeight = height;
        // 调整视图大小
        if (mSkyBoxFilter != null) {
            mSkyBoxFilter.setViewSize(width, height);
        }
        if (mParticleFilter != null) {
            mParticleFilter.setViewSize(width, height);
        }
    }

    @Override    
    public void onDrawFrame(GL10 glUnused) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        // 绘制天空盒
        drawSkybox();
        // 绘制粒子系统
        drawParticles();
    }
    /**
     * 绘制粒子特效
     */
    private void drawParticles() {
        // 混合
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE);
        if (mParticleFilter != null) {
            mParticleFilter.drawParticle();
        }
        GLES20.glDisable(GLES20.GL_BLEND);

    }

ParticleFilter类主要是通过计算位置、颜色、运动方向等实现粒子的随机运动, 实现如下:

public class ParticleFilter {

    // 最大粒子数
    private static final int MaxParticleCount = 1000;

    private Context mContext;
    private int mViewWidth;
    private int mViewHeight;

    // 创建粒子系统开始时间
    private long mGlobalStartTime;

    // GLSL句柄
    private int mProgramHandle;
    private int mMVPMatrixHandle;
    private int uTimeLocation;
    private int aPositionLocation;
    private int aColorLocation;
    private int aDirectionVectorLocation;
    private int aParticleStartTimeLocation;
    private int aOpenMouthLocation;

    private int mParticleTexture;

    private float[] mProjectionMatrix = new float[16];
    private float[] mViewMatrix = new float[16];
    private float[] mMVPMatrix = new float[16];


    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 float[] mParticles;
    private FloatBuffer mVertexBuffer;
    private int mCurrentParticleCount;
    private int mNextParticle;



    // ParticleShooter内容
    private Geometry.Point mPosition; // 起始位置
    private int mColor; // 颜色

    private float mAngleVariance;
    private float mSpeedVariance;

    private Random mRandom = new Random();

    // 旋转矩阵
    private float[] mRotationMatrix = new float[16];
    // 目的向量
    private float[] mDirectionVector = new float[4];
    // 最终的结果
    private float[] mResultVector = new float[4];


    public ParticleFilter(Context context) {
        mContext = context;
        mGlobalStartTime = System.nanoTime();

        mPosition = new Geometry.Point(0f, 0f, 0f);
        mDirectionVector[0] = 0.0f;
        mDirectionVector[1] = 0.5f;
        mDirectionVector[2] = 0.0f;
        mAngleVariance = 5f;
        mSpeedVariance = 1.0f;
        mColor = Color.rgb(255, 255, 255);

        // 创建粒子
        mParticles = new float[MaxParticleCount * TOTAL_COMPONENT_COUNT];
        mVertexBuffer = GlUtil.createFloatBuffer(mParticles);
    }

    /**
     * 创建Program
     */
    public void createProgram() {
        String vertexShader = ResourceUtil.readTextFileFromResource(mContext,
                R.raw.vertex_particle);
        String fragmentShader = ResourceUtil.readTextFileFromResource(mContext,
                R.raw.fragment_particle);
        mProgramHandle = GlUtil.createProgram(vertexShader, fragmentShader);

        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix");
        uTimeLocation = GLES20.glGetUniformLocation(mProgramHandle, "uTime");
        aOpenMouthLocation = GLES20.glGetUniformLocation(mProgramHandle, "a_OpenMouth");

        aPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
        aColorLocation = GLES20.glGetAttribLocation(mProgramHandle, "a_Color");
        aDirectionVectorLocation = GLES20.glGetAttribLocation(mProgramHandle, "a_DirectionVector");
        aParticleStartTimeLocation = GLES20.glGetAttribLocation(mProgramHandle, "a_ParticleStartTime");

        // 创建粒子的Texture
        mParticleTexture = TextureHelper.loadTexture(mContext, R.drawable.particle_texture);
    }

    /**
     * 更新视图大小
     * @param width
     * @param height
     */
    public void setViewSize(int width, int height) {
        mViewWidth = width;
        mViewHeight = height;
        // 设置透视矩阵
        float ratio = (float) width / (float) height;
        MatrixHelper.perspectiveM(mProjectionMatrix, 45, ratio, 1f, 300f);
    }

    /**
     * 绘制粒子
     */
    public void drawParticle() {
        GLES20.glUseProgram(mProgramHandle);
        float currentTime = (System.nanoTime() - mGlobalStartTime) / 1000000000f;
        GLES20.glUniform1f(uTimeLocation, currentTime);
        calculate(currentTime, 1);
        calculateMatirx();
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mParticleTexture);
        bindData();
        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, mCurrentParticleCount);

        GLES20.glUseProgram(0);
    }

    private void bindData() {
        int dataOffset = 0;
        // position
        mVertexBuffer.position(dataOffset);
        GLES20.glEnableVertexAttribArray(aPositionLocation);
        GLES20.glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, mVertexBuffer);
        mVertexBuffer.position(0);
        dataOffset += POSITION_COMPONENT_COUNT;

        // color
        mVertexBuffer.position(dataOffset);
        GLES20.glEnableVertexAttribArray(aColorLocation);
        GLES20.glVertexAttribPointer(aColorLocation, COLOR_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, mVertexBuffer);
        mVertexBuffer.position(0);
        dataOffset += COLOR_COMPONENT_COUNT;

        // direction
        mVertexBuffer.position(dataOffset);
        GLES20.glEnableVertexAttribArray(aDirectionVectorLocation);
        GLES20.glVertexAttribPointer(aDirectionVectorLocation, VECTOR_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, mVertexBuffer);
        mVertexBuffer.position(0);
        dataOffset += VECTOR_COMPONENT_COUNT;

        // startTime
        mVertexBuffer.position(dataOffset);
        GLES20.glEnableVertexAttribArray(aParticleStartTimeLocation);
        GLES20.glVertexAttribPointer(aParticleStartTimeLocation,
                PARTICLE_START_TIME_COMPONENT_COUNT,
                GLES20.GL_FLOAT, false, STRIDE, mVertexBuffer);
        mVertexBuffer.position(0);
    }

    /**
     * 计算粒子
     * @param time
     * @param count
     */
    private void calculate(float time, int count) {
        for (int i = 0; i < count; i++) {
            // 随机产生(-1.0 ~ 1.0)之间的起始位置
            float startX = mRandom.nextFloat() * 2 - 1.0f;
            float startY = mRandom.nextFloat() * 2 - 1.0f;
            mPosition = new Geometry.Point(startX, startY, 0);

            // 设置欧拉角
            Matrix.setRotateEulerM(mRotationMatrix, 0,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance);

            // 向量矩阵乘法
            Matrix.multiplyMV(mResultVector, 0, mRotationMatrix, 0, mDirectionVector, 0);

            // 调整速度
            float speedAdjustment = 1f + mRandom.nextFloat() * mSpeedVariance;

            // 计算最终的方向向量
            Geometry.Vector direction = new Geometry.Vector(
                    mResultVector[0] * speedAdjustment,
                    mResultVector[1] * speedAdjustment,
                    mResultVector[2] * speedAdjustment);

            addParticle(mPosition, mColor, direction, time);
        }
    }

    /**
     * 添加粒子
     * @param position
     * @param color
     * @param direction
     * @param startTime
     */
    private void addParticle(Geometry.Point position, int color,
                             Geometry.Vector direction, float startTime) {
        final int particleOffset = mNextParticle * TOTAL_COMPONENT_COUNT;
        int currentOffset = particleOffset;
        mNextParticle++;

        if (mCurrentParticleCount < MaxParticleCount) {
            mCurrentParticleCount++;
        }

        if (mNextParticle == MaxParticleCount) {
            mNextParticle = 0;
        }

        // 原始位置
        mParticles[currentOffset++] = position.x;
        mParticles[currentOffset++] = position.y;
        mParticles[currentOffset++] = position.z;

        // 颜色
        mParticles[currentOffset++] = Color.red(color) / 255f;
        mParticles[currentOffset++] = Color.green(color) / 255f;
        mParticles[currentOffset++] = Color.blue(color) / 255f;

        // 目的位置
        mParticles[currentOffset++] = direction.x;
        mParticles[currentOffset++] = direction.y;
        mParticles[currentOffset++] = direction.z;

        mParticles[currentOffset++] = startTime;

        mVertexBuffer.position(particleOffset);
        mVertexBuffer.put(mParticles, particleOffset, TOTAL_COMPONENT_COUNT);
        mVertexBuffer.position(0);
    }

    /**
     * 计算总变换
     */
    private void calculateMatirx() {
        // 计算综合矩阵
        Matrix.setIdentityM(mViewMatrix, 0);
        Matrix.translateM(mViewMatrix, 0, 0f, 0f, -5f);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);
    }

    /**
     * 设置点击的位置
     * @param x
     * @param y
     * @param z
     */
    public void setDirection(float x, float y, float z) {
        mDirectionVector[0] = x;
        mDirectionVector[1] = y;
        mDirectionVector[2] = z;
    }

}

GLSL的代码如下:
VertexShader:

uniform mat4 uMVPMatrix;
uniform float uTime;
uniform int a_OpenMouth;

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 = uTime - a_ParticleStartTime;
    // 重力加速度
    float gravityFactor = v_ElapsedTime * v_ElapsedTime / 8.0;
    // 当前位置
    vec3 currentPosition = a_Position + (a_DirectionVector * v_ElapsedTime);
    // 如果张开嘴巴,则将当前位置运动到嘴巴中心点
    if (a_OpenMouth == 1) {
        currentPosition = a_Position + ((a_DirectionVector - a_Position) * v_ElapsedTime);
    } else {
        currentPosition.y -= gravityFactor;
    }
    gl_Position = uMVPMatrix * vec4(currentPosition, 1.0);
    gl_PointSize = 25.0;
}

FragmentShader:

precision mediump float; 

uniform sampler2D u_TextureUnit;

varying vec3 v_Color;
varying float v_ElapsedTime;
                                                                            
void main()                         
{
    gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0)
                 * texture2D(u_TextureUnit, gl_PointCoord);
}

实现的效果如下:

Screenshot_20170831-160500.png

详情请参考:
https://github.com/CainKernel/SkyBox

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容