【Android 应用开发】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解
最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 .
案例下载地址 : http://download.csdn.net/detail/han1202012/6651095 需要SDK-10 版本2.3.3
.
作者 :万境绝尘
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835
.
一. 程序介绍
1. 样例展示
该程序打开之后会出现一个旋转的三角形, 该三角形一直绕x轴z方向旋转 如图 :
2. 程序结构
本程序中定义了四个类 : ShaderUtil , Triangle , MyTDView , MainActivity .
在Activity中加载myTDView对象, MyTDView对象中绘制Triangle 三角形图形, Triangle调用ShaderUtil加载着色脚本并创建着色程序.
四个类之间的关系 :
3. 方法介绍
(1) ShaderUtil方法
a. 加载着色器方法 : 根据着色器类型 和 着色器脚本字符串获取着色器
public static int loadShader(int shaderType , String source)
流程 : 创建着色器 -> 加载着色器脚本 -> 编译着色器 -> 获取着色器编译结果
b. 检查错误方法 : 检查每一步是否出现错误
public static void checkGLError(String op)
流程 : 循环获取错误信息, 知道出现异常将异常信息打印出来
c. 创建着色器方法 : 根据顶点着色器和片元着色器创建着色程序
public static int createProgram(String vertexSource , String fragmentSource)
流程 : 调用loadShader()加载顶点,片元着色器 -> 创建着色程序 -> 向着色程序中加载顶点,片元着色器 -> 连接程序 -> 获取链接结果
d. 获取着色脚本 : 从assets目录中的着色脚本中获取着色脚本的字符串信息
public static String loadFromAssetsFile(String fileName, Resources resources)
流程 : 打开assets输入流 -> 创建带缓冲区的输出流 -> 读取输入流信息放入缓冲区 -> 将缓冲区数据转为字符
二 ShaderUtils类介绍
1.安卓基本API
(1) 创建assets目录中文件的输入流
InputStream is = resources.getAssets().open(fileName);
参数 : assets目录中着色脚本的文件名;
返回值 : 着色脚本文件的输入流;
作用 : 使用该输入流可以读取着色脚本信息
(2)带缓冲区的输出流
创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 逐个字节读取数据, 并将读取的数据放入缓冲器中 while((ch = is.read()) != -1){ baos.write(ch); } 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串 byte[] buffer = baos.toByteArray();
读写单位 : 这个输出流读取字节的单位是int, 这里要特别注意;
读取方法 : read()方法每次读取一个字节, 并返回读取到的字节;
写出方法 : write()方法将一个字节写入到ByteArrayOutputStream的缓冲区中;
导出数据 : 调用toByteArray()方法可以将缓冲区中的数据转为字节数组, 并返回这个数组;
2.着色器相关API介绍
(1)创建着色程器
int shader = GLES20.glCreateShader(shaderType);
参数 : 这个函数的作用根据着色器类型 , 着色器的类型有两种 , GLES20.GL_VERTEX_SHADER 顶点着色器 , GLES20.GL_FRAGMENT_SHADER 片元着色器
返回值 : 该方法返回的是着色器的引用
(2)加载着色器源代码
GLES20.glShaderSource(shader, source);
(3)编译着色器
GLES20.glCompileShader(shader);
(4)获取着色器编译情况
int complied[] = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, complied, 0);
参数 :
- shader : 着色器引用 , 这个着色器已经加载了着色脚本字符串以及经过了编译 ;
- pname : GLES20.GL_COMPILE_STATUS : 获取信息类型代码 : 我们要获取编译情况 , 这里是编译状态的代码
- params[] : compile : 存放结果数组
- index : 存放结果索引 , 将编译成功的脚本数放在数组的哪个索引下
(5)删除着色器
GLES20.glDeleteShader(shader);
3.着色程序相关的API
(1)创建OpenGL程序
int program = GLES20.glCreateProgram();
(2)获取OpenGL中的错误信息
GLES20.glGetError();
(3)向程序中加入着色器
GLES20.glAttachShader(program, vertextShader);
参数 : program 是调用GLES20.glCreateProgram()方法创建程序的返回值 , 这是程序的引用 . vertextShader是着色器的引用 , 注意 这个着色器是加载了着色脚本并且成功编译的着色器引用 .
(4)连接程序
GLES20.glLinkProgram(program);
(5)获取链接程序结果
int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
(6)删除着色程序
GLES20.glDeleteProgram(program);
参数 : 着色程序的引用;
4. 源码
package shuliang.han.rotatetriangle; 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; } }
三. Triangle 3D三角形数据
1. 顶点数据容器相关api
(1) 创建ByteBuffer对象
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4)
(2) 设置字节缓冲区顺序
vbb.order(ByteOrder.nativeOrder());
设置字节缓冲区的顺序为本地顺序.
(3) 将字节缓冲区转为浮点缓冲区
mVertexBuffer = vbb.asFloatBuffer();
(4) 向字节缓冲区中存入数据
mColorBuffer.put(colors);
(5)指定浮点型缓冲区起始位置
mColorBuffer.position(0);
2. 初始化着色器相关api
(1) 获取着色器属性变量引用
int maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
该方法从着色程序中的顶点着色器中获取属性变量(Attribute) aPosition.
(2) 获取着色器一直变量引用
float[] muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
该方方法从着色程序中的顶点着色器获取一致变量
3. 绘制3D图形相关api
(1) 指定着色器程序
GLES20.glUseProgram(mProgram);
参数 : 着色程序的引用id
(2) 设置旋转初始情况
Matrix.setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z)
(3) 设置位移
Matrix.translateM(float[] m, int mOffset, float x, float y, float z)
参数 : m 变换矩阵; mOffset 变换矩阵的起始位置; 剩下的三个是位移向量.
(4) 设置旋转矩阵
Matrix.rotateM(float[] m, int mOffset, float a, float x, float y, float z)
参数 : m 变换矩阵; mOffset 变换矩阵起始位置; a 旋转的角度; 剩下的三个参数是旋转的轴;
(5) 应用投影和视口变换
GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset)
参数 :
(6) 将顶点数据传进渲染管线
GLES20.glVertexAttribPointer( maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer );
参数 : 顶点位置数据引用 几个一组 单位 false 个数 数据缓冲区.
(7) 启用传入的数据
GLES20.glEnableVertexAttribArray(maPositionHandle);
作用 : 将刚才传入渲染管线的数据启用;
(8) 执行绘制方法
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
该方法绘制三角形
4. 矩阵计算相关api
Matrix.multiplyMM(float[] result, int resultOffset, float[] lhs, int lhsOffset, float[] rhs, int rhsOffset)
参数 : 三组, 一个矩阵带着一个起始位置.
5. 源码
package shuliang.han.rotatetriangle; 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]; //4 * 4 投影矩阵 public static float[] mVMatrix = new float[16]; //摄影机位置朝向参数矩阵 public static float[] mMVPMatrix; //最后起作用的总变换矩阵 int mProgram; //自定义渲染管线着色程序id /* * 下面的三个变量是顶点着色器中定义的三个变量 * 其中的总变换矩阵属性 是 一致变量 * 顶点位置 和 颜色属性 是 属性变量 */ int muMVPMatrixHandle; //总变换矩阵的引用 int maPositionHandle; //顶点位置属性引用 int maColorHandle; //顶点颜色属性引用 String mVertexShader; //顶点着色器脚本代码 String mFragmentShader; //片元着色器脚本代码 /* * 这个变换矩阵 在设置变换 , 位移 , 旋转的时候 将参数设置到这个矩阵中去 */ static float[] mMMatrix = new float[16]; //具体物体的3D变换矩阵, 包括旋转, 平移, 缩放 /* * 这两个缓冲获得方法 * ①创建ByteBuffer, 创建时赋予大小 设置顺序 * ②将ByteBuffer 转为FloatBuffer * ③给FloatBuffer设置值, 设置起始位置 */ FloatBuffer mVertexBuffer; //顶点坐标数据缓冲 FloatBuffer mColorBuffer; //顶点着色数据缓冲 int vCount = 0; //顶点数量 float xAngle = 0; //绕x轴旋转角度 /** * 构造方法 * @param mv GLSurfaceView子类对象, 显示3D画面的载体 */ public Triangle(MyTDView mv){ initVertexData(); initShader(mv); } /** * 初始化顶点数据 * * 该方法制定顶点坐标和颜色数据, 并将数据输入到缓冲区 * * 创建一个ByteBuffer缓冲区, 然后将ByteBuffer缓冲区转为FloatBuffer缓冲区 * a. 创建float数组, 将对应的顶点(颜色)数据放到数组中去; * b. 创建ByteBuffer对象, 根据之前创建的float数组的字节大小创建这个ByteBuffer对象,使用allocateDirect(int)分配大小 * c. 设置ByteBuffer对象的顺序, 调用order(ByteOrder.nativeOrder),设置为本地操作系统顺序 * d. 将ByteBuffer对象转为FloatBuffer对象, 调用asFloatBuffer()方法; * e. 给FloatBuffer对象设置数组, 将开始创建的float数组设置给FloatBuffer对象; * f. 设置FloatBuffer对象缓冲区的起始位置为0 */ public void initVertexData() { //设置定点数为3 vCount = 3; //计算三角形顶点的单位 final float UNIT_SIZE = 0.2f; /* * 这个float数组9个浮点数, 每3个为一个顶点的坐标 */ float vertices[] = new float[]{ -4 * UNIT_SIZE, 0 , 0, //x轴左边的坐标 0, -4 * UNIT_SIZE, 0, //y轴坐标 4 * UNIT_SIZE, 0, 0 //x轴右边的坐标 }; /* * 创建一个ByteBuffer对象, 这个对象中缓冲区大小为vertices数组大小的4倍 * 因为每个float占4个字节, 创建的缓冲区大小正好将vertices装进去 */ ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); //设置字节顺序为本地操作系统顺序 vbb.order(ByteOrder.nativeOrder()); //将该缓冲区转换为浮点型缓冲区 mVertexBuffer = vbb.asFloatBuffer(); //将顶点的位置数据写入到顶点缓冲区数组中 mVertexBuffer.put(vertices); //设置缓冲区的起始位置为0 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);//创建ByteBuffer cbb.order(ByteOrder.nativeOrder());//设置字节顺序 mColorBuffer = cbb.asFloatBuffer();//将字节缓冲转为浮点缓冲 mColorBuffer.put(colors); mColorBuffer.position(0); } /** * 初始化着色器 * * 流程 : * ① 从资源中获取顶点 和 片元着色器脚本 * ② 根据获取的顶点 片元着色器脚本创建着色程序 * ③ 从着色程序中获取顶点位置引用 , 顶点颜色引用, 总变换矩阵引用 * * @param mv MyTDView对象, 是GLSurfaceView对象 */ public void initShader(MyTDView mv){ /* * mVertextShader是顶点着色器脚本代码 * 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中 * 传入的两个参数是 脚本名称 和 应用的资源 * 应用资源Resources就是res目录下的那写文件 */ mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources()); mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources()); /* * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错 */ mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader); /* * 从着色程序中获取 属性变量 顶点坐标(颜色)数据的引用 * 其中的"aPosition"是顶点着色器中的顶点位置信息 * 其中的"aColor"是顶点着色器的颜色信息 */ maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor"); /* * 从着色程序中获取一致变量 总变换矩阵 * uMVPMatrix 是顶点着色器中定义的一致变量 */ muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); } /** * 绘制三角形方法 * * 绘制流程 : * ① 指定着色程序 * ② 设置变换矩阵 * ③ 将顶点位置 颜色 数据传进渲染管线 * ④ 启动顶点位置 颜色 数据 * ⑤ 执行绘制 */ public void drawSelf(){ //根据着色程序id 指定要使用的着色器 GLES20.glUseProgram(mProgram); /* * 设置旋转变化矩阵 * 参数介绍 : ① 3D变换矩阵 ② 矩阵数组的起始索引 ③旋转的角度 ④⑤⑥ */ Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0); /* * 设置沿z轴正方向位移 * 参数介绍 : ① 变换矩阵 ② 矩阵索引开始位置 ③④⑤设置位移方向z轴 */ 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, //每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); } /** * 计算最终投影的矩阵 * @param spec * @return */ 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; } }
四. GLSurfaceView相关api
1. 相关api
(1) 设置OpenGL版本
GLSurfaceView.setEGLContextClientVersion(int version)
作用 : 设置OPenGL的版本号, version 是 2 , 就是设置OpenGLES2.0;
(2) 设置背景颜色
GLES20.glClearColor(0, 0, 0, 1.0f);
(3) 设置视口大小
GLES20.glViewport(int x, int y, int width, int height)
(4) 设置透视矩阵
Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)
参数 : m 投影矩阵; offset 投影矩阵起始位置; 剩下的参数为 左 右 下 上 近视点 远视点;
(5) 设置摄像机参数
Matrix.setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)
参数 : rm 摄像机参数矩阵; rmOffset 摄像机参数矩阵起始位置; 剩下的三个一组, 分别是 摄像机位置 摄像机朝向摄像机上方朝向 ;
(6) 清除深度缓冲与颜色缓冲
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
2. 源码
package shuliang.han.rotatetriangle; 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 { private final float ANGLE_SPAN = 0.375f; //三角形每次旋转的角度 private RotateThread mRotateThread; //该线程用来改变图形角度 private SceneRenderer mSceneRender; //渲染器 public MyTDView(Context context) { super(context); //设置OpenGLES版本为2.0 this.setEGLContextClientVersion(2); //设置渲染器 渲染模式 mSceneRender = new SceneRenderer(); this.setRenderer(mSceneRender); this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); } /** * 渲染器 * 实现了下面三个方法 : * 界面创建 : * 界面改变 : * 界面绘制 : * @author HanShuliang * */ private class SceneRenderer implements Renderer{ Triangle triangle; @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { //设置屏幕背景色 GLES20.glClearColor(0, 0, 0, 1.0f); //创建三角形对象 triangle = new Triangle(MyTDView.this); //打开深度检测 GLES20.glEnable(GLES20.GL_DEPTH_TEST); mRotateThread = new RotateThread(); mRotateThread.start(); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { //设置视窗大小及位置 GLES20.glViewport(0, 0, width, height); //计算GLSurfaceView的宽高比 float ratio = (float)width/height; /* * 产生透视矩阵 * 参数介绍 : * ① 4 * 4 投影矩阵 * ② 投影矩阵的起始位置 * 后面的四个参数分别是 左 右 下 上 的距离 * 最后两个参数是 近视点 和 远视点 距离 */ Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10); /* * 设置摄像机参数矩阵 * 参数介绍 : * 前两个参数是摄像机参数矩阵 和 矩阵数组的起始位置 * 后面三个一组是三个空间坐标 先后依次是 摄像机的位置 看的方向 摄像机上方朝向 */ Matrix.setLookAtM(Triangle.mVMatrix, 0, 0f,0f,3f, 0f,0f,0f, 0f,1.0f,0.0f); } @Override public void onDrawFrame(GL10 gl) { //清除深度缓冲与颜色缓冲 GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); //绘制三角形 triangle.drawSelf(); } } /** * 这个线程是用来改变三角形角度用的 */ public class RotateThread extends Thread{ public boolean flag = true; @Override public void run() { while(flag){ mSceneRender.triangle.xAngle = mSceneRender.triangle.xAngle + ANGLE_SPAN; try { Thread.sleep(20); } catch (Exception e) { e.printStackTrace(); } } } } }
五 MainActivity相关
1. 相关api
(1) 设置界面为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
(2) 界面获取焦点
View.requestFocus()
(3) 设置可获取焦点
View.setFocusableInTouchMode(boolean focusableInTouchMode)
作用 : 在触摸的时候获取焦点
2. 源码
package shuliang.han.rotatetriangle; import android.app.Activity; import android.content.pm.ActivityInfo; import android.os.Bundle; public class MainActivity extends Activity { private MyTDView myTDView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置界面显示为竖屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //创建OpenGL的显示界面 myTDView = new MyTDView(this); myTDView.requestFocus(); myTDView.setFocusableInTouchMode(true); //将OpenGL显示界面设置给Activity setContentView(myTDView); } @Override public void onResume() { super.onResume(); myTDView.onResume(); } @Override public void onPause() { super.onPause(); myTDView.onPause(); } }
六. 着色器脚本
uniform mat4 uMVPMatrix; //总变换矩阵 attribute vec3 aPosition; //顶点位置 attribute vec4 aColor; //顶点颜色 varying vec4 vColor; //用于传递给片元着色器的变量 void main() { gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置 vColor = aColor;//将接收的颜色传递给片元着色器 }
片元着色器 :
precision mediump float; varying vec4 vColor; //接收从顶点着色器过来的参数 void main() { gl_FragColor = vColor;//给此片元颜色值 }
【Android 应用开发】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解的更多相关文章
- OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解
最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 . 案例下载地址 : http://download.csdn.net/detail/han120201 ...
- OpenGL ES 3.0 点,线,三角形绘制形式总结
OpenGL ES 3.0 顶点 -1, 1, 0, -0.5f, 0, 0, 0, -1, 0, -1, 0, 0, 0.5f, 0, 0, 1, -1, ...
- OpenGL ES 3.0之Uniform详解
Uniform是变量类型的一种修饰符,是OpenGL ES 中被着色器中的常量值,使用存储各种着色器需要的数据,例如:转换矩阵.光照参数或者颜色. uniform 的空间被顶点着色器和片段着色器分享 ...
- OpenGL ES 3.0之VertexAttributes,Vertex Arrays,and Buffer Objects(九)
顶点数据,也称为顶点属性,指每一个顶点数据.指能被用来描述每个顶点的数据,或能被所有顶点使用的常量值.例如你想绘制一个具有颜色的立方体三角形.你指定一个恒定的值用于三角形的所有三个顶点颜色.但三角形的 ...
- OpenGL ES 3.0之Shading Language(八)
每个OpenGL ES 3.0程序要求一个顶点着色器和一个片段着色器去渲染一个图形.着色器概念是API 的中心,本篇将介绍着色器语言部分包含下面几项 1.变量和变量类型 2.矢量和矩阵创建及选择 3. ...
- 在Android中使用OpenGL ES开发第(五)节:GLSL基础语法
一.前期基础储备笔者之前的四篇文综述了Android中使用OpenGL ES绘制基本图形和实现了简单的相机预览,初次接触OpenGL ES开发的读者可能对其中新的概念比较迷惑,尤其是其中的顶点着色器( ...
- 在Android中使用OpenGL ES进行开发第(三)节:绘制图形
一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...
- 在Android中使用OpenGL ES进行开发第(二)节:定义图形
一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...
- 在Android中使用OpenGL ES进行开发第(一)节:概念先行
一.前期基础是知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGL ES绘制 ...
随机推荐
- Apache shiro集群实现 (二) shiro 的INI配置
Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...
- FORM开发技术之动态控制某些item的属性
利用FORM内置函数控制ITEM包括按钮,普通ITEM等等的属性,更多内置函数学习课参考我的博客FORM内置系统函数 http://blog.csdn.net/cai_xingyun/article/ ...
- ROS探索总结(十六)——HRMRP机器人的设计
1. HRMRP简介 HRMRP(Hybrid Real-time Mobile Robot Platform,混合实时移动机器人平台)机器人是我在校期间和实验室的其他小伙伴一起从零开 ...
- Android 增量更新和升级
在年初的时候,尝试了一把热修复技术,当时选择的是阿里的andfix,使用起来也很简单,这里就不在多少,如果你对andfix有兴趣请链接:点击打开链接.虽然网上将热修复的文章很多,不过我还是想说原理,然 ...
- SSH网上商城---需求分析+表关系分析
SSH---小编初次接触的时候傻傻的以为这个跟SHE有什么关系呢?又是哪路明星歌手,后来才知道小编又土鳖了,原来SSH是这个样子滴,百度百科对她这样阐述,SSH即 Spring + Struts +H ...
- 【IOS 开发】Objective-C Foundation 框架 -- 字符串 | 日期 | 对象复制 | NSArray | NSSet | NSDictionary | 谓词
一. 字符串 API 1. NSString 用法简介 (1) NSString API 介绍 NSString 功能 : -- 创建字符串 : 使用 init 开头的实例方法, 也可以使用 Stri ...
- Android批量打包-如何一秒内打完几百个apk渠道包
在国内Android常用渠道可能多达几十个,如: 谷歌市场.腾讯应用宝.百度手机助手.91手机商城.360应用平台.豌豆荚.安卓市场.小米.魅族商店.oppo手机.联想乐商.中兴汇天地.华为.安智.应 ...
- Android View框架总结(一)
View和Activity的区别 View有哪些? ViewGroup是什么? 为什么Google产生ViewGroup? View的层级结构是什么? View的onMeasure()/onLayou ...
- A*寻路算法入门(五)
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- python上下文管理器ContextLib及with语句
http://blog.csdn.net/pipisorry/article/details/50444736 with语句 with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能 ...