王朝网络
分享
 
 
 

【翻译】Managed DirectX(第九章)

王朝other·作者佚名  2006-02-01
宽屏版  字体: |||超大  

第九章 使用其它Mesh类型

翻译:clayman

clayman_joe@yahoo.com.cn

仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^

翻译:clayman

clayman_joe@yahoo.com.cn

仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^

我们花了大量的时间来学习扩展库中的基本Mesh类,但实际上,还有3种其它Mesh类型我们没有讨论过,在这一章中,我们就来看看这几个对象。

我们已经知道如何通过使用Mesh类的Simplify方法创建一个简化过的mesh。通常情况下,使用简化mesh只是为了显示低细节的模型,但在任何场景中,都不可能为一个mesh储存太多版本,特别是其中的很多版本你可能根本不会用到。

SimplificationMesh方法可以用来压缩简化mesh的过程。但是,不能直接使用这个对象进行渲染你必须先通过simplification mesh创建一个真正的mesh。还记得先前简化mesh的例子吗,我们在例子里展示了两种情况。一种是在很近的情况下观察未经简化的mesh,另一种则是在比较远的的情况下显示简化了很多面和顶点的mesh。我们将再次做类似工作,不过这一次,我们将逐渐的移动摄像机。

还是以第五章的例子为基础,逐渐添加代码。首先,添加SimplificationMesh对象的声明,同时还有控制摄像机位子的变量:

private SimplificationMesh simplifiedMesh = null;

private float camreaPos = 580.0f;

把摄像机的深度位置作为变量,可以方便以后的修改。接下来更新view transform,让他使用正确的位置信息。在SetupCamera方法中,添加更新如下代码:

device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,cameraPos), new Vector3(), new Vector3(0,1,0));

显然,由于每一帧都会调用SetupCamera方法,因此任何对camera变量的改变都会马上得到更新,这正是我们想要的效果。SimplifiycationMesh中储存了我们将用于简化的mesh。注意到没,我们没有创建任何额外的Mesh对象。每次简化之后,只需用他代替原来的mesh就可以了。

修改LoadMesh方法确保mesh是经过了clean的,并且正确创建了simplifycationMesh对象。如下修改代码:

private void LoadMesh(string file)

{

ExtendedMaterial[] mtrl;

// Load our mesh

mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);

// If we have any materials, store them

if ((mtrl != null) && (mtrl.Length > 0))

{

meshMaterials = new Material[mtrl.Length];

meshTextures = new Texture[mtrl.Length];

// Store each material and texture

for (int i = 0; i < mtrl.Length; i++)

{

meshMaterials[i] = mtrl[i].Material3D;

if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))

{

// We have a texture, try to load it

meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" + mtrl[i].TextureFilename);

}

}

}

//Clean our mesh

Mesh tempMesh = Mesh.Clean(mesh,adj,adj);

//replace extsting mesh with this one

mesh.Dispose();

mesh = tempMesh;

//creat simiplification mesh

simplificationMesh = new SimplificationMesh(mesh,adj);

}

(注:新版的MDX中应为Mesh tempMesh = Mesh.Clean(CleanType. Simplification,mesh,adj,adj); 另外,在使用了mesh.Dispose();之后程序有可能会出现异常。)

如你所见,在clean方法和simplificationMesh方法中都要用到邻接信息。在加载了mesh创建了纹理之后,我们就对mesh进行clean,准备简化。接下来,用clean过的mesh创建simplificationMesh对象。

事实上,现在不需要做任何修改,也能正确绘制mesh。但是,我们想添加一些代码让摄像机慢慢远离mesh,同时,减少mesh的细节。使用键盘来控制摄像机的移动,添加代码:

protected override void OnKeyPress(KeyPressEventArgs e)

{

if(e.KeyChar == '+')

{

cameraPos += (MoveAmount * 2);

simplifiedMesh.ReduceFaces(mesh.NumberFaces - MoveAmount);

simplifiedMesh.ReduceVertices(mesh.NumberVertices - MoveAmount);

mesh.Dispose();

mesh = simplifiedMesh.Clone(simplifiedMesh.Options.Value,simplifiedMesh.VertexFormat,device);

}

if(e.KeyChar == 'w')

device.RenderState.FillMode = FillMode.WireFrame;

if(e.KeyChar == 's')

device.RenderState.FillMode = FillMode.Solid;

base.OnKeyPress (e);

}

注意到,这里使用了一个统一的常量来控制每次按下按键时移动的总数。你可以根据实际情况来设置这个常量,以下是我使用的值:

private const int MoveAmount = 100;

在这个方法里,按下W键的时候,跳转为线框模式,这样可以很容易看出实际绘制的三角形。按下S键则跳回填充模式。按下+键,则会使摄像机远离模型。移动摄像机的时候,根据指定的常理来减少mesh的顶点和面。然后释放原来的mesh。使用simplifiedMesh的克隆代替原mesh,并进行渲染。

现在运行程序,可以使用+键来移动物体,并且在线框模式和填充模式之间转换,但离远了之后,几乎看不出模型是经过简化的。我们再添加一些文本来显示所渲染的面数和顶点数吧。添加字体变量:

private Microsoft.DirectX.Direct3D.Font font = null;

在绘制文本前必须先初始化字体对象,在IntializeGraphics方法的LoadMesh之后,添加代码:

font = new Microsoft.DirectX.Direct3D.Font(device,new System.Drawing.Font("Arial",14.0f,FontStyle.Bold | FontStyle.Italic));

可以根据自己的喜好来改变字体的类型和大小。现在可以绘制字体了。在调用DrawMesh方法之后,添加如下代码:

font.DrawText(null,string.Format("number vertices in mesh: {0}",mesh.NumberVertices),new Rectangle(10,10,0,0),DrawTextFormat.NoClip,Color.BlanchedAlmond);

font.DrawText(null,string.Format("number faces in mesh: {0}",mesh.NumberFaces),new Rectangle(10,30,0,0),DrawTextFormat.NoClip,Color.BlanchedAlmond);

好了,现在可以方便的看到移动模型时顶点和面的数量都减少了。你可以获得如下的结果:

这个程序的主要缺点就在于我们不能恢复丢失的顶点。当然,我们可以出色的完成简化任务,但是,当我们需要再次近距离观察模型怎么办呢?没有RaiseVertices方法,调用ReduceVerctices方法,传入一个很大的顶点数也是没有用的。SimplificationMesh就是设计来简化mesh的,仅此而已,不能恢复。ProgressiveMesh对象就是用来处理这个问题的。

使用Progressive Meshes控制细节级别

有些情况下,你只需要简化mesh。但是,一味的简化,意味着无法再恢复对象的细节,而同时需要简化和增加对象细节的情况确是很常见的。我相信看到这里,你已经知道progressive mesh方法名字的来历了吧。

为了显示progressiveMesh方法的行为,我们将写一个和上一部分很类似的程序。这一次,我们不但可以简化mesh,还可以在摄像机靠近模型时,添加mesh的细节。还是从第五章的文件开始。

Progressivemesh类于Mesh类一样,都继承于BaseMesh类。与SimplificationMesh类相比,可以直接用progressiveMesh对象来绘制物体。这样,就可以用以下变量来代替Mesh对象了:

同样,你应该把原来所有的mesh变量都替换为这个变量。之后,更新LoadMesh方法,因为代码现在不能通过编译了。使用一个类似的方法;我们将在最后创建progressiveMesh对象。添加代码:

private void LoadMesh(string file)

{

ExtendedMaterial[] mtrl;

GraphicsStream adj;

// Load our mesh

using(Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl)

{

// If we have any materials, store them

if ((mtrl != null) && (mtrl.Length > 0))

{

meshMaterials = new Material[mtrl.Length];

meshTextures = new Texture[mtrl.Length];

// Store each material and texture

for (int i = 0; i < mtrl.Length; i++)

{

meshMaterials[i] = mtrl[i].Material3D;

if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))

{

// We have a texture, try to load it

meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" + mtrl[i].TextureFilename);

}

}

}

//clean our main mesh

using(Mesh tempMesh = Mesh.Clean(mesh,adj,adj)

{

//create our progressive mesh

progressiveMesh = new ProgressiveMesh(tempMesh,adj,null,1,MeshFlags.SimplifyVertex);

//set the initial mesh to the max

progressiveMesh.NumberFaces = progressiveMesh.MaxFaces;

progressiveMesh.NumberVertices = progressiveMesh.MaxVertices;

}

}

}

注意,我们使用了两个临时mesh来创建progressiveMesh对象。其中一个clean过的mesh来完成真正的创建。对于progressiveMesh的构造函数来说,第四个参数是最重要的:根据所使用的MeshFlags选项,它是所创建mesh的顶点或面的最小值。显然,这只是一个近似值,即使不能把mesh简化到这个级别,这个方法还是会成功。

你应该还注意到,我们马上就把面数和顶点数设置为了最大值。创建mesh并保留在简化状态(generating the mesh leaves it simplified)。设置progressiveMesh中面或顶点数中的任意一个,都可以在渲染时更改mesh的细节。因为我们想初始化时显示所有细节,自然把面和顶点值都设置为最大值。

为了让程序通过编译,还需要修改DrawMesh中的DrawMesh方法:

progressiveMesh.DrawSubset(i);

现在运行程序,可以看到和原来一样的效果。显示了所有细节的模型不停旋转。现在来处理移动摄像机的键盘事件。这里的代码和之前simplification mesh例子里的很相似。先声明摄像机的位置变量以及每次移动的距离常量。

private float cameraPos = 580.0f;

private const int MoveAmount = 50;

再次修改view transform以显示摄像机位置的更新。

device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,cameraPos), new Vector3(), new Vector3(0,1,0));

最后,处理键盘事件:

protected override void OnKeyPress(KeyPressEventArgs e)

{

if(e.KeyChar == '+')

{

cameraPos += (MoveAmount * 2);

progressiveMesh.NumberVertices = ((BaseMesh)progressiveMesh).NumberVertices - MoveAmount;

progressiveMesh.NumberFaces = ((BaseMesh)progressiveMesh).NumberFaces - MoveAmount;

}

if(e.KeyChar == '-')

{

cameraPos -= (MoveAmount * 2);

progressiveMesh.NumberVertices = ((BaseMesh)progressiveMesh).NumberVertices + MoveAmount;

progressiveMesh.NumberFaces = ((BaseMesh)progressiveMesh).NumberFaces + MoveAmount;

}

if(e.KeyChar == 'w')

device.RenderState.FillMode = FillMode.WireFrame;

if(e.KeyChar == 's')

device.RenderState.FillMode = FillMode.Solid;

base.OnKeyPress (e);

}

运行程序来看看结果吧。另外,注意到,当需要获得面数和顶点数时,必须先把progressiveMesh对象转换为BaseMesh对象。应为progressiveMesh对象只有set属性,而get属性只在基类才有,所以这个转换是必须的。

再添加一些文本来显示当前的面数和顶点数。为了不在重复,参照上一个例子中添加字体变量的代码。之后,添加如下代码:

font.DrawText(null,string.Format("number vertices in mesh: {0}",((BaseMesh)progressiveMesh).NumberVertices),

new Rectangle(10,10,0,0), DrawTextFormat.NoClip,Color.BlanchedAlmond);

font.DrawText(null,string.Format("number faces in mesh: {0}",((BaseMesh)progressiveMesh).NumberFaces),

new Rectangle(10,30,0,0),DrawTextFormat.NoClip,Color.BlanchedAlmond);

特别提示:储存多个细节级别

更常见的做法是储存多个细节级别的mesh,而不是使用一个单独的progressiveMesh对象来控制整个细节范围。如果你查找SDK中Progressive Mesh的示例,可以看到这种方法的实现。可以使用ProgressiveMesh对象上的TrimByFaces和TrimByVertices方法来改变细节的级别。

Rendering Patch Meshes

简化mesh是一个常见的操作,但万一你获得的mesh太简单了怎办?虽然增加模型细节的情况不多见,但却是可能的,patch mesh就是用来完成这个任务的。大多数现代的三维建模程序都有patch mesh的影子,或其他一些高要求的图元,比如NURBS或细分面。

本书的目的不是讲解patch mesh背后的原理。因此,我们所做的只是知道如何使用它,以及如何增加模型的细节级别。为此,我们将创建一个程序通过细分(subdivide)顶点创建一个复杂模型。

好了,还是从第五章的程序开始。代码将会用到SDK中的两个模型(tiger.x 和cube.x),除此之外,还有个一小球体的模型。确保你把老虎模型的纹理一起拷贝到了当前目录下。添加如下变量:

private float tessLevel = 1.0f;

private const float tessIncrement = 1.0f;

private string filename = @"..\..\sphere.x";

private Microsoft.DirectX.Direct3D.Font font = null;

先保存了当前的细分级别(tessellation level)(初始化为1.0f),同时也包括我们每次增加细节的增量。可以任意的修改这个常量,对这个例子来说,默认的值就可以了。另外我们还声明了当前模型的名字和渲染时使用的字体。

还需要修改一下SetupCamera方法,上一次使用了一个非常大的模型,而这次的却小得多。做如下更新:

private void SetupCamera()

{

device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 100.0f);

device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, 5.0f), new Vector3(), new Vector3(0,1,0));

//device.RenderState.Ambient = Color.DarkBlue;

device.Lights[0].Type = LightType.Directional;

device.Lights[0].Diffuse = Color.DarkKhaki;

device.Lights[0].Direction = new Vector3(0, -1, -1);

device.Lights[0].Commit();

device.Lights[0].Enabled = true;

}

这里简单的设置了摄像机和灯光。虽然每一帧都这么做不是很高效,但为了简单明了,例子里暂时这么做。当然,还应该检查图形卡是否支持方向光,不过这里也省略了。在InitializeGraphics()里使用如下代码替代之前加载mesh的LoadMesh方法:

CreatePatchMesh(filename,tessLevel);

font = new Microsoft.DirectX.Direct3D.Font(device,new System.Drawing.Font("Arial",14.0f,FontStyle.Bold | FontStyle.Italic));

device.RenderState.FillMode = FillMode.WireFrame;

显然,我们创建了patch mesh以及字体。同时把默认的现实模式设置为线框模式,这样能清楚的看到增加的三角形。还没有定义创建patch mesh的方法呢,添加代码:

private void CreatPatchMesh(string file,float tessLevel)

{

if(tessLevel < 1.0f) //nothing to do

return;

if(mesh != null)

mesh.Dispose();

using(Mesh tempMesh = LoadMesh(file))

{

using(PatchMesh patch = PatchMesh.CreateNPatchMesh(tempMesh))

{

//calculate the neew number of faces/vertices

int numberFaces = (int)(tempMesh.NumberFaces * Math.Pow(tessLevel,3));

int numberVerts = (int)(tempMesh.NumberVertices * Math.Pow(tessLevel,3));

mesh = new Mesh(numberFaces,numberVerts,MeshFlags.Managed | MeshFlags.Use32Bit,tempMesh.VertexFormat,device);

patch.Tessellate(tessLevel,mesh);

}

}

}

如果当前细分级别小于1.0f(默认值),那么就什么都不作。否则,创建新的细分mesh。因为我们要取代已有的mesh,所以先调用dispose方法。当前的LoadMesh方法还不会返回任何值,更新代码:

private Mesh LoadMesh(string file)

{

ExtendedMaterial[] mtrl;

Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);

if ((mtrl != null) && (mtrl.Length > 0))

{

meshMaterials = new Material[mtrl.Length];

meshTextures = new Texture[mtrl.Length];

// Store each material and texture

for (int i = 0; i < mtrl.Length; i++)

{

meshMaterials[i] = mtrl[i].Material3D;

if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))

{

// We have a texture, try to load it

meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" + mtrl[i].TextureFilename);

}

}

}

if((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)

{

Mesh tempMesh = mesh.Clone(mesh.Options.Value,mesh.VertexFormat | VertexFormats.Normal,device);

tempMesh.ComputeNormals();

mesh.Dispose();

mesh = tempMesh;

}

return mesh;

}

这里没有什么新内容。我们创建mesh,如果他没有法线信息,我们就克隆mesh,计算法线。因为在对patch mesh细分时需要用到法线信息。最后,返回mesh。

特别提示:使用using语句

注意,我们在返回的meh和创建的patch mesh都使用了using语句,这将保证在完成操作之后自动释放资源。

之后,使用新返回的mesh对象创建patch mesh。因为要把patch mesh细分为一个新的mesh,我们还需要知道这个mesh将有多少面和顶点。在计算了这2个值之后,终于可以创建新的细分之后的mesh了。注意到我使用了MeshFlags.Use32Bit标志。在细分之后,我们很有可能获得一个很大的mesh,必须确保可以支持大的mesh。现在可以运行程序来看看了,不过却是有些单调。先添加一些文本吧:

font.DrawText(null, string.Format("Number Vertices: {0}\r\nNumber Faces: {1}",mesh.NumberVertices,mesh.NumberFaces),new Rectangle(10,10,0,0), DrawTextFormat.NoClip, Color.Black);

最后,添加键盘事件来控制增加或减少细分层次。

protected override void OnKeyPress(KeyPressEventArgs e)

{

if (e.KeyChar == '+')

{

tessLevel += tessIncrement;

CreatePatchMesh(filename, tessLevel);

}

if (e.KeyChar == '-')

{

tessLevel -= tessIncrement;

CreatePatchMesh(filename, tessLevel);

}

if (e.KeyChar == 'c')

{

filename = @"..\..\cube.x";

tessLevel = 1.0f;

CreatePatchMesh(filename, tessLevel);

}

if (e.KeyChar == 'o')

{

filename = @"..\..\sphere.x";

tessLevel = 1.0f;

CreatePatchMesh(filename, tessLevel);

}

if (e.KeyChar == 't')

{

filename = @"..\..\tiger.x";

tessLevel = 1.0f;

CreatePatchMesh(filename, tessLevel);

}

if (e.KeyChar == 'w')

device.RenderState.FillMode = FillMode.WireFrame;

if (e.KeyChar == 's')

device.RenderState.FillMode = FillMode.Solid;

}

现在可以用“w”和“s”键来切换线框或实心模式,使用“c”切换到立法体模型,使用“t”切换到老虎模型,使用“o”回到球体。

需要源码的同志可以到这个地方去下http://bbs.gameres.com/showthread.asp?threadid=37943

需要源码的同志可以到这个地方去下http://bbs.gameres.com/showthread.asp?threadid=37943

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有