本文共 12981 字,大约阅读时间需要 43 分钟。
本节书摘来自异步社区《Android 应用案例开发大全(第3版)》一书中的第2章,第2.5节 辅助绘制类,作者 吴亚峰 , 苏亚光 , 于复兴,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.5 辅助绘制类
上一节介绍了实现壁纸的开发,本节将对辅助绘制类的开发进行详细介绍。在绘制百纳水族馆动态壁纸中各个物体之前,必须要做好准备工作,而这些准备工作就包括辅助绘制类的开发。其中包括背景图辅助绘制类Background,气泡辅助绘制类Bubble,鱼类辅助绘制类MS3DModel以及模型辅助绘制类LoadedObjectVertexNormalTexture,下面就对这些类的开发进行详细的介绍。2.5.1 背景辅助绘制类——Background
本小节将对本案例的背景辅助绘制类进行详细介绍,这个类的作用是绘制百纳水族馆的背景模型,在此逼真的深海背景下,所有的鱼、珍珠贝以及气泡都在此背景前呈现,使整个百纳水族馆更加地活灵活现。(1)首先来看本类的框架结构,本类中包括对背景图的顶点坐标及纹理坐标的初始化方法,以及对着色器初始化的initShader()方法和drawSelf()方法。仔细学习本类的结构,有助于读者更快地掌握本类所讲的知识,对读者有很大的帮助。其具体代码如下。
1 package wyf.lxg.background; //声明包名2 ......//此处省略部分类和包的引入代码,读者可自行查阅随书光盘中的源代码3 public class Background{4 int mProgram; //自定义渲染管线程序id5 ......//此处省略了本类中部分成员变量的声明,读者可自行查阅随书光盘中的源代码6 public Background(MySurfaceView mv){7 initVertexData(); //初始化顶点坐标与着色数据8 initShader(mv); //初始化着色器9 }10 public void initVertexData(){ //初始化顶点坐标与着色数据的方法11 //顶点坐标数据的初始化12 vCount=6; //顶点的数量13 float vertices[]=new float[] { //顶点坐标数据数组14 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,15 -30*Constant.SCREEN_SCALEZ,16 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,17 -30*Constant.SCREEN_SCALEZ,18 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,19 -30*Constant.SCREEN_SCALEZ,20 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,21 -30*Constant.SCREEN_SCALEZ,22 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,23 -30*Constant.SCREEN_SCALEZ,24 *Constant.SCREEN_SCALEX,*Constant.SCREEN_SCALEY,25 -30*Constant.SCREEN_SCALEZ,26 };27 //创建顶点坐标数据缓冲28 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);29 vbb.order(ByteOrder.nativeOrder()); //设置字节顺序30 mVertexBuffer = vbb.asFloatBuffer(); //转换为Float型缓冲31 mVertexBuffer.put(vertices); //向缓冲区中放入顶点坐标数据32 mVertexBuffer.position(0); //设置缓冲区起始位置33 //创建纹理坐标缓冲34 float textureCoors[]=new float[]{ //顶点纹理S、T坐标值数组35 0,0,0, 1,1,0, 0,1,1, 1,1,0};36 //创建顶点纹理数据缓冲37 ByteBuffer cbb = ByteBuffer.allocateDirect(textureCoors.length*4);38 cbb.order(ByteOrder.nativeOrder()); //设置字节顺序39 mTexCoorBuffer = cbb.asFloatBuffer(); //转换为Float型缓冲40 mTexCoorBuffer.put(textureCoors); //向缓冲区中放入顶点着色数据41 mTexCoorBuffer.position(0); //设置缓冲区起始位置42 }43 ......//此处省略了部分源代码,将在后面的步骤中给出44 ......//此处省略了部分源代码,将在后面的步骤中给出45 }
第6~9行是这个类的构造方法,此方法调用初始化顶点坐标与着色数据的initVertexData()方法和初始化着色器的initShader(mv)方法。
第13~32行是对顶点坐标数据的初始化,用三角形卷绕方式创建一个背景模型,将顶点数据存到float类型的顶点数组中,并创建顶点坐标缓冲,同时对顶点字节进行设置,然后放入顶点坐标缓冲区,设置缓冲区的起始位置。第33~41行是对创建好的背景模型进行纹理创建,对顶点纹理S、T坐标进行初始化,将顶点纹理坐标数据存入float类型的纹理数组中,并创建纹理坐标缓冲,对纹理坐标进行设置然后送入缓冲区并设置起始位置。(2)读者对本类的框架掌握后,下面将为读者介绍本类中对着色器初始化的方法initShader()方法以及绘制矩形的drawSelf()方法。drawSelf()方法最后为画笔指定顶点位置数据和画笔指定顶点纹理坐标数据,绘制纹理矩形。其具体代码如下。1 public void initShader(MySurfaceView mv){ //初始化着色器2 //加载顶点着色器的脚本内容3 mVertexShader=ShaderUtil.loadFromAssetsFile("back_vertex.sh", mv.getResources());4 //加载片元着色器的脚本内容5 mFragmentShader=ShaderUtil.loadFromAssetsFile("back_frag.sh", mv.getResources());6 //基于顶点着色器与片元着色器创建程序7 mProgram = createProgram(mVertexShader, mFragmentShader);8 //获取程序中顶点位置属性引用id9 maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");10 //获取程序中顶点纹理坐标属性引用id11 maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");12 //获取程序中总变换矩阵引用id13 muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");14 }15 public void drawSelf(int texId){16 GLES20.glUseProgram(mProgram); //制定使用某套shader程序17 //将最终变换矩阵传入shader程序18 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState. getFinalMatrix(), 0);19 GLES20.glVertexAttribPointer( maPositionHandle, 3, //为画笔指定顶点位置数据20 GLES20.GL_FLOAT, false,3*4, mVertexBuffer );21 GLES20.glVertexAttribPointer( maTexCoorHandle,2, //指定顶点纹理坐标数据22 GLES20.GL_FLOAT, false,2*4, mTexCoorBuffer );23 GLES20.glEnableVertexAttribArray(maPositionHandle); //允许顶点位置数据数组24 GLES20.glEnableVertexAttribArray(maTexCoorHandle); //允许顶点纹理坐标数组25 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); //绑定纹理26 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);27 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount); //绘制纹理矩形28 }
第1~14行是本类中对着色器的初始化,将着色器脚本内容加载并基于其顶点与片元着色器来创建程序供显卡使用,并从程序中获取顶点位置属性、顶点纹理坐标属性、总变换矩阵属性的id引用来使用。
第15~28行是本类中的drawSlef()方法,其作用是绘制矩形,根据数据画出需要的矩形。首先制定某套shader程序,将一些数据传入shader程序,最后为画笔指定顶点位置数据和为画笔指定顶点纹理坐标数据,绘制纹理矩形。2.5.2 气泡辅助绘制类——Bubble
本小节将对案例中的气泡辅助绘制类进行详细的介绍,在该壁纸的场景中,有三处气泡位置在不断地冒出透明的气泡,这些气泡上升的高度不同,并且这些气泡随着高度的不断增加,大小也在不断变大,要绘制这些气泡,首先需要构造气泡模型,其具体代码如下所示。1 package wyf.lxg.bubble; //声明包名2 ......//此处省略部分类和包的引入代码,读者可自行查阅光盘中的源代码3 public class Bubble{4 ......//此处省略了本类中部分成员变量的声明,读者可自行查阅随书光盘中的源代码5 int vCount=0; //顶点的数量6 public Bubble(MySurfaceView mv){7 initVertexData(); //调用初始化顶点数据的initVertexData方法8 initShader(mv); //调用初始化着色器的intShader方法9 }10 public void initVertexData(){ //顶点坐标数据的初始化11 vCount=6; //顶点的数量12 float vertices[]=new float[] { //顶点坐标数据数组13 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,14 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,15 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,16 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,17 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,18 *Constant.UNIT_SIZE,*Constant.UNIT_SIZE,0,19 };20 //创建顶点坐标数据缓冲21 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);22 vbb.order(ByteOrder.nativeOrder()); //设置字节顺序23 mVertexBuffer = vbb.asFloatBuffer(); //转换为int型缓冲24 mVertexBuffer.put(vertices); //向缓冲区中放入顶点坐标数据25 mVertexBuffer.position(0); //设置缓冲区起始位置26 float textureCoors[]=new float[]{0,0,0,1,1,0, 0,1,1,1,1,0 }; //顶点纹理S、T坐标值数组27 ByteBuffer cbb = ByteBuffer.allocateDirect(textureCoors.length*4); //创建纹理数据缓冲28 cbb.order(ByteOrder.nativeOrder()); //设置字节顺序29 mTexCoorBuffer = cbb.asFloatBuffer(); //转换为int型缓冲30 mTexCoorBuffer.put(textureCoors); //向缓冲区中放入顶点着色数据31 mTexCoorBuffer.position(0); //设置缓冲区起始位置32 }33 ......//该处省略了与上节类似的initShader()方法,读者可自行查阅随书光盘中的源代码34 ......// 该处省略了与上节类似的drawSlef()方法,读者可自行查阅随书光盘中的源代码35 }
第6~9行是这个类的构造方法,调用initVertexData()方法对顶点坐标数据与顶点纹理坐标数据进行初始化,并调用initShader(mv)方法对着色器进行初始化。
第11~25行是对气泡模型的顶点坐标数据的初始化,首先以三角形卷绕的方式组装顶点数据并将数据存放到float类型的数组中,并创建顶点坐标数据缓冲,对顶点数据进行设置并放入缓冲区,设置缓冲区的起始位置。第26~32行是对气泡模型的顶点纹理坐标S、T进行初始化,其坐标的组装需要与顶点卷绕方式、方向一致,并将顶点纹理坐标数据存入float类型的数组中,创建顶点纹理坐标缓冲,设置顶点纹理坐标,放入缓冲区,并设置缓冲区的起始位置。说明因其模型辅助绘制类LoadedObjectVertexNormalTexture与背景图辅助绘制类Background,气泡辅助绘制类Bubble中的代码类似,所以只对以上两个小节的辅助绘制类做详细介绍,其模型辅助绘制类LoadedObjectVertexNormalTexture读者可查看随书光盘中的源代码。2.5.3 鱼类辅助绘制类——MS3DModel
本小节将对鱼类辅助绘制类MS3DModel进行详细的介绍,在该壁纸中的鱼不但可以游来游去,而且鱼的本身也是有动作的,含有动画的模型就是骨骼动画。本小节将介绍如何对有骨骼动画的MS3D文件类型进行加载,并存储其动画的相关数据,以及执行模型绘制的MS3DModel类。(1)下面将详细介绍的是MS3DModel类的框架结构,这个类的主要作用是用于存储从ms3d文件中加载的动画相关数据,以及执行模型绘制。读者理解其代码框架,有助于更好地对本案例中鱼类的加载有更加深刻的理解。其代码框架如下。
1 package com.bn.ms3d.core; //声明包名2 ......//此处省略部分类和包的引入代码,读者可自行查阅光盘中的源代码3 public class MS3DModel{4 public FloatBuffer[] vertexCoordingBuffer; //顶点坐标数据缓冲5 public FloatBuffer[] texCoordingBuffer; //纹理坐标数据缓冲6 public FloatBuffer[] normalCoordingBuffer; //顶点法向量缓冲7 public TextureManager textureManager; //纹理管理器8 public MS3DHeader header; //头信息9 public MS3DVertex[] vertexs; //顶点信息 10 public MS3DTriangle[] triangles; //三角形索引11 public MS3DGroup[] groups; //组信息12 public MS3DMaterial[] materials; //材质信息(纹理)13 public float fps; //fps信息14 public float current_time; //当前时间 15 public float totalTime; //总时间16 public float frame_count; //关键帧数 17 public MS3DJoint[] joints; //关节信息18 ......//此处省略了本类中部分成员变量的声明,读者可自行查看随书光盘中的源代码19 private MS3DModel(MySurfaceView mv){20 initShader(mv); //初始化着色器21 }22 ......//该处省略了与上节类似的initShader()方法,读者可自行查阅随书光盘中的源代码23 public final void animate(float time,int texid){ //进行动画的方法24 if(this.current_time != time){ //相同时间不做更新25 this.updateJoint(time); //更新关节26 this.updateVectexs(); //更新顶点27 this.draw(true,texid); //执行绘制28 }else{29 this.draw(false,texid); //执行绘制30 }}31 public void updateJoint(float time){ //更新关节的方法32 this.current_time = time; //更新当前时间33 if(this.current_time > this.totalTime){ this.current_time = ; } //时间超过总时间置为零34 int size = this.joints.length; //获取关节数量35 for(int i=0; i
第4~18行定义了一些本类中需要的一些成员变量,其中最为重要的是第8~17行的成员变量,这些成员变量分别对应于ms3d文件头信息、顶点信息、三角形索引、组信息等重要数据,读者了解后有助于更好地理解本类。
第23~30行为更新关节、顶点数据到动画中指定时刻及绘制模型的animate方法,若需要更新到的动画时刻与当前不同,则首先需要更新关节数据,再更新顶点数据,再执行模型的绘制,否则直接绘制模型。第31~37行为更新关节数据的updateJoint方法,首先判断新的当前时间是否大于总的动画时间,若大于总的动画时间则将其设置为0,表示一轮动画播放完毕后从头开始。接着对每个关节进行遍历,调用每个关节信息对象的update方法更新每个关节。第38~50行为省略掉的一些重要的功能方法,将在后面的步骤中详细介绍。(2)下面介绍的是步骤(1)中省略的MS3DModel类中用于绘制模型的draw方法,此方法是将加载ms3d文件中的各种数据进行组装送入缓冲区,然后进行绘制的重要功能方法,读者理解其功能,有助于读者更好地理解本类。其具体代码如下。1 public void draw(boolean isUpdate,int texid){ //绘制模型2 GLES20.glUseProgram(mProgram); //指定使用某套shader程序3 MatrixState.copyMVMatrix(); //调用copyMVMatrix()方法复制矩阵4 //将最终变换矩阵传入shader程序5 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinal Matrix(), 0); //将变换矩阵传入shader程序6 GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixState.getM Matrix(), 0);7 //将光源位置传入着色器程序8 GLES20.glUniform3fv(maLightLocationHandle, 1, MatrixState.lightPositionFB);9 //将摄像机位置传入着色器程序10 GLES20.glUniform3fv(maCameraHandle, 1, MatrixState.cameraFB);11 int group_size = this.groups.length; //获取组的数量12 MS3DTriangle triangle = null; //获取处理三角形信息对象引用13 MS3DGroup group = null; //当前组信息引用14 int[] indexs = null; //三角形的索引数组15 int[] vertexIndexs = null; //顶点索引16 FloatBuffer buffer = null; //buffer缓冲17 MS3DMaterial material = null; //材质18 for(int i=0; i-1){ //有材质(需要贴纹理)23 material = this.materials[group.getMaterialIndex()];24 this.textureManager.fillTexture(material.getName());25 GLES20.glVertexAttribPointer(maTexCoorHandle,//将纹理坐标缓冲送入渲染管线26 2, GLES20.GL_FLOAT,false, 2*4,this.texCoordingBuffer[i]);27 GLES20.glEnableVertexAttribArray(maTexCoorHandle);//启用纹理坐标数组28 }29 if(isUpdate){//更新顶点缓冲30 buffer = this.vertexCoordingBuffer[i];31 for(int j=0; j
第2~10行是首先指定某套shader程序,然后将矩阵复制。本案例中因为给鱼类加上了灯光,所以就需要把总变换矩阵、变换矩阵、摄像机位置、灯光位置传入以进行操作。这样就可以在着色器中根据灯光位置、摄像机位置来给鱼类添加灯光特效。
第18~28行是对纹理坐标数据的操作,用一轮遍历来遍历获取当前组信息对象、组内三角形的索引数组及组内三角形的数量。并根据一个标志位来判断是否有材质,来断定是否需要纹理贴图,然后将纹理坐标缓冲送入渲染管线,启用纹理坐标数组。第29~39行是动态的计算有动画的模型的顶点数据,所以在顶点数据有更新的情况下,根据三角形组装信息(组成三角形的3个顶点的索引)重新填充模型顶点坐标数据缓冲,以便绘制出最新姿态的模型,并将顶点坐标缓冲送入渲染管线。第41~49行首先将顶点法向量数据送入渲染管线,然后开启顶点坐标数据、顶点法向量数组,并将纹理绑定。因为本案例中鱼类本身有逼真的深水明暗条纹,所以需要将明暗采样纹理传入着色器以进行操作,最后用定点法进行绘制。(3)接下来介绍的是MS3DModel类中省略的用于更新顶点数据的updateVectexs方法,以及用于更新指定索引顶点数据updateVectex方法,具体代码如下。1 private void updateVectexs(){ //动画中更新顶点数据的方法2 int count = this.vertexs.length; //获取顶点数量3 for(int i=0; i
第1~5行为更新顶点数据的updateVectexs方法,其中对每一个顶点进行遍历,调用updateVectex方法更新每一个顶点的数据。
第6~16行为更新指定索引顶点数据的updateVectex方法,其中对有关关节控制的顶点根据关节的实时变换情况计算出定点经关节影响后的位置。(4)接下来介绍步骤(1)中的用于从指定ms3d模型文件的输入流中加载ms3d模型的load方法。该方法并不复杂,它按照ms3d文件数据组织格式依次加载了各部分的数据。读者理解这部分有助于提高对ms3d文件的认知。其具体代码如下。1 //加载模型的方法2 public final static MS3DModel load(InputStream is, TextureManager manager, MySurfaceView mv){3 MS3DModel model = null; //ms3d模型对象引用4 SmallEndianInputStream fis = null; //特殊输入流对象引用5 try{6 fis = new SmallEndianInputStream(is); //创建特殊输入流对象7 model = new MS3DModel(mv); //创建ms3d模型对象8 model.textureManager = manager; //纹理管理器9 model.header = MS3DHeader.load(fis); //加载头信息10 model.vertexs = MS3DVertex.load(fis); //加载顶点信息11 model.triangles = MS3DTriangle.load(fis); //加载三角形组装信息12 model.groups = MS3DGroup.load(fis); //加载组信息13 model.materials = MS3DMaterial.load(fis, manager); //加载材质信息14 model.fps = fis.readFloat(); //加载帧速率信息15 model.current_time = fis.readFloat(); //当前时间16 model.frame_count = fis.readInt(); //关键帧数17 model.totalTime = model.frame_count / model.fps; //计算动画总时间18 model.joints = MS3DJoint.load(fis); //加载关节信息19 model.initBuffer(); //初始化缓冲20 }catch (IOException e){21 e.printStackTrace(); //打印异常22 }23 finally{24 if(fis != null){ //若输入流不为空25 try {26 fis.close(); //关闭输入流27 }catch (IOException e){ //异常处理28 e.printStackTrace(); //打印异常信息29 }}}30 System.gc(); //申请垃圾回收31 return model; //返回加载的模型对象32 }
第3~4行首先拿到ms3d模型的引用,然后再拿到特殊输入流对象SmallEndianInputStream的引用,为后面的模型导入做准备。
第6~32行是按照ms3d文件数据组织格式依次加载了各部分的数据,依次加载的数据为文件头、顶点、三角形组装、组、材质、帧速率、当前播放时间,关键帧数量关节信息,进行异常检查,保证加载的正确性,并返回模型。说明本节中用于从输入流加载ms3d模型的load方法并不复杂,它按照ms3d文件格式依次加载各部分的数据。各部分数据的具体加载并不是在此方法中实现的,而是在各部分数据对应的类中实现的,读者可查看随书光盘中的源代码。另外,此方法中包含的一个输入流类——SmallEndianInputStream,并不复杂,读者可查看随书光盘中的源代码。转载地址:http://fdoao.baihongyu.com/