在上文中,我们使用OpenGL定义了能够被绘制出来的形状了,现在我们想绘制出来它们。使用OpenGLES 2.0来绘制形状会比你想象的需要更多的代码。因为OpenGL的API提供了大量的对渲染管线的控制能力。
本文就将讲述如何使用OpenGL ES 2.0 API来绘制出来我们上节定义的形状。
一、初始化形状
在你做任何绘制操作之前,你必须要初始化并加载你准备绘制的形状。除非形状的结构(指原始的坐标)在执行过程中发生改变,你都应该在你的Renderer的方法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();
}
...
}
二、绘制形状
使用OpenGLES 2.0画一个定义好的形状需要比较多的代码,因为你必须为图形渲染管线提供一大堆信息。特别的,你必须定义以下几个东西:
- Vertex Shader - 用于渲染形状的顶点的OpenGLES 图形代码。
- Fragment Shader - 用于渲染形状的外观(颜色或纹理)的OpenGLES 代码。
- Program - 一个OpenGLES对象,包含了你想要用来绘制一个或多个形状的shader。
你至少需要一个vertexshader来绘制一个形状和一个fragmentshader来为形状上色。这些形状必须被编译然后被添加到一个OpenGLES program中,program之后被用来绘制形状。下面是一个展示如何定义一个可以用来绘制形状的基本shader的例子:
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;" +
"}";
...
}
Shader们包含了OpenGLShading Language (GLSL)代码,必须在使用前编译。要编译这些代码,在你的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;
}
为了绘制你的形状,你必须编译shader代码,添加它们到一个OpenGLES program 对象然后链接这个program。在renderer对象的构造器中做这些事情,从而只需做一次即可。
注:编译OpenGLES shader们和链接linkingprogram们是很耗CPU的,所以你应该避免多次做这些事。如果在运行时你不知道shader的内容,你应该只创建一次code然后缓存它们以避免多次创建。
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()方法负责绘制形状。下面的代码设置位置和颜色值到形状的vertexshader和fragmentshader,然后执行绘制功能:
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);
}
一旦完成了所有这些代码,绘制该对象只需要在渲染器的onDrawFrame()方法中调用draw()方法:
public void onDrawFrame(GL10 unused) {
...
mTriangle.draw();
}
当你运行程序的时候,你就应该看到以下的内容:
When you run the application, it should look something like this:
此例子中的代码还有很多问题。首先,它不会打动你和你的朋友。其次,三角形会在你从竖屏变为横屏时被压扁。三角形变形的原因是其顶点们没有跟据屏幕的宽高比进行修正。而且这里展示出来的三角形是静止的,这样的图形是有点无聊的,在“添加动画”的文章中,我们会使用OpenGL ES 的视图管线来旋转此形状。
在下一篇文章,我们将使用投影和摄像头视图来修正显示的问题。