原文地址:http://android.xsoftlab.net/training/graphics/opengl/draw.html
如果你还不清楚如何定义图形及坐标系统,请移步:Android官方开发文档Training系列课程中文版:OpenGL绘图之图形定义。
在定义了图形之后,你接下来需要做的就是将它绘制到屏幕上。不过使用OpenGL ES 2.0 API来绘制这个图形所需要的代码量可能要比想象中的多一些,这是因为API为图形渲染管道提供了大量的控制细节。
这节课会展示如何绘制上节课所定义的图形。
初始化图形
在开始任何绘制之前,你必须先初始化并加载这个图形。除非是在执行的过程中图形的结构发生了改变。这个时候你应该在渲染器的onSurfaceCreated()方法中去初始化它们,这样可以使内存和进程的效率提升。
public class MyGLRenderer implements GLSurfaceView.Renderer {
...
private Triangle mTriangle;
private Square mSquare;
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square();
}
...
}
绘制图形
绘制自定义图形需要大量的代码,因为你必须给图形渲染管道提供大量的渲染细节。尤其是下面这些必须定义:
- Vertex Shader - 图形顶点的渲染.
- Fragment Shader - 图形表面的颜色或纹理的渲染。
- Program - 一个含有多个渲染器的OpenGL ES对象,可以用它来绘制一个或者多个图形。
你需要至少一个顶点渲染器来绘制图形,并需要一个表面渲染器来为图形着色。这些渲染器首先必须是可执行的,然后才能将其添加到OpenGL ES程序中,这时才能被用来绘制图形。下面定义了一个最基本的可以用来绘制图形的渲染器:
public class Triangle {
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
...
}
渲染器包含了OpenGL渲染语言代码,这些代码必须先在OpenGL ES环境中编译通过。为了编译这些代码,需要在渲染器类中创建一个功能方法:
public static int loadShader(int type, String shaderCode){
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
为了可以绘制图形,必须先编译这些渲染器代码,然后再将其添加到OpenGL程序中,最后再链接到程序中。需要将这些工作放入绘制对象的构造方法中,所以这些工作只用做一次。
Note: OpenGL ES的编译与链接过程需要消耗较高的CPU资源与时间,所以你应该避免这些工作做多次。如果在程序运行之前不知道渲染器的代码,应该确保这部分的构建代码只会执行一次,并需要将其缓存下来以便稍后使用。
public class Triangle() {
...
private final int mProgram;
public Triangle() {
...
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// create empty OpenGL ES Program
mProgram = GLES20.glCreateProgram();
// add the vertex shader to program
GLES20.glAttachShader(mProgram, vertexShader);
// add the fragment shader to program
GLES20.glAttachShader(mProgram, fragmentShader);
// creates OpenGL ES program executables
GLES20.glLinkProgram(mProgram);
}
}
这时就可以真正的开始绘制了。图形的绘制需要提供若干的参数来告诉渲染管道想要绘制什么及如何绘制。因为绘制选项可以定义多种多样的图形形式,所以可以自定义一个拥有独立绘制逻辑的类来绘制各种图形。
创建一个draw()方法开始绘制这个图形。这部分代码将会为顶点渲染器设置位置数据,并为表面渲染器设置颜色数据。然后开始执行绘制功能。
private int mPositionHandle;
private int mColorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
public void draw() {
// Add program to OpenGL ES environment
GLES20.glUseProgram(mProgram);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// get handle to fragment shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// Set color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
// Draw the triangle
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// Disable vertex array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
一旦完成以上所有的代码,最后只需要调用一下draw()方法就可以开始绘制了:
public void onDrawFrame(GL10 unused) {
...
mTriangle.draw();
}
当程序启动之后,设备上就会出现以下图形:
上面的示例代码还有几个问题:首先,它不会给人留下什么深刻的印象。其次,当屏幕旋转的时候,这个三角形会有一点被压扁的感觉。这是因为在旋转的时候,代码中所定义的顶点的相对位置被压缩了。这些问题将会在下节课得到解决。
最后,这三角形是固定不变的,这会有些让人有些不爽的感觉。在Adding Motion的课程中将会使这个图形可以随着手势旋转而旋转,还可以通过渲染管道做到其它更多有意思的事情。