OpenGL ES是基于OpenGL三维图形API的子集,主要针对于手机以及PDA等嵌入式设备设计的。
下面是我们最主要的类,Triangle类。该类主要功能是初始化顶点数据、初始化着色器、设置相应的平移矩阵以及旋转矩阵。
最后是我们的View类,也就是3D场景类。该类继承自GLSurfaceView,并且在该类中通过内部类的形式创建了渲染器,代码如下:
好了,代码就这么多。完全手打好累啊。不知道看了今天的教程大家是不是对OpenGL ES 2.0产生了兴趣呢?
随着Android系统版本以及硬件水平的提升,OpenGL ES版本也由原先仅支持固定渲染管线的OpenGL ES 1.X升级为
支持自定义渲染管线的OpenGL ES 2.0。这使得使用OpenGL ES 2.0渲染的3D场景更加真实从而能够创造全新的用户体验。
现今较为知名的3D图形API有OpenGL、DirectX以及OpenGL ES,它们各自的应用领域如下:
DirectX:主要应用与Windows下游戏开发。
OpenGL:应用领域比较广泛,适用于UNIX、Mac OS、Linux以及Microsof等几乎所有操作系统,可以开发游戏、工业建模以及嵌入式设备。
OpenGL ES:专门针对于嵌入式设备的,其实是OpenGL的剪裁版本,去除了OpenGL中许多不是必须存在的特性,如GL_QUADS(四边形)与GL_POLGONS(多边形)绘制模式以及glBegin(开始)/ glEnd(结束)等操作。
先为大家提供一个Hello World的程序,该程序涉及到了着色器的概念,大家可能一头雾水,不过不要紧,在以后的教程里我会为大家仔细讲解。
该程序绘制一个三角形,该三角形沿着底边旋转。先看一下效果:
首先提供一个着色器工具类,用来创建着色器和检查GL错误。代码如下。
[代码]java代码:
package com.bn.Sample3_1;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;
//加载顶点Shader与片元Shader的工具类
public class ShaderUtil {
// 加载制定shader的方法
public static int loadShader(int shaderType, String source) {
// 创建一个新shader
int shader = GLES20.glCreateShader(shaderType);
// 若创建成功则加载shader
if (shader != 0) {
// 加载shader的源代码
GLES20.glShaderSource(shader, source);
// 编译shader
GLES20.glCompileShader(shader);
// 存放编译成功shader数量的数组
int[] compiled = new int[1];
// 获取Shader的编译情况
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {// 若编译失败则显示错误日志并删除此shader
Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
}
return shader;
}
// 创建shader程序的方法
public static int createProgram(String vertexSource, String fragmentSource) {
// 加载顶点着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) {
return 0;
}
// 加载片元着色器
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) {
return 0;
}
// 创建程序
int program = GLES20.glCreateProgram();
// 若程序创建成功则向程序中加入顶点着色器与片元着色器
if (program != 0) {
// 向程序中加入顶点着色器
GLES20.glAttachShader(program, vertexShader);
checkGlError("glAttachShader");
// 向程序中加入片元着色器
GLES20.glAttachShader(program, pixelShader);
checkGlError("glAttachShader");
// 链接程序
GLES20.glLinkProgram(program);
// 存放链接成功program数量的数组
int[] linkStatus = new int[1];
// 获取program的链接情况
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
// 若链接失败则报错并删除程序
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e("ES20_ERROR", "Could not link program: ");
Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
GLES20.glDeleteProgram(program);
program = 0;
}
}
return program;
}
// 检查每一步操作是否有错误的方法
public static void checkGlError(String op) {
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.e("ES20_ERROR", op + ": glError " + error);
throw new RuntimeException(op + ": glError " + error);
}
}
// 从sh脚本中加载shader内容的方法
public static String loadFromAssetsFile(String fname, Resources r) {
String result = null;
try {
InputStream in = r.getAssets().open(fname);
int ch = 0;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((ch = in.read()) != -1) {
baos.write(ch);
}
byte[] buff = baos.toByteArray();
baos.close();
in.close();
result = new String(buff, "UTF-8");
result = result.replaceAll("\\r\\n", "\n");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
代码的loadShader方法通过glCreateShader方法创建了一个着色器;如果着色器创建成功则加载着色器源代码,并编译着色器。编译完成后检查编译情况。若编译成功则返回着色器的ID,反之删除着色器并且打印错误信息。
代码的createProgram方法通过调用loadShader方法分别加载顶点着色器与片元着色器的源代码进GPU,并分别进行编译。
下面来看一个我们熟悉的类。TestActivity,该类继承自Activity,在程序开始时执行。主要工作是创建MyTDView类的对象,然后调用setConentView方法跳转到相关界面。代码如下:
[代码]java代码:
package com.manyou.opengl;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class TestActivity extends Activity {
MyTDView mview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置为竖屏模式
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mview = new MyTDView(this);
mview.requestFocus();
mview.setFocusableInTouchMode(true);
setContentView(mview);
}
@Override
public void onResume() {
super.onResume();
mview.onResume();
}
@Override
public void onPause() {
super.onPause();
mview.onPause();
}
}
代码如下:
[代码]java代码:
package com.manyou.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
import android.opengl.Matrix;
//三角形
public class Triangle {
public static float[] mProjMatrix = new float[16];// 4x4矩阵 投影用
public static float[] mVMatrix = new float[16];// 摄像机位置朝向9参数矩阵
public static float[] mMVPMatrix;// 最后起作用的总变换矩阵
int mProgram;// 自定义渲染管线程序id
int muMVPMatrixHandle;// 总变换矩阵引用id
int maPositionHandle; // 顶点位置属性引用id
int maColorHandle; // 顶点颜色属性引用id
String mVertexShader;// 顶点着色器
String mFragmentShader;// 片元着色器
static float[] mMMatrix = new float[16];// 具体物体的移动旋转矩阵,旋转、平移
FloatBuffer mVertexBuffer;// 顶点坐标数据缓冲
FloatBuffer mColorBuffer;// 顶点着色数据缓冲
int vCount = 0;
float xAngle = 0;// 绕x轴旋转的角度
public Triangle(MyTDView mv) {
// 初始化顶点坐标与着色数据
initVertexData();
// 初始化shader
initShader(mv);
}
public void initVertexData() {
// 顶点坐标数据的初始化
vCount = 3;
final float UNIT_SIZE = 0.2f;
float vertices[] = new float[] { -4 * UNIT_SIZE, 0, 0, 0, -4 * UNIT_SIZE, 0, 4 * UNIT_SIZE, 0, 0 };
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
float colors[] = new float[] { 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 };
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
}
// 初始化shader
public void initShader(MyTDView mv) {
// 加载顶点着色器的脚本内容
mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
// 加载片元着色器的脚本内容
mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
// 基于顶点着色器与片元着色器创建程序
mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
// 获取程序中顶点位置属性引用id
maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
// 获取程序中顶点颜色属性引用id
maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
// 获取程序中总变换矩阵引用id
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
}
public void drawSelf() {
// 制定使用某套shader程序
GLES20.glUseProgram(mProgram);
// 初始化变换矩阵
Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);
// 设置沿Z轴正向位移1
Matrix.translateM(mMMatrix, 0, 0, 0, 1);
// 设置绕x轴旋转
Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);
//
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
// 为画笔指定顶点位置数据
GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4 * 4, mColorBuffer);
// 允许顶点位置数据数组
GLES20.glEnableVertexAttribArray(maPositionHandle);
GLES20.glEnableVertexAttribArray(maColorHandle);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
}
public static float[] getFianlMatrix(float[] spec) {
mMVPMatrix = new float[16];
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
return mMVPMatrix;
}
}
[代码]java代码:
package com.manyou.opengl;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
public class MyTDView extends GLSurfaceView {
final float ANGLE_SPAN = 0.375f;
RotateThread rthread;
SceneRenderer mRenderer;
public MyTDView(Context context) {
super(context);
this.setEGLContextClientVersion(2);
mRenderer = new SceneRenderer();
this.setRenderer(mRenderer);
this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
private class SceneRenderer implements GLSurfaceView.Renderer {
Triangle tle;
public void onDrawFrame(GL10 gl) {
// 清除深度缓冲与颜色缓冲
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// 绘制三角形对
tle.drawSelf();
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置视窗大小及位置
GLES20.glViewport(0, 0, width, height);
// 计算GLSurfaceView的宽高比
float ratio = (float) width / height;
// 调用此方法计算产生透视投影矩阵
Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
// 调用此方法产生摄像机9参数位置矩阵
Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// 设置屏幕背景色RGBA
GLES20.glClearColor(0, 0, 0, 1.0f);
// 创建三角形对对象
tle = new Triangle(MyTDView.this);
// 打开深度检测
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
rthread = new RotateThread();
rthread.start();
}
}
public class RotateThread extends Thread {
public boolean flag = true;
@Override
public void run() {
while (flag) {
mRenderer.tle.xAngle = mRenderer.tle.xAngle + ANGLE_SPAN;
try {
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
希望在以后的教程里能带给大家更多收获的喜悦。