首先我们需要知道opengles在屏幕上的坐标,即中间坐标点是x=0,y=0; x轴往右是正,往左是负,y轴往上是正,往下是负,且x, y的范围都是-1到1;所以我们先定义坐标数组,顺序是逆时针方向:
private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f};
为了最大限度地提高效率,我们可以将这些坐标写入FloatBuffer,然后将其传递给OpenGL ES图形管道进行处理:
private FloatBuffer mTriangleCoordBuffer;
public Triangle() {
// 数组转化为buffer,提高opengles性能
mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
// 设置从第一位开始读
mTriangleCoordBuffer.position(0);
}
现在我们要把这些坐标数据传给opengles图形管道,那么我们怎么传呢,这里需要用到shader,即是着色器,opengles里分为两种shader,一种是vertex shader(我们的顶点坐标就是传到这里), 另一种是fragment shader(我们的颜色数据传到这),并且由opengles特有的语言把这两种shader组织起来,下面我们来看看例子:
private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
它们的写法类似于C语言,叫做GLES语言,当有一些自己独有的东西;
attribute仅在vertex shader中使用,常用于指出vertex shader的输入数据;
vec4代表这个向量里有四个float型的数据;
vPosition是我们自己起的一个变量名;
gl_Position是opengles的特有名词,指的是顶点的坐标;
uniform用于存储shader需要的各种数据,例如转换矩阵、光线参数和颜色等。可以被vertex shader 和 fragment shader 共享使用;
gl_FragColor也是opengles特有的名词,指的是颜色;
好了,写完GLES代码,我们需要把代码编译后传给opengles,下面看下该怎么做:
我们先写一个加载shader的方法
private int getShader(int type, String source) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
// 获取编译的状态
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
compileStatus, 0);
Log.v(TAG, "代码编译结果:" + "\n" + source
+ "\n:" + GLES20.glGetShaderInfoLog(shader));
if (compileStatus[0] == 0) {
Log.w(TAG, "编译失败!.");
return 0;
}
return shader;
}
接着把得到的shader和program连接,这样opengles就知道该怎么处理我们的着色逻辑了;
private int mVertexShader, mFragmentShader;
public Triangle() {
// ...
// 得到编译后的shader
mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
// 创建并连接program
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, mVertexShader);
GLES20.glAttachShader(program, mFragmentShader);
GLES20.glLinkProgram(program);
GLES20.glUseProgram(program);
}
我们之前定义好的顶点buffer该怎么传进去给opengles呢,这里我们就需要先找到对应的变量的locaction:
private static final String POSITION_NAME = "vPosition";
private static final String COLOR_NAME = "vColor";
public Triangle() {
// ...
// 找到对应变量的index
mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
}
传入数据,就可以基本完成我们的绘图了:
public void draw() {
// 由于性能方面的考虑,opengles默认读取不到缓冲区里的数据,需要先enable后再读
GLES20.glEnableVertexAttribArray(mPositionLocation);
GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);
GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);
// 调用完glDrawArrays后才能disable;
GLES20.glDisableVertexAttribArray(mPositionLocation);
}
这里给出较完整的代码:
public class MyRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTriangle.draw();
}
}
public class Triangle {
private static final String TAG = "Triangle";
// 三角形的三个顶点
private static final float[] TRIANGLE_VERTEX_COORD = {0, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f};
private static final int VERTEX_SIZE = 3;
private static final int COORDS_PER_VERTEX = TRIANGLE_VERTEX_COORD.length / VERTEX_SIZE;
private FloatBuffer mTriangleCoordBuffer;
private static final String VERTEX_SHADER_SOURCE = "attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private static final String FRAGMENT_SHADER_SOURCE = "uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private int mVertexShader, mFragmentShader;
private static final String POSITION_NAME = "vPosition";
private static final String COLOR_NAME = "vColor";
private int mPositionLocation, mFragColorLocation;
public Triangle() {
// 数组转化为buffer,提高opengles性能
mTriangleCoordBuffer = ByteBuffer.allocateDirect(TRIANGLE_VERTEX_COORD.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTriangleCoordBuffer.put(TRIANGLE_VERTEX_COORD);
// 设置从第一位开始读
mTriangleCoordBuffer.position(0);
// 得到编译后的shader
mVertexShader = getShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_SOURCE);
mFragmentShader = getShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE);
// 创建并连接program
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, mVertexShader);
GLES20.glAttachShader(program, mFragmentShader);
GLES20.glLinkProgram(program);
GLES20.glUseProgram(program);
// 找到对应变量的index
mPositionLocation = GLES20.glGetAttribLocation(program, POSITION_NAME);
mFragColorLocation = GLES20.glGetUniformLocation(program, COLOR_NAME);
}
private int getShader(int type, String source) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
// 获取编译的状态
final int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,
compileStatus, 0);
Log.v(TAG, "代码编译结果:" + "\n" + source
+ "\n:" + GLES20.glGetShaderInfoLog(shader));
if (compileStatus[0] == 0) {
Log.w(TAG, "编译失败!.");
return 0;
}
return shader;
}
public void draw() {
// 由于性能方面的考虑,opengles默认读取不到缓冲区里的数据,需要先enable后再读
GLES20.glEnableVertexAttribArray(mPositionLocation);
GLES20.glVertexAttribPointer(mPositionLocation, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false, 0, mTriangleCoordBuffer);
GLES20.glUniform4f(mFragColorLocation, 0, 0.5f, 0, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_SIZE);
GLES20.glDisableVertexAttribArray(mPositionLocation);
}
}
运行后的结果:
不过我们的三角型有点变形,我们定义的明明是等边三角型,但实际画出来的不是,这里是因为opengles的坐标和实际屏幕的坐标是不对应的,opengles的长和宽都是一样的,就是-1到1,实际屏幕的长和宽肯定不一样,没有正方形的手机屏吧,该如何解决呢?这里我们需要用到矩阵,下节再说。