\最近研究了一下OpenGL ES3.0,参照了网上很多的示例,在这里总结一部分经验
1)数据
OpenGL ES3.0 中的3D数据模型是由无数个三角形组成的。例如下面这段数据,就是一个绘制立方体的模型。float数组中每个三个元素代表一个顶点(x,y, z),没三个顶点(九个元素)代表一个三角形。opengles 的数据特点是在-1到1的范围表示整个空间。下面的数据就是用来绘制一个长宽高都占半个空间的立方体。
float vertexes[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
float[] colors = {
1f, 0f, 0f, 1f,
1f, 0f, 0f, 1f,
1f, 0f, 0f, 1f,
1f, 0f, 0f, 1f,
1f, 0f, 0f, 1f,
1f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 0f, 1f,
0f, 0f, 1f, 1f,
0f, 0f, 1f, 1f,
0f, 0f, 1f, 1f,
0f, 0f, 1f, 1f,
0f, 0f, 1f, 1f,
0f, 0f, 1f, 1f,
0.2f, 1f, 0.2f, 1f,
0.2f, 1f, 0.2f, 1f,
0.2f, 1f, 0.2f, 1f,
0.2f, 1f, 0.2f, 1f,
0.2f, 1f, 0.2f, 1f,
0.2f, 1f, 0.2f, 1f,
1f, 1f, 1f, 1f,
1f, 1f, 1f, 1f,
1f, 1f, 1f, 1f,
1f, 1f, 1f, 1f,
1f, 1f, 1f, 1f,
1f, 1f, 1f, 1f,
0.3f, 0.4f, 0.5f, 1f,
0.3f, 0.4f, 0.5f, 1f,
0.3f, 0.4f, 0.5f, 1f,
0.3f, 0.4f, 0.5f, 1f,
0.3f, 0.4f, 0.5f, 1f,
0.3f, 0.4f, 0.5f, 1f
};
这里先把数据加载到 FloatBuffer 中
vertexBuffer=loadData(vertexes);
colorBuffer=loadData(colors);
private FloatBuffer loadData(float [] arr){
ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer floatBuffer = bb.asFloatBuffer();
floatBuffer.put(arr);
floatBuffer.position(0);
return floatBuffer;
}
那么opengl es 是怎么获取这些数据的呢,就是用顶点着色器,下面是一段顶点着色器的源码,3.0必须指定版本好,在Android api里面是直接把顶点着色器的源码以String的类型读取,可以放在文件中,也可以放在String对象里。下面的这段源码 layout 关键字后面的括号中的location 是在载入数据的时候指定的 ,in 就是指数据是输入数据。 vec3 是一种类型,可以理解为矢量数据,用来存放顶点数据。out 是指输出,将顶点着色器的颜色输出给片源着色器。片源着色器把顶点着色器中传递进来的颜色输出。
private final String vertexShaderCode ="//指定版本号\n" +
"#version 300 es\n" +
"uniform mat4 transform;" +
"layout (location = 0) in vec3 aPos;\n" +
"layout (location = 1) in vec4 aColor;\n" +
"out vec4 color;\n" +
"void main()\n" +
"{\n" +
" // gl_Position (内置函数) 赋值位置\n" +
" gl_Position = transform*vec4(aPos, 1.0);\n" +
" color = aColor;\n" +
"}";
private final String fragmentShaderCode =
"#version 300 es\n" +
"precision mediump float;\n" +
"\n" +
"out vec4 fragColor;\n" +
"in vec4 color;\n" +
"void main()\n" +
"{\n" +
" fragColor = color;\n" +
"}" ;
那么这些数据如何读取,就使用下面的代码,mProgram是一个int类型,相当于顶点着色器的位置指示。显示载入着色器的源码,这里面GL_VERTEX_SHADER 是顶点着色器用于绘制顶点, GL_FRAGMENT_SHADER是 片源着色器用于绘制颜色。
然后是一个着色器的程序,如果不为0级说明创建成功了,之后的代码是连接着色器。连接后会有一个状态数组返回,第一个用元素就是状态。载入着色器的程序也类似,先调用载入源码,之后编译源码,最后看返回的数据,如果编译成功就不为0.这里的返回数据就用来传递给着色器程序。(vertexShader 和framentShader中存的int值) 。最后要配置使程序相应指定的数据,第一个参数就是之前在着色器中指定的location (GLES30.glVertexAttribPointer(0, VERTEX_POSITION_SIZE, GLES30.GL_FLOAT, false, 0, vertexBuffer); GLES30.glEnableVertexAttribArray(0);) 代码里从程序中读取了两套,一个是用于绘制顶点,一个是用于绘制颜色, 就是对应顶点着色器中 in 关键字修饰的 vec 。 这里准备工作就做好了。下面就是绘制。
private void initProgram(){
int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER,vertexShaderCode);
int fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER,fragmentShaderCode);
mProgram = GLES30.glCreateProgram();
if(mProgram != 0 ){
GLES30.glAttachShader(mProgram,vertexShader);
GLES30.glAttachShader(mProgram,fragmentShader);
GLES30.glLinkProgram(mProgram);
int[] linkStatus = new int[1];
GLES30.glGetProgramiv(mProgram, GLES30.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES30.GL_TRUE) {
Log.e(TAG, "连接着色器失败 ");
Log.e(TAG, GLES30.glGetProgramInfoLog(mProgram));
GLES30.glDeleteProgram(mProgram);
mProgram = 0;
}
}
GLES30.glVertexAttribPointer(0, VERTEX_POSITION_SIZE, GLES30.GL_FLOAT, false, 0, vertexBuffer);
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(1, VERTEX_COLOR_SIZE, GLES30.GL_FLOAT, false, 0, colorBuffer);
GLES30.glEnableVertexAttribArray(1);
}
private int loadShader(int type,String shaderCode){
int shader = GLES30.glCreateShader(type);
if(shader != 0 ){
GLES30.glShaderSource(shader,shaderCode);
GLES30.glCompileShader(shader);
int[] compiled = new int[1];
GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
GLES30.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
绘制的代码,这里的transform 是用于图形变换的,在顶点着色器的源码中可以看到他是一个mat4类型,他是一个矩阵,网上有大量的文章说明了opengl es中利用矩阵运算,进行图形转换的示例,在顶点着色器源码中也可以看到,是做了矩阵的相乘。这里opengl es的api中也提供了相应生成变换矩阵的方法。 setIdentityM 就相当于声明了一个矩阵,这个矩阵的需要我们提前在内存中创建,它的长度是固定的。然后 调用 gluniformmatrix4fv就相当于把 modelMatrix的数据传递给 顶点着色器中与顶点数据相乘的transform 。
int transform = GLES30.glGetUniformLocation(mProgram, "transform");
GLES30.glUniformMatrix4fv(transform, 1, false, modelMatrix, 0);
GLES30.glClearColor(0.2f, 0.1f, 0.3f, 1.0f);
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
GLES30.glUseProgram(mProgram);
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36);
modelMatrix =new float[16];
Matrix.setIdentityM(modelMatrix, 0);
Matrix.rotateM(modelMatrix, 0, 30, 0f, 1f, 0f);
//
Matrix.rotateM(modelMatrix, 0, 30, 0f, 0f, 1f);