opengl 制作 3D 彩色旋转三角形

来源:互联网 发布:淘宝网企业店铺 编辑:程序博客网 时间:2024/06/11 19:36

原文:

[置顶] OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解

链接:http://blog.csdn.net/shulianghan/article/details/17020359


分类: OpenGL ES 2.0 2077人阅读 评论(3)收藏举报
OpenGL顶点着色器片元着色器3DOpenGLES2.0

目录(?)[+]

最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 .


案例下载地址 : http://download.csdn.net/detail/han1202012/6651095 需要SDK-10 版本2.3.3


一. 程序介绍

 

1. 样例展示 


该程序打开之后会出现一个旋转的三角形, 该三角形一直绕x轴z方向旋转 如图 : 





2. 程序结构


本程序中定义了四个类 : ShaderUtil , Triangle , MyTDView , MainActivity . 


在Activity中加载myTDView对象, MyTDView对象中绘制Triangle 三角形图形, Triangle调用ShaderUtil加载着色脚本并创建着色程序.


四个类之间的关系




3. 方法介绍


(1) ShaderUtil方法


a. 加载着色器方法 : 根据着色器类型 和 着色器脚本字符串获取着色器

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static int loadShader(int shaderType , String source)  

流程 : 创建着色器 -> 加载着色器脚本 -> 编译着色器 -> 获取着色器编译结果


b. 检查错误方法 : 检查每一步是否出现错误

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static void checkGLError(String op)  
流程 : 循环获取错误信息, 知道出现异常将异常信息打印出来


c. 创建着色器方法 : 根据顶点着色器和片元着色器创建着色程序

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static int createProgram(String vertexSource , String fragmentSource)  
流程 : 调用loadShader()加载顶点,片元着色器 -> 创建着色程序 -> 向着色程序中加载顶点,片元着色器 -> 连接程序 -> 获取链接结果


d. 获取着色脚本 : 从assets目录中的着色脚本中获取着色脚本的字符串信息

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static String loadFromAssetsFile(String fileName, Resources resources)  
流程 : 打开assets输入流 -> 创建带缓冲区的输出流 -> 读取输入流信息放入缓冲区 -> 将缓冲区数据转为字符


二  ShaderUtils类介绍  


1.安卓基本API


(1) 创建assets目录中文件的输入流

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. InputStream is = resources.getAssets().open(fileName);  
参数 : assets目录中着色脚本的文件名;

返回值 : 着色脚本文件的输入流;

作用 : 使用该输入流可以读取着色脚本信息


(2)带缓冲区的输出流

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型  
  2. ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  3. 逐个字节读取数据, 并将读取的数据放入缓冲器中  
  4. while((ch = is.read()) != -1){  
  5.     baos.write(ch);  
  6. }  
  7. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串  
  8. byte[] buffer = baos.toByteArray();  

读写单位 : 这个输出流读取字节的单位是int, 这里要特别注意;

读取方法 : read()方法每次读取一个字节, 并返回读取到的字节;  

写出方法 : write()方法将一个字节写入到ByteArrayOutputStream的缓冲区中;

导出数据 : 调用toByteArray()方法可以将缓冲区中的数据转为字节数组, 并返回这个数组;




2.着色器相关API介绍

加载着色器流程 :创建着色器 -->加载着色器脚本 -->编译着色器 -->获取着色器编译结果

(1)创建着色程器

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int shader = GLES20.glCreateShader(shaderType);  
参数 : 这个函数的作用根据着色器类型 , 着色器的类型有两种 , GLES20.GL_VERTEX_SHADER 顶点着色器 , GLES20.GL_FRAGMENT_SHADER 片元着色器

返回值 : 该方法返回的是着色器的引用


(2)加载着色器源代码

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glShaderSource(shader, source);  
参数 : shader是着色器的引用 , 是glCreateShader()方法的返回值 ;  source是着色器脚本的字符串形式 .
返回值 : 该方法没有返回值 
这样就相当于将代码添加到了着色器中, 注意此时着色器还不能使用 , 还要编译之后才能使用.

(3)编译着色器

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glCompileShader(shader);  
参数 : shader是着色器的引用 
返回值 : 该方法没有返回值类型
执行这个方法的前提是 , 该着色器已经加载了着色器脚本字符串, 否则会编译错误

(4)获取着色器编译情况

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int complied[] = new int[1];  
  2. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, complied, 0);  
参数 : 
  • shader : 着色器引用 , 这个着色器已经加载了着色脚本字符串以及经过了编译 ;
  • pname : GLES20.GL_COMPILE_STATUS : 获取信息类型代码 : 我们要获取编译情况 , 这里是编译状态的代码
  • params[] : compile : 存放结果数组
  • index : 存放结果索引 , 将编译成功的脚本数放在数组的哪个索引下 
返回值 : 该方法没有返回值

(5)删除着色器

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glDeleteShader(shader);  
参数 : shader 是着色器的引用
返回值 : 该方法没有返回值
如果着色器编译没有通过 , 那么就删除这个着色器

3.着色程序相关的API

创建着色程序流程 :加载顶点着色器 -->加载片元着色器 -->创建着色程序 -->将顶点着色器加入着色程序 -->将片元着色器加入着色程序 -->链接着色程序 -->获取链接着色程序结果

(1)创建OpenGL程序

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int program = GLES20.glCreateProgram();  
调用GLES20.glCreateProgram()方法 , 可以创建一个3D程序 , 返回程序的引用 , 如果不返回0 , 说明没有创建成功.

(2)获取OpenGL中的错误信息

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glGetError();  
返回一个int类型的错误码 , 如果没有错误 , 就会返回 GLES20.GL_NO_ERROR 常量.

(3)向程序中加入着色器

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glAttachShader(program, vertextShader);  
参数 : program 是调用GLES20.glCreateProgram()方法创建程序的返回值 , 这是程序的引用 .   vertextShader是着色器的引用 , 注意 这个着色器是加载了着色脚本并且成功编译的着色器引用 .
返回值 : 该方法没有返回值;

(4)连接程序

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glLinkProgram(program);  
参数 : 需要链接的程序的引用, 即着色程序容器的句柄;
作用 : 着色程序中存放定点着色器与片元着色器;

(5)获取链接程序结果


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int[] linkStatus = new int[1];  
  2. GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);  
参数 : program , 程序的引用 ;
GLES20.GL_LINK_STATUS , 想要获取的信息的类别;
linkStatus , 存放结果的数组;
index , 将结果存放的数组的下标;

作用 : 这个方法可以获取到链接程序操作是否成功, 如果结果不为1, 说明链接程序失败;

(6)删除着色程序

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glDeleteProgram(program);  
参数 : 着色程序的引用;
作用 : 删除链接失败的着色程序;

4. 源码


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package shuliang.han.rotatetriangle;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.InputStream;  
  5.   
  6. import android.content.res.Resources;  
  7. import android.opengl.GLES20;  
  8. import android.util.Log;  
  9.   
  10. /* 
  11.  * 这个工具类用来加载定点着色器与片元着色器 
  12.  */  
  13. public class ShaderUtil {  
  14.       
  15.     /** 
  16.      * 加载着色器方法 
  17.      *  
  18.      * 流程 :  
  19.      *  
  20.      * ① 创建着色器 
  21.      * ② 加载着色器脚本 
  22.      * ③ 编译着色器 
  23.      * ④ 获取着色器编译结果 
  24.      *  
  25.      * @param shaderType 着色器类型,顶点着色器(GLES20.GL_FRAGMENT_SHADER), 片元着色器(GLES20.GL_FRAGMENT_SHADER) 
  26.      * @param source 着色脚本字符串 
  27.      * @return 返回的是着色器的引用, 返回值可以代表加载的着色器 
  28.      */  
  29.     public static int loadShader(int shaderType , String source){  
  30.         //1.创建一个着色器, 并记录所创建的着色器的id, 如果id==0, 那么创建失败  
  31.         int shader = GLES20.glCreateShader(shaderType);  
  32.         if(shader != 0){  
  33.             //2.如果着色器创建成功, 为创建的着色器加载脚本代码  
  34.             GLES20.glShaderSource(shader, source);  
  35.             //3.编译已经加载脚本代码的着色器  
  36.             GLES20.glCompileShader(shader);  
  37.             int[] compiled = new int[1];  
  38.             //4.获取着色器的编译情况, 如果结果为0, 说明编译失败  
  39.             GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);  
  40.             if(compiled[0] == 0){  
  41.                  Log.e("ES20_ERROR""Could not compile shader " + shaderType + ":");  
  42.                  Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));  
  43.                  //编译失败的话, 删除着色器, 并显示log  
  44.                  GLES20.glDeleteShader(shader);  
  45.                  shader = 0;  
  46.             }  
  47.         }  
  48.         return shader;  
  49.     }  
  50.       
  51.     /** 
  52.      * 检查每一步的操作是否正确 
  53.      *  
  54.      * 使用GLES20.glGetError()方法可以获取错误代码, 如果错误代码为0, 那么就没有错误 
  55.      *  
  56.      * @param op 具体执行的方法名, 比如执行向着色程序中加入着色器,  
  57.      *      使glAttachShader()方法, 那么这个参数就是"glAttachShader" 
  58.      */  
  59.     public static void checkGLError(String op){  
  60.         int error;  
  61.         //错误代码不为0, 就打印错误日志, 并抛出异常  
  62.         while( (error = GLES20.glGetError()) != GLES20.GL_NO_ERROR ){  
  63.              Log.e("ES20_ERROR", op + ": glError " + error);  
  64.              throw new RuntimeException(op + ": glError " + error);  
  65.         }  
  66.     }  
  67.       
  68.     /** 
  69.      * 创建着色程序 
  70.      *  
  71.      * ① 加载顶点着色器 
  72.      * ② 加载片元着色器 
  73.      * ③ 创建着色程序 
  74.      * ④ 向着色程序中加入顶点着色器 
  75.      * ⑤ 向着色程序中加入片元着色器 
  76.      * ⑥ 链接程序 
  77.      * ⑦ 获取链接程序结果 
  78.      *  
  79.      * @param vertexSource      定点着色器脚本字符串 
  80.      * @param fragmentSource    片元着色器脚本字符串 
  81.      * @return 
  82.      */  
  83.     public static int createProgram(String vertexSource , String fragmentSource){  
  84.         //1. 加载顶点着色器, 返回0说明加载失败  
  85.         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);  
  86.         if(vertexShader == 0)  
  87.             return 0;  
  88.         //2. 加载片元着色器, 返回0说明加载失败  
  89.         int fragShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);  
  90.         if(fragShader == 0)  
  91.             return 0;  
  92.         //3. 创建着色程序, 返回0说明创建失败  
  93.         int program = GLES20.glCreateProgram();  
  94.         if(program != 0){  
  95.             //4. 向着色程序中加入顶点着色器  
  96.             GLES20.glAttachShader(program, vertexShader);  
  97.             checkGLError("glAttachShader");  
  98.             //5. 向着色程序中加入片元着色器  
  99.             GLES20.glAttachShader(program, fragShader);  
  100.             checkGLError("glAttachShader");  
  101.               
  102.             //6. 链接程序  
  103.             GLES20.glLinkProgram(program);  
  104.             int[] linkStatus = new int[1];  
  105.             //获取链接程序结果  
  106.             GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);  
  107.             if(linkStatus[0] != GLES20.GL_TRUE){  
  108.                 Log.e("ES20.ERROR""链接程序失败 : ");  
  109.                 Log.e("ES20.ERROR", GLES20.glGetProgramInfoLog(program));  
  110.                 //如果链接程序失败删除程序  
  111.                 GLES20.glDeleteProgram(program);  
  112.                 program = 0;  
  113.             }             
  114.         }  
  115.         return program;  
  116.     }  
  117.       
  118.     /** 
  119.      * 从assets中加载着色脚本 
  120.      *  
  121.      * ① 打开assets目录中的文件输入流 
  122.      * ② 创建带缓冲区的输出流 
  123.      * ③ 逐个字节读取文件数据, 放入缓冲区 
  124.      * ④ 将缓冲区中的数据转为字符串 
  125.      *  
  126.      * @param fileName assets目录中的着色脚本文件名 
  127.      * @param resources 应用的资源 
  128.      * @return 
  129.      */  
  130.     public static String loadFromAssetsFile(String fileName, Resources resources){  
  131.         String result = null;  
  132.         try {  
  133.             //1. 打开assets目录中读取文件的输入流, 相当于创建了一个文件的字节输入流  
  134.             InputStream is = resources.getAssets().open(fileName);  
  135.             int ch = 0;  
  136.             //2. 创建一个带缓冲区的输出流, 每次读取一个字节, 注意这里字节读取用的是int类型  
  137.             ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  138.             //3. 逐个字节读取数据, 并将读取的数据放入缓冲器中  
  139.             while((ch = is.read()) != -1){  
  140.                 baos.write(ch);  
  141.             }  
  142.             //4. 将缓冲区中的数据转为字节数组, 并将字节数组转换为字符串  
  143.             byte[] buffer = baos.toByteArray();  
  144.             baos.close();  
  145.             is.close();  
  146.             result = new String(buffer, "UTF-8");  
  147.             result = result.replaceAll("\\r\\n""\n");  
  148.         } catch (Exception e) {  
  149.             e.printStackTrace();  
  150.         }  
  151.         return result;  
  152.     }  
  153. }  


三. Triangle 3D三角形数据



1. 顶点数据容器相关api



初始化顶点数据流程 :创建ByteBuffer对象 ->设置ByteBuffer对象顺序 ->将ByteBuffer对象转为FloatBuffer对象 ->设置FloatBuffer对象值 ->设置FloatBuffer对象起始位置


(1) 创建ByteBuffer对象


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4)  
allocateDirect()方法创建ByteBuffer对象, 同时分配该字节缓冲去的大小, 注意这个对象最终要转为FloatBuffer对象, 每个float占4个字节, 一共有vertices.length个浮点数, 因此要分配vertices.length * 4 个字节大小.

(2) 设置字节缓冲区顺序


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. vbb.order(ByteOrder.nativeOrder());  
设置字节缓冲区的顺序为本地顺序. 

(3) 将字节缓冲区转为浮点缓冲区


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. mVertexBuffer = vbb.asFloatBuffer();  

(4) 向字节缓冲区中存入数据


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. mColorBuffer.put(colors);  
直接调用put方法, 将浮点型数组放入缓冲区.

(5)指定浮点型缓冲区起始位置


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. mColorBuffer.position(0);  

2. 初始化着色器相关api


初始化着色器流程 :获取顶点,片元着色器 ->创建着色程序 ->从着色程序中的顶点着色器获取顶点位置,颜色,投影矩阵引用

(1) 获取着色器属性变量引用


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");  
该方法从着色程序中的顶点着色器中获取属性变量(Attribute) aPosition.

(2) 获取着色器一直变量引用


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. float[] muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");  
该方方法从着色程序中的顶点着色器获取一致变量

3. 绘制3D图形相关api


绘制三角形流程 : 

(1) 指定着色器程序


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glUseProgram(mProgram);  
参数 : 着色程序的引用id
作用 : 该方法的作用是指定程序中要使用的着色器

(2) 设置旋转初始情况


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Matrix.setRotateM(float[] rm, int rmOffset, float a, float x, float y, float z)  
参数 : rm 变换矩阵; rmOffset 变换矩阵的索引; a 旋转角度; 剩下的三个是旋转的轴
这个方法的作用是设置旋转变化矩阵

(3) 设置位移


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Matrix.translateM(float[] m, int mOffset, float x, float y, float z)  
参数 : m 变换矩阵; mOffset 变换矩阵的起始位置; 剩下的三个是位移向量.

(4) 设置旋转矩阵


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Matrix.rotateM(float[] m, int mOffset, float a, float x, float y, float z)  
参数 : m 变换矩阵; mOffset 变换矩阵起始位置; a 旋转的角度; 剩下的三个参数是旋转的轴;

(5) 应用投影和视口变换


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glUniformMatrix4fv(int location, int count, boolean transpose, float[] value, int offset)  
参数 : 

(6) 将顶点数据传进渲染管线


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glVertexAttribPointer(  
  2.         maPositionHandle,   
  3.         3,   
  4.         GLES20.GL_FLOAT,   
  5.         false,   
  6.         3 * 4,   
  7.         mVertexBuffer  
  8. );  
参数 : 顶点位置数据引用 几个一组 单位 false 个数 数据缓冲区.

(7) 启用传入的数据


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glEnableVertexAttribArray(maPositionHandle);  
参数 : 从着色程序中获取的数据引用
作用 : 将刚才传入渲染管线的数据启用;

(8) 执行绘制方法


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);  
该方法绘制三角形

4. 矩阵计算相关api


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Matrix.multiplyMM(float[] result, int resultOffset, float[] lhs, int lhsOffset, float[] rhs, int rhsOffset)  
参数 : 三组, 一个矩阵带着一个起始位置.
作用 : 计算投影变换矩阵, 将前两个矩阵计算结果存入第三个矩阵;

5. 源码


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package shuliang.han.rotatetriangle;  
  2.   
  3. import java.nio.ByteBuffer;  
  4. import java.nio.ByteOrder;  
  5. import java.nio.FloatBuffer;  
  6.   
  7. import android.opengl.GLES20;  
  8. import android.opengl.Matrix;  
  9.   
  10. public class Triangle {  
  11.       
  12.     public static float[] mProjMatrix = new float[16];  //4 * 4 投影矩阵  
  13.     public static float[] mVMatrix = new float[16];     //摄影机位置朝向参数矩阵  
  14.     public static float[] mMVPMatrix;                   //最后起作用的总变换矩阵  
  15.       
  16.     int mProgram;                                       //自定义渲染管线着色程序id  
  17.     /* 
  18.      * 下面的三个变量是顶点着色器中定义的三个变量 
  19.      * 其中的总变换矩阵属性 是 一致变量 
  20.      * 顶点位置 和 颜色属性 是 属性变量 
  21.      */  
  22.     int muMVPMatrixHandle;                              //总变换矩阵的引用  
  23.     int maPositionHandle;                               //顶点位置属性引用  
  24.     int maColorHandle;                                  //顶点颜色属性引用  
  25.       
  26.     String mVertexShader;                               //顶点着色器脚本代码  
  27.     String mFragmentShader;                             //片元着色器脚本代码  
  28.       
  29.     /* 
  30.      * 这个变换矩阵 在设置变换 , 位移 , 旋转的时候 将参数设置到这个矩阵中去 
  31.      */  
  32.     static float[] mMMatrix = new float[16];            //具体物体的3D变换矩阵, 包括旋转, 平移, 缩放  
  33.       
  34.     /* 
  35.      * 这两个缓冲获得方法 
  36.      * ①创建ByteBuffer, 创建时赋予大小 设置顺序 
  37.      * ②将ByteBuffer 转为FloatBuffer 
  38.      * ③给FloatBuffer设置值, 设置起始位置 
  39.      */  
  40.     FloatBuffer mVertexBuffer;                          //顶点坐标数据缓冲  
  41.     FloatBuffer mColorBuffer;                           //顶点着色数据缓冲  
  42.       
  43.     int vCount = 0;                                         //顶点数量  
  44.     float xAngle = 0;                                       //绕x轴旋转角度  
  45.       
  46.     /** 
  47.      * 构造方法 
  48.      * @param mv GLSurfaceView子类对象, 显示3D画面的载体 
  49.      */  
  50.     public Triangle(MyTDView mv){  
  51.         initVertexData();  
  52.         initShader(mv);  
  53.     }  
  54.       
  55.     /** 
  56.      * 初始化顶点数据 
  57.      *  
  58.      * 该方法制定顶点坐标和颜色数据, 并将数据输入到缓冲区 
  59.      *  
  60.      * 创建一个ByteBuffer缓冲区, 然后将ByteBuffer缓冲区转为FloatBuffer缓冲区 
  61.      * a. 创建float数组, 将对应的顶点(颜色)数据放到数组中去; 
  62.      * b. 创建ByteBuffer对象, 根据之前创建的float数组的字节大小创建这个ByteBuffer对象,使用allocateDirect(int)分配大小 
  63.      * c. 设置ByteBuffer对象的顺序, 调用order(ByteOrder.nativeOrder),设置为本地操作系统顺序 
  64.      * d. 将ByteBuffer对象转为FloatBuffer对象, 调用asFloatBuffer()方法; 
  65.      * e. 给FloatBuffer对象设置数组, 将开始创建的float数组设置给FloatBuffer对象; 
  66.      * f. 设置FloatBuffer对象缓冲区的起始位置为0 
  67.      */  
  68.     public void initVertexData() {  
  69.         //设置定点数为3  
  70.         vCount = 3;   
  71.         //计算三角形顶点的单位  
  72.         final float UNIT_SIZE = 0.2f;  
  73.         /* 
  74.          * 这个float数组9个浮点数, 每3个为一个顶点的坐标 
  75.          */  
  76.         float vertices[] = new float[]{  
  77.                 -4 * UNIT_SIZE, 0 , 0,  //x轴左边的坐标   
  78.                 0, -4 * UNIT_SIZE, 0,   //y轴坐标  
  79.                 4 * UNIT_SIZE, 00     //x轴右边的坐标  
  80.         };  
  81.         /* 
  82.          * 创建一个ByteBuffer对象, 这个对象中缓冲区大小为vertices数组大小的4倍 
  83.          * 因为每个float占4个字节, 创建的缓冲区大小正好将vertices装进去 
  84.          */  
  85.         ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);  
  86.         //设置字节顺序为本地操作系统顺序  
  87.         vbb.order(ByteOrder.nativeOrder());  
  88.         //将该缓冲区转换为浮点型缓冲区  
  89.         mVertexBuffer = vbb.asFloatBuffer();  
  90.         //将顶点的位置数据写入到顶点缓冲区数组中  
  91.         mVertexBuffer.put(vertices);  
  92.         //设置缓冲区的起始位置为0  
  93.         mVertexBuffer.position(0);  
  94.           
  95.         /* 
  96.          * 顶点颜色数组 
  97.          * 每四个浮点值代表一种颜色 
  98.          */  
  99.         float colors[] = new float[]{  
  100.                 1110,  
  101.                 0010,  
  102.                 0100  
  103.         };  
  104.         ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);//创建ByteBuffer  
  105.         cbb.order(ByteOrder.nativeOrder());//设置字节顺序  
  106.         mColorBuffer = cbb.asFloatBuffer();//将字节缓冲转为浮点缓冲  
  107.         mColorBuffer.put(colors);  
  108.         mColorBuffer.position(0);  
  109.     }  
  110.       
  111.     /** 
  112.      * 初始化着色器 
  113.      *  
  114.      * 流程 :  
  115.      *      ① 从资源中获取顶点 和 片元着色器脚本 
  116.      *      ② 根据获取的顶点 片元着色器脚本创建着色程序 
  117.      *      ③ 从着色程序中获取顶点位置引用 , 顶点颜色引用,  总变换矩阵引用 
  118.      *  
  119.      * @param mv MyTDView对象, 是GLSurfaceView对象 
  120.      */  
  121.     public void initShader(MyTDView mv){  
  122.         /* 
  123.          * mVertextShader是顶点着色器脚本代码 
  124.          * 调用工具类方法获取着色器脚本代码, 着色器脚本代码放在assets目录中 
  125.          * 传入的两个参数是 脚本名称 和 应用的资源 
  126.          * 应用资源Resources就是res目录下的那写文件 
  127.          */  
  128.         mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());  
  129.         mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());  
  130.           
  131.         /* 
  132.          * 创建着色器程序, 传入顶点着色器脚本 和 片元着色器脚本 注意顺序不要错 
  133.          */  
  134.         mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);  
  135.           
  136.         /* 
  137.          * 从着色程序中获取 属性变量 顶点坐标(颜色)数据的引用 
  138.          * 其中的"aPosition"是顶点着色器中的顶点位置信息 
  139.          * 其中的"aColor"是顶点着色器的颜色信息 
  140.          */  
  141.         maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");  
  142.         maColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");  
  143.           
  144.         /* 
  145.          * 从着色程序中获取一致变量  总变换矩阵 
  146.          * uMVPMatrix 是顶点着色器中定义的一致变量 
  147.          */  
  148.         muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");  
  149.           
  150.     }  
  151.       
  152.     /** 
  153.      * 绘制三角形方法 
  154.      *  
  155.      * 绘制流程 :  
  156.      *      ① 指定着色程序 
  157.      *      ② 设置变换矩阵 
  158.      *      ③ 将顶点位置 颜色 数据传进渲染管线 
  159.      *      ④ 启动顶点位置 颜色 数据 
  160.      *      ⑤ 执行绘制 
  161.      */  
  162.     public void drawSelf(){  
  163.         //根据着色程序id 指定要使用的着色器  
  164.         GLES20.glUseProgram(mProgram);  
  165.         /* 
  166.          * 设置旋转变化矩阵  
  167.          * 参数介绍 : ① 3D变换矩阵 ② 矩阵数组的起始索引 ③旋转的角度 ④⑤⑥ 
  168.          */  
  169.         Matrix.setRotateM(mMMatrix, 00010);  
  170.         /* 
  171.          * 设置沿z轴正方向位移 
  172.          * 参数介绍 : ① 变换矩阵 ② 矩阵索引开始位置 ③④⑤设置位移方向z轴 
  173.          */  
  174.         Matrix.translateM(mMMatrix, 0001);  
  175.         /* 
  176.          * 设置绕x轴旋转 
  177.          * 参数介绍 : ① 变换矩阵 ② 索引开始位置 ③ 旋转角度 ④⑤⑥ 设置绕哪个轴旋转 
  178.          */  
  179.         Matrix.rotateM(mMMatrix, 0, xAngle, 100);  
  180.         /* 
  181.          * 应用投影和视口变换 
  182.          */  
  183.         GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1false, Triangle.getFianlMatrix(mMMatrix), 0);  
  184.         /* 
  185.          * 将顶点位置数据传送进渲染管线, 为画笔指定定点的位置数据 
  186.          */  
  187.         GLES20.glVertexAttribPointer(  
  188.                 maPositionHandle,   //顶点位置数据引用  
  189.                 3,                  //每3个数字代表一个坐标   
  190.                 GLES20.GL_FLOAT,    //坐标的单位是浮点型  
  191.                 false,   
  192.                 3 * 4,              //每组数据有多少个字节  
  193.                 mVertexBuffer       //缓冲区  
  194.         );  
  195.         /* 
  196.          * 将顶点颜色数据传送进渲染管线, 为画笔指定定点的颜色数据 
  197.          */  
  198.         GLES20.glVertexAttribPointer(  
  199.                 maColorHandle,   
  200.                 4,   
  201.                 GLES20.GL_FLOAT,   
  202.                 false,   
  203.                 4 * 4,   
  204.                 mColorBuffer  
  205.         );  
  206.         //启用顶点位置数据  
  207.         GLES20.glEnableVertexAttribArray(maPositionHandle);  
  208.         //启用顶点颜色数据  
  209.         GLES20.glEnableVertexAttribArray(maColorHandle);  
  210.         //执行绘制  
  211.         GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);  
  212.     }  
  213.       
  214.     /** 
  215.      * 计算最终投影的矩阵 
  216.      * @param spec 
  217.      * @return 
  218.      */  
  219.     public static float[] getFianlMatrix(float[] spec){  
  220.         mMVPMatrix = new float[16];  
  221.         /* 
  222.          * 计算矩阵变换投影 
  223.          *  
  224.          * 参数介绍 :  
  225.          *  ① 总变换矩阵              ② 总变换矩阵起始索引 
  226.          *  ③ 摄像机位置朝向矩阵      ④ 摄像机朝向矩阵起始索引 
  227.          *  ⑤ 投影变换矩阵             ⑥ 投影变换矩阵起始索引 
  228.          */  
  229.         Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);  
  230.         Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);  
  231.         return mMVPMatrix;  
  232.     }  
  233.       
  234. }  


四. GLSurfaceView相关api


GLSurfaceView主要是创建渲染器, 实现其中的三个方法 onSurfaceCreated(), onSurfaceChanged(), onDrawFrame();

1. 相关api


(1) 设置OpenGL版本


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLSurfaceView.setEGLContextClientVersion(int version)  
作用 : 设置OPenGL的版本号, version 是 2 , 就是设置OpenGLES2.0;

(2) 设置背景颜色


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glClearColor(0001.0f);  

(3) 设置视口大小


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glViewport(int x, int y, int width, int height)  

(4) 设置透视矩阵


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)  
参数 : m 投影矩阵; offset 投影矩阵起始位置; 剩下的参数为 左 右 下 上 近视点 远视点;
左 右 的值是宽高比, 左边为负数, 右边为正数;

(5) 设置摄像机参数


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 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) 清除深度缓冲与颜色缓冲


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);  

2. 源码


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package shuliang.han.rotatetriangle;  
  2.   
  3. import javax.microedition.khronos.egl.EGLConfig;  
  4. import javax.microedition.khronos.opengles.GL10;  
  5.   
  6. import android.content.Context;  
  7. import android.opengl.GLES20;  
  8. import android.opengl.GLSurfaceView;  
  9. import android.opengl.Matrix;  
  10.   
  11. public class MyTDView extends GLSurfaceView {  
  12.   
  13.     private final float ANGLE_SPAN = 0.375f;        //三角形每次旋转的角度  
  14.       
  15.     private RotateThread mRotateThread;     //该线程用来改变图形角度  
  16.     private SceneRenderer mSceneRender;     //渲染器  
  17.       
  18.     public MyTDView(Context context) {  
  19.         super(context);  
  20.         //设置OpenGLES版本为2.0  
  21.         this.setEGLContextClientVersion(2);   
  22.           
  23.         //设置渲染器 渲染模式  
  24.         mSceneRender = new SceneRenderer();  
  25.         this.setRenderer(mSceneRender);  
  26.         this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);  
  27.     }  
  28.   
  29.     /** 
  30.      * 渲染器 
  31.      * 实现了下面三个方法 :  
  32.      *      界面创建 :  
  33.      *      界面改变 :  
  34.      *      界面绘制 :  
  35.      * @author HanShuliang 
  36.      * 
  37.      */  
  38.     private class SceneRenderer implements Renderer{  
  39.         Triangle triangle;  
  40.           
  41.         @Override  
  42.         public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
  43.             //设置屏幕背景色  
  44.             GLES20.glClearColor(0001.0f);  
  45.             //创建三角形对象  
  46.             triangle = new Triangle(MyTDView.this);  
  47.             //打开深度检测  
  48.             GLES20.glEnable(GLES20.GL_DEPTH_TEST);  
  49.             mRotateThread = new RotateThread();  
  50.             mRotateThread.start();  
  51.         }  
  52.   
  53.         @Override  
  54.         public void onSurfaceChanged(GL10 gl, int width, int height) {  
  55.             //设置视窗大小及位置  
  56.             GLES20.glViewport(00, width, height);  
  57.             //计算GLSurfaceView的宽高比  
  58.             float ratio = (float)width/height;  
  59.             /* 
  60.              * 产生透视矩阵 
  61.              * 参数介绍 :  
  62.              * ① 4 * 4 投影矩阵 
  63.              * ② 投影矩阵的起始位置 
  64.              * 后面的四个参数分别是 左 右 下 上 的距离 
  65.              * 最后两个参数是 近视点 和 远视点 距离 
  66.              */  
  67.             Matrix.frustumM(Triangle.mProjMatrix, 0,   
  68.                     -ratio, ratio,   
  69.                     -11,   
  70.                     110);  
  71.             /* 
  72.              * 设置摄像机参数矩阵 
  73.              * 参数介绍 :  
  74.              * 前两个参数是摄像机参数矩阵 和 矩阵数组的起始位置 
  75.              * 后面三个一组是三个空间坐标 先后依次是 摄像机的位置  看的方向 摄像机上方朝向 
  76.              */  
  77.             Matrix.setLookAtM(Triangle.mVMatrix, 0,   
  78.                     0f,0f,3f,  
  79.                     0f,0f,0f,  
  80.                     0f,1.0f,0.0f);  
  81.         }  
  82.   
  83.         @Override  
  84.         public void onDrawFrame(GL10 gl) {  
  85.             //清除深度缓冲与颜色缓冲  
  86.             GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);  
  87.             //绘制三角形  
  88.             triangle.drawSelf();  
  89.         }  
  90.     }  
  91.       
  92.       
  93.     /** 
  94.      * 这个线程是用来改变三角形角度用的 
  95.      */  
  96.     public class RotateThread extends Thread{  
  97.           
  98.         public boolean flag = true;  
  99.           
  100.         @Override  
  101.         public void run() {  
  102.             while(flag){  
  103.                 mSceneRender.triangle.xAngle = mSceneRender.triangle.xAngle + ANGLE_SPAN;  
  104.                 try {  
  105.                     Thread.sleep(20);  
  106.                 } catch (Exception e) {  
  107.                     e.printStackTrace();  
  108.                 }  
  109.             }  
  110.         }  
  111.           
  112.     }   
  113.       
  114. }  

五 MainActivity相关


1. 相关api


(1) 设置界面为竖屏


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);  

(2) 界面获取焦点


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. View.requestFocus()  

(3) 设置可获取焦点


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. View.setFocusableInTouchMode(boolean focusableInTouchMode)  
作用 : 在触摸的时候获取焦点


2. 源码


[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package shuliang.han.rotatetriangle;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.pm.ActivityInfo;  
  5. import android.os.Bundle;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     private MyTDView myTDView;  
  10.       
  11.     @Override  
  12.     public void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.         //设置界面显示为竖屏  
  15.         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);  
  16.         //创建OpenGL的显示界面  
  17.         myTDView = new MyTDView(this);  
  18.         myTDView.requestFocus();  
  19.         myTDView.setFocusableInTouchMode(true);  
  20.         //将OpenGL显示界面设置给Activity  
  21.         setContentView(myTDView);  
  22.     }  
  23.       
  24.     @Override  
  25.     public void onResume() {  
  26.         super.onResume();  
  27.         myTDView.onResume();  
  28.     }  
  29.       
  30.     @Override  
  31.     public void onPause() {  
  32.         super.onPause();  
  33.         myTDView.onPause();  
  34.     }  
  35.       
  36. }  

六. 着色器脚本


顶点着色器 : 
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. uniform mat4 uMVPMatrix; //总变换矩阵  
  2. attribute vec3 aPosition;  //顶点位置  
  3. attribute vec4 aColor;    //顶点颜色  
  4. varying  vec4 vColor;  //用于传递给片元着色器的变量  
  5.   
  6. void main()       
  7. {                                     
  8.    gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置  
  9.    vColor = aColor;//将接收的颜色传递给片元着色器   
  10. }                        

片元着色器 : 
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. precision mediump float;  
  2. varying  vec4 vColor; //接收从顶点着色器过来的参数  
  3.   
  4. void main()                           
  5. {                         
  6.    gl_FragColor = vColor;//给此片元颜色值  

案例下载地址 : http://download.csdn.net/detail/han1202012/6651095 需要SDK-10 版本2.3.3

0 0