用OpenGL構建粒子噴泉

用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的一個程序才能使用。
需要使用這幾個工具類

構建粒子系統

ParticlesRenderer

ParticlesShooter

ParticlsSystem

向粒子系統中填充數據


     /**
     * 向系統中添加粒子,每次添加一個
     *
     * @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中加入一些調用統一管理這一切。

項目地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章