IrrLicht引擎研究
鬼火引擎研究
posts - 22,  comments - 40,  trackbacks - 0

测试过irrlicht中ms3d模型的朋友可能发现了这样的现象,如果ms3d模型只有一个贴图,则无论贴图文件放到和demo可执行程序同一目录下或者调用setMaterialTexture()函数都可以让模型正常被正常渲染,而当模型拥有两个或以上贴图时,则只有贴图和可执行程序在同一目录被自动加载时才能正确渲染,而用setMaterialTexture(0,。。。),setMaterialTexture(1,。。。)时似乎只有第一个贴图起作用。仔细查看引擎的代码发现,这不是偶然的现象,也可以算是引擎对ms3d模型支持的一个bug吧。在说到具体的解决方案之前,我们先来说一下ms3d模型中组的概念。“组”顾名思义应该是拥有一些共同属性的事物集合了。在ms3d中,一个组内的三角形就拥有同一个贴图文件。在引擎装载ms3d模型数据时,已经记下了组对应的贴图文件名,因此自动贴图不会出错。而在用setMaterialTexture函数指定时却并没有指定是哪个组的贴图,因此就会出现只有一个贴图起效果的现像,其实从代码中可以看出,它是遍历了所有组,但都用了setMaterialTexture(0,...)指定的这一个贴图。
好了,知道原因后,我们大概就可以想到解决办法了。就是在setMaterialTexture时也指定是哪个组。对就是这样。我们提供了另外两个针对MS3D的setMaterialTexture函数,分别是:
void  CAnimatedMeshSceneNode::setMS3DMaterialTexture(const c8 * groupName,s32 textureLayer, video::ITexture* texture)
{
  if (textureLayer<0 || textureLayer>= video::MATERIAL_MAX_TEXTURES)
    return;

  // 根据组名得到MeshBuffer
  IMesh * pMesh = Mesh->getMesh(0,0);
  
  for(s32 i = 0; i<pMesh->getMeshBufferCount();i++)
  {
   IMeshBuffer* pMeshBuf = pMesh->getMeshBuffer(i);
   if(strcmp(pMeshBuf->getGroupName(),groupName) == 0){
    irr::video::SMaterial & mat = pMeshBuf->getMaterial();
    mat.Textures[textureLayer] = texture;
    Materials[i].Texture1 = texture;
    break;
   }
  }

}
void  CAnimatedMeshSceneNode::setMS3DMaterialTexture(s32 groupIndex,s32 textureLayer, video::ITexture* texture)
{
  if (textureLayer<0 || textureLayer>= video::MATERIAL_MAX_TEXTURES)
    return;
  
 
  // 根据组名得到MeshBuffer
  IMesh * pMesh = Mesh->getMesh(0,0);
  
  if(groupIndex < 0 || groupIndex > pMesh->getMeshBufferCount())
   return;

  
  IMeshBuffer* pMeshBuf = pMesh->getMeshBuffer(groupIndex);
  // yfxu
  irr::video::SMaterial & mat = pMeshBuf->getMaterial();
  mat.Textures[textureLayer] = texture;
  if(textureLayer == 0) mat.Texture1 = texture; 
  Materials[groupIndex].Texture1 = texture;

}
一个是根据组名设贴图,一个是根据组索引设贴图。
值得一起的是 引擎在载如ms3d模型数据在取组名时也有一点问题,原来的代码如下:
for (i=0; i<numGroups; ++i)
 {
  Groups.push_back(SGroup());
  SGroup& grp = Groups.getLast();
  // 组名称
  grp.Name = (const c8*)pPt;
。。。

参考ms3d的文件格式可知改为:
 grp.Name = (const c8*)(pPt+1);
才能正确取到组名。
到此为止,加上动画模型旋转bug,和无效帧问题的解决。irrlicht对ms3d模型的支持基本就完美了。

下图中的美女,身体和头发是分开的贴图:


posted @ 2006-07-28 13:25 安徽飞雪游戏工作室 阅读(871) 评论(1) 编辑
这是Irrlicht SDK中给的一个例子,用来演示如何在Irricht中冲突检测与响应,其它的例子还有十三个,俺会陆续翻译出来。自己英语水平很洼,但对翻译又有点瘾,所以就帖到这里来,有译错或表达不好的地方请指正。

 

 例7,碰撞检测与响应
  这一节,我将演示如何在Irrlicht引擎里进行碰撞检测。有三种方法:自动的碰撞检测,例如上下楼梯时移动全局坐标;手工拾取三角形和场景结点。
  演示程序效果图:

  开始吧!
  简单起见,我们拿那个加载并显示Quake3地图的例2开刀:在里面四处走动并从中拾取三角形。另外,为了说明场景结点的拾取我们再放三个精灵在里面。下面的代码启动引擎并加载Quake3地图。不再多加解释(详情参见例2)。

#include <irrlicht.h>
#include <iostream>
using namespace irr;

#pragma comment(lib, "Irrlicht.lib")

int main()
{
  // let user select driver type
  video::E_DRIVER_TYPE driverType;  printf("Please select the driver you want for this example:\n"\    " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\    " (d) Software Renderer\n (e) Apfelbaum Software Renderer\n"\    " (f) NullDevice\n (otherKey) exit\n\n");  char i;  std::cin >> i;  switch(i)  {  case 'a': driverType = video::EDT_DIRECT3D9;break;  case 'b': driverType = video::EDT_DIRECT3D8;break;  case 'c': driverType = video::EDT_OPENGL;   break;  case 'd': driverType = video::EDT_SOFTWARE; break;  case 'e': driverType = video::EDT_SOFTWARE2;break;    case 'f': driverType = video::EDT_NULL;     break;  default: return 0;  }
  // create device
  IrrlichtDevice *device = createDevice(driverType,
     core::dimension2d<s32>(640, 480), 16, false);
  if (device == 0)    return 1; // could not create selected driver.  video::IVideoDriver* driver = device->getVideoDriver();  scene::ISceneManager* smgr = device->getSceneManager();  device->getFileSystem()->addZipFileArchive      ("../../media/map-20kdm2.pk3");


scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
scene::ISceneNode* q3node = 0;

if (q3levelmesh)
q3node = smgr->addOctTreeSceneNode(q3levelmesh->getMesh(0));

 
  到此为止,我们已经像例2那样加载了Quake3地图。接下来的情况有所不同:我们创建了一个三角形的选择器。这是一个类,你可以用它来从场景结点中拾取三角形,以用作各种不同的用途,比如碰撞检测。引擎里有几种三角选择器,它们都可以用ISceneManager来创建。在这个例子中我们创建OctTreeTriangleSelector,你可以照字面意思理解,是的,这是个树状的选择器,“树”就是数据结构里面的树,我们以此来对三角形数目的输出做些优化。这样对像Quake3之类大的地图是很有用的。
  创建完三角形选择器就把它“系”到场景结点上去,此处是q3node。并不是必需这么做,这是“懒汉”式做法,不用再考虑选择器了,比如到底什么时候用不着了才好释放它。
  Code..................................
  还是像例2,为了能在Quake3地图里面移动,我们添加一个FPS(First Person Shooter第一人称射击游戏)相机。但这一次,还要给相机加个特殊的animator:带冲突响应的animator(注3)。它会调整与自己关联的场景结点,使它看起来受重力影响并且不会穿墙而过。对于animator,我们要做的唯一的事情就是告诉它世界是个什么样子,场景结点有多大,重力有多大等等。把带冲突响应的animator加到相机上后,就不必再做任何关于碰撞检测的事――一切都是自动的!。下面有关碰撞检测的代码是是用来拾取的。还有一个令人高兴的、很酷的特性:可以把带冲突响应的animator的事附加到任何场景结点上,不只是相机。另外,它也可以与别的场景结点animator混合使用。如此以来,在Irrlicht引擎中使用冲突检测与响应就非常、非常地简单。
  好了,来看一下createCollisionResponseAnimator() 的参数。第一个参数是TriangleSelector,指明碰撞检测完成后世界看起来是个什么样子。 第二个参数是场景结点,指定受碰撞检测影响的对象,在我们的例子中就是相机。第三个定义对象的大小,即椭球半径。试一试:把半径改小后是不是能够靠墙更近呢?下一个参数是重力的大小和方向。你可以将方向设为(0,0,0)以消除重力。最后一个仅用于转换:否则的话,计算好碰撞检测的椭球会包住相机,而相机位于椭球的中央。但是从人的角度,我们习惯于“眼睛在身体的上方”,而眼睛在身体中间是同我们的现实世界相抵触的。所以我们用这个参数把场景结点从椭球中央上移50个单位。好,现在碰撞检测可以起作用了。
  Code………………………….
  因为在Irrlicht里碰撞检测并不是很大的一块(引擎要做的事多着呢,译注)。下一节我将描述如何使用两种不同类型的拾取。但在这之前我会为场景稍作准备。需要三个用来拾取的角色:精灵,一个动态光源以照亮它们,一个用来指出交叉点的白板,还有,噢,我得把光标去掉。 :)
  Code………………………..
  为了不致太复杂,我会在渲染循环里进行拾取。我们先定义两个指针用来保存当前和最后一个场景结点,然后进入循环。
  Code…………………
  用smgr->drawAll()绘制完全部场景后,开始第一次拾取:我们想知道自己正瞧着整个世界坐标中的哪个三角形。另外,准确一些,还想知道这是在Quake3地图中的哪个点。为此我们创建一个三维的射线:从相机位置开始,穿过目标点。
  然后向碰撞管理器询问:这条射线是否同存储在三角选择器里的三角形发生碰撞?如果是,我们画一个三维的三角形并把白板位置设在交叉点上。
  Code……………….
  Irrlicht引擎支持的另一种拾取方法是基于包围盒的节点拾取。每个场景结点已有一个包围盒包着,正因如此,拾取速度非常快,例如在拾取相机看到的场景结点时。再说明一次,我们是向碰撞管理器询问得到这个结果的,如果得到了一个场景结点,就通过取消它材质的光照特性而加亮它,当然,如果它不是白板或Quake3地图的话。
  Code……………….
  就这些,下面要做的只是完成绘制。
  Code…………………
  ――――――――End―――Of―――File ―――――――――
注:
  1.虽然是科技文,但为了便于理解采用意译。
  2.原文见:Irrlicht手册

posted @ 2006-07-28 09:48 安徽飞雪游戏工作室 阅读(945) 评论(1) 编辑

试验过在IrrLicht中导入播放骨骼动画模型的朋友可能会发现,引擎存在下列问题:一, 当一次循环结束时会产生无效帧,在原模型不远的位置出现一片不规则的图片,严重影响显示效果。二,当模型包含旋转动作时发现模型在旋转过程中方向会突然反向,猜想也是计算错误。针对这两个问题,可以采用如下解决办法。
针对出现无效帧的问题,代码做如下修改:
在类SMS3DMeshBuffer的loadFile方法中(请对照原代码)
 // 获得旋转的关键帧
  //>>rxue add
  s32 j;
  SKeyframe k;
  MS3DKeyframe* eka = NULL; // this contains data of first keyframe
  f32 first = 0; // this is time of first keyframe
  //<<rxue add end

  for (j=0; j<pJoint->NumRotationKeyframes; ++j)
  {
   MS3DKeyframe* kf = (MS3DKeyframe*)pPtr;
   pPtr += sizeof(MS3DKeyframe);
   //>>rxue add
   if(j==0)
   {
    eka = kf;
    first = kf->Time;
   }
   k.timeindex = (kf->Time-first) * 1000.0f;
   //<<rxue add end
   k.data.X = kf->Parameter[0];
   k.data.Y = kf->Parameter[1];
   k.data.Z = kf->Parameter[2];
   jnt.RotationKeys.push_back(k);
  }
  //>>rxue add
  totalTime -= first;
  if(eka)
  {
   k.timeindex = totalTime;
   k.data.X = eka->Parameter[0];
   k.data.Y = eka->Parameter[1];
   k.data.Z = eka->Parameter[2];
   jnt.RotationKeys.push_back(k);
  }
  eka = NULL;
  //<<rxue add end


  // 获得变换的关键帧
  first = 0; //rxue add
  for (j=0; j<pJoint->NumTranslationKeyframes; ++j)
  {
   MS3DKeyframe* kf = (MS3DKeyframe*)pPtr;
   pPtr += sizeof(MS3DKeyframe);
   
   //>>rxue add
   if(j==0)
   {
    eka = kf;
    first = kf->Time;
   }
   
   k.timeindex = (kf->Time-first) * 1000.0f;
   //<<rxue add end   
   k.data.X = kf->Parameter[0];
   k.data.Y = kf->Parameter[1];
   k.data.Z = kf->Parameter[2];
   jnt.TranslationKeys.push_back(k);
  }
  //>> rxue add Let's add one keyframe to the end
  if(eka)
  {
   k.timeindex = totalTime;
   k.data.X = eka->Parameter[0];
   k.data.Y = eka->Parameter[1];
   k.data.Z = eka->Parameter[2];
   jnt.TranslationKeys.push_back(k);
  }
  //<<rxue add end
旋转反向的问题:
修改SMS3DMeshBuffer类的getKeyframeData方法如下:(注意:s32type为增加的参数,所以函数声明也要做相应修改
void CAnimatedMeshMS3D::getKeyframeData(core::array<SKeyframe>& keys, f32 time, core::vector3df& outdata,s32 type)
{
 if(type == 0)
 {
  for (s32 i=0; i<(s32)keys.size()-1; ++i)
  {
   if (keys[i].timeindex <= time && keys[i+1].timeindex >= time)
   {    
    f32 interpolate = (time - keys[i].timeindex)/(keys[i+1].timeindex - keys[i].timeindex);    
    outdata = keys[i].data + ((keys[i+1].data - keys[i].data) * interpolate);   
    return;
   }
  }
 }
 else
 {
  for (s32 i=0; i<(s32)keys.size()-1; ++i)
  {
   if (keys[i].timeindex <= time && keys[i+1].timeindex >= time)
   {
    irr::core::quaternion q1(keys[i].data.X,keys[i].data.Y,keys[i].data.Z);
    irr::core::quaternion q2(keys[i+1].data.X,keys[i+1].data.Y,keys[i+1].data.Z);     
    f32 interpolate = (time - keys[i].timeindex)/(keys[i+1].timeindex - keys[i].timeindex);
    q2 = q1.slerp(q1,q2,interpolate);
    q2.toEuler(outdata);
    return;
   }
  }
 }
}

SMS3DMeshBuffer类animate方法修改如下:
//find keyframe translation and roation
//getKeyframeData(Joints[i].TranslationKeys, time, translation);
//getKeyframeData(Joints[i].RotationKeys, time, rotation);
  
  getKeyframeData(Joints[i].TranslationKeys, time, translation,0);//rxue
  getKeyframeData(Joints[i].RotationKeys, time, rotation,1);//rxue 重新计算
至此,解决了模型动画的播放问题。在我们这里测试正常,有问题的朋友可以留言讨论。

posted @ 2006-07-28 09:23 安徽飞雪游戏工作室 阅读(1269) 评论(0) 编辑