C#实现三维数字地形漫游
 
 
马省轩  任丽娜
  要:本文采用C#编程语言,利用Irrlicht三维图形引擎实现了三维数字地形的漫游。为三维数字地形显示提供了较易实现的解决方案。
关键词:C#  高度图 Irrlicht引擎  三维地形  场景漫游

一、概述
三维数字地形系统是地理信息系统的重要组成部分,现在被应用于许多领域。我们可以从模拟飞行游戏、Google数字地球中体验到三维数字地形生动、形象以及具有良好互动性等特点。三维数字地形已成为具有很强应用价值的技术,但是单纯利用Direct3DOpenGL来实现三维地形需要大量专业知识,同时编程量巨大;如果使用专业三维地形引擎则价格昂贵,这些限制了广大爱好者对三维数字地形的开发研究。不过,随着开源运动的广泛开展,目前有许多开源三维图形游戏引擎如OgreKlayGENebulaIrrlicht等可用于三维数字地形系统的开发,使三维数字地形程序变得易于开发。其中由德国电脑游戏专家Nikolaus Gebhardt设计Irrlicht三维图形引擎,可以应用于各种.NET语言中,在普通电脑即可运行,易于掌握,并由活跃的开发团队支持。比较适合进行三维数字地形漫游系统的开发。
文中采用目前广泛使用的C#语言,结合Irrlicht三维图形引擎实现了三维数字地形的漫游。由于采用三维图形引擎,免去了许多底层编程工作;同时,C#的特性又使该程序开发速度较快。所以本文采用的方法具有较强的实用性。
二、引擎配置
这里以Microsoft Visual Studio .NET 2003为例介绍开发过程。
1      引擎下载,从Irrlicht三维图形引擎的主页:irrlicht.sourceforge/ Download栏目中下载Irrlicht引擎 SDK压缩包,目前该引擎的最新版本是Irrlicht-1.3.1。整个压缩文件约为16MB解压后生成irrlicht-1.3.1目录,该目录包括Irrlicht引擎C++源代码、引擎动态链接库、使用手册以及一些三维素材。
2  建立C#工程。
New Project中选择建立C# Console Application工程,如图1所示,这里将新工程命名为Terrain
3、在工程中加入Irrlicht引擎
鼠标右键点击新建工程的References项,选择Add References加入引擎的动态链接库Irrlicht.NET.dll,该文件在irrlicht-1.3.1\bin\Win32-VisualStudio目录中。如图2所示。
1 建立C#工程
 
2 添加irrlicht.NET引擎
成功加入引擎后,在References项中会显示新子项Irrlicht.NET,如图3所示。
 
3 References项中显示Irrlicht.NET
三、地形生成方法
    用电脑生成三维地形的方法有许多种,其中使用最广泛的就是高度图生成法。我们经常会看到用不同的颜表示海拔高度的地图。高度图生成法与采用相同原理,只不过为了便于计算机处理,海拔高度值用图像中的亮度值表示。即图像中每一点处的灰度值代表该坐标处的地形海拔高度,越接近黑则海拔越低,反之越接近白则海拔越高。要使地形具有真实感,地形图像必须符合一些要求,主要是灰度数值的连续变化,两像素距离越近的点灰度值越接近。地形图像较容易得到,我们可以利用图像处理软件Adobe Photoshop制作,利用其“Render”特效的“Clouds”项,将前景设为黑,背景设为白,这样得到的图像可以表现出非常真实的地形效果。也可以从网上直接下载相关的用灰度值表示的高度图。本文采用irrlicht引擎中多媒体素材文件夹meida中的高度图terrain-heightmap.bmp,如图4所示。为了便于编程实现,这里将路径为irrlicht-1.3.1\media的素材文件夹拷贝到与新建C#工程Terrain相同的目录下。
 
4 高度图
这张高度图尺寸为256X256。可生成216个三维网格顶点,这是普通电脑系统一次所能绘制的最多顶点数。如果要绘制更大的三维地形,则需要分批多次绘制。通过高度图生成三维图形后,可在Irrlicht引擎的支持下增加控制功能以及碰撞检测用于实现三维数字地形漫游。具体实现过程如下。
四、地形漫游程序
三维地形漫游程序采用console风格,程序的整体框架如下:
using System;
using System.Text;
using System.IO;
using Irrlicht;
using Irrlicht.Video;
using Irrlicht.Core;
using Irrlicht.Scene;
using Irrlicht.GUI;
namespace TerrainRoam //命名空间指定为为地形漫游
{
class Program: IEventReceiver
    {
//高度图及纹理载入路径
string path="../../../../media/";
//定义地形场景节点
ITerrainSceneNode terrain;
      static void Main(string[] args)
public bool OnEvent(Event p_event)
public void run()
}
}
其中主类Programirrlicht事件接收器类IEventReceiver继承。该类成员函数包括程序进入点Main(),引擎事件接收器类成员函数的重载函数OnEvent(),地形载入及主循环函数run()。另外在程序开始通过使用using指令指定包含文件和相应的命名空间。包括Irrlicht的视频驱动、引擎核心、场景管理以及用户图形界面的命名空间。用于实现console风格的编程。下面,分别介绍Program类的成员函数。
1.程序进入点函数
首先用[STAThread]指示应用程序的默认线程模型是单线程单元,然后用new从堆上创建类Program对象,并利用成员函数run()载入地形并进入地形场景渲染循环。
[STAThread]
static void Main(string[] args)
{
Program prog = new Program();
prog.run();
}
2  事件响应函数
OnEvent()是irrlicht事件接收器类成员函数的重载,用于响应用户自定义的事件。这里使用该函数实现三维地形网格模式和纹理显示模式的实时转换。用户可通过w键实现这一切换。
public bool OnEvent(Event p_event)
{
// 检测用户是否按下W
if (p_event.Type == EventType.KeyInput &&
!p_event.KeyPressedDown)
{
switch(p_event.Key)
{
case KeyCode.KEY_KEY_W:
isWireframe=!isWireframe;
terrain.SetMaterialFlag(MaterialFlag.WIREFRAME,isWireframe);
break;
}
}
return false;
}
3  地形载入及主循环函数
该函数的作用包括图形程序库的选择、设置视窗和镜头、载入高度图、碰撞检测的实现以及建立程序主循环。
其中可供选择的图形程序库包括Driect3D 9.0Driect3D 8.0OpenGL,涵盖了普通PC机上使用的主流图形驱动库。另外如果用户电脑上没装上述程序库的话,也可选择irrlicht软图形驱动,但是显示效果会差一些。
public void run()
{
DriverType driverType;
// 询问用户选择什么图形库
StringBuilder sb = new StringBuilder();
sb.Append("Please select the driver you want for this example:\n");
sb.Append("\n(a) Direct3D 9.0c\n(b) Direct3D 8.1\n(c) OpenGL 1.5");
sb.Append("\n(d) Software Renderer\n (otherKey) exit \n\n");
// 获取用户输入
TextReader tIn = Console.In;
TextWriter tOut = Console.Out;
tOut.Write(sb.ToString());
string input = tIn.ReadLine();
// 根据用户输入确定相应的图形库
switch (input)
{
case "a":
driverType = DriverType.DIRECT3D9;
break;
case "b":
driverType = DriverType.DIRECT3D8;
break;
case "c":
driverType = DriverType.OPENGL;
break;
case "d":
driverType = DriverType.SOFTWARE;
break;
default:
return;
    }
接下来设置视窗和镜头。设置窗口尺寸为640X480,并创建Irrlicht设备device用于调用引擎核心支持,实例化引擎的场景管理对象、视频驱动对象、图形界面接口对象,用于场景管理、图形程序库调用以及图形界面的管理。设置镜头类型为FPS型(第一人称),用户可以从第一人称视点观察三维地形,并可控通过移动鼠标实现抬头、低头功能,通过键盘上的“↑、↓、→、←”进行镜头前、后、左、右移动。
// 创建Irrlicht设备,如果创建失败则退出程序
IrrlichtDevice device = new IrrlichtDevice(
driverType, new Dimension2D(640, 480), 32, false, true, true);
if (device == null)
{
tOut.Write("Device creation failed.");
nodeselectorreturn;
}
//引擎响应当前类的事件
device.EventReceiver=this;
ISceneManager smgr=device.SceneManager;
IVideoDriver driver=device.VideoDriver;
IGUIEnvironment env= device.GUIEnvironment;
// 加入FPS镜头,并设置镜头位置
ICameraSceneNode camera = smgr.AddCameraSceneNodeFPS(null,100.0f,1200.0f,-1);
camera.Position=new Vector3D(1900*2,255*2,3700*2);
camera.Target= new Vector3D(2397*2,343*2,2700*2);
camera.FarValue=12000.0f;
// 设置鼠标箭头不可见
    device.CursorControl.Visible=false;
下一步导入高度图,这里利用addTerrainSceneNode()函数载入地形场景节点,用于地形场景的管理和渲染。在该函数中设置需要载入高度图的路径,场景管理器会自动载入高度图并根据其创建三维地形。为了使地形场景看起来更大一些,设置比例尺度向量为(40, 4.4, 40)
terrain = smgr.AddTerrainSceneNode(
path+"terrain-heightmap.bmp",null,-1,
new Vector3D(),new Vector3D(40, 4.4f, 40), new Color(255,255,255,255));
碰撞检测的加入可防止漫游时镜头穿透地形表面。为了使地形漫游有碰撞的功能,加入三角形选择器,检测三角形面片。同时将碰撞检测同镜头关联并设置碰撞动作,这样当有山峰挡在镜头前面时,镜头不会从山体中间穿过,而是依地形起伏运动。
//创建地形节点三角形选择器
ITriangleSelector selector =
smgr.CreateTerrainTriangleSelector(terrain, 0);
//创建碰撞反应动作并与镜头关联
ISceneNodeAnimator anim = smgr.CreateCollisionResponseAnimator(
selector, camera, new Vector3D(60,100,60),
new Vector3D(0,0,0),
new Vector3D(0,50,0),0.0005f);
    camera.AddAnimator(anim);
最后建立程序主循环。当Irrlicht设备运行状态device.Run为真时,绘制出场景中所有的物体,其中整型变量lastFPS用于记录每秒钟渲染帧数并将结果显示在窗口标题栏上。当主循环结束后,调用GC.Collect()回收资源。
int lastFPS = -1;
while (device.Run())
{
if (device.WindowActive)
{
device.VideoDriver.BeginScene(true, true, new Color(0, 200, 200, 200));
device.SceneManager.DrawAll();
device.VideoDriver.EndScene();
int fps = device.VideoDriver.FPS;
if (lastFPS != fps)
{
device.WindowCaption = " Terrain [" +
device.VideoDriver.Name + "] FPS:" + fps.ToString();
lastFPS = fps;
}
}
}
    GC.Collect();
}
通过上述程序,可实现三维地形显示的基本功能。
五、显示结果
对上述工程进行编译并运行。程序运行后首先会弹出console界面,请用户选择图形程序库。这里使用Driect3D 9.0,如图。
 
5 选择图形程序库
选择完后,系统会自动加载高度图并建立三维地形,结果如图5。这是用网格线表示的地形。用户可通过键盘和鼠标控制实现第一人称三维场景漫游。
5 选择图形程序库
为了使地形效果更生动,在导入高度图函数AddTerrainSceneNode()后加入如下代码。
//设置纹理放大比例
terrain.ScaleTexture(1.0f, 20.0f);
terrain.SetMaterialFlag(MaterialFlag.LIGHTING, false);
//多细节纹理映射
terrain.SetMaterialType(MaterialType.DETAIL_MAP);
terrain.SetMaterialTexture(0, driver.GetTexture(path+"terrain-texture.jpg"));
terrain.SetMaterialTexture(1, driver.GetTexture(path+"detailmap3.jpg"));
//加入天空盒
smgr.AddSkyBoxSceneNode(
driver.GetTexture(path+"irrlicht2_up.jpg"),
driver.GetTexture(path+"irrlicht2_dn.jpg"),
driver.GetTexture(path+"irrlicht2_lf.jpg"),
driver.GetTexture(path+"irrlicht2_rt.jpg"),
driver.GetTexture(path+"irrlicht2_ft.jpg"),
driver.GetTexture(path+"irrlicht2_bk.jpg"),null,0);
//使用mipmap效果
driver.SetTextureCreationFlag(TextureCreationFlag.CREATE_MIP_MAPS, true);
这里通过SetMaterialFlag()函数设置场景光照效果为全亮显示,同时利用GetTexture()函数导入低、高细节层次纹理贴图,利用引擎支持的mipmap技术根据镜头离场景远近进行动态映射。离镜头较远的场景只使用低细节层次纹理。镜头附近的场景则同时使用两种纹理的混合,这样即可以生动表示地形场景,也可减少系统资源消耗。如图7所示。
 

 
7 高、低细节层次纹理
为了使三维地形更生动,加入天空盒,在场景上、下、左、右、前、后都导入贴图,形成远景,具有透视感。这里的低细节层次纹理采用irrlicht素材文件夹media中的静态阴影贴图,使地形具有真实感。最终显示结果如图8所示。
7 显示结果
五、结语
文中提供了一种实现三维数字地形漫游的解决方案。在Irricht三维图形引擎的支持下,利用C#可较容易地实现三维数字地形的漫游。本文提供的程序框架是可以扩充的,读者可参考Irrlicht引擎使用手册,加入更多功能,从而编写出更好的三维地形漫游程序。
参考文献:
1.Nikolaus Gebhardt. Irrlicht.NET Documentation. irrlicht.sourceforge,2007
    2.麦中凡,陆永宁编著.C#编程语言.北京航空航天大学出版社 2001