在定义了将要被OpenGL绘制的形状之后,你当然想要绘制它们。使用OpenGL ES 2.0绘制图形需要的代码可能比你想象的要多,因为API提供了大量的图形渲染管道控制接口。
这一章将介绍如何使用OpenGL ES 2.0 API绘制上一章中定义的形状
1. 初始化形状
在你做任何的绘制操作之前,你都必须进行初始化和加载计划绘制的形状。除非在执行的过程中形状所在的结构(原坐标)发生变化,你应该在render中的onSurfaceCreated()方法中初始化它们以提高内存和执行效率。
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
...
// initialize a triangle
mTriangle = new Triangle();
// initialize a square
mSquare = new Square();
}
2. 绘制形状
使用OpenGL ES 2.0绘制定义的形状需要大量的代码,因为你必须给图形渲染管线提供许多细节,特别是,你需要定义下面的细节:
Vertex Shader - OpenGL ES graphics code for rendering the vertices of a shape.
译:顶点着色器 - 渲染形状顶点的OpenGL ES图形代码
Fragment Shader - OpenGL ES code for rendering the face of a shape with colors or textures.
译:片元着色器 - 使用颜色或者纹理渲染形状表面的OpenGL ES图形代码
Program - An OpenGL ES object that contains the shaders you want to use for drawing one or more shapes.
译:Program - 包含了用来绘制形状的着色器的OpenGL ES对象
你至少需要一个顶点着色器来绘制形状,一个片元着色器来对形状着色。这些着色器必须被编译和被添加到OpenGL ES程序中。下面是一个如何通过定义着色器来绘制图形的例子:
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着色语言(GLSL)代码,在OpenGL ES环境中使用之前必须被编译,为了编译这些代码,在你的renderer类中创建一个方法:
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 ES编程对象中,链接程序,在绘制对象的构造方法中执行这些操作,所以只会执行一次。
注意:编译OpenGL着色器和链接程序从CPU处理周期和时间来看消耗是比较昂贵的,所以你应该避免执行超过一次。如果你在运行时还不知道着色器的内容,你应该只在着色器被创建的时候编译一次,然后缓存起来使用。
public class Triangle() {
...
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // create empty OpenGL ES Program
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // creates OpenGL ES program executables
}
这个时候,你已经准备好执行实际的绘图命令了,使用OpenGL ES绘图需要指定一些参数来告诉渲染管线画什么和怎么画。因为绘图选项可以通过形状区分,所以让你的shape类包含绘制逻辑是个不错的方法。
创建draw()方法来进行绘制,下面的代码设置了顶点着色器和片元着色器的position和color值,然后执行绘制方法。
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);
}
当这些代码都有的时候,绘制对象的时候只需要在renderer的onDrawFrame()方法下调用draw方法。下面是程序的执行效果:
这个代码示例还有一些问题。首先它不会让你的朋友印象深刻,其次当屏幕方向改变的时候形状会改变,有点被压扁。形状被扭曲的原因是对象的顶点没有跟着屏幕上GLSurfaceView展示的区域的比例进行修正,你可以使用下一节介绍的projection and camera view来修复这个问题。
最后,这个三角形是固定的,有点枯燥。在后面的课程中,你可以学习使这个形状旋转,使OpenGL ES图形管线更加有趣。