初识OpenGL ES2.0

  OpenGL ES是基于OpenGL三维图形API的子集,主要针对于手机以及PDA等嵌入式设备设计的。
  随着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();
	}
}
下面是我们最主要的类,Triangle类。该类主要功能是初始化顶点数据、初始化着色器、设置相应的平移矩阵以及旋转矩阵。
代码如下:

[代码]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;
	}
}
最后是我们的View类,也就是3D场景类。该类继承自GLSurfaceView,并且在该类中通过内部类的形式创建了渲染器,代码如下:

[代码]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();
				}
			}
		}
	}
}
好了,代码就这么多。完全手打好累啊。不知道看了今天的教程大家是不是对OpenGL ES 2.0产生了兴趣呢?
希望在以后的教程里能带给大家更多收获的喜悦。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值