【Android应用开发】 OpenGL ES -- 透视投影 和 正交投影

博客地址 : http://blog.csdn.net/shulianghan/article/details/46680803

源码下载 : http://download.csdn.net/detail/han1202012/8903437


正交投影效果 : 



透视投影效果 : 



一. 投影简介

1. 摄像机位置

摄像机参数 :

-- 摄像机位置 : 摄像机的 三维坐标位置 x, y, z 坐标;

-- 观察方向 : 摄像机镜头的朝向, 是一个三维向量, 指向一个三维坐标方向;

-- up 方向 : 有了位置 和 朝向, 此时摄像机可以 360 度旋转, 这是我们需要一个 up 方向, 将摄像机固定在一个位置一个方向;

设置摄像机的方法 :

void android.opengl.Matrix.setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

-- float[] rm 参数 : 生成矩阵元素的 float[] 数组;

-- int rmOffset 参数 : 矩阵元素数组的起始偏移量;

-- float eyeX, float eyeY, float eyeZ 参数 : 摄像机位置的 x, y, z 三维坐标;

-- float centerX, float centerY, float centerZ 参数 : 摄像机镜头朝向的点, 该点与摄像机位置连线的方向就是摄像机方向;

-- float upX, float upY, float upZ 参数 : 摄像机 up 方向, 该点与摄像机连线的方向, 就是摄像机的 up 方向;

2. 正交投影简介

投影简介 :

-- 视景体 : 管线会确定的一个可视空间区域, 由 上平面(up), 下平面(down), 左平面(left), 右平面(right), 远平面(far), 近平面(near) 六个平面组成;

-- 视景体与投影 : 视景体内的物体会投影到近平面, 视景体之外的内容会被裁减掉, 例如眼睛看不到的范围就是处于视景体外即被裁减掉的;

正交投影 : 正交投影属于平行投影, 投影线平行, 视景体是长方形的, 投影的内容不会出现近大远小的效果;

-- 投影线 : 物体顶点 与 近平面的对应的物体顶点 投影的连线;

正交投影方法 : Matrix.orthoM() 方法设置正交投影;

void android.opengl.Matrix.orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)

-- float[] m 参数 : 生成矩阵元素的 float[] 数组;

-- int mOffset 参数 : 矩阵数组的起始偏移量;

-- float left, float right, float bottom, float top 参数 : 近平面的 左, 右, 下, 上 的值;

-- float near 参数 : 近平面 与 视点之间的距离;

-- float far 参数 : 远平面 与 视点之间的距离;

视口 : 视景体中的物体投影到近平面后, 最终会映射到显示屏的视口中, 视口就相当于眼睛 或者 手机屏幕的一部分;

-- 说明 : 视口并不是占手机全部屏幕, 是显示投影的部分, 也可以是一个 View 组件;

视口设置方法 :

 void android.opengl.GLES20.glViewport(int x, int y, int width, int height)

-- int x, int y 参数 : x, y 是视口在手机屏幕左上角的坐标;

-- int width, int height 参数 : 视口的宽度 与 高度;

3. 透视投影简介

透视投影 : 与现实世界观察物体一样, 有 近大远小 的效果, 这种投影更加真实;

-- 投影线介绍 : 透视投影的投影线不平行, 相交于视点;

-- 视景体 : 透视投影中视景体是锥台形区域;

-- 用处 : 所有的 3D 游戏都采用了透视投影的效果, 我们控制物体向前行走, 远处的物体不断变大就是这种效果;

二. 正交透视投影源码详解

1. 源码结构详解

源码组成 :

-- MatrixState : 矩阵相关的辅助类;

-- OrthogonalProjectionActivity : 显示具体 OpenGL 图像的 Activity;

-- ProjectionGLSurfaceView : 自定义的 GLSurfaceView, 该 View 可以显示 OpenGL 图像内容;

-- ShaderUtil : 着色器工具类;

-- SixPointedStar : 具体的图形类, 如何生成该图形;

2. MatrixState 详解

(1) 设置摄像机参数

设置摄像机参数 :

-- 相关内容 :

	/**
	 * 设置摄像机的参数
	 *
	 * @param cx
	 *            摄像机位置的 x 坐标
	 * @param cy
	 *            摄像机位置的 y 坐标
	 * @param cz
	 *            摄像机位置的 z 坐标
	 * @param tx
	 *            摄像机朝向 x 坐标
	 * @param ty
	 *            摄像机朝向 y 坐标
	 * @param tz
	 *            摄像机朝向 z 坐标
	 * @param upx
	 *            摄像机上方朝向 x 坐标
	 * @param upy
	 *            摄像机上方朝向 y 坐标
	 * @param upz
	 *            摄像机上方朝向 z 坐标
	 */
	public static void setCamera(float cx, float cy, float cz, float tx,
			float ty, float tz, float upx, float upy, float upz) {
		Matrix.setLookAtM(mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
	}

-- 方法作用 : 设置了摄像机的相关参数;

(2) 设置正交投影参数

代码解析 :

-- 作用 : 设置正交投影的近平面相关信息, 近平面与远平面距离;

-- 代码相关内容 :

	/**
	 * 设置正交投影的参数
	 *
	 * @param left
	 *            近平面的 left
	 * @param right
	 *            近平面的 right
	 * @param bottom
	 *            近平面的 bottom
	 * @param top
	 *            近平面的 top
	 * @param near
	 *            近平面的距离
	 * @param far
	 *            远平面的距离
	 */
	public static void setProjectOrtho(float left, float right, float bottom,
			float top, float near, float far) {
		Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
	}

(3) 设置透视投影参数

代码详解 :

-- 作用 : 设置透视投影 近平面 以及近平面 远平面与视点间的距离;

-- 代码内容 :

	/**
	 * 设置正交投影的参数
	 *
	 * @param left
	 *            近平面的 left
	 * @param right
	 *            近平面的 right
	 * @param bottom
	 *            近平面的 bottom
	 * @param top
	 *            近平面的 top
	 * @param near
	 *            近平面的距离
	 * @param far
	 *            远平面的距离
	 */
	public static void setProjectOrtho(float left, float right, float bottom,
			float top, float near, float far) {
		Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
	}

(4) 获取物体的总变换矩阵

代码详解 :

-- 作用 : 将摄像机矩阵, 投影矩阵, 着色矩阵相乘, 就是最终矩阵;

-- 代码内容 :

	/**
	 * 获取物体的总变换矩阵
	 *
	 * @param spec
	 * @return
	 */
	public static float[] getFinalMatrix(float[] spec) {
		mMVPMatrix = new float[16];
		/*
		 *  矩阵乘法计算, 将两个矩阵相乘, 并存入到第三个矩阵中
		 *  六个参数 :
		 *  ①② 参数 : 结果矩阵, 结果矩阵起始位移
		 *  ③④ 参数 : 左矩阵, 结果矩阵起始位移
		 *  ⑤⑥ 参数 : 右矩阵, 结果矩阵起始位移
		 */
		Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
		Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
		return mMVPMatrix;
	}

(5) MatrixState 源码

源码内容 :

package cn.org.octopus.opengl.projection;

import android.opengl.Matrix;

/**
 * 存储矩阵状态的类
 *
 * @author octopus
 *
 */
public class MatrixState {

	private static float[] mProjMatrix = new float[16]; // 4x4矩阵 投影用
	private static float[] mVMatrix = new float[16]; // 摄像机位置朝向9参数矩阵
	private static float[] mMVPMatrix; // 最后起作用的总变换矩阵

	/**
	 * 设置摄像机的参数
	 *
	 * @param cx
	 *            摄像机位置的 x 坐标
	 * @param cy
	 *            摄像机位置的 y 坐标
	 * @param cz
	 *            摄像机位置的 z 坐标
	 * @param tx
	 *            摄像机朝向 x 坐标
	 * @param ty
	 *            摄像机朝向 y 坐标
	 * @param tz
	 *            摄像机朝向 z 坐标
	 * @param upx
	 *            摄像机上方朝向 x 坐标
	 * @param upy
	 *            摄像机上方朝向 y 坐标
	 * @param upz
	 *            摄像机上方朝向 z 坐标
	 */
	public static void setCamera(float cx, float cy, float cz, float tx,
			float ty, float tz, float upx, float upy, float upz) {
		Matrix.setLookAtM(mVMatrix, 0, cx, cy, cz, tx, ty, tz, upx, upy, upz);
	}

	/**
	 * 设置透视投影参数
	 *
	 * @param left
	 *            近平面的 left
	 * @param right
	 *            近平面的 right
	 * @param bottom
	 *            近平面的 bottom
	 * @param top
	 *            近平面的 top
	 * @param near
	 *            近平面与视点的距离
	 * @param far
	 *            远平面与视点的距离
	 */
	public static void setProjectFrustum(float left, float right, float bottom,
			float top, float near, float far) {
		Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
	}

	/**
	 * 设置正交投影的参数
	 *
	 * @param left
	 *            近平面的 left
	 * @param right
	 *            近平面的 right
	 * @param bottom
	 *            近平面的 bottom
	 * @param top
	 *            近平面的 top
	 * @param near
	 *            近平面的距离
	 * @param far
	 *            远平面的距离
	 */
	public static void setProjectOrtho(float left, float right, float bottom,
			float top, float near, float far) {
		Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
	}

	/**
	 * 获取物体的总变换矩阵
	 *
	 * @param spec
	 * @return
	 */
	public static float[] getFinalMatrix(float[] spec) {
		mMVPMatrix = new float[16];
		/*
		 *  矩阵乘法计算, 将两个矩阵相乘, 并存入到第三个矩阵中
		 *  六个参数 :
		 *  ①② 参数 : 结果矩阵, 结果矩阵起始位移
		 *  ③④ 参数 : 左矩阵, 结果矩阵起始位移
		 *  ⑤⑥ 参数 : 右矩阵, 结果矩阵起始位移
		 */
		Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
		Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
		return mMVPMatrix;
	}
}

3. ShaderUtil 着色工具详解

该代码在 http://blog.csdn.net/shulianghan/article/details/17020359 中详细的讲解;

(1) 源码

ShaderUtil 源码 :

package cn.org.octopus.opengl.projection;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;

import android.content.res.Resources;
import android.opengl.GLES20;
import android.util.Log;

/*
 * 这个工具类用来加载定点着色器与片元着色器
 */
public class ShaderUtil {

	/**
	 * 加载着色器方法
	 *
	 * 流程 :
	 *
	 * ① 创建着色器
	 * ② 加载着色器脚本
	 * ③ 编译着色器
	 * ④ 获取着色器编译结果
	 *
	 * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER)
	 * @param source 着色脚本字符串
	 * @return 返回的是着色器的引用, 返回值可以代表加载的着色器
	 */
	public static int loadShader(int shaderType , String source){
		//1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败
		int shader = GLES20.glCreateShader(shaderType);
		if(shader != 0){
			//2.如果着色器创建成功, 为创建的着色器加载脚本代码
			GLES20.glShaderSource(shader, source);
			//3.编译已经加载脚本代码的着色器
			GLES20.glCompileShader(shader);
			int[] compiled = new int[1];
			//4.获取着色器的编译情况, 如果结果为0, 说明编译失败
			GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
			if(compiled[0] == 0){
				 Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
	             Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
	             //编译失败的话, 删除着色器, 并显示log
	             GLES20.glDeleteShader(shader);
	             shader = 0;
			}
		}
		return shader;
	}

	/**
	 * 检查每一步的操作是否正确
	 *
	 * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误
	 *
	 * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器,
	 * 		使glAttachShader()方法, 那么这个参数就是"glAttachShader"
	 */
	public static void checkGLError(String op){
		int error;
		//错误代码不为0, 就打印错误日志, 并抛出异常
		while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){
			 Log.e("ES20_ERROR", op + ": glError " + error);
	         throw new RuntimeException(op + ": glError " + error);
		}
	}

	/**
	 * 创建着色程序
	 *
	 * ① 加载顶点着色器
	 * ② 加载片元着色器
	 * ③ 创建着色程序
	 * ④ 向着色程序中加入顶点着色器
	 * ⑤ 向着色程序中加入片元着色器
	 * ⑥ 链接程序
	 * ⑦ 获取链接程序结果
	 *
	 * @param vertexSource		定点着色器脚本字符串
	 * @param fragmentSource	片元着色器脚本字符串
	 * @return
	 */
	public static int createProgram(String vertexSource , String fragmentSource){
		//1. 加载顶点着色器, 返回0说明加载失败
		int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
		if(vertexShader == 0)
			return 0;
		//2. 加载片元着色器, 返回0说明加载失败
		int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
		if(fragShader == 0)
			return 0;
		//3. 创建着色程序, 返回0说明创建失败
		int program = GLES20.glCreateProgram();
		if(program != 0){
			//4. 向着色程序中加入顶点着色器
			GLES20.glAttachShader(program, vertexShader);
			checkGLError("glAttachShader");
			//5. 向着色程序中加入片元着色器
			GLES20.glAttachShader(program, fragShader);
			checkGLError("glAttachShader");

			//6. 链接程序
			GLES20.glLinkProgram(program);
			int[] linkStatus = new int[1];
			//获取链接程序结果
			GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
			if(linkStatus[0] != GLES20.GL_TRUE){
				Log.e("ES20.ERROR", "链接程序失败 : ");
				Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));
				//如果链接程序失败删除程序
				GLES20.glDeleteProgram(program);
				program = 0;
			}
		}
		return program;
	}

	/**
	 * 从assets中加载着色脚本, 最终获得一个着色器脚本字符串
	 *
	 * ① 打开assets目录中的文件输入流
	 * ② 创建带缓冲区的输出流
	 * ③ 逐个字节读取文件数据, 放入缓冲区
	 * ④ 将缓冲区中的数据转为字符串
	 *
	 * @param fileName assets目录中的着色脚本文件名
	 * @param resources	应用的资源
	 * @return
	 */
	public static String loadFromAssetsFile(String fileName, Resources resources){
		String result = null;
		try {
			//1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流
			InputStream is = resources.getAssets().open(fileName);
			int ch = 0;
			//2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			//3. 逐个字节读取数据, 并将读取的数据放入缓冲器中
			while((ch = is.read()) != -1){
				baos.write(ch);
			}
			//4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串
			byte[] buffer = baos.toByteArray();
			baos.close();
			is.close();
			result = new String(buffer, "UTF-8");
			result = result.replaceAll("\\r\\n", "\n");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
}

4. SixPointedStar 六角形形成类

(1) 源码

package cn.org.octopus.opengl.projection;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import android.opengl.GLES20;
import android.opengl.Matrix;

/**
 * 单个六角星元素
 *
 * @author octopus
 *
 */
public class SixPointedStar {
	int mProgram; 								// 自定义渲染管线着色器程序id

	static float[] mMMatrix = new float[16]; 	// 具体物体的3D变换矩阵,包括旋转、平移、缩放
	int muMVPMatrixHandle; 						// 总变换矩阵引用

	int maPositionHandle; 						// 顶点位置属性引用
	int maColorHandle; 							// 顶点颜色属性引用

	String mVertexShader; 						// 顶点着色器代码脚本
	String mFragmentShader; 					// 片元着色器代码脚本

	FloatBuffer mVertexBuffer; 					// 顶点坐标数据缓冲
	FloatBuffer mColorBuffer; 					// 顶点着色数据缓冲

	int vCount = 0;								// 顶点个数
	public float yAngle = 0; 							// 绕y轴旋转的角度
	public float xAngle = 0; 							// 绕z轴旋转的角度
	final float UNIT_SIZE = 1;

	public SixPointedStar(ProjectionGLSurfaceView mv, float r, float R, float z) {
		// 调用初始化顶点数据的initVertexData方法
		initVertexData(R, r, z);
		// 调用初始化着色器的intShader方法
		initShader(mv);
	}

	/**
	 * 自定义初始化顶点数据的initVertexData方法
	 * @param R 外圆半径, 最外面6个点组成的圆
	 * @param r 内圆半径, 最里面6个点组成的圆, 6个凹槽处的点
	 * @param z 深度
	 */
	public void initVertexData(float R, float r, float z) {
		List<Float> flist = new ArrayList<Float>();
		float tempAngle = 360 / 6;
		// 每 60 度绘制一个四边形, 每个四边形由 2 个三角形组成, 箭头形的平行四边形
		for (float angle = 0; angle < 360; angle += tempAngle) {
			// 第一个三角形, (angle = 60度时, 这是处于 60 ~ 90度的三角形)
			// 第一个中心点, 正中心的点
			flist.add(0f);	//屏幕中心
			flist.add(0f);	//屏幕中心
			flist.add(z);	//深度, z轴, 垂直于屏幕
			// 第二个点, (angle = 60度时 第一象限 60度 右上的点)
			flist.add((float) (R * UNIT_SIZE * Math.cos(Math.toRadians(angle))));	// 公式 : R / x = cos60, x = R * cos60
			flist.add((float) (R * UNIT_SIZE * Math.sin(Math.toRadians(angle))));	// 公式 : R / y = cos60, y = R * sin60
			flist.add(z);	//深度
			// 第三个点, 顺时针方向的三角形的另一个点
			flist.add((float) (r * UNIT_SIZE * Math.cos(Math.toRadians(angle
					+ tempAngle / 2))));
			flist.add((float) (r * UNIT_SIZE * Math.sin(Math.toRadians(angle
					+ tempAngle / 2))));
			flist.add(z);

			// 第二个三角形
			// 第一个中心点, 最中心的点
			flist.add(0f);
			flist.add(0f);
			flist.add(z);
			// 第二个点, (angle = 60度时, 这是处于 90 ~ 120 的三角形)
			flist.add((float) (r * UNIT_SIZE * Math.cos(Math.toRadians(angle
					+ tempAngle / 2))));
			flist.add((float) (r * UNIT_SIZE * Math.sin(Math.toRadians(angle
					+ tempAngle / 2))));
			flist.add(z);
			// 第三个点
			flist.add((float) (R * UNIT_SIZE * Math.cos(Math.toRadians(angle
					+ tempAngle))));
			flist.add((float) (R * UNIT_SIZE * Math.sin(Math.toRadians(angle
					+ tempAngle))));
			flist.add(z);
		}

		//顶点个数, 集合个数 / 3
		vCount = flist.size() / 3;
		//创建一个顶点数组, 大小为顶点集合的大小, 将顶点数组的元素拷贝到顶点集合中
		float[] vertexArray = new float[flist.size()];
		for (int i = 0; i < vCount; i++) {
			vertexArray[i * 3] = flist.get(i * 3);
			vertexArray[i * 3 + 1] = flist.get(i * 3 + 1);
			vertexArray[i * 3 + 2] = flist.get(i * 3 + 2);
		}

		//创建一个字节数组缓冲, 大小为 顶点个数 * 4
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertexArray.length * 4);
		// 设置字节顺序为本地操作系统顺序
		vbb.order(ByteOrder.nativeOrder());
		//将 byte 缓冲 转为 float 缓冲, 赋值给 顶点数据缓冲
		mVertexBuffer = vbb.asFloatBuffer();
		mVertexBuffer.put(vertexArray);
		//设置缓冲区的起始位置
		mVertexBuffer.position(0);

		/*
		 * 下面是初始化顶点颜色数据
		 */

		//共有 vCount 个顶点, 每个顶点颜色值是 4个分别是 RGBA
		float[] colorArray = new float[vCount * 4];
		//中心点设置一个颜色, 其它点设置一个颜色
		for (int i = 0; i < vCount; i++) {
			if (i % 3 == 0) {// 中心点为白色
				colorArray[i * 4] = 1;
				colorArray[i * 4 + 1] = 1;
				colorArray[i * 4 + 2] = 1;
				colorArray[i * 4 + 3] = 0;
			} else {// 边上的点为淡蓝色
				colorArray[i * 4] = 0.45f;
				colorArray[i * 4 + 1] = 0.75f;
				colorArray[i * 4 + 2] = 0.75f;
				colorArray[i * 4 + 3] = 0;
			}
		}

		ByteBuffer cbb = ByteBuffer.allocateDirect(colorArray.length * 4);
		cbb.order(ByteOrder.nativeOrder()); // 设置字节顺序为本地操作系统顺序
		//将颜色Byte缓冲转为 Float缓冲
		mColorBuffer = cbb.asFloatBuffer();
		//将颜色缓冲数据放入 颜色数据缓冲成员变量中
		mColorBuffer.put(colorArray);
		mColorBuffer.position(0);
		// 特别提示:由于不同平台字节顺序不同数据单元不是字节的一定要经过ByteBuffer
		// 转换,关键是要通过ByteOrder设置nativeOrder(),否则有可能会出问题

	}

	/**
	 * 初始化着色器
	 * ① 加载顶点着色器与片元着色器脚本
	 * ② 基于加载的着色器创建着色程序
	 * ③ 根据着色程序获取 顶点属性引用 顶点颜色引用 总变换矩阵引用
	 * @param mv
	 */
	public void initShader(ProjectionGLSurfaceView mv) {
		/*
         * mVertextShader是顶点着色器脚本代码
         * 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中
         * 传入的两个参数是 脚本名称 和 应用的资源
         * 应用资源Resources就是res目录下的那写文件
         */ 

		//① 加载顶点着色器的脚本内容
		mVertexShader = ShaderUtil.loadFromAssetsFile("vertex_projection.sh",
				mv.getResources());
		//② 加载片元着色器的脚本内容
		mFragmentShader = ShaderUtil.loadFromAssetsFile("frag_projection.sh",
				mv.getResources());
		//③ 基于顶点着色器与片元着色器创建程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错
		mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);

		/*
         * 从着色程序中获取 属性变量 顶点坐标(颜色)数据的引用
         * 其中的"aPosition"是顶点着色器中的顶点位置信息
         * 其中的"aColor"是顶点着色器的颜色信息
         */  

		//④ 获取程序中顶点位置属性引用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);
		// 设置绕y轴旋转
		Matrix.rotateM(mMMatrix, 0, yAngle, 0, 1, 0);
		// 设置绕z轴旋转
		Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);
		// 将最终变换矩阵传入shader程序
		GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false,
				MatrixState.getFinalMatrix(mMMatrix), 0);
		// 为画笔指定顶点位置数据
		GLES20.glVertexAttribPointer(maPositionHandle, 	// 顶点位置数据引用
				3, 										// 每 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);
	}
}

5. ProjectionGLSurfaceView 自定义View显示类

(1) 正交透视投影设置

关键成员变量 :

public static boolean isOrth

-- 正交投影 : 设置为 true, 时为正交投影;

-- 透视投影 : 设置为 false 时, 为透视投影;

(3) 源码

源码 :

package cn.org.octopus.opengl.projection;

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.view.MotionEvent;

/**
 * 自定义显示 OpenGL 图形的 SurfaceView
 *
 * ① 初始化 SurfaceView
 * 		a. 设置 OpenGL ES 版本
 * 		b. 创建场景渲染器
 * 		c. 设置场景渲染器
 * 		d. 设置场景渲染器模式
 * ② 自定义场景渲染器
 * 		a. 创建时 设置背景 -> 创建绘制元素 -> 打开深度检测
 * 		b. 场景改变时 设置视口参数 -> 设置投影参数 -> 设置摄像机参数
 * 		c. 绘制时 清楚颜色,深度缓冲 -> 绘制元素
 * @author octopus
 *
 */
public class ProjectionGLSurfaceView extends GLSurfaceView {
	public static boolean isOrth = true;

	private final float TOUCH_SCALE_FACTOR = 180.0f / 320; 	// 角度缩放比例
	private SceneRenderer mRenderer; 						// 场景渲染器

	private float mPreviousY;								//上次触摸位置的Y坐标
	private float mPreviousX;								//上次触摸位置的X坐标

	/**
	 * 初始化 GLSurfaceView
	 * ① 设置 OpenGL ES 的版本
	 * ② 创建场景渲染器
	 * ③ 设置场景渲染器
	 * ④ 设置场景渲染模式
	 * @param context
	 */
	public ProjectionGLSurfaceView(Context context) {
		super(context);
		this.setEGLContextClientVersion(2); 					// 设置OpenGL ES 版本为 2.0
		mRenderer = new SceneRenderer(); 						// 创建场景渲染器
		setRenderer(mRenderer); 								// 设置场景渲染器
		setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);	// 设置场景渲染模式
	}

	// 触摸方法
	@Override
	public boolean onTouchEvent(MotionEvent e) {
		float y = e.getY();	//获取当前触摸的 y 坐标
		float x = e.getX();	//获取当前触摸的 x 坐标
		switch (e.getAction()) {	//获取触摸类型
		case MotionEvent.ACTION_MOVE:
			float dy = y - mPreviousY;// 计算 y 方向的位移
			float dx = x - mPreviousX;// 计算 x 方向的位移
			for (SixPointedStar h : mRenderer.ha) {
				h.yAngle += dx * TOUCH_SCALE_FACTOR;// 设置六角星绕 x 轴旋转角度
				h.xAngle += dy * TOUCH_SCALE_FACTOR;// 设置六角星绕 y 轴旋转角度
			}
		}
		mPreviousY = y;// 将本次触摸的 y 坐标记录为历史坐标
		mPreviousX = x;// 将本次触摸的 x 坐标记录为历史坐标
		return true;
	}

	/**
	 * 场景渲染器
	 * 创建六角星数组中得六角星对象, 将六角星显示在屏幕中
	 * @author octopus
	 *
	 */
	private class SceneRenderer implements GLSurfaceView.Renderer {
		SixPointedStar[] ha = new SixPointedStar[6];// 六角星数组

		/**
		 * ① 清楚深度缓冲 与 颜色缓冲
		 * ② 重新绘制各个元素
		 */
		public void onDrawFrame(GL10 gl) {
			// 清除深度缓冲与颜色缓冲
			GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT
					| GLES20.GL_COLOR_BUFFER_BIT);
			// 循环绘制各个六角星
			for (SixPointedStar h : ha) {
				h.drawSelf();
			}
		}

		/**
		 * Surface 改变时
		 * ① 设置视口参数
		 * ② 设置投影参数
		 * ③ 设置摄像机参数
		 */
		public void onSurfaceChanged(GL10 gl, int width, int height) {
			// 设置视口的大小及位置
			GLES20.glViewport(0, 0, width, height);
			// 设置视口的宽高比, 注意视口的长宽比与近平面的长宽比需要相同, 否则显示内容会变形
			float ratio = (float) width / height;
			// 设置正交投影, 如果是透视投影, 就在这里使用透视投影
			if(isOrth){
				//设置正交投影
				MatrixState.setProjectOrtho(-ratio, ratio, -1, 1, 1, 10);
			}else{
				//设置透视投影
	        	MatrixState.setProjectFrustum(-ratio*0.4f, ratio*0.4f, -1*0.4f, 1*0.4f, 1, 50);
			}

			// 设置摄像机位置
			MatrixState.setCamera(0, 0, 3f, 0, 0, 0f, 0f, 1.0f, 0.0f);
		}

		/**
		 * 创建时回调
		 * ① 设置北京颜色
		 * ② 创建绘制元素
		 * ③ 打开深度检测
		 */
		public void onSurfaceCreated(GL10 gl, EGLConfig config) {
			// 设置屏幕的背景颜色
			GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);

			float distance = 0f;
			if(isOrth){
				distance = -1.0f;
			}else{
				distance = -1.0f;
			}

			// 创建六角星数组中得各个六角星
			for (int i = 0; i < ha.length; i++) {
				ha[i] = new SixPointedStar(ProjectionGLSurfaceView.this, 0.2f, 0.5f,
						distance * i);
			}
			// 打开深度检测
			GLES20.glEnable(GLES20.GL_DEPTH_TEST);
		}
	}
}

6. OrthogonalProjectionActivity 类

源码 :

package cn.org.octopus.opengl.projection;
import cn.org.octopus.opengl.R;
import cn.org.octopus.opengl.utils.DLog;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;

/**
 * Activity 显示 OpenGL 流程
 * ① 设置屏幕参数
 * ② 初始化 GLSurfaceView
 * ③ 设置显示 GLSurface
 *
 * 在onResume 和 onPause 中分别调用 GLSurfaceView 的 onResume 和 onPause 方法
 * @author octopus
 *
 */
public class OrthogonalProjectionActivity extends Activity {

	public static final String TAG = "octopus.OrthogonalProjectionActivity";

	private ProjectionGLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //① 设置屏幕参数
        requestWindowFeature(Window.FEATURE_NO_TITLE); 						//设置无标题
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN ,  	//设置全屏充满
		              WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);	//设置屏幕为竖屏

		//② 初始化GLSurfaceView
        mGLSurfaceView = new ProjectionGLSurfaceView(this);

		//③ 设置显示 GLSurfaceView
		setContentView(mGLSurfaceView);					//设置界面显示该 GLSurfaceView
        mGLSurfaceView.requestFocus();					//获取焦点
        mGLSurfaceView.setFocusableInTouchMode(true);	//设置为可触控  

    }

    public void onClick(View view) {
    	DLog.i(TAG, "点击了按钮");
    	int id = view.getId();
    	switch (id) {
		case R.id.bt_switch_orth:
			ProjectionGLSurfaceView.isOrth = true;
			break;
		case R.id.bt_switch_flu:
			ProjectionGLSurfaceView.isOrth = false;
			break;

		default:
			break;
		}
	}

    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();	// GLSurfaceView 根据 Acivity 周期变化
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();	// GLSurfaceView 根据 Acivity 周期变化
    }
}

.

博客地址 : http://blog.csdn.net/shulianghan/article/details/46680803

源码下载 : http://download.csdn.net/detail/han1202012/8903437

博客地址 : http://blog.csdn.net/shulianghan/article/details/46680803

源码下载 : http://download.csdn.net/detail/han1202012/8903437

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-11 05:07:04

【Android应用开发】 OpenGL ES -- 透视投影 和 正交投影的相关文章

iOS 平台开发OpenGL ES程序注意事项

本人最近从Android平台的OpenGL ES开发转到iOS平台的OpenGL ES开发,由于平台不同,所以开发中会有一些区别,再次列出需要注意的几点. 1.首先需要了解iOS主要开发框架,再次仅介绍开发OpenGL ES应用常用的几个: CoreGraphics.framework  ——包含Quartz 2D接口(Core Graphics框架参考) Foundation.framework ——包含Cocoa Foundation层的类和方法(Foundation框架参考) OpenGL

在Android中使用OpenGL ES进行开发第(一)节:概念先行

一.前期基础是知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGL ES绘制2D/3D图形的第一步:定义图形:——运用部分 ③使用OpenGL ES绘制出②步骤中定义好的图形:——运用部分,难点所在 通过这三篇文章的分析,就像给万丈高楼垫定了基石,万丈高楼平地起,后面利用OpenGLES做各种效果,各种变换都是建立在这三步的图形编程理解之上的. 话不多说正文开始 (1)什么

Android学习笔记&mdash;&mdash;OpenGL ES的基本用法、绘制流程与着色器编译

首先声明下,本文为笔者学习<OpenGL ES应用开发实践指南(Android卷)>的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载. 在Android.iOS等移动平台上,开发者可以使用跨平台应用编程接口创建二维或者三维图形,或进行图像处理和计算机视觉应用,结合两者将能构建丰富有趣的交互体验.前者称为OpenGL,后者称为OpenCV,不过本文主要介绍前者,OpenCV在后续文章中涉及.OpenGL应用于桌面系统的历史已经很长了,但考虑到移动平台的特点(计算能力.性能等),将

如何使用Android中的OpenGL ES媒体效果

Android的媒体效果框架允许开发者可以很容易的应用多种令人印象深刻的视觉效果到照片或视频之上.作为这个媒体效果的框架,它使用GPU来处理图片处理的过程,它仅仅接收OpenGL的纹理(texture)作为输入.在本次教程中,你将会学习到如何使用OpenGL ES2.0将图片资源转化为纹理,以及如何使用框架为图片应用不同的处理效果. 准备 为了开始本次的教程,你必须具备: 1.一款支持Android开发的IDE,如果你没有的话,可以在Android Developer website下载最新版本

iOS开发- OpenGL ES屏幕截图

之前写过一个常规的屏幕截图:http://blog.csdn.net/hitwhylz/article/details/17189351 可是发现这个办法对于OpenGL 无用.  获取到的数据为空. 所以这里介绍下OpenGL ES屏幕截图. 1.初始化. CAEAGLLayer *eaglLayer = (CAEAGLLayer *) self.layer; eaglLayer.drawableProperties = @{ kEAGLDrawablePropertyRetainedBack

iOS开发-OpenGL ES入门教程1

http://www.jianshu.com/p/750fde1d8b6a 这里是一篇新手教程,环境是Xcode7+OpenGL ES 2.0,目标写一个OpenGL ES的hello world.OpenGL ES系列教程在这里.OpenGL ES系列教程的代码地址 你的star和fork是我的源动力,你的意见能让我走得更远. 核心思路 通过GLKit,尽量简单地实现把一张图片绘制到屏幕. 效果展示 具体细节 1.新建OpenGL ES上下文 - (void)setupConfig { //新

opengl ES中的正交投影

由于设备使用的归一化坐标系与像素无关,范围在三个方向上都是-1~1,所以直接硬编码绘制图元的时候可能会因为设备像素问题产生横向或是竖向被压缩(扭曲). 书中的解决方法是将操作放在一个虚拟坐标空间中,就是我们设计顶点数值的时候假象自己就是在虚拟坐标空间中进行的,至于如何映射到设备上,可以不必考虑. 正交投影是一种将一个空间区域投影到一个二维平面上的方法(投影就是把高维降到地位的过程),这里所说的这个空间区域是一个无论多远去看物体的大小都不会变得一个三维空间中的区域,超过这个区域的部分不能被看到.

Android OpenGL ES(三)----编程框架

首先当然是创建Android项目,你可以选择最新的Android Studio也可以选择eclipse都是一样的.我们重点讲解开发OpenGL ES的流程 1.定义顶点着色器和片段着色器 第一节我们讲解的已经很细致了,为了便于理解在这里在详细的说明一下.并且换一种方式定义着色器. 我们知道第一篇定义的顶点的坐标和颜色是分开的,这样可以但如果把它们放在一起会方便许多. 假设我们要绘制一个长方形和两条直线,二个定点,我们就需要这样来定义这个数组. float[] tableVerticesWithT

Android OpenGL ES 应用(一)

OpenGL已经成了3D的一个"标准" 因为它能跨平台,接口也比较丰富,几乎大部分的手机3D游戏都和OpenGL有关系. 当然还有微软有direct X 但只能在微软平台上使用. OpenGL底层是c/c++实现,JAVA中使用都是用封装好的类库.Android提供了以下几个接口包 可使用,基本能达到3D技术的要求. Android平台用OpenGL ES 这个子集来处理图像,现在OpenGL ES基本用2.0的了,很少再用1.0的,3.0还未流行起来. 首先编写判断Android设备