刚学OpenGL 2个多星期,也算是入门了吧
在看了老外写的书 OpenGL ES 2 for Android A Quick - Start Guide.pdf
后,由于这本书上给的代码不够全,
自己功力太浅,对于一些了解有点疑惑,
费了差不多两天才调出想要的结果,下面就分享一下我的经历,若有问题,希望大家多多交流
本代码中包含的内容还是比较多的,使用OpenGL ES2.0编写,包含有顶点 shader和片段shader,
还包含了VBO(vertex buffer object),这里是我当时最疑惑的地方,因为对VBO的用法不熟悉,上网搜索了导致出不来结果,
后来还是在一个外国网站上找到答案的。
粒子系统是通过将时间作为一个变量绑定到绘制的点的位置信息中,由于时间是在流动的,因此粒子也会跟着在移动;从而就形成了粒子流,这里比较难理解
不废话了,直接上代码吧;
本程序代码结构:
先看MainActivity.java
package com.cxy.particles;
import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends Activity {
GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glSurfaceView = new GLSurfaceView(this);
// 设置OpenGLES版本为2.0
glSurfaceView.setEGLContextClientVersion(2);
// 设置渲染器 渲染模式
MyRenderer mRenderer = new MyRenderer(this,glSurfaceView);
glSurfaceView.setRenderer(mRenderer);
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
// 显示渲染内容
setContentView(glSurfaceView);
}
}
最重要的渲染的文件MyRenderer.java
package com.cxy.particles;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Color;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
public class MyRenderer implements GLSurfaceView.Renderer {
GLSurfaceView glView;
private Context context;
//视图矩阵
private final float[] projectionMatrix=new float[16];
private final float[] viewMatrix=new float[16];
private final float[] viewProjectionMatrix=new float[16];
private ParticleShaderProgram particleProgram;
private ParticleSystem particleSystem;
private ParticleShooter redParticleShooter;
private ParticleShooter greenParticleShooter;
private ParticleShooter blueParticleShooter;
private long globalStartTime;
//private float globalStartTime;
public MyRenderer(Context context,GLSurfaceView view){
//保存上下文,个GLSurfaceView;
this.context=context;
this.glView = view;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置屏幕背景色
GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);
//获取GLSL脚本语言编译好后的programID
particleProgram=new ParticleShaderProgram(context,glView);
//获取粒子系统的实例,初始化粒子系统包含10000个粒子
particleSystem=new ParticleSystem(10000);
//获取系统时间
globalStartTime=System.nanoTime();
//globalStartTime = System.nanoTime()/1000000000f;
//设置粒子发射的方向,Y轴向上
final Vector3 particleDirection=new Vector3(0f,0.5f,0f);
//新建粒子发射器(红色粒子),获取实例
redParticleShooter=new ParticleShooter(
new Point3(-1f,0f,0f),
particleDirection,
Color.rgb(255,50,5));
//新建粒子发射器(绿色粒子),获取实例
greenParticleShooter=new ParticleShooter(
new Point3(0f,0f,0f),
particleDirection,
Color.rgb(25,255,25));
//新建粒子发射器(蓝色粒子),获取实例
blueParticleShooter=new ParticleShooter(
new Point3(1f,0f,0f),
particleDirection,
Color.rgb(5,50,255));
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置当前的视点适应新的尺寸
GLES20.glViewport(0,0,width,height);
Matrix.perspectiveM(
projectionMatrix,
0,
45,
(float)width/(float)height,
1f,
10f);
Matrix.setIdentityM(viewMatrix,0);
Matrix.translateM(viewMatrix,0,0f,-1.5f,-5f);
//设置最终的视点
Matrix.multiplyMM(
viewProjectionMatrix,
0,
projectionMatrix,
0,
viewMatrix,
0);
}
@Override
public void onDrawFrame(GL10 gl) {
//清除位缓存
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
float currentTime=(System.nanoTime()-globalStartTime)/1000000000f;
//float currentTime=(System.nanoTime()/1000000000f);
//Log.d("cxy", "CurrentTime = "+currentTime);
/*为粒子发射器添加粒子*/
redParticleShooter.addParticles(particleSystem,currentTime,5);
greenParticleShooter.addParticles(particleSystem,currentTime,5);
blueParticleShooter.addParticles(particleSystem,currentTime,5);
/*redParticleShooter.addParticles(particleSystem,globalStartTime,5);
greenParticleShooter.addParticles(particleSystem,globalStartTime,5);
blueParticleShooter.addParticles(particleSystem,globalStartTime,5);*/
//使用着色器绘制粒子,
particleProgram.useProgram();
//这是
particleProgram.setUniforms(viewProjectionMatrix,currentTime);
/**必须在USE progrem之后,才能进行数据绑定,即操作glVertexAttribPointer之类的函数
* bindData()将所有的数据:粒子顶点数据、颜色数据、方向数据、时间捆绑在一个VBO中,然后
* 通过这个VBO绘制的,
**/
particleSystem.bindData(particleProgram);
//绘制粒子
particleSystem.draw(particleProgram);
}
}
在上面的OnSurfaceCreate中,新建的
ParticleShaderProgram实例,下面看看这个类是怎么实现的
ParticleShaderProgram.java
package com.cxy.particles;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
public class ParticleShaderProgram {
protected static final String U_TIME = "u_Time";
protected static final String U_MATRIX = "u_Matrix";
protected static final String A_POSITION = "a_Position";
protected static final String A_COLOR = "a_Color";
protected static final String A_DIRECTION_VECTOR = "a_DirectionVector";
protected static final String A_PARTICLE_START_TIME = "a_ParticleStartTime";
// Uniformlocations
private int uMatrixLocation;
private int uTimeLocation;
// Attributelocations
private int aPositionLocation;
private int aColorLocation;
private int aDirectionVectorLocation;
private int aParticleStartTimeLocation;
// Shader着色器的代码
private String mVertexShader;
private String mFragmentShader;
// program ID;
int mProgram;
/**
* 构造方法
*
* @param mv GLSurfaceView子类对象, 显示3D画面的载体
*/
public ParticleShaderProgram(Context context,GLSurfaceView mv) {
initShader(mv);
}
/**
* 初始化着色器
*
* 流程 : ① 从资源中获取顶点 和 片元着色器脚本 ② 根据获取的顶点 片元着色器脚本创建着色程序 ③ 从着色程序中获取顶点位置引用 ,
* 顶点颜色引用, 总变换矩阵引用
*
* @param mv
* MyTDView对象, 是GLSurfaceView对象
*/
public void initShader(GLSurfaceView mv) {
/*mVertextShader是顶点着色器脚本代码 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中
* 传入的两个参数是 脚本名称 和 应用的资源 应用资源Resources就是res目录下的那写文件
*/
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh",
mv.getResources());
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh",
mv.getResources());
/*
* 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错
*/
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
/*
* 从着色程序中获取一致变量
*/
uMatrixLocation = GLES20.glGetUniformLocation(mProgram, U_MATRIX);
uTimeLocation = GLES20.glGetUniformLocation(mProgram, U_TIME);
/*
* 从着色程序中获取 属性变量 ,数据引用 其中的"aPosition"是顶点着色器中的顶点位置信息
* 其中的"aColor"是顶点着色器的颜色信息等,返回一个ID,openGL程序就通过这个ID与GLSL
* 进行数据交互
*/
aPositionLocation = GLES20.glGetAttribLocation(mProgram, A_POSITION);
aColorLocation = GLES20.glGetAttribLocation(mProgram, A_COLOR);
aDirectionVectorLocation = GLES20.glGetAttribLocation(mProgram,
A_DIRECTION_VECTOR);
aParticleStartTimeLocation = GLES20.glGetAttribLocation(mProgram,
A_PARTICLE_START_TIME);
}
//使用GLSL程序
public void useProgram(){
GLES20.glUseProgram(mProgram);
}
public void setUniforms(float[] matrix, float elapsedTime) {
//将ViewPort矩阵变量传递给顶点Shader中的u_Matrix
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);
//将GlobalTime传递给顶点Shader的u_Time
GLES20.glUniform1f(uTimeLocation, elapsedTime);
}
//获取与GLSL交互的点的位置变量的ID
public int getPositionAttributeLocation() {
return aPositionLocation;
}
//获取与GLSL交互的点的颜色变量的ID
public int getColorAttributeLocation() {
return aColorLocation;
}
//获取与GLSL交互的点的方向变量的ID
public int getDirectionVectorAttributeLocation() {
return aDirectionVectorLocation;
}
//获取与GLSL交互的点的时间变量的ID
public int getParticleStartTimeAttributeLocation() {
return aParticleStartTimeLocation;
}
}
ParticleShooter.java
package com.cxy.particles;
public class ParticleShooter {
private final Point3 position;
private final Vector3 direction;
private final int color;
/**
* 创建粒子流
* @1:位置桌标
* @2:粒子流的放向量坐标
* @3:颜色
**/
public ParticleShooter(Point3 position,Vector3 direction,int color){
this.position=position;
this.direction=direction;
this.color=color;
}
public void addParticles(ParticleSystem particleSystem,float currentTime,
int count){
for(int i=0;i
ParticleSystem.java
package com.cxy.particles;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Vector;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.graphics.Point;
import android.opengl.GLES20;
import android.util.Log;
public class ParticleSystem {
private static final int BYTES_PER_FLOAT = 4;
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 final float[] particles;
// private final VertexArray vertexArray;
private final int maxParticleCount;
private int currentParticleCount;
private int nextParticle = 0;
// Used for VBO
private int[] vaoID;
private int[] vboID = new int[1];
private FloatBuffer vertexArrayBuffer;
private int dataOffset = 0;
public ParticleSystem(int maxParticleCount) {
particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT];
this.maxParticleCount = maxParticleCount;
/*包装particles成为FloatBuffer*/
// 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
// 数组排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
vertexArrayBuffer = qbb.asFloatBuffer();
}
public void addParticle(Point3 position, int color, Vector3 direction,
float particleStartTime) {
int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT;
int currentOffset = particleOffset;
nextParticle++;
//计算用于glDrawArrays的最后一个参数
if (currentParticleCount < maxParticleCount) {
currentParticleCount++;
}
if (nextParticle == maxParticleCount) {
// Start over at the beginning,but keep current ParticleCount so
// that all the other particles still get drawn.
nextParticle = 0;
}
particles[currentOffset++] = position.Px;
particles[currentOffset++] = position.Py;
particles[currentOffset++] = position.Pz;
particles[currentOffset++] = Color.red(color) / 255f;
particles[currentOffset++] = Color.green(color) / 255f;
particles[currentOffset++] = Color.blue(color) / 255f;
particles[currentOffset++] = direction.Vx;
particles[currentOffset++] = direction.Vy;
particles[currentOffset++] = direction.Vz;
particles[currentOffset++] = particleStartTime;
//Log.d("cxy", "currentOffset ("+currentOffset +")"+"maxParticleCount("+ maxParticleCount+")" );
//更新vertexArrayBuffer的数据
vertexArrayBuffer.position(particleOffset);
//Log.d("cxy", "ramining ("+vertexArrayBuffer.remaining() +")");
vertexArrayBuffer.put(particles, particleOffset, TOTAL_COMPONENT_COUNT);
vertexArrayBuffer.position(0);
}
public void bindData(ParticleShaderProgram particleProgram) {
int dataOffset = 0;
// 将particles包含的package数组包装成FloatBuffer
/*vertexArrayBuffer = floatBufferUtil(particles);
vertexArrayBuffer.put(particles);
vertexArrayBuffer.position(0);*/
// 创建VBO
GLES20.glGenBuffers(1, vboID, 0);
// 绑定VBO
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID[0]);
// 绑定数据,最后一个参数要改为GL_DYNAMIC_DRAW,表示数据是在变化的
//Log.d("cxy", "vertexArrayBuffer.capacity ( "+vertexArrayBuffer.capacity()+")");
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
vertexArrayBuffer.capacity() * BYTES_PER_FLOAT,
vertexArrayBuffer, GLES20.GL_DYNAMIC_DRAW);
//启用顶点位置数据的 属性数组
GLES20.glEnableVertexAttribArray(particleProgram
.getPositionAttributeLocation());
GLES20.glVertexAttribPointer(
particleProgram.getPositionAttributeLocation(),
POSITION_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += POSITION_COMPONENT_COUNT * BYTES_PER_FLOAT;
GLES20.glEnableVertexAttribArray(particleProgram
.getColorAttributeLocation());
GLES20.glVertexAttribPointer(
particleProgram.getColorAttributeLocation(),
COLOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += COLOR_COMPONENT_COUNT * BYTES_PER_FLOAT;
GLES20.glEnableVertexAttribArray(particleProgram
.getDirectionVectorAttributeLocation());
GLES20.glVertexAttribPointer(
particleProgram.getDirectionVectorAttributeLocation(),
VECTOR_COMPONENT_COUNT, GLES20.GL_FLOAT, false, STRIDE,
dataOffset);
dataOffset += VECTOR_COMPONENT_COUNT * BYTES_PER_FLOAT;
GLES20.glEnableVertexAttribArray(particleProgram
.getParticleStartTimeAttributeLocation());
GLES20.glVertexAttribPointer(
particleProgram.getParticleStartTimeAttributeLocation(),
PARTICLE_START_TIME_COMPONENT_COUNT, GLES20.GL_FLOAT, false,
STRIDE, dataOffset);
// 这里很重要,处理完后,需要解除数据绑定
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
}
public void draw(ParticleShaderProgram particleProgram) {
//开始绘制这些粒子(点)
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);
/**
* 这里非常非常的重要,不加上这句话,会照成内存泄露,程序运行几秒就蹦了
* 猜测可能是GLES20.glBufferData()函数里有申请内存的语句,开始以为解除
* 绑定就可以了即(GLES20.glBindBuffer),但是发现结果还是一样,后来上
* 官网(http://www.khronos.org/opengles/sdk/docs/man/)查看发现如下:
* "glBufferData creates a new data store for the buffer object currently bound to target"
* 应该要使用GLES20.glDeleteBuffers才能释放内存
*/
GLES20.glDeleteBuffers(1, vboID, 0);
GLES20.glDisableVertexAttribArray(particleProgram
.getPositionAttributeLocation());
GLES20.glDisableVertexAttribArray(particleProgram
.getColorAttributeLocation());
GLES20.glDisableVertexAttribArray(particleProgram
.getDirectionVectorAttributeLocation());
GLES20.glDisableVertexAttribArray(particleProgram
.getParticleStartTimeAttributeLocation());
}
// 定义一个工具方法,将float[]buffer数据转换为OpenGL ES所需要的FloatBuffer
public FloatBuffer floatBufferUtil(float[] arr) {
FloatBuffer mbuffer;
// 初始化ByteBuffer,长度为arr.length * 4,因为float占4个字节
ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 4);
// 数组排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
mbuffer = qbb.asFloatBuffer();
mbuffer.put(arr);
mbuffer.position(0);
return mbuffer;
}
}
VBO将数据打包:
这里用到了一个 VBO,并且使用了将所有数据打包在一个buffer中处理,刚开始在调试的时候,由于对VBO的使用不是很清楚,犯下了很多错误,而在网上找到的也大都是Windows版本的,和Android上的用法还是有区别的,最终在一个外国网站上找到了解决思路;
http://www.learnopengles.com/android-lesson-seven-an-introduction-to-vertex-buffer-objects-vbos/
这个网站,讲解的很不错,分类讲解了
VBO处理单个数据和 VBO将数据打包在一起的使用
在了解这个内容之前,如果你对shader不熟悉,建议你看一本书,专门讲解OpenGL ES2.0 GLSL使用的,在这里附上下载地址: http://download.csdn.net/detail/cxy200927099/7733243 这个还是中文版的,讲的很详细
这里我就只说一下VBO将数据打包在一起的用法:
前提需要声明的变量:
private int[] vboID = new int[1];
private FloatBuffer vertexArrayBuffer;
private int[] vboID = new int[1];
private FloatBuffer vertexArrayBuffer;
为FloatBuffer分配空间:
ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
// 数组排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
vertexArrayBuffer = qbb.asFloatBuffer();
ByteBuffer qbb = ByteBuffer.allocateDirect(maxParticleCount * 4 *TOTAL_COMPONENT_COUNT);
// 数组排列用nativeOrder
qbb.order(ByteOrder.nativeOrder());
vertexArrayBuffer = qbb.asFloatBuffer();
关于怎么把
顶点位置坐标、方向坐标、颜色、时间等打包在一起到
vertexArrayBuffer
请直接参考代码
VBO用法流程:
1.创建VBO:
GLES20.glGenBuffers(1, vboID, 0);
2.绑定VBO:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID[0]);
3.绑定数据,最后一个参数要改为GL_DYNAMIC_DRAW,表示数据是在变化的
这里既是将
vertexArrayBuffer绑定了
GL_ARRAY_BUFFER,而
vertexArrayBuffer的内容既是我们在OpenGL程序里面添加包括,顶点位置坐标、方向坐标、颜色、时间等打包在一起的数据包
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
vertexArrayBuffer.capacity() * BYTES_PER_FLOAT,
vertexArrayBuffer, GLES20.GL_DYNAMIC_DRAW);
4.解除绑定:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
将打包的VBO拆分传递给 shader:
具体怎么将这些数据从VBO Buffer中拆分出来传递给 GLSL shader,我们继续分析:
其实就是采用了
GLES20.glVertexAttribPointer()函数,但是这个函数有两个原形:
public static native void glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
int offset
);
public static void glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
java.nio.Buffer ptr
) {
glVertexAttribPointerBounds(
indx,
size,
type,
normalized,
stride,
ptr,
ptr.remaining()
);
}
这两个函数就只有最后一个参数不同,
@1: 既是GLES20.glGetAttribLocation()的返回值,可理解为表示和shader中的变量交互的句柄
@2: 每一个顶点数据包含的 字节:只能是1,2,3,4;
@3: 数组的类型,如
GL_BYTE,
GL_UNSIGNED_BYTE,
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_FIXED, or
GL_FLOAT,默认的是GL_FLOAT;
@4: 是否将数据归一化处理,GL_TRUE表示归一化,GL_FALSE表示不归一化
@5: stride表示紧密联系的数组的字节偏移,0,表示最后数组数据时紧密联系在一起
像我们这里
传递顶点位置属性的时候,stride = 顶点位置大小(byte)
传递顶点方向属性的时候,
stride =
方向大小
(byte)
传递顶点颜色属性的时候,
stride =
颜色大小
(byte)
传递顶点时间属性的时候,
stride =
时间大小
(byte)
@6:第二个函数比较好理解,最后一个参数就是上面定义的
vertexArrayBuffer;即理解为把这个相关联的数组传递给shader,
而在:第一个函数的,最后一个是偏移量;
刚开始不是很理解,仅仅传偏移量进去,他是怎么关联Buffer的,后来才慢慢理解GLES20.glBufferData()函数
关联了Buffer数组,因此这个glVertexAttribPointer()函数在使用的时候,必须搭配着VBO的整套流程使用,
即先创建VBO,然后绑定name,绑定数据,解除绑定
而以往我们使用glVertexAttribPointer()函数的时候,不使用VBO时,我们传递的最后一个参数都是直接传的Buffer实例,当时在网上收集资料,看到的大都是C++写的
绑定完成之后,就可在调用
GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount);绘制点了
关于一些参数,定义请参见上面.java文件中较全的代码:
注意:这里会有个问题,当我们创建VBO的时候,在绑定数据时
GLES20.glBufferData()这个函数申请了内存空间,而我原以为解除绑定后,应该就会释放空间了,但事实上并非如此:
经过我亲自测试,发现此时会照成内存泄露
唯有:调用
GLES20.glDeleteBuffers(1, vboID, 0);内存空间才会释放,
因此,最后一定记得调用
glDeleteBuffers()函数,具体参见代码原文。
相关的API的查询: http://www.khronos.org/opengles/sdk/docs/man/
ShaderUtil shader使用小工具类
package com.cxy.particles;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
/*
* 这个工具类用来加载定点着色器与片元着色器
*/
public class ShaderUtil {
/**
* 加载着色器方法
*
* 流程 :
*
* ① 创建着色器
* ② 加载着色器脚本
* ③ 编译着色器
* ④ 获取着色器编译结果
*
* @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
* @param source 着色脚本字符串
* @return 返回的是着色器的引用, 返回值可以代表加载的着色器
*/
public static int loadShader(int shaderType , String source){
//1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
int shader = GLES20.glCreateShader(shaderType);
if(shader != 0){
//2.如果着色器创建成功, 为创建的着色器加载脚本代码
GLES20.glShaderSource(shader, source);
//3.编译已经加载脚本代码的着色器
GLES20.glCompileShader(shader);
checkGLError("glCompileShader");
int[] compiled = new int[1];
//4.获取着色器的编译情况, 如果结果为0, 说明编译失败
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if(compiled[0] == 0){
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", "ERROR: "+GLES20.glGetShaderInfoLog(shader));
//编译失败的话, 删除着色器, 并显示log
GLES20.glDeleteShader(shader);
shader = 0;
}
}
else{
Log.e("ES20_ERROR", "Could not Create shader " + shaderType + ":"+
"Error:"+ shader);
}
return shader;
}
/**
* 检查每一步的操作是否正确
*
* 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
*
* @param op 具体执行的方法名, 比如执行向着色程序中加入着色器,
* 使glAttachShader()方法, 那么这个参数就是"glAttachShader"
*/
public static void checkGLError(String op){
int error;
//错误代码不为0, 就打印错误日志, 并抛出异常
while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){
Log.e("ES20_ERROR", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
/**
* 创建着色程序
*
* ① 加载顶点着色器
* ② 加载片元着色器
* ③ 创建着色程序
* ④ 向着色程序中加入顶点着色器
* ⑤ 向着色程序中加入片元着色器
* ⑥ 链接程序
* ⑦ 获取链接程序结果
*
* @param vertexSource 定点着色器脚本字符串
* @param fragmentSource 片元着色器脚本字符串
* @return
*/
public static int createProgram(String vertexSource , String fragmentSource){
//1. 加载顶点着色器, 返回0说明加载失败
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if(vertexShader == 0)
{
Log.e("ES20_ERROR", "加载顶点着色器失败");
return 0;
}
//2. 加载片元着色器, 返回0说明加载失败
int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if(fragShader == 0)
{
Log.e("ES20_ERROR", "加载片元着色器失败");
return 0;
}
//3. 创建着色程序, 返回0说明创建失败
int program = GLES20.glCreateProgram();
if(program != 0){
//4. 向着色程序中加入顶点着色器
GLES20.glAttachShader(program, vertexShader);
//检查glAttachShader操作有没有失败
checkGLError("glAttachShader");
//5. 向着色程序中加入片元着色器
GLES20.glAttachShader(program, fragShader);
//检查glAttachShader操作有没有失败
checkGLError("glAttachShader");
//6. 链接程序
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
//获取链接程序结果
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if(linkStatus[0] != GLES20.GL_TRUE){
Log.e("ES20.ERROR", "链接程序失败 : ");
Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));
//如果链接程序失败删除程序
GLES20.glDeleteProgram(program);
program = 0;
}
}
else{
Log.e("ES20_ERROR", "glCreateProgram Failed: "+program);
}
return program;
}
/**
* 从assets中加载着色脚本
*
* ① 打开assets目录中的文件输入流
* ② 创建带缓冲区的输出流
* ③ 逐个字节读取文件数据, 放入缓冲区
* ④ 将缓冲区中的数据转为字符串
*
* @param fileName assets目录中的着色脚本文件名
* @param resources 应用的资源
* @return
*/
public static String loadFromAssetsFile(String fileName, Resources resources){
String result = null;
try {
//1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流
InputStream is = resources.getAssets().open(fileName);
int ch = 0;
//2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//3. 逐个字节读取数据, 并将读取的数据放入缓冲器中
while((ch = is.read()) != -1){
baos.write(ch);
}
//4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
byte[] buffer = baos.toByteArray();
baos.close();
is.close();
result = new String(buffer, "UTF-8");
result = result.replaceAll("\\r\\n", "\n");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
Vector3向量
package com.cxy.particles;
public class Vector3{
protected float Vx;
protected float Vy;
protected float Vz;
public Vector3(float x,float y,float z){
Vx = x;
Vy = y;
Vz = z;
};
}
Point3向量
package com.cxy.particles;
public class Point3{
protected float Px;
protected float Py;
protected float Pz;
public Point3(float x,float y,float z){
Px = x;
Py = y;
Pz = z;
};
}
下面附上Shader的代码:
顶点Shader:
uniform mat4 u_Matrix; //定义粒子接收openGL代码传过来的参数
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; //传递粒子颜色给FragShader
v_ElapsedTime=u_Time-a_ParticleStartTime;//计算当前过了多少时间
vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapsedTime);
gl_Position=u_Matrix*vec4(currentPosition,1.0);
gl_PointSize=10.0; //set particles size is 10 pixels
}
片段shader:
precision mediump float;
varying vec3 v_Color;
varying float v_ElapsedTime;
void main()
{
gl_FragColor=vec4(v_Color/v_ElapsedTime,1.0);
}
最让我困惑的就是这里,我们看到在顶点shader中
v_ElapsedTime=u_Time-a_ParticleStartTime;
而u_Time和a_ParticleStartTime这两个值从OpenGL 程序传进来的都是同一个值;
代码如下:
/*为粒子发射器添加粒子,currentTime传递到shader中的a_ParticleStartTime变量*/
redParticleShooter.addParticles(particleSystem,currentTime,5);
greenParticleShooter.addParticles(particleSystem,currentTime,5);
blueParticleShooter.addParticles(particleSystem,currentTime,5);
//使用着色器绘制粒子,
particleProgram.useProgram();
//这是传递到shader中的u_Time的变量
particleProgram.setUniforms(viewProjectionMatrix,currentTime);
这两个值既然一样了,在顶点shader中就相当于v_ElapsedTime等于0,currentPosition变量也应该等于零,因此可以,这样改一下shader:
vec3 currentPosition=a_Position+(a_DirectionVector*v_ElapsedTime);
改为:==> vec3 currentPosition=a_Position;
片段 shader中:
gl_FragColor=vec4(v_Color/v_ElapsedTime,1.0);
改为:===> gl_FragColor=vec4(v_Color,1.0);
理论上应该是这样的,但实际结果得不到想要的结果,还希望大神指点指点?????
程序最终的运行结果:
代码下载地址:
Android OpenGL ES2.0粒子系统
http://download.csdn.net/detail/cxy200927099/7759485