一、開始
在之前的4篇文章里面,我們完成了一個桌上冰球的游戲匆绣,接下來我們要做一個噴射的煙花系統(tǒng),我們要做出來的效果是這個樣子的:
我們分兩部分來完成迷守,首先我們先做出噴射的效果犬绒,然后我們再去著重優(yōu)化噴射的每個粒子的繪制旺入。
二兑凿、噴射效果的實現(xiàn)
要完成噴射效果,我們的整體思路是這個樣子的:
- 先實現(xiàn)不斷往上移動的粒子系統(tǒng)
- 然后我們讓往上的粒子隨機的有一些角度
- 我們給這些粒子的移動向量加上一個重力加速度的衰減變量茵瘾,讓粒子的速度逐漸減慢最終像反方向移動
那現(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();
}
}
運行一下,你就能看到下面的效果了:
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);
再運行一遍然想,你就能看到下面的效果了:
3.加上衰減
我們只需要在shader程序里面算出來一個合適的值莺奔,然后一直衰減點的y值就可以了,在particle_vertex_shader.glsl
文件中加上如下代碼:
float gravityFactor = v_ElapsedTime * v_ElapsedTime / 8.0;
currentPosition.y -= gravityFactor;
然后運行一遍就能看到如下效果了:
三、優(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.
個人理解就是把原來在這里的顏色和將要繪制在這里的顏色按一定的比例混合顯示噩死,理解可能有誤,大家可以自己查資料理解神年。
加上這兩行代碼之后的效果是這樣的:
接下來我們就要優(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是繪制的堂鲜,圓之外不繪制,這樣就繪制出一個圓點了护奈。運行下看看吧缔莲!
最后我們用一張圖片去繪制我們的點,如果大家忘了如何繪制圖片可以看看前面的文章逆济。我們先把我們的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);
最后運行看看抛虫!是不是好看很多了。
這一部分主要是對之前知識的回顧简僧,大家可以好好整理下建椰!
項目代碼在這里:https://github.com/KevinKmoo/Particles
能力有限,自己讀書的學習所得岛马,有錯誤請指導棉姐,輕虐!
轉載請注明出處啦逆。----by kmoo