深入Managed DirectX9(十六)
现在运行程序,可以看到和原来一样的效果。显示了所有细节的模型不停旋转。现在来处理移动摄像机的键盘事件。这里的代码和之前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”回到球体。
~~~~~~~~~~~~~~~~~第九章完~~~~~~~~~~~~~~~~~~
下一章我们将学习如何绘制线条,三维字体,在屏幕表面渲染(类似极品飞车里倒视镜的效果)以及天空盒,然后整个第二大部分--中级图形内容就完了^_^
下载代码
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”回到球体。
~~~~~~~~~~~~~~~~~第九章完~~~~~~~~~~~~~~~~~~
下一章我们将学习如何绘制线条,三维字体,在屏幕表面渲染(类似极品飞车里倒视镜的效果)以及天空盒,然后整个第二大部分--中级图形内容就完了^_^
下载代码