托管的 Direct3D之 使用 Frame Hierarchy 创建动画(翻译)
托管的 Direct3D之 使用 Frame Hierarchy 创建动画(翻译) 找了很久关于托管的DirectX编的资料实在是很少,无意中发现了这编文章http://www.jkarlsson.com/Articles/loadframes.asp 觉得对学习托管的Direct3D学习很有帮助。所以翻译来与大家共享。
另外,本人的英语水平有限,翻不好的地方大家不要见怪。欢迎大家指出有误的地方。谢谢。
本人QQ:173257128 (koy0755)欢迎高手交流。
好了,废话少说,开始转入正题DirectX 的图形场景由一系列的 帧(Frame)组成, 这允许你单独地存储几何对象和操作他们。帧由父(parent)、子(children)、兄弟(sibling)帧组成一个层次结构.对父帧的任何操作都会应用到它的子帧有子帧的子帧.
想象一下你在建立一个包括身体(body),炮塔(turret),枪(gun)的坦克模型. 更深入地想象一下你把“身体帧”(body frame)作为根.炮塔作为身体的子帧..“枪帧”作为“身体帧”的子帧. 如果你要在前景里移动你的坦克,你可以转移和旋转“身体帧”,炮塔和枪将公跟着坦克相互移动和旋转。另外你还能把旋转只应用到炮塔上,这样枪将会跟着炮塔相互旋转。你还能应用适当的变化来单独改变“枪帧”或其它帧的仰视角度。
The DirectX (.x) File
这篇文章假设我们要从.X文件加载的场景是SDK提供的类支持的。让我们看看这坦克在初期是怎么在DirextX文件里声明的。
xof 0303txt 0032
...
Frame Body
{
FrameTransformMatrix
{
1.000000,0.000000,0.000000,0.000000,
0.000000,1.000000,0.000000,0.000000,
0.000000,0.000000,1.000000,0.000000,
-1.401197,0.000000,0.279802,1.000000;;
}
Mesh
{
20;
-29.527559;0.000000;-49.212597;,
...
}
Frame Turret
{
FrameTransformMatrix
{
1.000000,0.000000,0.000000,0.000000,
0.000000,1.000000,0.000000,0.000000,
0.000000,0.000000,1.000000,0.000000,
1.435083,29.904209,-0.126913,1.000000;;
}
Mesh
{
116;
0.000000;0.000000;0.000000;
...
}
Frame Gun
{
FrameTransformMatrix
{
1.000000,0.000000,0.000000,0.000000,
0.000000,0.000000,-1.000000,0.000000,
0.000000,1.000000,0.000000,0.000000,
1.016502,4.063328,21.468393,1.000000;;
}
Mesh
{
116;
0.000000;0.000000;0.000000;,
...
}
}
}
}
完整的DirectX文件会比这里的多很多,但上面的元素是关于“帧”的讨论的要用到的。让我们看看上面这文档的不同部份。
Frame Body {}
这里声明了一个名为Body的帧.注意另外两个帧“frame”,Turret 和 Gun 是彼此嵌套地建立了一个帧的层次结构(hierarchy)。
FrameTransformMatrix {}
这是声明当前帧(frame)的mesh和他的子帧的mesh用放大,移动和旋转把当前帧放置到相对于其它帧的几何对象的合适位置的变换矩阵。考虑一下坦克上的炮塔这个例子, 他可能在建模时作为了个“圆桶状”被放置在原点。如果没有对它进行变换,它就会被放置在身体(body)里面了。配合变换矩阵我们就有足够的信息把炮塔放置到正确的位置了。
Mesh {}
它包含组成这个帧的顶点的坐标。它与这个帧的变换矩阵一起,我们就有了表现这个帧相对于其它帧的对象的足够信息。在内存中会把这个场景存储在一个AnimationRootFrame 对象中。AnimationRootFrame 让我们可以存储一个“帧的层次结构”在它的FrameHierarchy 属性里。 AnimationRootFrame 还可以对一个“帧层次结构”执行其它操作,但我们现在忽略它们。
Loading a Frame Hierarchy我们需要用一些代码来获取一个DirectX文件和创建帧层次(Frame Hiearchy)来存储我们的AnimationRootFrame 对象。我们使用静态方法Mesh.LoadHierachyFromFile 来帮助我们实现上面的功能。这个方法需要我们定义类来实现真正的分配类层次中每部份的操作。我们需要继承抽象类,AllocateHierarchy , Frame和MeshContainer。
以下所示是我们这个类的第一个版本:
public class GraphicsObject
{
// Path to the x-file containing the frame hierarchy for this class.
protected string xFilePath;
// Container for the frames
protected AnimationRootFrame rootFrame;
public GraphicsObject(string xFilePath, Device device)
{
this.xFilePath = xFilePath;
this.device = device;
}
// Load the frame hierarchy from file.
public virtual void Initialize()
{
this.rootFrame = Mesh.LoadHierarchyFromFile(this.xFilePath,
MeshFlags.Managed,
this.device,
new CustomAllocateHierarchy(this.xFilePath),
null);
}
}
Mesh.LoadHierachyFromFile 获取一个DirectX 文件并返回一个包含这个文件里第一个”Frame hierarchy”的 AnimationRootFrame 对象。第一个参数是这个.X文件的路径。第二个第二个参数决定把这个文件的信息保存在哪里(个人理解).第三个参数是一个用于表现的Device类的引用.第二个参数是我们自己定义的控制在.X文件中遇到的Frame和Mesh对象怎么分配的自定义对象. 在这里先忽略第五个参数.
Mesh.LoadHierarchyFromFile需要一个继承自抽象类AllocateHierachy的对象引用.这个类必须定义了于用在.X文件中找到每帧里调用的方法CreateFrame .和在.X文件中找到关于mesh住息时调用的CreateMeshContainer 这两个方法都返回抽象类. CreateFrame返回Frmae类的对象..CreateMeshContainer返回MeshContainer类的对象.所以我们必须定义继承自这些类的类。
The AllocateHierarchy Class
以下是CustomAllocateHierarchy类的定义:
public class CustomAllocateHierarchy : AllocateHierarchy
{
//保存我们迟些要用到的DirectX 文件的路径
private string xFilePath;
public CustomAllocateHierarchy(string xFilePath) : base()
{
this.xFilePath = xFilePath;
}
public override Frame CreateFrame(string name)
{
return new CustomFrame(name);
}
public override MeshContainer CreateMeshContainer(string name,
MeshData meshData,
ExtendedMaterial[] materials,
EffectInstance[] effectInstances,
GraphicsStream adjacency,
SkinInformation skinInfo)
{
CustomMeshContainer mc = new CustomMeshContainer(this.xFilePath, name, meshData, materials,
effectInstances, adjacency, skinInfo);
mc.Initialize();
return mc;
}
}
我们定义的CustomAllocateHierarchy 保存了我们将要加载的 DirectX文件的路径在xFilePath字段。迟些在我们建mesh对象时要用到它。 CreateFrame 方法创建一个CustomFrame 类的实例。CreateMeshContainer方法创建一个CustomMeshContainer类的实例。
The Frame Class
我们专用的 Frame 类就像这样:
public class CustomFrame : Frame
{
// 这个变换矩阵允许我们在帧层次里增加单独的变换。
protected Matrix customTransform = Matrix.Identity;
public CustomFrame() : base()
{
}
public CustomFrame(string name) : base()
{
this.Name = name;
}
public Matrix CustomTransform
{
get
{
return this.customTransform;
}
set
{
this.customTransform = value;
}
}
}
我们的 CustomFrame 类定义了一个当前帧的变换矩阵。这将会在对帧层次中单独的分枝使用自定义的变换时用到。考虑一下炮塔在坦克上的例子。存储在DirectX文件中炮塔帧的变换矩阵使它的的父帧(坦克)能正确地表现出炮塔。外加一个自定义的矩阵我们就可以旋转这个炮塔和它的枪了。customTransform matrix 要用一个矩阵来初始化,所以它没有默认值。
我们将会在rendering code里看到它是怎么用的。
The MeshContainer Class以下是MeshContainer 的定义:
public class CustomMeshContainer : MeshContainer
{
private string xFilePath;
private Texture[] textures;
public CustomMeshContainer(string xFilePath, string name,
MeshData meshData, ExtendedMaterial[] materials,
EffectInstance[] effectInstances, GraphicsStream adjacency,
SkinInformation skinInfo) : base()
{
this.Name = name;
this.MeshData = meshData;
this.SetMaterials(materials);
this.SetEffectInstances(effectInstances);
this.SetAdjacency(adjacency);
this.SkinInformation = skinInfo;
this.textures = null;
this.xFilePath = xFilePath;
}
public void Initialize()
{
// Load the textures for the mesh.
ExtendedMaterial[] m = this.GetMaterials();
this.textures = new Texture[m.Length];
for(int i=0; i<m.Length; i++)
{
if(m[i].TextureFilename != null)
{
string path =
Path.Combine(Path.GetDirectoryName(this.xFilePath),
m[i].TextureFilename);
this.textures[i] = TextureLoader.FromFile(
this.MeshData.Mesh.Device, path);
}
}
}
public Texture[] Textures
{
get
{
return this.textures;
}
}
}
构造函数获得了一些参数差设置了一些属性。我认为在一早设定这些属性比等到必须设定个别属性时再设置个别性性好一点。这个类也有一个用来从DirectX文件加载mesh纹理的initialization方法。Initialize 方法的代码假设了纹理文件与directX文件保存在相同的目录下。
这个类还有一个Textures属性,它在我们渲染(rendering)场景时要用到。
以上是我们加载一个frame hierarchy 所需的所有代码。下面我们再增加一些渲染它的代码。
Rendering the Scene我们从给这类增加一个公共的render方法开始。在我们渲染任何东西之前之前,我们要给我们的用户提供改变单独帧的变换矩阵和实现动话等功能的可能性。我选择使用事件来实现这个功能。当进行渲染对象时任何关于这个事件的事件处理程序都会实通知,这时就可以调节这个对象的自定义的变换矩阵。
// Event used to perform pre-render operations
public event EventHandler SetupCustomTransform;
// Render the frame hierarchy.
public bool Render()
{
// Allow any event listeners to adjust the custom transformation
// matrixes for the individual frames in the hierarchy.
if(this.SetupCustomTransform != null)
{
this.SetupCustomTransform(this, null);
}
// Begin recusively rendering the frames.
// Use the identity matrix as the custom transform for the entire
// frame hierarchy.
this.RenderFrame(this.rootFrame.FrameHierarchy,
Matrix.Identity);
return true;
}
然后 Render方法继续进行并调用受保护(protected)的建立每一帧的变换矩阵和宣染每一帧的递归方法RenderFrame, RenderFrame方法在参数里获取要渲染的帧和它的父帧的变换矩阵,如果是根帧(rootFrame)我们传达一个单位矩阵。
protected void RenderFrame(Frame frame, Matrix parentTransformationMatrix)
{
// First, render all sibling frames at this level passing the parent's
// aggregated transformation matrix.
if(frame.FrameSibling != null)
{
this.RenderFrame(frame.FrameSibling, parentTransformationMatrix);
}
// Aggregate the transformation matrix to be used for this frame.
// 1. Apply the frames transformation as specified in the x-file.
// 2. Apply the custom transformation for this individual frame.
// 3. Apply the parent's aggregated transformation matrix.
Matrix tm =
frame.TransformationMatrix *
((CustomFrame)frame).CustomTransform *
parentTransformationMatrix;
// Go on and render the children of this frame, passing the transformation
// we just aggregated.
if(frame.FrameFirstChild != null)
{
this.RenderFrame(frame.FrameFirstChild, tm);
}
// TODO: Adjust for the possibility of a mesh container hierarchy.
// Perform the actual rendering for this frame.
if(frame.MeshContainer != null)
this.DoRender(frame, tm);
}
对于层次中的每一帧,RenderFrame 方法先传递它的父帧的变换矩阵递归调用它的同层的其它帧。然后再计算它的父帖和自己的变换矩阵来递归调用它的子帧(children)。最后调用DoRender来实现渲染当前场景。
protected void DoRender(Frame frame, Matrix m)
{
this.device.Transform.World = m;
ExtendedMaterial[] em = frame.MeshContainer.GetMaterials();
Texture[] t = ((CustomMeshContainer)(frame.MeshContainer)).Textures;
for(int i=0; i < em.Length; i++)
{
this.device.Material = em[i].Material3D;
if(t[i] != null)
{
this.device.SetTexture(0, t[i]);
}
frame.MeshContainer.MeshData.Mesh.DrawSubset(i);
}
}
Retrieving Frames
如果我还要对帧层次中的个别帧进行转换。我们需要一上保存帧的方法。所以我们就增加了一个由帧的名字返回一个帧的函数GetFrame.
// Allow caller to get a hold of a named frame within the
// frame hierarchy.
public CustomFrame GetFrame(string name)
{
return (CustomFrame)Frame.Find(this.rootFrame.FrameHierarchy, name);
}
Using the Class
让我们看看我们刚才创建的类是怎么建立一个坦克模形专用的类来。我们定义了一个 Tank 类,还给它增加了一此关于它的位置,方向,斜率等属性。还有炮塔的角度。
我们重写了GraphicsObject类的Initialize方法。增加了一个事件处理程序给SetupCustomTransform 事件。这让我们在宣染对象前有设置自定义变换的机会。Tank_SetupCustomTransform 事件处理程序找到body帧并从它的bearing、pitch、roll、position属性计算出CustomTransform 。然后找到turret 帧计算出炮台的自义角度。
public class Tank : GraphicsObject
{
...
public override void Initialize()
{
base.Initialize();
this.SetupCustomTransform +=
new EventHandler(this.Tank_SetupCustomTransform);
}
public void Tank_SetupCustomTransform(object sender, EventArgs e)
{
// Apply the transformation on the whole tank.
this.GetFrame('Body').CustomTransform =
Matrix.RotationYawPitchRoll(this.bearing, this.pitch, this.roll)*
Matrix.Translation(this.position);
// Apply this transformation on the turret only.
this.GetFrame('Turret').CustomTransform =
Matrix.RotationY(this.turretAngle);
}
...
}
完整代码:
模型下载:http://www.jkarlsson.com/Articles/tank1.x
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace Joakim.DirectX
{
public class CustomFrame : Frame
{
// This transformation matrix allows us to add a transformation
// to an individual frame within the frame hierarchy.
protected Matrix customTransform = Matrix.Identity;
public CustomFrame() : base()
{
}
public CustomFrame(string name) : base()
{
this.Name = name;
}
public Matrix CustomTransform
{
get
{
return this.customTransform;
}
set
{
this.customTransform = value;
}
}
}
public class CustomMeshContainer : MeshContainer
{
private string xFilePath;
private Texture[] textures;
public CustomMeshContainer(string xFilePath, string name, MeshData meshData, ExtendedMaterial[] materials,
EffectInstance[] effectInstances, GraphicsStream adjacency, SkinInformation skinInfo) : base()
{
this.Name = name;
this.MeshData = meshData;
this.SetMaterials(materials);
this.SetEffectInstances(effectInstances);
this.SetAdjacency(adjacency);
this.SkinInformation = skinInfo;
this.textures = null;
this.xFilePath = xFilePath;
}
public void Initialize()
{
// Load the textures for the mesh.
ExtendedMaterial[] m = this.GetMaterials();
this.textures = new Texture[m.Length];
for(int i=0; i<m.Length; i++)
{
if(m[i].TextureFilename != null)
{
string path =
Path.Combine(Path.GetDirectoryName(this.xFilePath), m[i].TextureFilename);
this.textures[i] = TextureLoader.FromFile(this.MeshData.Mesh.Device, path);
}
}
}
public Texture[] Textures
{
get
{
return this.textures;
}
}
}
public class CustomAllocateHierarchy : AllocateHierarchy
{
private string xFilePath;
public CustomAllocateHierarchy(string xFilePath) : base()
{
this.xFilePath = xFilePath;
}
public override Frame CreateFrame(string name)
{
return new CustomFrame(name);
}
public override MeshContainer CreateMeshContainer(string name, MeshData meshData,
ExtendedMaterial[] materials, EffectInstance[] effectInstances, GraphicsStream adjacency,
SkinInformation skinInfo)
{
CustomMeshContainer mc = new CustomMeshContainer(this.xFilePath, name, meshData, materials,
effectInstances, adjacency, skinInfo);
mc.Initialize();
return mc;
}
}
public class GraphicsObject
{
// Path to the x-file containing the frame hierarchy for this GO.
protected string xFilePath;
// Container for the frames
protected AnimationRootFrame rootFrame;
// The device for the scene
protected Device device;
// Event used to perform pre-render operations
public event EventHandler SetupCustomTransform;
public GraphicsObject(string xFilePath, Device device)
{
this.xFilePath = xFilePath;
this.device = device;
}
// Allow caller to get a hold of a named frame within the
// frame hierarchy.
public CustomFrame GetFrame(string name)
{
return (CustomFrame)Frame.Find(this.rootFrame.FrameHierarchy, name);
}
// Load the frame hierarchy from file.
public virtual void Initialize()
{
this.rootFrame = Mesh.LoadHierarchyFromFile(this.xFilePath,
MeshFlags.Managed,
this.device,
new CustomAllocateHierarchy(this.xFilePath), // AllocateHierarchy
null); // LoadUserData
}
// Render the frame hierarchy.
public bool Render()
{
// Allow any event listeners to adjust the custom transformation
// matrixes for the individual frames in the hierarchy.
if(this.SetupCustomTransform != null)
{
this.SetupCustomTransform(this, null);
}
// Begin recusively rendering the frames.
// Use the identity matrix as the custom transform for the entire
// frame hierarchy.
this.RenderFrame(this.rootFrame.FrameHierarchy,
Matrix.Identity);
return true;
}
protected void RenderFrame(Frame frame, Matrix parentTransformationMatrix)
{
// First, render all sibling frames at this level passing the parent's
// aggregated transformation matrix.
if(frame.FrameSibling != null)
{
this.RenderFrame(frame.FrameSibling, parentTransformationMatrix);
}
// Aggregate the transformation matrix to be used for this frame.
// 1. Apply the frames transformation as specified in the x-file.
// 2. Apply the custom transformation for this individual frame.
// 3. Apply the parent's aggregated transformation matrix.
Matrix tm =
frame.TransformationMatrix *
((CustomFrame)frame).CustomTransform *
parentTransformationMatrix;
// Go on and render the children of this frame, passing the transformation
// we just aggregated.
if(frame.FrameFirstChild != null)
{
this.RenderFrame(frame.FrameFirstChild, tm);
}
// TODO: Adjust for the possibility of a mesh container hierarchy.
// Perform the actual rendering for this frame.
if(frame.MeshContainer != null)
this.DoRender(frame, tm);
}
protected void DoRender(Frame frame, Matrix m)
{
this.device.Transform.World = m;
ExtendedMaterial[] em = frame.MeshContainer.GetMaterials();
Texture[] t = ((CustomMeshContainer)(frame.MeshContainer)).Textures;
for(int i=0; i < em.Length; i++)
{
this.device.Material = em[i].Material3D;
if(t[i] != null)
{
this.device.SetTexture(0, t[i]);
}
frame.MeshContainer.MeshData.Mesh.DrawSubset(i);
}
}
}
public class Tank : GraphicsObject
{
protected float turretAngle = 0.0F;
protected float bearing = 0.0F;
protected Vector3 position = new Vector3(0,0,0);
protected float pitch = 0.0F;
protected float roll = 0.0F;
public Tank(string xFilePath, Device device) : base(xFilePath, device)
{
}
public override void Initialize()
{
base.Initialize();
this.SetupCustomTransform += new EventHandler(this.Tank_CalcCustomTransform);
}
public void Tank_CalcCustomTransform(object sender, EventArgs e)
{
this.GetFrame('Body').CustomTransform =
Matrix.RotationYawPitchRoll(this.bearing, this.pitch, this.roll)*
//Matrix.RotationY(this.bearing) *
Matrix.Translation(this.position);
this.GetFrame('Turret').CustomTransform = Matrix.RotationY(this.turretAngle);
}
public float TurretAngle
{
get
{
return this.turretAngle;
}
set
{
this.turretAngle = value;
}
}
public float Bearing
{
get
{
return this.bearing;
}
set
{
this.bearing = value;
}
}
public Vector3 Position
{
get
{
return this.position;
}
set
{
this.position = value;
}
}
public float Pitch
{
get
{
return this.pitch;
}
set
{
this.pitch = value;
}
}
public float Roll
{
get
{
return this.roll;
}
set
{
this.roll = value;
}
}
}
public class Form1 : Form
{
private Device device;
private Tank tank;
public Form1()
{
}
[STAThread]
static void Main()
{
Form1 app = new Form1();
app.Initialize();
app.Show();
while(app.Created)
{
app.UpdateGraphics();
app.Render();
Application.DoEvents();
}
}
private void Initialize()
{
this.SetupDevice();
this.tank = new Tank(@'.\tank1.x', this.device);
this.tank.Initialize();
this.tank.Position = new Vector3(0, -50, 250);
this.tank.Bearing = (float)(Math.PI/2.0);
}
private void SetupDevice()
{
PresentParameters presParams = new PresentParameters();
presParams.Windowed = true;
presParams.SwapEffect = SwapEffect.Discard;
presParams.AutoDepthStencilFormat = DepthFormat.D16;
presParams.EnableAutoDepthStencil = true ;
this.device = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing, presParams);
this.device.RenderState.CullMode = Cull.CounterClockwise;
this.device.RenderState.ZBufferEnable = true;
this.device.RenderState.Lighting = false;
this.device.Transform.View = Matrix.LookAtLH( new Vector3(0, 0.5F, -10),
new Vector3(0, 0.5F, 0), new Vector3(0, 1, 0));
this.device.Transform.Projection =
Matrix.PerspectiveFovLH(( float )Math.PI/4.0F, 1.0F, 1.0F, 10000.0F);
device.RenderState.Lighting = true ;
device.Lights[0].Diffuse = Color.White;
device.Lights[0].Specular = Color.White;
device.Lights[0].Type = LightType.Directional;
device.Lights[0].Direction = new Vector3(-1, -1, 3);
device.Lights[0].Enabled = true ;
device.RenderState.Ambient = System.Drawing.Color.White;
}
protected void UpdateGraphics()
{
float elapsed = Environment.TickCount;
this.tank.Bearing = elapsed/1000.0F;
Vector3 pos = new Vector3(0,0,1);
pos.TransformCoordinate(Matrix.RotationY(this.tank.Bearing));
this.tank.Position += pos;
this.tank.TurretAngle = elapsed/750.0F;
}
private void Render()
{
this.device.Clear(
ClearFlags.Target | ClearFlags.ZBuffer,
Color.Fuchsia, 1.0F, 0);
this.device.BeginScene();
this.tank.Render();
this.device.EndScene();
try
{
this.device.Present();
}
catch(DeviceLostException)
{
}
}
}
}