一、OpenGL ES
OpenGL是一个供运行在Linux、Unix、Mac和Windows的桌面系统使用的跨平台标准的API,OpenGL ES是以手持和嵌入式设备为目标的高级3D图形应用程序编程接口。即OpenGLES是OpenGL的子集,去除了复杂的不常用的功能,专门为PDA等为目标的图形API。
OpenGLES经常用在对图片、视频、相机、游戏等方面。它是使用了GPU的计算的,从而解放CPU的使用,加速渲染能力。
二、前言
作为一门语言的入门,都是从HelloWord学起的。当然OpenGL也不例外,绘制一个三角形应该就是OpenGLES的“HelloWorld”了,但是这个HelloWorld有的复杂,并不像java等语言输入一句话就行了。绘制一个三角形虽然是“HelloWorld”,但是麻雀虽小五脏俱全,工作量和操作步骤却没有变小。
本文选择的渲染载体为:GLSurfaceView
简单的介绍下GLSurfaceView,它是继承自SurfaceView,一个双缓冲机制并可在子线程更新的具有两个View体系结构的View,在SurfaceView的基础上增加了对EGL的管理。减少了我们对EGL的操作,简化了步骤。简单说EGL是OpenGLES和底层硬件的过渡层,他抽象了硬件的细节,完成了OpenGLES的跨平台性。
编写前,先介绍一个概念:图形渲染管线,它是从输入数据开始到绘制到界面的过程。大体步骤,可以参考如下官方给出的步骤。蓝色部分是用户可定制开发的。
- 顶点着色器:它把一个顶点作为输入。把3D坐标转为标准化设备坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理,如矩阵变换。
- 图元装配:它将所有的顶点作为输入,将其装配成一个具体的形状。
- 几何着色器:几何着色器把图元形式的一系列顶点的集合作为输入,通过产生新顶点构造出新的图元来生成其他形状。
- 光栅化: 图元映射为最终屏幕上相应的像素
- 片段着色器: 计算一个像素的最终颜色
- 测试和混合: 根据Z轴选择是否抛弃该点(被遮住),以及对于透明的颜色进行混合。
三、绘制三角形
绘制三角形,分为如下几步:
- 存储顶点数据到ByteBuffer中
- 编写顶点和片段着色器,并加载相应的着色器(shader)
- 创建着色器程序,并对顶点和片段着色器进行链接
- 视口变换操作(glViewport)
- 传递顶点和颜色数据
- 绘制物体
1)、环境准备
在AndroidManifest中配置OpenGLES版本
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
并用GLSurfaceView搭建好一个EGL环境,所有的操作在GLSurfaceView.Render的回调中进行。回调开始的时候代表着EGL环境搭建完毕,否则会导致操作失败。
2)、存储顶点数据到ByteBuffer中
// 三维的顶点坐标,有方向的
private static final float triangleCoords[] = {
-0.5f, 1f, 0.0f, // bottom right
-1f, -1f, 0.0f, // bottom left
0.5f, 1f, 0.0f, // top
};
private static final short indices[] = {
0,1,2
};
// 颜色
private static final float colors[] = {0.8f, 0.4f, 0.1f, 0f};
// 1、存储顶点坐标
vertexBuffer = initBuffer(triangleCoords,4);
ByteBuffer mbb = ByteBuffer.allocateDirect(triangleCoords.length * 4);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
FloatBuffer floatBuffer = mbb.asFloatBuffer();
floatBuffer.put(vertexBuffer);
floatBuffer.flip();
mbb = ByteBuffer.allocateDirect(indices.length * 2);
// 数组排列用nativeOrder
mbb.order(ByteOrder.nativeOrder());
indiceBuffer = mbb.asShortBuffer();
indiceBuffer.put(indices);
indiceBuffer.flip();
将顶点坐标存放在ByteBuffer中,供下文传递数据到Shader中。
3)、编写顶点和片段着色器代码,并加载相应的着色器
分别编写顶点和片段GLSL代码,Shader是通过输入和输出进行通信的。顶点着色器中用四个元素的向量的属性值传递顶点值,传递到内置的变量gl_Position中。片段着色器是输入颜色的,这里传入的类型是uniform。precision mediump float是代表精度。
// 顶点着色器code
private static final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}";
// 片元着色器code
private static final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
加载shader:
分别创建、加载以及编译着色器代码:
private int loadShader(int type, String shaderCode) {
//根据type创建顶点着色器或者片元着色器
int shader = GLES20.glCreateShader(type);
//将资源加入到着色器中,并编译
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);
4)、创建着色器程序,并对顶点和片段着色器进行链接
顶点和片段着色器只有在链接到着色器程序中,才会起作用。
//创建一个空的OpenGLES程序
mProgram = GLES20.glCreateProgram();
//将顶点着色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader);
//将片元着色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
//连接到着色器程序
GLES20.glLinkProgram(mProgram);
5)、视口变换操作(glViewport)
// 设置窗口的大小
GLES20.glViewport(0,0,width,height);
6)、传递顶点和颜色数据
现在需要将顶点、颜色、顶点索引数据传入到相应的地方,这样才会绘制出想要的形状。
首先需要获取属性为vPosition的句柄和uniform为vColor的句柄,并根据Attribute和Uniform两种方式传入对应的数据。
GLES20.glUseProgram(mProgram);
GLES20.glClearColor(0, 0, 0, 1);
GLES20.glDisable(GLES20.GL_DEPTH_TEST); // 当我们需要绘制透明图片时,就需要关闭它
// 填充数据
//获取顶点着色器的vPosition成员句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
//启用三角形顶点的句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);
//准备三角形的坐标数据
GLES20.glVertexAttribPointer(mPositionHandle, 3,
GLES20.GL_FLOAT, false,
12, vertexBuffer);
mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
Log.e(TAG, "onDrawFrame: mPositionHandle="+mPositionHandle+" mColorHandle="+mColorHandle);
//设置绘制三角形的颜色
GLES20.glUniform4fv(mColorHandle, 1, colors, 0);
7)、绘制物体
通过glDrawElements方法绘制图形,glDrawElements需要使用到索引数组。绘制的API还可以是glDrawArrays,这里不需要明确的设置绘制的顺序,而是通过绘制的模式来决定的。
//绘制三角形
GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, 3,GLES20.GL_UNSIGNED_SHORT,indiceBuffer);
四、绘制模式详解
对于绘制的模式,上面的代码使用的是GLES20.GL_TRIANGLE_STRIP,除此之外还有些其它的模式:、
- GLES20.GL_POINTS:绘制点精灵模式
- GLES20.GL_LINES:对于n各定点,会绘制(v0、v1)、(v2、v3)、(v4、v5)、(vn-2、vn-1)条线段
- GLES20.GL_LINE_LOOP:对于n各定点,会绘制(v0、v1)、(v1、v2)、(v2、v3)、(vn-2、vn-1)条线
- GLES20.GL_LINE_STRIP:对于n各定点,会绘制(v0、v1)、(v1、v2)、(v2、v3)、(vn-2、vn-1)、(vn-1、v0)条线,这绘制的会多了个首位相连的线段。
- GLES20.GL_TRIANGLES:同GL_LINES,它是不重复的使用顶点
- GLES20.GL_TRIANGLE_STRIP :绘制一系列相互连接的三角形,对于5个顶点,它绘制的是(v0,v1,v2)、(v2,v1,v3)、(v2,v3,v4)
- GLES20.GL_TRIANGLE_FAN: 绘制一系列相互连接的三角形,对于5个顶点,它绘制的是(v0,v1,v2)、(v0,v2,v3)、(v0,v3,v4)