12 TerrainRendering
官方代码($sdk)\examples\12.TerrainRendering
地形渲染,听起来很复杂,实际上这例子并没有讲复杂的渲染方法,而是简单的介绍如何使用irr引擎的地形场景节点,已经如何使用三角形选择器进行摄像机与地形场景节点之间的碰撞检测。
Irr地形场景节点是使用小型的高度图来生成大型的地形场景,使用了LOD技术。对地形渲染感兴趣的话,这个场景节点的源码非常值得学习。可惜官方例子里并没有写地形渲染是如何实现的。下面只好看看例子里是怎么使用地形场景节点的。
代码一开始又是一个前面见过好多次的用户自定义事件接受器MyEventReceiver,用来监听用户输入。在MyEventReceiver添加成员变量scene::ISceneNode* Terrain地形场景节点,scene::ISceneNode* Skybox天空盒场景节点和scene::ISceneNode* Skydome天空模型节点,在覆盖的OnEvent函数中设置W、P、D、S和X五个按键功能。
按下W切换线状地线渲染
Terrain->setMaterialFlag(video::EMF_WIREFRAME,!Terrain->getMaterial(0).Wireframe);
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, false);
按下P切换点状地线渲染
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, !Terrain->getMaterial(0).PointCloud);
Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
按下D标准绘制与细节绘制切换
Terrain->setMaterialType(Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?video::EMT_DETAIL_MAP : video::EMT_SOLID);
按下S显示和关闭天空盒
showBox=!showBox;
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
按下X显示和隐藏Debug信息
showDebug=!showDebug;
Terrain->setDebugDataVisible(showDebug?scene::EDS_BBOX_ALL:scene::EDS_OFF);
在main函数中使用ISceneManager::addTerrainSceneNode()来创建地形场景节点。第一个参数是使用的高度图的文件名。高度图实际上就是一个简单的灰度纹理图,地形渲染器通过它创建3D地形。为了使用地形看起来更大一些,创建时对其进行放大。场景中暂时不需要灯光,应此关闭了灯光。之后设置terrain-texture.jpg作为地图的第一层纹理,而detailmap3.jpg做为第二层纹理:最后保持第一层纹理大小不变,第二层纹理放大了20倍才进行渲染。
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode("../../media/terrain-heightmap.bmp",//高度图
0, //父节点
-1, //节点ID
core::vector3df(0.f, 0.f, 0.f), // 位置
core::vector3df(0.f, 0.f, 0.f), // 旋转
core::vector3df(40.f, 4.4f, 40.f), // 缩放
video::SColor ( 255, 255, 255, 255 ), // 顶点颜色
5, // LOD最大值
scene::ETPS_17, //地形块大小
4 // 平滑因子
);
terrain->setMaterialFlag(video::EMF_LIGHTING, false);// 关闭灯光
terrain->setMaterialTexture(0,driver->getTexture("../../media/terrain-texture.jpg"));//设置地形的第一层纹理
terrain->setMaterialTexture(1,driver->getTexture("../../media/detailmap3.jpg"));//设置地形的第二层纹理
terrain->setMaterialType(video::EMT_DETAIL_MAP);//设置材质类型
terrain->scaleTexture(1.0f, 20.0f);//缩放纹理
为了能与地形进行碰撞检测,需要创建一个三角碰撞检测器。这个内容在例子07.Collision中详细介绍过。创建了一个摄象机,并为它创建了一个碰撞器,这样,摄象机与地形之间就会有碰撞处理,摄象机就不会移动出地表了。
scene::ITriangleSelector* selector=smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);
// 为摄像机场景节点创建一个碰撞相应运动器
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(selector,camera,core::vector3df(60,100,60),core::vector3df(0,0,0),core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();
如果需要访问地形顶点数据,可以按下面代码的方法直接访问
scene::CDynamicMeshBuffer* buffer = new cene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
这样就可以直接用索引中data缓存上进行数据操作了
buffer->drop();
不再使用时同样需要drop缓存。
我自己写的仿魔兽3地线也是用同样的方法操作地形数据。
创建天空盒。因天空盒不需要使用MIPMAPS问题,使用setTextureCreationFlag关闭创建MIPMAPS纹理。
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);
天空盒创建后重新开启创建MIPMAPS纹理
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
创建一个用户自定义事件接收器的实例
MyEventReceiver receiver(terrain, skybox, skydome);
device->setEventReceiver(&receiver);
帧循环部分跟以往几乎一样,只不过是每帧获取一次摄像机的坐标位置并把它显示到窗口标题栏上。这个例子的主要代码就这些,所谓的地形渲染就是使用irr已经做好的ITerrainSceneNode地形场景节点,没有任何难度。稍微复杂一点的就是地形材质使用了双层纹理。