博客地址 : 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
版权声明:本文为博主原创文章,未经博主允许不得转载。