OpenGl-ES2.0 For Android 讀書筆記(五)

一、開始

在之前的4篇文章里面,我們完成了一個桌上冰球的游戲匆绣,接下來我們要做一個噴射的煙花系統(tǒng),我們要做出來的效果是這個樣子的:


效果圖.gif

我們分兩部分來完成迷守,首先我們先做出噴射的效果犬绒,然后我們再去著重優(yōu)化噴射的每個粒子的繪制旺入。

二兑凿、噴射效果的實現(xiàn)

要完成噴射效果,我們的整體思路是這個樣子的:

  1. 先實現(xiàn)不斷往上移動的粒子系統(tǒng)
  2. 然后我們讓往上的粒子隨機的有一些角度
  3. 我們給這些粒子的移動向量加上一個重力加速度的衰減變量茵瘾,讓粒子的速度逐漸減慢最終像反方向移動

那現(xiàn)在就讓我們一步一步去實現(xiàn):

1.先實現(xiàn)不斷往上移動的粒子系統(tǒng)

我們可以使用之前代碼的一些工具類礼华,所以Copy之前的代碼,然后新建一個ParticlesRenderer.java類實現(xiàn)Renderer.java接口拗秘,在MainActivity.java中移除監(jiān)聽事件圣絮,然后把這個Renderer設置給GLView。
我們思考一下雕旨,一個不斷往上移動的粒子系統(tǒng)要如何去實現(xiàn)扮匠,要知道一個粒子如何移動,我們需要知道他的原始位置凡涩,粒子創(chuàng)建的時間棒搜,移動的方向向量,這樣我們就可以根據(jù)當前時間去推算出一個粒子當前應該在的位置活箕,現(xiàn)在我們就用代碼去實現(xiàn)力麸,新建一個particle_vertex_shader.glsl文件,實現(xiàn)如下代碼:

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 + (v_ElapsedTime * a_DirectionVector);
    gl_Position = u_Matrix * vec4(currentPosition , 1.0);
    gl_PointSize = 10.0;
}

然后我們新建一個particle_fragment_shader.glsl文件實現(xiàn)如下代碼:

precision mediump float;

varying vec3 v_Color;
varying float v_ElapsedTime;

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

v_Color / v_ElapsedTime是為了讓粒子隨著時間的流逝逐漸變的黯淡育韩,現(xiàn)在Shader程序完成了克蚂,我們需要在Java里面封裝一下,我們先在ShaderProgram.java中添加如下聲明:

protected static final String U_TIME = "u_Time";
protected static final String A_DIRECTION_VECTOR = "a_DirectionVector";
protected static final String A_PARTICLE_START_TIME = "a_ParticleStartTime";

然后新建一個ParticleShaderProgram.java類筋讨,實現(xiàn)如下代碼:

public class ParticleShaderProgram extends ShaderProgram{

    private int mUMatrixLocation;
    private int mUTimeLocation;
    private int mAPositionLocation;
    private int mAColorLocation;
    private int mADirectionVectorLocation;
    private int mAParticleStartTimeLocation;

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

        mUMatrixLocation = glGetUniformLocation(mProgram , U_MATRIX);
        mUTimeLocation = glGetUniformLocation(mProgram , U_TIME);

        mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);
        mAColorLocation = glGetAttribLocation(mProgram , A_COLOR);
        mADirectionVectorLocation = glGetAttribLocation(mProgram , A_DIRECTION_VECTOR);
        mAParticleStartTimeLocation = glGetAttribLocation(mProgram , A_PARTICLE_START_TIME);
    }

    public void setUniforms(float[] matrix , float elapsedTime){
        glUniformMatrix4fv(mUMatrixLocation , 1 , false , matrix , 0);
        glUniform1f(mUTimeLocation , elapsedTime);
    }

    public int getAPositionLocation(){
        return mAPositionLocation;
    }

    public int getAColorLocation(){
        return mAColorLocation;
    }

    public int getADirectionVectorLocation(){
        return mADirectionVectorLocation;
    }

    public int getAParticleStartTimeLocation(){
        return mAParticleStartTimeLocation;
    }
}

然后我們在objects包下新建ParticleSystem.java類埃叭,去實現(xiàn)我們的粒子系統(tǒng):

public class ParticleSystem {

    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 * Constants.BYTE_PRE_FLOAT;

    private final float[] mParticles;
    private final VertexArray mVertexArray;
    private final int mMaxParticleCount;

    private int mCurrentParticleCount;
    private int mNextParticle;

    public ParticleSystem(int maxParticleCount){
        mParticles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];
        mVertexArray = new VertexArray(mParticles);
        mMaxParticleCount = maxParticleCount;
    }

    public void addParticle(Geometry.Point position , int color , Geometry.Vector directionVection ,
                            float startTime){
        final int particleOffset = mNextParticle * TOTAL_COMPONENT_COUNT;

        int currentOffset = particleOffset;
        mNextParticle++;

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

        if (mNextParticle == mMaxParticleCount){
            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++] = directionVection.x;
        mParticles[currentOffset++] = directionVection.y;
        mParticles[currentOffset++] = directionVection.z;
        mParticles[currentOffset++] = startTime;

        //更新在native的數(shù)據(jù)
        mVertexArray.updateBuffer(mParticles , particleOffset , TOTAL_COMPONENT_COUNT);
    }

    public void bindData(ParticleShaderProgram particleShaderProgram){
        mVertexArray.setVertexAttribPointer(0 ,
                particleShaderProgram.getAPositionLocation() , POSITION_COMPONENT_COUNT , STRIDE);
        mVertexArray.setVertexAttribPointer(POSITION_COMPONENT_COUNT ,
                particleShaderProgram.getAColorLocation() , COLOR_COMPONENT_COUNT , STRIDE);
        mVertexArray.setVertexAttribPointer(POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT,
                particleShaderProgram.getADirectionVectorLocation() , VECTOR_COMPONENT_COUNT , STRIDE);
        mVertexArray.setVertexAttribPointer(POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT + VECTOR_COMPONENT_COUNT,
                particleShaderProgram.getAParticleStartTimeLocation() , PARTICLE_START_TIME_COMPONENT_COUNT , STRIDE);
    }

    public void draw(){
        glDrawArrays(GL_POINTS, 0, mCurrentParticleCount);
    }


}

粒子系統(tǒng)完成了,接下來我們還需要一個噴泉去使用我們的粒子系統(tǒng)悉罕,在objects包下再新建一個類ParticleShooter.java類赤屋,實現(xiàn)如下代碼:

public class ParticleShooter {

    private final Geometry.Point mPosition;
    private final Geometry.Vector mDirectionVector;
    private final int mColor;

    public ParticleShooter(Geometry.Point position , Geometry.Vector directionVector , int color){
        mPosition = position;
        mDirectionVector = directionVector;
        mColor = color;
    }

    public void addParticles(ParticleSystem particleSystem , float currentTime , int count){
        for (int i = 0; i < count; i++) {
            particleSystem.addParticle(mPosition , mColor , mDirectionVector , currentTime);
        }
    }

}

最后去修改我們的Renderer就實現(xiàn)了第一步了:

public class ParticlesRenderer implements GLSurfaceView.Renderer{

    private Context mContext;

    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final float[] mViewProjectionMatrix = new float[16];

    private ParticleShaderProgram mParticleShaderProgram;
    private ParticleSystem mParticleSystem;
    private ParticleShooter mRedShooter;
    private ParticleShooter mGreenShooter;
    private ParticleShooter mBlueShooter;
    private float mGlobleStartTime;

    public ParticlesRenderer(Context context){
        mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(0f ,0f , 0f, 0f);

        mParticleShaderProgram = new ParticleShaderProgram(mContext);
        mParticleSystem = new ParticleSystem(10000);
        mGlobleStartTime = System.nanoTime();

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

        mRedShooter = new ParticleShooter(new Geometry.Point(-1 , 0 , 0) ,
                particleDirection , Color.rgb(255, 50, 5));
        mGreenShooter = new ParticleShooter(new Geometry.Point(0 , 0 , 0) ,
                particleDirection , Color.rgb(25, 255, 25));
        mBlueShooter = new ParticleShooter(new Geometry.Point(1 , 0 , 0) ,
                particleDirection , Color.rgb(5, 50, 255));
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0 , 0 , width , height);

        MatrixHelper.perspectiveM(mProjectionMatrix, 45, (float) width
                / (float) height, 1f, 10f);
        setIdentityM(mViewMatrix, 0);
        translateM(mViewMatrix, 0, 0f, -1.5f, -5f);
        multiplyMM(mViewProjectionMatrix, 0, mProjectionMatrix, 0,
                mViewMatrix, 0);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        glClear(GL_COLOR_BUFFER_BIT);

        float currentTime = (System.nanoTime() - mGlobleStartTime) / 1000000000f;

        mRedShooter.addParticles(mParticleSystem , currentTime , 5);
        mGreenShooter.addParticles(mParticleSystem , currentTime , 5);
        mBlueShooter.addParticles(mParticleSystem , currentTime , 5);

        mParticleShaderProgram.useProgram();
        mParticleShaderProgram.setUniforms(mViewProjectionMatrix , currentTime);
        mParticleSystem.bindData(mParticleShaderProgram);
        mParticleSystem.draw();
    }
}

運行一下,你就能看到下面的效果了:

Step1.gif

2.加上隨機角度

我們知道粒子的移動是方向向量決定的蛮粮,所以我們的變動要加到方向向量上益缎,我們也需要給我的隨機變動加上一個范圍,主要是改動ParticleShooter.java類:

public class ParticleShooter {

    private final Geometry.Point mPosition;
    private final Geometry.Vector mDirectionVector;
    private final int mColor;

    private final float mAngleVariance;
    private final float mSpeedVariance;

    private final Random mRandom = new Random();

    private float[] mRotationMatrix = new float[16];
    private float[] mDirection = new float[4];
    private float[] mResultVector = new float[4];

    public ParticleShooter(Geometry.Point position , Geometry.Vector directionVector , int color ,
                           float angleVariance , float speedVariance){
        mPosition = position;
        mDirectionVector = directionVector;
        mColor = color;
        mAngleVariance = angleVariance;
        mSpeedVariance = speedVariance;

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

    public void addParticles(ParticleSystem particleSystem , float currentTime , int count){

        for (int i = 0; i < count; i++) {

            setRotateEulerM(mRotationMatrix, 0,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance,
                    (mRandom.nextFloat() - 0.5f) * mAngleVariance);

            multiplyMV(
                    mResultVector, 0,
                    mRotationMatrix, 0,
                    mDirection, 0);

            float speedAdjustment = 1f + mRandom.nextFloat() * mSpeedVariance;

            Geometry.Vector thisVector = new Geometry.Vector(
                    mResultVector[0] * speedAdjustment,
                    mResultVector[1] * speedAdjustment,
                    mResultVector[2] * speedAdjustment);

            particleSystem.addParticle(mPosition , mColor , thisVector , currentTime);
        }
    }

}

然后修改Renderer里面的代碼:

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

mRedShooter = new ParticleShooter(new Geometry.Point(-1 , 0 , 0) ,
        particleDirection , Color.rgb(255, 50, 5) , angleVarianceInDegrees , speedVariance);
mGreenShooter = new ParticleShooter(new Geometry.Point(0 , 0 , 0) ,
        particleDirection , Color.rgb(25, 255, 25) , angleVarianceInDegrees , speedVariance);
mBlueShooter = new ParticleShooter(new Geometry.Point(1 , 0 , 0) ,
       particleDirection , Color.rgb(5, 50, 255) , angleVarianceInDegrees , speedVariance);

再運行一遍然想,你就能看到下面的效果了:


Step2.gif

3.加上衰減

我們只需要在shader程序里面算出來一個合適的值莺奔,然后一直衰減點的y值就可以了,在particle_vertex_shader.glsl文件中加上如下代碼:

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

然后運行一遍就能看到如下效果了:

Step3.gif

三、優(yōu)化粒子的顯示

不知道大家有沒有感覺我們上面的煙花有點怪怪的令哟,就是在煙花多得地方應該是比較亮的恼琼,但是我們上面的沒有那種效果,那我們要如何去實現(xiàn)那種效果呢屏富?其實很簡單晴竞,只需要在onSurfaceCreated()方法中調用如下兩個方法就可以了:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE , GL_ONE);

第一行代碼的意思就是打開Blend效果,下面的方法是一個輸出效果的方程狠半,原文是這樣解釋的:

output = (source factor * source fragment) + (destination factor * destination fragment)

In OpenGL, blending works by blending the result of the fragment shader with the color that’s already there in the frame buffer.

個人理解就是把原來在這里的顏色和將要繪制在這里的顏色按一定的比例混合顯示噩死,理解可能有誤,大家可以自己查資料理解神年。
加上這兩行代碼之后的效果是這樣的:

ShapeStep1.gif

接下來我們就要優(yōu)化下粒子的形狀了已维,現(xiàn)在的正方形是不是感覺不舒服,我們先修改成圓形吧已日!這個時候我們需要用到一個叫做gl_PointCoord的東西垛耳,我們把particle_fragment_shader.glsl修改如下:

precision mediump float;

varying vec3 v_Color;
varying float v_ElapsedTime;

void main() {
    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);
    }
}

gl_PointCoord的解釋原文是這樣說的:

For each point, when the fragment shader is run, we’ll get a two-dimensional gl_PointCoord coordinate with each component ranging from 0 to 1 on each axis, depending on which fragment in the point is currently being rendered.

代碼里面的意思就是以(0.5,0.5)為圓心飘千,0.5為半徑之內的圓的fragment是繪制的堂鲜,圓之外不繪制,這樣就繪制出一個圓點了护奈。運行下看看吧缔莲!

ShapeStep2.gif

最后我們用一張圖片去繪制我們的點,如果大家忘了如何繪制圖片可以看看前面的文章逆济。我們先把我們的particle_fragment_shader.glsl文件修改成下面的樣子:

precision mediump float;

varying vec3 v_Color;
varying float v_ElapsedTime;

uniform sampler2D u_TextureUnit;

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

意思就是用原來的顏色去畫現(xiàn)在的圖酌予。
然后我們需要去修改ParticleShaderProgram.java文件,我們需要先聲明我們要用到的位置:

private int mUTextureUnitLocation;

然后再初始化的方法中去賦值:

mUTextureUnitLocation = glGetUniformLocation(mProgram , U_TEXTURE_UNIT);

最后需要修改setUniforms()方法奖慌,添加一個參數(shù)int textureId然后添加如下代碼:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D , textureId);
glUniform1i(mUTextureUnitLocation , 0);

最后我們在Renderer中加載Texture并給Program設置使用:

int textureId = TextureHelper.loadTexture(mContext , R.drawable.particle_texture);
mParticleShaderProgram.setUniforms(mViewProjectionMatrix , currentTime , textureId);

最后運行看看抛虫!是不是好看很多了。


ShapeStep3.gif

這一部分主要是對之前知識的回顧简僧,大家可以好好整理下建椰!

項目代碼在這里:https://github.com/KevinKmoo/Particles

能力有限,自己讀書的學習所得岛马,有錯誤請指導棉姐,輕虐!
轉載請注明出處啦逆。----by kmoo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末伞矩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子夏志,更是在濱河造成了極大的恐慌乃坤,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異湿诊,居然都是意外死亡狱杰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門厅须,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仿畸,“玉大人,你說我怎么就攤上這事朗和〈砉粒” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵例隆,是天一觀的道長甥捺。 經常有香客問我抢蚀,道長镀层,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任皿曲,我火速辦了婚禮唱逢,結果婚禮上,老公的妹妹穿的比我還像新娘屋休。我一直安慰自己坞古,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布劫樟。 她就那樣靜靜地躺著痪枫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叠艳。 梳的紋絲不亂的頭發(fā)上奶陈,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音附较,去河邊找鬼吃粒。 笑死,一個胖子當著我的面吹牛拒课,可吹牛的內容都是我干的徐勃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼早像,長吁一口氣:“原來是場噩夢啊……” “哼僻肖!你這毒婦竟也來了?” 一聲冷哼從身側響起卢鹦,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤臀脏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谁榜,經...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡幅聘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窃植。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帝蒿。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖巷怜,靈堂內的尸體忽然破棺而出葛超,到底是詐尸還是另有隱情,我是刑警寧澤延塑,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布绣张,位于F島的核電站,受9級特大地震影響关带,放射性物質發(fā)生泄漏侥涵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一宋雏、第九天 我趴在偏房一處隱蔽的房頂上張望芜飘。 院中可真熱鬧,春花似錦磨总、人聲如沸嗦明。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娶牌。三九已至,卻和暖如春馆纳,著一層夾襖步出監(jiān)牢的瞬間诗良,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工厕诡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留累榜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓灵嫌,卻偏偏與公主長得像壹罚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子寿羞,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內容

  • 一猖凛、開始 OpenGl主要需要實現(xiàn)兩個類: **GLSurfaceView**This class is a Vi...
    KmooKay閱讀 1,652評論 3 11
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,293評論 25 707
  • 一、開始 在上篇文章中绪穆,我們已經讓桌面的顏色有了變化辨泳,也讓桌面適配了屏幕的比例虱岂,讓手機在旋轉的時候桌面也不會產生變...
    KmooKay閱讀 717評論 0 0
  • 一、開始 現(xiàn)在我們要做的內容是讓我們的桌面看起來更真實一些菠红,我們基本上來說有兩個事情要做第岖,一個是讓桌面的顏色不要那...
    KmooKay閱讀 1,472評論 1 3
  • 世途險惡,人心不古试溯。真心待人蔑滓,怎能安然度過,世上的聰明人太多了遇绞。逢人要說三分話键袱,未可全拋一片心。教會徒弟摹闽,餓死師傅...
    一念之間花開花落閱讀 209評論 0 0