托管D3D:使用frame层次

 

[翻译]托管D3D:使用frame层次

这篇文章假设我们的场景是从.x文件中导入。SDK中提供能支持导入此文件格式的类库。

原文出处:http://www.jkarlsson.com/Articles/loadframes.asp 转载请注明出处,模型及完整源代码请到原文处下载

 

Directx(.x) 文件

 



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;, 
    [翻译]托管D3D:使用frame层次
  } 

  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;
      [翻译]托管D3D:使用frame层次
    } 

    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;,
        [翻译]托管D3D:使用frame层次
      } 
    } 
  } 

整个.x文件比这个要多的多,但是以上是和框架这个议题相关的内容。让我们来看看它们的玄机。

 

Frame Body {}

它描述名为body的frame。再看看另两个frame,Turret和Gun,Gun嵌套于Turret中,而Turret嵌套与Body中,就这样组成了层次关系。

 

FrameTransformMatrix {}

当前frame的FrameTransformMatrix{}声明了frame的网格及其子frame的网格将以怎样的规则排列、移动、旋转、这样的话frame将得以与其它几何体以正确的方式放置。考虑以Turret为例,它应该被建模为一个圆柱并放置在车身表面。但如果不为它提供变换的方式,它将可能掉落于车身中。有了变换矩阵,我们就有了足够的信息去正确地放置Turret。

 

Mesh {}

包括了组成frame的顶点的坐标。和变换矩阵一起使用,我们就有了足够的信息将不同的frame放在一起正确地渲染。

 

 

载入Frame层次

 

让我们开始设计一个能够将对frame层次操作封装起来的类。我们由载入.x文件和在内存里创造实现frame的对象开始。内部存储的场景将被以一个AnimationRootFrame对象保存。它将frame层次保存在FrameHierarchy属性中。它也可以对frame结构完成其它的操作,但这里我们暂时忽略掉它。

我们需要额外写一些代码去提取.x文件,创造frame层次并把它存储在AnimationRootFrame对象中。我们使用静态方法Mesh.LoadHierarchyFromFile。这个方法需要我们通过编写类来实现部分对建立frame层次具有实际意义的操作。我们需要从AllocateHierarchy,Frame,MeshContainer这些抽象类中继承。这篇文章中,我们将重写的类命名为CustomAllocateHierarchy,CustomFrame,和CustomMeshContainer。

代码的第一部分。

 GraphicsObject
{
  //.xfile的物理地址
  protected string xFilePath;

  
//frame的容器
  protected AnimationRootFrame rootFrame;

  
public GraphicsObject(string xFilePath, Device device)
  { 
    
this.xFilePath = xFilePath;
    
this.device = device;
  }

  
// 从文件中导入frame层次
  public virtual void Initialize()
  {
    
this.rootFrame = Mesh.LoadHierarchyFromFile(this.xFilePath, 
      MeshFlags.Managed,
      
this.device, 
      
new CustomAllocateHierarchy(this.xFilePath), 
      
null);
  }

 

静态方法Mesh.LoadHierarchyFromFile 接受一个.x文件 并且返回一个AnimationRootFrame对象,文件中包含了一个frame层次。第一个参数是文件的物理地址。第二个参数决定了网格将以怎样的方式被创建。第三个参数是对渲染设备的引用。第四个参数是对派生类的引用。忽略第五个参数。
Mesh.LoadHierarchyFromFile 期望引用一个从抽象方法AllocateHierarchy派生的对象。类中必须定义CreateFrame方法,将被每一个从.x文件中被检测到的frame调用。这些方法返回的都是抽象类,frame对应CreateFrame方法,MeshContainer对应CreateMeshContainer对象,所以我们将派生这些类。


The AllocateHierarchy Class

CustomAllocateHierarchy类的声明如下。

 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;
  } 
}

我们的CustomAllocateHierarchy保存了将会载入的.x文件的路径。在待会创建网格对象时将会使用它。马上我们将看到,使用CreateFrame方法创造的CustomFrame类的实例,使用CreateMeshContainer方法创造了CustomMeshContainer类的实例。

 

Frame类

 CustomFrame : Frame
{
  // 变换矩阵让我们可以对frame层次中单个的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类为当前Frame定义了变换矩阵。它将用于定制frame层次中单独分支的变化。举个例子,对于坦克模型中的Turret。有了定义在.x文件中的Turretframe和其父frame的变换矩阵,我们就可以正确地渲染Turret。有了添加的定制的矩阵,我们还可以旋转Turrt和Gun。CustomTransform矩阵被初始化为标准矩阵。所以没有定义任何变换。我们将在以后的代码中看到它怎样被用在渲染中。

 

The MeshContainer Class
MeshContainer类的清单

 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;
    }
  }
}

 

构造函数接受了相应的参数并将相应的属性赋值。我人为这比单独去设置每一个属性要简单。类公开了一个初始化方法,使用.x文件的信息载入网格的贴图。Initialize方法中的代码假设贴图存储在与.x文件相同的路径之中。这个类同时将textures公开为一个属性。这对我们接下来渲染场景是必要的。

以上是载入frame层次所需的全部代码,下面添加渲染代码。

 

渲染一个场景

 

我们由添加公共的渲染方法开始。在开始渲染之前,我们要给予所有对象的用户这样的可能性,是否单个frame的定制转换矩阵需要被调整以表现出动画效果。我选择用事件机制来实现。
这个事件的所有句柄将被在渲染对象开始之前通知。并且可能会调整对象相应的定制变换矩阵。

 Render()
{
  // 允许事件的任何订阅者调整定制变换矩阵
  if(this.SetupCustomTransform != null)
  {
    
this.SetupCustomTransform(thisnull);
  }

  
// 开始渲染
  
// 使用初始矩阵作为所有定制变换的矩阵
  this.RenderFrame(this.rootFrame.FrameHierarchy, 
    Matrix.Identity);

  
return true;
}

Render方法将会调用受保护的RenderFrame方法,这个方法将递归地调用并设置变换矩阵和渲染它们,这个方法接受将被渲染的frame和他们的父frame作为参数。为root frame传入标准矩阵。

 RenderFrame(Frame frame, Matrix parentTransformationMatrix)
{
  // 首先渲染所有的兄弟frame
  if(frame.FrameSibling != null)
  {
    
this.RenderFrame(frame.FrameSibling, parentTransformationMatrix);
  }

  
// 合并所有相关的矩阵
  
// 1. 使用x-file中的指定矩阵.
  
// 2. 使用单个frame的矩阵.
  
// 3. 使用相关联的父frame的矩阵.
  Matrix tm = 
    frame.TransformationMatrix 
* 
    ((CustomFrame)frame).CustomTransform 
*
    parentTransformationMatrix;

  
// 使用我们刚刚得到的矩阵继续渲染子fram
  if(frame.FrameFirstChild != null)
  {
    
this.RenderFrame(frame.FrameFirstChild, tm);
  }

  
// TODO: 看看网格层次是否会调整

  
// 开始正式渲染.
  if(frame.MeshContainer != null)
    
this.DoRender(frame, tm);
}

 

 

对层次中的每个frame,RenderFrame方法首先对于所有兄弟frame,使用父frame的变换矩阵,递归地调用它自己。然后使之和父frame,当前frame,当前的frame定制的变换矩阵相关联。然后递归地处理子frame,使用当前的矩阵做参数。最后调用DoRender完成真正的渲染。

 

 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


假如我们有可能使用到层次中单个frame的变换,我们需要一个方式去从层次中找到它。我们添加GetFrame方法。

 name)
{
  return (CustomFrame)Frame.Find(this.rootFrame.FrameHierarchy, name);
}


 

使用这个类

 

让我们看看我们刚刚创造的类将怎样为我们专门为tank模型所创造的类所使用。我们声明tank的position,bearing,roll和pitch的属性,也包括Turret的角度。

我们重载GraphicsObject类的Initialize方法并为SetupCustomTransform事件注册句柄。Tank_SetupCustomTransform事件句柄寻找body frame并从bearing,pitch,roll,和position属性中计算它的CustomTransform。然后找到Turret frame并计算指定角度的CustomTransform。

 

 Tank : GraphicsObject
{

    [翻译]托管D3D:使用frame层次

  public override void Initialize()
  {
    
base.Initialize();
    
this.SetupCustomTransform += 
      
new EventHandler(this.Tank_SetupCustomTransform);
  }

  
public void Tank_SetupCustomTransform(object sender, EventArgs e)
  {
    
// 提供整个坦克的变换
    this.GetFrame("Body").CustomTransform = 
      Matrix.RotationYawPitchRoll(
this.bearing, this.pitch, this.roll)*
      Matrix.Translation(
this.position);

    
// 仅提供Turret的变换
    this.GetFrame("Turret").CustomTransform = 
      Matrix.RotationY(
this.turretAngle);
  }

    [翻译]托管D3D:使用frame层次

}

 

相关文章:

  • 2021-07-29
  • 2021-12-29
  • 2022-02-08
  • 2022-01-22
  • 2021-10-28
  • 2021-08-01
  • 2021-06-22
猜你喜欢
  • 2021-04-05
  • 2021-07-28
  • 2021-12-26
  • 2021-11-23
  • 2022-03-03
  • 2021-05-04
相关资源
相似解决方案