OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
OpenGL之纹理
OpenGL之构建简单物体
OpenGL之触控反馈
粒子着色器
首先,需要某种方式在内存中表示所有的粒子,可以为此使用一个Java对象的数组,但是在运行时创建、删除大量的对象消耗可能太大了,而且没有一种简单的方法把数据传递给OpenGL,因此,可以把所有的粒子数据内嵌到一个固定大小的数组中,要添加一个粒子,我们只需要增加粒子的计数,把数据写进粒子数组,并把变动的内容复制到本地缓冲区
我们也需要一个方法来绘制每个粒子,可以把每个粒子表示为一个顶点,并把这些顶点绘制为一组点,每个点都有一个唯一的位置和颜色
最后,还需要某种更新这些粒子的方法。通过把这些逻辑放进一个着色器程序中可以让GPU做部分的更新工作。我们要为每个粒子存储一个方向向量和一个创建时间,用这个创建时间,我们可以计算出粒子自被创建出来之后运行了多少时间,然后,可以用这个运行时间、方向向量和位置推算出粒子当前的位置
顶点着色器
uniform float u_Time;
uniform mat4 u_Matrix;
attribute vec3 a_Position;
attribute vec3 a_Color;
attribute float a_Start_Time;
attribute vec3 a_Direction;
varying vec3 v_Color;
varying float v_ElapsedTime;
void main(){
v_Color=a_Color;
v_ElapsedTime=u_Time-a_Start_Time;
float gravityFactor=v_ElapsedTime*v_ElapsedTime/8.0;
vec3 currentPosition=a_Position+(a_Direction*v_ElapsedTime);
currentPosition.y-=gravityFactor;
gl_Position=u_Matrix*vec4(currentPosition, 1.0);
gl_PointSize=15.0;
}
因为在glsl中写注释,会出现各种问题,因此将说明放到了后面
定义 | 说明 |
---|---|
uniform float u_Time; | 粒子系统启动的时间 |
uniform mat4 u_Matrix; | 模型视图投影矩阵 |
attribute vec3 a_Position; | 粒子的最初位置 |
attribute vec3 a_Color; | 粒子的最初颜色 |
attribute float a_Start_Time; | 每个粒子开始运行时间 |
attribute vec3 a_Direction; | 粒子运行方向 |
varying vec3 v_Color; | 片段着色器中也需要使用颜色,将粒子的颜色传给片段着色器 |
varying float v_ElapsedTime; | 每个粒子从创建至今的时间,并传给片段着色器 |
// 将粒子颜色赋值给v_Color,传到片段着色器
v_Color=a_Color;
// 计算粒子从创建到现在过去的时间,并传到片段着色器
v_ElapsedTime=u_Time-a_Start_Time;
// 重力因子,可以是其他的值,只要符合现实逻辑,粒子会下落并越来越快就行
float gravityFactor=v_ElapsedTime*v_ElapsedTime/8.0;
// 根据粒子的方向,计算粒子当前位置
vec3 currentPosition=a_Position+(a_Direction*v_ElapsedTime);
// 将位置信息加上重力导致的向下距离
currentPosition.y-=gravityFactor;
// 将顶点的位置进行模型视图投影矩阵的计算,并赋值给最终位置
gl_Position=u_Matrix*vec4(currentPosition, 1.0);
// 定义粒子的大小
gl_PointSize=15.0;
做数学运算时,要确保不会意外地把w分量弄混乱,这是很重要的。因此,我们将用 vec3 表示位置和方向,只有需要把它与 u_Matrix 相乘时,才把它转换为完全的vec 4,这确保上面的数学运算只影响x、y和z分量
片段着色器
precision mediump float;
varying vec3 v_Color;
varying float v_ElapsedTime;
uniform sampler2D u_TextureUnit;
void main(){
gl_FragColor=vec4(v_Color/(v_ElapsedTime+0.0001), 1.0)*texture2D(u_TextureUnit, gl_PointCoord);
}
通过把颜色除以运行时间,会使新的粒子明亮,使旧的粒子暗淡。给分母加上一个很小的数是为了防止除以0的情况,乘以texture2D(u_TextureUnit, gl_PointCoord),会使用 gl_PointCoord 作为纹理坐标在每个点上绘制纹理,纹理的颜色会与点的颜色相乘
封装着色器
public class ParticlesProgram extends BaseProgram {
// uniform的位置
private final int mUMatrix;
private final int mUTime;
private final int mUTextureUnit;
// attribute的位置
private final int mAColor;
private final int mAPosition;
private final int mAStartTime;
private final int mADirection;
public ParticlesProgram(Context context) {
// 在父类中编译、附加、链接着色器程序
super(context, R.raw.particles8_vertex, R.raw.particles8_fragment);
// 获取 uniform 的位置
mUTime = glGetUniformLocation(mProgram, U_TIME);
mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
mUTextureUnit = glGetUniformLocation(mProgram, U_TEXTURE_UNIT);
// 获取 attribute 的位置
mAColor = glGetAttribLocation(mProgram, A_COLOR);
mAPosition = glGetAttribLocation(mProgram, A_POSITION);
mAStartTime = glGetAttribLocation(mProgram, A_START_TIME);
mADirection = glGetAttribLocation(mProgram, A_DIRECTION);
}
/**
* 为各个 uniform 设置值
*
* @param matrix
* @param currentTime
* @param textureId
*/
public void setUniforms(float[] matrix, float currentTime, int textureId) {
glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
glUniform1f(mUTime, currentTime);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureId);
glUniform1i(mUTextureUnit, 0);
}
public int getAColor() {
return mAColor;
}
public int getAPosition() {
return mAPosition;
}
public int getAStartTime() {
return mAStartTime;
}
public int getADirection() {
return mADirection;
}
}
粒子系统
public class ParticleSystem {
private static final int POSITION_COMPONENT_COUNT = 3;
private static final int COLOR_COMPONENT_COUNT = 3;
private static final int DIRECTION_COMPONENT_COUNT = 3;
private static final int START_TIME_COMPONENT_COUNT = 1;
private static final int TOTAL_COMPONENT_COUNT = (POSITION_COMPONENT_COUNT +
COLOR_COMPONENT_COUNT + DIRECTION_COMPONENT_COUNT + START_TIME_COMPONENT_COUNT);
private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constants.BYTES_PER_FLOAT;
// 最大粒子数
private int mMaxParticleCount;
// 当前粒子数
private int mCurrentParticleCount;
// 下一个粒子数据的编号
private int mNextParticle;
// 复制到 native 层的顶点数据
private VertexArray mVertexArray;
// 存储粒子顶点数据的数组
private float[] mData;
public ParticleSystem(int maxParticleCount) {
mMaxParticleCount = maxParticleCount;
mData = new float[mMaxParticleCount * TOTAL_COMPONENT_COUNT];
mVertexArray = new VertexArray(mData);
}
/**
* 添加粒子
* @param position 粒子位置
* @param color 粒子颜色
* @param direction 粒子发射方向
* @param currentTime
*/
public void addParticle(Point position, int color, Vector direction, float currentTime) {
// 在顶点数据数组中的偏移值
int currentOffset = mNextParticle * TOTAL_COMPONENT_COUNT;
// 位置
mData[currentOffset++] = position.getX();
mData[currentOffset++] = position.getY();
mData[currentOffset++] = position.getZ();
// 颜色
mData[currentOffset++] = Color.red(color) / 255f;
mData[currentOffset++] = Color.green(color) / 255f;
mData[currentOffset++] = Color.blue(color) / 255f;
// 方向
mData[currentOffset++] = direction.getX();
mData[currentOffset++] = direction.getY();
mData[currentOffset++] = direction.getZ();
mData[currentOffset++] = currentTime;
// 把这个新的粒子复制到本地缓冲区,以便OpenGL可以存取这些新的数据
mVertexArray.updateBuffer(mData, mNextParticle * TOTAL_COMPONENT_COUNT, TOTAL_COMPONENT_COUNT);
// 自增下一个粒子数据的编号
mNextParticle++;
// 如果下一个粒子数据的编号到了最大值,将其赋值为0,之后添加粒子的话,就又是从头开始存储了
if (mNextParticle >= mMaxParticleCount) {
mNextParticle = 0;
}
// 增加当前的粒子数目,不会大于最大值
if (mCurrentParticleCount < mMaxParticleCount) {
mCurrentParticleCount++;
}
}
/**
* 绑定各个着色器的属性数据
*
* @param particlesProgram
*/
public void bindData(ParticlesProgram particlesProgram) {
//位置
int dataOffset = 0;
mVertexArray.setVertexAttribPointer(
dataOffset,
particlesProgram.getAPosition(),
POSITION_COMPONENT_COUNT,
STRIDE
);
//颜色
dataOffset += POSITION_COMPONENT_COUNT;
mVertexArray.setVertexAttribPointer(
dataOffset,
particlesProgram.getAColor(),
COLOR_COMPONENT_COUNT,
STRIDE
);
//方向
dataOffset += COLOR_COMPONENT_COUNT;
mVertexArray.setVertexAttribPointer(
dataOffset,
particlesProgram.getADirection(),
DIRECTION_COMPONENT_COUNT,
STRIDE
);
//开始时间
dataOffset += DIRECTION_COMPONENT_COUNT;
mVertexArray.setVertexAttribPointer(
dataOffset,
particlesProgram.getAStartTime(),
START_TIME_COMPONENT_COUNT,
STRIDE
);
}
/**
* 绘制粒子
*/
public void draw() {
glDrawArrays(GL_POINTS, 0, mCurrentParticleCount);
}
}
粒子发射器
public class ParticleShooter {
private final Point mPosition;
private final int mColor;
// 控制粒子的角度
private final float mAngleVarianceInDegree;
// 控制粒子的速度
private final float mSpeedVariance;
// 随机生成器
private final Random mRandom = new Random();
// 旋转矩阵
private float[] rotationMatrix = new float[16];
// 方向向量,用于与旋转矩阵相乘,得到旋转过的方向向量
private float[] directionVector = new float[4];
// 保存旋转过的方向向量,之后在改变其速度
private float[] resultVector = new float[4];
public ParticleShooter(Point position, Vector direction, int color, float angleVarianceInDegree, float speedVariance) {
this.mPosition = position;
this.mColor = color;
this.mAngleVarianceInDegree = angleVarianceInDegree;
this.mSpeedVariance = speedVariance;
directionVector[0] = direction.getX();
directionVector[1] = direction.getY();
directionVector[2] = direction.getZ();
}
/**
* 给粒子系统添加粒子
*
* @param particleSystem
* @param currentTime
* @param count
*/
public void addParticle(ParticleSystem particleSystem, float currentTime, int count) {
for (int i = 0; i < count; i++) {
// Matrix.setRotateEulerM创建一个旋转矩阵
Matrix.setRotateEulerM(rotationMatrix, 0,
(mRandom.nextFloat() - 0.5f) * mAngleVarianceInDegree,
(mRandom.nextFloat() - 0.5f) * mAngleVarianceInDegree,
(mRandom.nextFloat() - 0.5f) * mAngleVarianceInDegree
);
// 把旋转矩阵与方向向量相乘得到一个旋转向量
Matrix.multiplyMV(resultVector, 0, rotationMatrix, 0, directionVector, 0);
// 方向向量的每一个分量都与相同的mSpeedVariance的随机调整值相乘
float speedAdjustment = 1f + mRandom.nextFloat() * mSpeedVariance;
Vector direction = new Vector(
resultVector[0] * speedAdjustment,
resultVector[1] * speedAdjustment,
resultVector[2] * speedAdjustment
);
particleSystem.addParticle(mPosition, mColor, direction, currentTime);
}
}
}
修改渲染器
在onSurfaceCreated方法中,创建粒子系统相关对象
// 粒子着色器程序
mParticlesProgram = new ParticlesProgram(mContext);
// 粒子系统,maxParticleCount如果太小的话,可能只能看到比较新的粒子
mParticleSystem = new ParticleSystem(10000);
// 粒子方向
Vector particleDirection = new Vector(0f, 0.5f, 0f);
// 粒子发射器
mRedParticleShooter = new ParticleShooter(new Point(-1f, 0f, 0f), particleDirection, Color.RED, mAngleVarianceInDegree, mSpeedVariance);
mGreenParticleShooter = new ParticleShooter(new Point(0f, 0f, 0f), particleDirection, Color.GREEN, mAngleVarianceInDegree, mSpeedVariance);
mBlueParticleShooter = new ParticleShooter(new Point(1f, 0f, 0f), particleDirection, Color.BLUE, mAngleVarianceInDegree, mSpeedVariance);
// 粒子系统启动的时间
mGlobalStartTime = System.nanoTime();
// 使用图片的样式绘制粒子
mTextureId = TextureHelper.loadTexture(mContext, R.drawable.particle_texture);
在onDrawFrame方法中,添加并绘制粒子
// 创建并添加粒子
float currentTime = (System.nanoTime() - mGlobalStartTime) / 1000000000f;
Log.d("TAG", "addParticle: currentTime=" + currentTime);
mRedParticleShooter.addParticle(mParticleSystem, currentTime, 5);
mGreenParticleShooter.addParticle(mParticleSystem, currentTime, 5);
mBlueParticleShooter.addParticle(mParticleSystem, currentTime, 5);
// 累计混合技术,粒子越多,就越亮
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
mParticlesProgram.useProgram();
// 设置uniform的值
mParticlesProgram.setUniforms(modelViewProjectionMatrix, currentTime, mTextureId);
// 绑定粒子系统数据
mParticleSystem.bindData(mParticlesProgram);
// 绘制粒子系统的所有粒子
mParticleSystem.draw();
// 一次绘制完成后,关闭累计混合技术
glDisable(GL_BLEND);