DirectX9 3D 快速上手 8
By sssa2000
5/4/2005
上一次中途结束了本来应该讲到的控制Mesh的细节程度的方法的,这一次补上。
我们这里使用的是简单的方法,并没有涉及到场景剔出等等复杂的方法,我这里主要还是用DX9提供给我们的类库,progressive meshe。
progressive meshes主要的优点就是允许我们控制顶点和面的数目,这样我们就可以灵活的控制mesh细节的显示程度。
和Mesh一样,progressive meshe也都是继承自BaseMesh这个类。比较重要的属性主要有2个NumberFaces和NumberVertices。从字面我们就可以看出两个属性的含义。
有了前面的基础在使用progressive mesh上也变得十分的简单,首先我们需要声明一个progressive mesh的变量,然后我们看看progressive mesh的构造函数:
public ProgressiveMesh(
Mesh mesh,
GraphicsStream adjacency,
GraphicsStream vertexWeights, //顶点的权值,越高越不容易被剔出,如果设为null就全部被设为1
int minValue, //设定被简化的最小值
MeshFlags options
);
以下就是核心的代码:
private void LoadMesh(string file)
{
ExtendedMaterial[] mtrl;
GraphicsStream adj;
// Load our mesh
using(Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device,
out adj, 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,微软的SDK也有一个例子,不过那个例子太过于庞大,大部分都是在处理UI方面,真正涉及到progressiveMesh的部分还是很少的。
以下是代码的事例图和完整代码:
这个例子里可以切换线框渲染模式和实体作色模式。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace progressiveMesh
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private Device device = null;
private ProgressiveMesh progressiveMesh = null;
private Material[] meshMaterials;
private Texture[] meshTextures;
private Microsoft.DirectX.Direct3D.Font font = null;
private float cameraPos = 8.0f;
private const int MoveAmount = 2;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
private float angle = 0.0f;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
}
/// <summary>
/// We will initialize our graphics device here
/// </summary>
public void InitializeGraphics()
{
// Set our presentation parameters
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
presentParams.EnableAutoDepthStencil = true;
// Create our device
device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
// Load our mesh
LoadMesh(@"..\..\SprintRacer.x");
// Create our font
font = new Microsoft.DirectX.Direct3D.Font(device, new System.Drawing.Font
("Arial", 14.0f, FontStyle.Bold | FontStyle.Italic));
}
private void LoadMesh(string file)
{
ExtendedMaterial[] mtrl;
GraphicsStream adj;
// Load our mesh
using(Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device,
out adj, 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(CleanType.Simplification ,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;
}
}
}
private void SetupCamera()
{
device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 10000.0f);
device.Transform.View = Matrix.LookAtLH(new Vector3(0,0, cameraPos),
new Vector3(), new Vector3(0,1,0));
device.RenderState.Ambient = Color.Blue;
device.Lights[0].Type = LightType.Directional;
device.Lights[0].Diffuse = Color.White;
device.Lights[0].Direction = new Vector3(0, -1, -1);
device.Lights[0].Update();
device.Lights[0].Enabled = true;
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Blue , 1.0f, 0);
SetupCamera();
device.BeginScene();
// Draw our Mesh
DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);
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);
device.EndScene();
device.Present();
this.Invalidate();
}
private void DrawMesh(float yaw, float pitch, float roll, float x, float y, float z)
{
angle += 0.11f;
device.Transform.World = Matrix.RotationYawPitchRoll(yaw, pitch, roll) * Matrix.Translation(x, y, z);
for (int i = 0; i < meshMaterials.Length; i++)
{
device.Material = meshMaterials[i];
device.SetTexture(0, meshTextures[i]);
progressiveMesh.DrawSubset(i);
}
}
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;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new Size(800,600);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
static void
Main()
{
using (Form1 frm = new Form1())
{
// Show our form and initialize our graphics engine
frm.Show();
frm.InitializeGraphics();
Application.Run(frm);
}
}
}
}
这里我们已经实现了能自由控制显示的细节问题,通过progressiveMesh 我们可以很方便的完成这一点,接下来,顺着这个话题我们来讨论一下如何增加细节的显示程度,也就是说我们将要把读入的Mesh的细节程度增多。这里我们部探讨原理,因为我数学也是很差,我们可以借助Patch Meshes 来实现。
Patch Meshes通过增加读入的Mesh的顶点来对细节程度进行增加。
public PatchMesh(ID3DXPatchMesh); 这就是PathMesh的构造函数,只要传入一个读入的Mesh就可以了。接下来我们将用一个例子展示怎么增加显示的细节程度
private void CreatePatchMesh(string file, float tessLevel)
{
if (tessLevel < 1.0f) //如果小于默认的值,不再增加,直接返回
return;
if (mesh != null)
mesh.Dispose();
using (Mesh tempmesh = LoadMesh(file))
{
using (PatchMesh patch = PatchMesh.CreateNPatchMesh(tempmesh))
{
// Calculate the new 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);
// Tessellate the patched mesh
patch.Tessellate(tessLevel, mesh); //在tessLevel的基础上把mesh分成小方格
}
}
}
private Mesh LoadMesh(string file)
{
……………………….略
if ((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)
//如果没有法线信息,就要计算法线,发现信息会在上面的Tessellate方法中被用到。
{
// We must have normals for our patch meshes
Mesh tempMesh = mesh.Clone(mesh.Options.Value,
mesh.VertexFormat | VertexFormats.Normal, device);
tempMesh.ComputeNormals();
mesh.Dispose();
mesh = tempMesh;
}
return mesh;
}
以下就是运行的图例
可以明显地看出,我们的程序还是有明显的效果的,不过对于增加的细节,程序将变得十分的缓慢。
以下是全部的代码:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace PathMesh
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private Device device = null;
private Mesh mesh = null;
private Material[] meshMaterials;
private Texture[] meshTextures;
private float tessLevel = 1.0f;
private const float tessIncrement = 1.0f;
private string filename = @"..\..\sphere.x";
private Microsoft.DirectX.Direct3D.Font font = null;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
private float angle = 0.0f;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
}
/// <summary>
/// We will initialize our graphics device here
/// </summary>
public void InitializeGraphics()
{
// Set our presentation parameters
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
presentParams.EnableAutoDepthStencil = true;
// Create our device
device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
// Create our patch mesh
CreatePatchMesh(filename, tessLevel);
// Create our font
font = new Microsoft.DirectX.Direct3D.Font(device, new System.Drawing.Font
("Arial", 14.0f, FontStyle.Bold | FontStyle.Italic));
// Default to wireframe mode first
device.RenderState.FillMode = FillMode.WireFrame;
}
/// <summary>
/// Creates a temporary mesh object, and a patch mesh from it
/// </summary>
/// <param name="file">The original mesh to use</param>
/// <param name="tessLevel">The tesselation level</param>
private void CreatePatchMesh(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 new 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);
// Tessellate the patched mesh
patch.Tessellate(tessLevel, mesh);
}
}
}
/// <summary>
/// Load a mesh from a file and return it
/// </summary>
/// <param name="file">The file to load</param>
/// <returns>The created mesh</returns>
private Mesh LoadMesh(string file)
{
ExtendedMaterial[] mtrl;
// Load our mesh
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);
}
}
}
if ((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)
{
// We must have normals for our patch meshes
Mesh tempMesh = mesh.Clone(mesh.Options.Value,
mesh.VertexFormat | VertexFormats.Normal, device);
tempMesh.ComputeNormals();
mesh.Dispose();
mesh = tempMesh;
}
return mesh;
}
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.Lights[0].Type = LightType.Directional;
device.Lights[0].Diffuse = Color.DarkKhaki;
device.Lights[0].Direction = new Vector3(0, 0, -1);
device.Lights[0].Update();
device.Lights[0].Enabled = true;
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0);
SetupCamera();
device.BeginScene();
// Draw our Mesh
DrawMesh(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI / 4.0f, 0.0f, 0.0f, 0.0f);
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);
device.EndScene();
device.Present();
this.Invalidate();
}
private void DrawMesh(float yaw, float pitch, float roll, float x, float y, float z)
{
angle += 0.01f;
device.Transform.World = Matrix.RotationYawPitchRoll(yaw, pitch, roll) * Matrix.Translation(x, y, z);
for (int i = 0; i < meshMaterials.Length; i++)
{
device.Material = meshMaterials[i];
device.SetTexture(0, meshTextures[i]);
mesh.DrawSubset(i);
}
}
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;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new Size(800,600);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
static void
Main()
{
using (Form1 frm = new Form1())
{
// Show our form and initialize our graphics engine
frm.Show();
frm.InitializeGraphics();
Application.Run(frm);
}
}
}
}
By sssa2000
5/4/2005