科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航



ZDNet>软件频道>中间件-zhiding>J2ME Mobile 3D入门教程系列文章

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

  3D图形技术在各个领域已经越来越多的被应用了,当然这也包括了J2ME领域。在J2ME中为我们提供了JSR184这样一个可选包,该套API实现手机上3D图形的编程。同时也伴随着移动设备硬件的发展,现在也出现了越来越的支持该可选包的手机了。

来源:中国IT实验室 2007年09月22日

关键字:java 编程


  3D图形技术在各个领域已经越来越多的被应用了,当然这也包括了J2ME领域。在J2ME中为我们提供了JSR184这样一个可选包,该套API实现手机上3D图形的编程。同时也伴随着移动设备硬件的发展,现在也出现了越来越的支持该可选包的手机了,例如Sony Ericsson的 K系列、S系列等等。
  
  正巧前一段时间我简单的学习了一下3D图形学,所以最近开始对Mobile 3D的学习,在这里把学习到的东西拿出来与大家共享,希望能对大家有所帮助,也同时希望大家能够一起来学习JSR184。
  
  下面言规正传,首先我们来想象一下在现实生活中,我们是如何观察这个世界的。我们是通过眼睛来观察,我们生活在一个3维坐标系构成的世界。在Mobile3D中同样的有World类让你随心所欲的构造你想要的世界,当然在这里专业些,在3D制图中我们叫它“场景”;另外还有Camera类来作为你的眼睛,你可以来设置它的位置角度等参数,来显示出不同的图像。
  
  在Mobile 3D中是如何实现3D图像的显示呢?首先你要建立或者载入3D模型,然后根据需要设置场景中的环境、渲染方式等一系列的参数,再后生成并设置一架摄影机并且调整好你想要的光线,调整好你所需要的位置和角度。OK还需要什么呢?按下快门,这一步就是被叫做“渲染”一切就搞定了,这听起来似乎很简单其实也并不难,其实这也不难。
  
  下面我们分步骤的来讲解这些步骤:
  
  首先说模型的建立,在Mobile 3D中和大多数的3D编程API是一样的有两种方式:1 及时运算生成;2 外部建模导入。由于外部建模导入会将环境信息同时导入,所以稍候给大家做详细的介绍,这里着重要给大家介绍“及时运算生成”的部分,这样一来有助于大家了解Mobile 3D的工作原理。在Mobile 3D中为我们提供了VertexArray和VertexBuffer俩个类,这两个类用来保存3维模型的顶点信息。
  
  其中VertexArray 类用处比较多,也比较灵活,这个类最常用的用法有3个,1 保存顶点坐标信息; 2 保存法线信息;3保存帖图信息。有人也许要问这一个类怎么管理3种不同的东西呢?那下面我们来分析一下该类,首先该类的构造器有3个参数:1 该实例要包括的元素的数量; 2 每个元素要包括的元素数量; 3每个子元素所占的字节数。这样一来似乎清楚很多为什么这个类能被使用到3种东西上。
  
  另外该类还有一个比较常用的方法set(int index,int length,short[] array0)这个方法是用来向该类的实例对象种存放数据的,第一个参数是指从第几个元素开始;第二个参数是指要设置多少个;第三个参数就是实际设置的置了。
  
  下面简单介绍VertexBuffer类,该类才是真正保存多边形的框架信息的类。改类通过设置顶点位置、发现、帖图信息,来建立图形。其中
  
  setPositions(VertexBuffer v,float s,float[]b)
  
  是用来设置顶点位置的,在这个方法中你会发现有3个参数,第一个不用说了,是顶点的坐标信息,后两个是用来做坐标偏移等操作的,操作是这样的数学公式:
  
  v'=v*s+b
  
  还有一个
  
  setNormals(vertexBuffer norm)
  
  方法来设置法线。还有一个非常重要的方法
  
  setTexCoords(int, VertexArray, float, float[])
  
  这个方法中参数除第一个以外,后三个和
  
  setPositions(VertexBuffer v,float s,float[]b)
  
  是一样的,第一个参数是开始的元素编号。这样说是不是有些抽象呢?给大家一个例子吧,好让大家理解。
  
  short x = 20;
  
  short y = 20;
  
  short z = 20;
  
  short fx = (short) -x;
  
  short fy = (short) -y;
  
  short fz = (short) -z;
  
  //定点坐标
  short[] vert = {x,y,z, fx,y,z, x,fy,z, fx,fy,z, //D
  
  fx,y,fz, x,y,fz, fx,fy,fz, x,fy,fz, //C
  
  fx,y,z, fx,y,fz, fx,fy,z, fx,fy,fz, //B
  
  x,y,fz, x,y,z, x,fy,fz, x,fy,z, //F
  
  x,y,fz, fx,y,fz, x,y,z, fx,y,z, //A
  
  x,fy,z, fx,fy,z, x,fy,fz, fx,fy,fz}; //E
  
  try{vertArray=new VertexArray(vert.length/3,3,2);
  vertArray.set(0,vert.length/3,vert);
  }catch(Exception e){System.out.println("vert");}
  
  //发线
  byte[] norm = { 0,0,127, 0,0,127, 0,0,127, 0,0,127,
  
  0,0,-127, 0,0,-127, 0,0,-127, 0,0,-127,
  
  -127,0,0, -127,0,0, -127,0,0, -127,0,0,
  
  127,0,0, 127,0,0, 127,0,0, 127,0,0,
  
  0,127,0, 0,127,0, 0,127,0, 0,127,0,
  
  0,-127,0, 0,-127,0, 0,-127,0, 0,-127,0};
  
  try{normArray=new VertexArray(norm.length/3,3,1);
  normArray.set(0,norm.length/3,norm);
  }catch(Exception e){System.out.println("norm");e.printStackTrace();}
  
  //给出顶点们对应图片上的点(vert和tex数组是一一对应的)
  short[] tex = { 1, 0, 0, 0, 1, 1, 0, 1,
  
  1, 0, 0, 0, 1, 1, 0, 1,
  
  1, 0, 0, 0, 1, 1, 0, 1,
  
  1, 0, 0, 0, 1, 1, 0, 1,
  
  1, 0, 0, 0, 1, 1, 0, 1,
  
  1, 0, 0, 0, 1, 1, 0, 1 };
  
  try{
  texArray=new VertexArray(tex.length/2,2,2);
  texArray.set(0,tex.length/2,tex);
  }catch(Exception e){System.out.println("tex");}
  
  //建立正方体
  vb=new VertexBuffer();
  vb.setPositions(vertArray,1.0f,null);
  vb.setNormals(normArray);
  vb.setTexCoords(0,texArray,1.0f,null);
  
  在上述代码中我建立一个正方体所需要全部顶点和面的信息,但大家要注意这里并没有生成对应的模型。原因就是我们还没有设置生成模型所以的其他信息,下面我们来看看TriangleStripArray类,该类是构成面所需要的三角面的信息类,熟悉3D制图的人都清楚构造3D图形是通过多个面构造一个3D实体的,而三角面是比较常用的一个方法。具体内容我这里不详细说明了。
  
  下面我们还需要设置一些环境和材质信息,这里要用到的类比较多Appearance、Texture2D、Material。先来看个例子:
  
  appearnce=new Appearance();
  
  //创建帖图
  Texture2D texture=new Texture2D(image2d);
  texture.setBlendColor(Texture2D.FUNC_DECAL);
  texture.setWrapping(Texture2D.WRAP_REPEAT,Texture2D.WRAP_REPEAT);
  texture.setFiltering(Texture2D.FILTER_NEAREST,Texture2D.FILTER_NEAREST);
  
  material=new Material();
  material.setColor(Material.DIFFUSE, 0xFFFFFFFF);
  material.setColor(Material.SPECULAR, 0xFFFFFFFF);
  material.setShininess(100.0f);
  
  appearnce.setTexture(0,texture);
  appearnce.setMaterial(material);
  
  mesh=new Mesh(vb,tsa,appearnce);
  mesh.setAppearance(0,appearnce);
  
  我个人感觉Appearance类有些类似VertexBuffer类,同样是多种属性的持有者;在这里要强调一点,就是Appearance类的设置远远不止上述给出的东西,还有很多的设置(例如FOG也就是雾的设置)。Texture2D是帖图类,用它来设置帖图的信息,例如帖图的方式是平铺等方式。Material物名斯意就是指材质,这里你可是设置“反光度”、“颜色”等等信息。另外这里我还要介绍一种设置渲染参数的方法
  
  //设置poly模式设置
  PolygonMode polygonMode=new PolygonMode();
  polygonMode.setShading(PolygonMode.SHADE_SMOOTH);
  polygonMode.setCulling(PolygonMode.CULL_NONE);
  
  //生成外貌
  appearnce=new Appearance();
  appearnce.setPolygonMode(polygonMode);
  
  看刚刚给出的代码,似乎比上面的更简单是吧?其实在PolygonMode中已经替我们做了很多工作。该的设置很类似3D MAX中的Poly的使用。
  
  刚才的代码中还给出了一个Mesh类,该类材质最后我们要的模型。建立模型后,我们要建立Camera。在Camera中我在这里只简单介绍两个方法setParallel(float, float, float, float)和setPerspective(float, float, float, float)。我们首先来看setParallel(float, float, float, float)该方法是设置Camera的视图方法为平视图;第一个参数是设置视角的高度,注意是高度,不是角度,因为这里是平视图;第二个参数是Camera的宽高比例,例如我们的电视是4:3、宽银幕电影是16:9;第三、四参数分别是最近和最远渲染的范围。同样的setPerspective是设置Camera为透视图,这种视图比较接近我们日常生活中的观察的角度,而该方法的后三个参数和setParallel的后三个参数是一样的,而第一个参数是可是角度,这里你可不要忽视这个角度问题,这个角度是在透视图中计算投影的一个重要参数。
  
  似乎一切都设置好了,其实不然,到目前为止我们仅仅是把我们所需要的素材都准备好了。下面我们来看看Mobile 3D的管理机制,熟悉3D制图的人都清楚大部分3D软件,3D API都是通过树状结构来管理素材的,这样的好处是每个模型、模型组、摄像机等元素作为节点都可以设置自己的旋转轴等等属性,并可以按照自己所设定好的动画信息进行运动。在Mobile 3D中为我们规定了该树状结构的根节点必须是World类的实例对象,其中摄影机和光线比较特殊,可以不被放置在这个树中,而是通过Graphics3D类的对象来设置(只是可以不

查看本文来源


  在这里我首先感谢大家对上一篇文章J2ME Mobile 3D入门教程系列文章之一的支持,也正是有了大家的支持,我决定把我的Mobile3D学习过程拿出来和大家共享,希望大家能一起来讨论Mobile3D。
  
  在上一次的教程中我为您详细的介绍了,在Moble3D中通过及时运算建立3D图形,并且对从*.m3g文件中导入模型做了一个简单的介绍,这次我想在这里通过*.m3g文件的使用简单的介绍一下Mobile3D对动画的控制,以及对模型的一些操作。
  
  首先我来简单的说一下m3g文件的建立,这个其实很简单,你只需要选择一种你所熟悉的3D图形制作软件,并安装对应的插件即可,在这里我使用的软件是MAYA和3DS MAX,插件使用的是H3T Export Plugin,该插件有maya和3ds max的,同时还需要下载一个软件M3G Tools kit。
  
  这些都可以从Sony Ericsson的网站上找到连接。插件的安装应该都没问题吧。插件安装完成后,就可以建立模型、设置材质帖图、设置摄影机、设置关键帧等等了,最后只要输出成H3T文件即可。然后再打开M3G Tools kit将h3t文件输出成m3g文件就可以了。在使用m3g文件之前最好用M3G Tools kit浏览一下该文件,为的是记录对应的信息,例如userid和该场景的树状结构等信息,这些都是非常重要的。如果你这样做了,你会发现这样导出的m3g文件中camera并没有在world树下,而是和world树是平级的。
  
  其实上一篇文章中我也说过,camera和渲染信息可以不被放在world树下,但是所有的模型信息必须被放到树下。当我们看m3g文件的时候比较重要的是看清楚world节点所在的位置,以及需要我们操作的模型的userid。
  
  准备工作差不多了,现在来看Mobile3D吧,在上一文章中我说过m3g文件使用通过javax.microedition.m3g.Loader.load(String url)载入的,而且该方法返回的是javax.microedition.m3g.Object3D的数组,也许有人要问既然world是根节点,那么为什么不直接返回一个World呢?
  
  刚才让大家看m3g文件的结构的时候就已经说明了摄影机和动画设定等等并没有被放到World为节点的书中,而是和World节点同级的。而World节点是场景的根节点才对。那么我们如何才正确的取出World节点呢?方法有两个:
  
  1、遍历该Object3D数组,并比较每个元素的userid如果正式World节点的useid将该元素取出。
  
  2、遍历该Object3D数组,并比较每个元素是不是World类的实例,那么既然World节点是场景的根节点,那么在该Object3D数组中也应该只有一个World类的实例对象。
  
  第一个方法比较简单,我在这里只给出第二个方法的片断代码。(其实是一样的)
  
  private void loadWorld(){
  System.out.println("now loading...");
  try{
  buffer=Loader.load("/img/TmpMicroFile.m3g");
  for(int i=0;i  if(buffer[i] instanceof World){
  world=(World)buffer[i];
  return ;
  }
  }
  }catch(Exception e){
  buffer=null;
  System.out.println("thorw a exception when loading");
  e.printStackTrace();
  }
  }
  
  然后我们设置摄影机,不同的是,这次我们从World中获得摄影机信息,获取后我们可以对其进行一些基本的设置这里不细说了。下面着重的说一下动画的部分。
  
  在我们导入这个m3g文件时候我们其实已经有了动画的信息了,并且这些动画信息有可能是很复杂的,我测试了连杆两轴的运动可以没问题,从一些资料上看到的IK反向运动也没有问题。也许你已经着急了,怎么还不说如何控制动画的播放呢?
  简单的说World.animate()来更新动画信息,这个方法需要传进去一个int类型的参数,当你第一次调用该方法的时候,系统会记录下这个数值,后面每次调用的时候都会和这个数值相比较,然后系统替我们计算出动画更新到什么位置,同时该方法会返回一个int类型的参数,这个参数表示的下一次更新的一个建议的数值(是以毫秒为单位的)。
  
  我们这时就可以让调用这个方法的线程休眠这个时间,以便下以次更新动画数据。这里也许你会有疑问,我的动画明明只有几十帧,可是这里却反复的播放;这虽然是个好消息,但在有很多情况播放的长度、什么时候播放,都需要我们自己来控制怎么办?
  
  这时候我们就需要用到javax.microedition.m3g.AnimationController类了,其实每个动画中的每个可以动的模型都有自己的AnimationController对象,和模型动画一样我们可以通过World.find(int controllerID)来获得,在该类中我们可以通过setActiveInterval(int activeTime,int unactiveTime)来设定动画在该系统中播放的起始和重点时间,另外通过setPosition(int startTime,int endTime)方法控制这个动画需要播放哪一段落。
  
  很抱歉有关两个方法的时候在这次教程实例中我并没有使用,不过如果您想了解关于这两个方法的使用,我建议你看看WTK2.2的那个Demo3D中袋鼠的例子,那里面写的挺详细的。这里呢我给出动画和绘制的代码片断吧。
  
  protected void paint(Graphics g) {
  
  startTime= System.currentTimeMillis() - worldStartTime;
  validity= world.animate((int)startTime);
  perFrameTime=(int)System.currentTimeMillis();
  
  g.setColor(0x00);
  g.fillRect(0,0,getWidth(), getHeight());
  
  g.setClip(0,0,getWidth(),getHeight());
  g3d.bindTarget(g,true,Graphics3D.DITHER|Graphics3D.TRUE_COLOR);
  g3d.setViewport(0,0,getWidth(),getHeight());
  
  g3d.render(world);
  
  g3d.releaseTarget();
  
  framePor=(int)1000/((int)System.currentTimeMillis()-perFrameTime);
  
  System.out.println("3D demo frame/sn:"+framePor);
  
  if(validity < 1)
  { // The validity too small; allow a minimum of 1ms.
  validity = 1;
  }
  
  if(validity == 0x7fffffff)
  { // The validity is infinite; scehdule a refresh in 1 second.
  validity=1000;
  }
  
  }
  
  public void run() {
  while(isRun){
  repaint();
  try{
  Thread.sleep(validity);
  }catch(Exception e){}
  }
  }
  
  这里如果你不喜欢使用线程的话,你可以换成Timer。个人喜好问题吧,我对线程比较熟悉,所以这里我使用的是线程。
  
  动画似乎我现在也只理解到这些,全部和大家共享出来了。下面我想说一说Transformable类中的几个方法。首先来介绍一下Transformable类吧。Transformable类是很重要的一个类,Node就是它的子类,知道它的重要性了吧。
  
  Transformable中有四个方法是今天我要简单说一下的postRotate(float, float, float, float)放是和物体的旋转有关系的在Mobile3D中默认所有的物体的旋转轴都在自身的中心,所以对物体进行旋转操作的话,一定是自转,而不是绕某个点、或某个轴公转,这一点一定要弄清楚。也许这样说有些抽象那么我换个说法,就是这里的旋转只改变物体的朝向,并不改变物体的位置。
  
  这下明白了吧,也许有人要问“这不正是我们需要的吗?为什么要强调呢?”原因其实是这样的,在有的3D引擎中,旋转是以原点为基础的。也就是说如果需要做自转运动,就必须做移动、再旋转、再移动这样的操作,其实这样做的好处是可以方便点的运算,因为点不存在自转的概念。而我们的Mobile3D在某种程度上说是不可以对点操作的,所以它的最小单位是Mesh,那么它如何实现自转呢?来看个矩阵你就明白了
  
  90,0,0,0
  0,0,0,0
  0,0,1,0
  0,0,0,0
  
  这个矩阵表示的当前的模型在Y轴上有90度的旋转。现在我们回头来看看postRotate(float a, float x, float y, float z)方法,其中有4个参数,第一个就是这次需要旋转的角度,后三个其实一个这次旋转的轴,是通过一个向量来表示的,postRotate方法就是在原来的朝向上继续进行新的旋转,如果用数学公式来表示的话就是使用原有的矩阵乘以
  
  a,0,0,0
  0,x,0,0
  0,0,y,0
  0,0,0,z
  
  这个矩阵就是新的方向矩阵。这个方法如果从数学角度听起来很难理解的话,你只要记住四个参数第一个是这次旋转的角度,后三个确定旋转轴。
  同postRotate方法类似的是setOrientation(float a, float x, float y, float z)。不同的是这次不再是旋转了而是直接设置到这个朝向。
  
  说过旋转再来看看移动,这个似乎就简单很多了translate(float x, float y, float z)这个就是指按照指定的向量平移;setTranslation(float x, float y, float z)这个更简单了,直接移动到指定的位置。
  
  其实还有一个方法scale(float sx, float sy, float sz)是用来进行缩放的,同样还有setScale(float sx, float sy, float sz)直接设置比例尺,这个六个方法是对物体进行直接的操作的,非常有用,尤其是前四个,在编写游戏的时候会经常用到,而后两个使用的频率似乎少一些,因为很多时候我们都是直接对摄像机的距离进行操作的(除非有特殊需要)。下面我给出个使用前四方法的举例吧。
  
  public void keyPressed(int keycode){
  float[] camerTra;
  float x;
  float z;
  switch(keycode){
  case GameCanvas.DOWN:
  break;
  case GameCanvas.UP:
  break;
  case 52:
  dir=dir-2;
  System.out.println(dir);
  x=(float)(3*Math.sin((dir * 3.14159f)

查看本文来源

推广二维码
邮件订阅

如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

重磅专题