Managed DirectX(第十三章)

[原创]Managed DirectX 基础使用教程(零) hangtao_me@yahoo.com.cn        由于下学期的毕业设计,最近开始研究起了.NET平台下的DirectX.本来是不打算用DirectX的,因为我不会DirectX - -||。但是DEMO出来后,发现用C#的GDI+绘图的效率是如此低下。最后不得不得硬着头皮上DirectX。而且是资料相对较少的Managed DirectX(托管版DX)。很多都是直接 阅读详情

第十三章 渲染骨骼动画(Rendering Skeletal Animation)

    

 

本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
翻译:clayman
Blog:http://blog.youkuaiyun.com/soilwork

   所有的游戏都不可能只有静态物体,而是充满了动态元素,比如带动画的人物。通常这些动画都是通过动作捕捉技术记录下来,然后通过3D模型里的骨骼来实现的。当然,除了这些复杂的动画以外,还包括缩放,旋转,位移等简单操作。这一章,我们将讨论以下内容:

l          加载帧层次(loading frame hierarchy)

l          创建骨骼信息(generation any skeletal information)

l          渲染每一帧(rendering each frame)

l          控制动画时间(advancing the animation time)

l          使用索引动画提高性能(using indexed animation for better performance)

(注:最好先对骨骼动画和计算机动画有所了解,不然这章看起来会比较难懂)

 

创建帧层次

     大部分mesh(包括不含动画信息的mesh)都包含某些层次信息;比如手臂连接到身体上,而手指又连接到手臂上。通过Direct3D扩展库,我们可加载并控制这些层次。

     为了保存这些信息,需要先创建两个抽象类来控制这些层次:Frame类和MeshContainer类。每一个Frame可以保存0到多个兄弟(sibling)帧,当然,同时也包括可能存在的子帧(child frame)。每个Frame同时也能包含0到多个meshContainer。(注:帧层次是一种树状的数据结构)

     当然在开始之前,创建一个新程序,并且为DirectX程序做好准备。之后,添加如下变量:

private AnimationRootFrame rootFrame;

private Vector3 objectCenter;

private float objectRedius;

private float elapsedTime;

     AnimationRootFrame结构将保存层次树的根节点帧,同时还包括AnimationController对象。接下来的两个变量保存了所加载模型的中心点,以及围住模型的边界球体半径。设置摄像机位置时需要用到这几个参数,保证整个模型都是可见的。最后一个变量保存了程序流逝的时间,我们将依照这个时间来更新动画。

     由于各动画mesh都是不同的,Direct3D没有强制使用一种特定方法来加载层次结构。所以,需要继承AllocateHierarchy来创建一个新类,在程序中添加代码:

public class AllocateHierarchyDerived : AllocateHierarchy

{

     Form1 app = null;

     public AllocateHierarchyDerived(Form1 parent)

     {

          app = parent;

     }

}

     现在看来,这段代码什么也没干。构造函数保存了一个主窗口的实例而已,这样稍后才能使用它来调用其它方法。但是现在,程序甚至不能通过编译。我们还必须实现两个抽象方法。这两个方法用来创建保存层次信息的frame和mesh container对象。由于这两个类也是抽象的,因此同样需要先创建派生类。我们还需要在派生类中添加一些额外信息来帮助实现渲染。

public class FrameDrived  : Frame

{

     private Matrix combined = Matrix.Identity;

     public Matrix CominedTramsformationMatrix

     {

         get

         {

              return conbined;

         }

         set

         {

              combined = value;

         }

     }

}

除了保存普通的帧信息以外,每一帧还必须保存它自己以及所有父节点帧的混合变换矩阵。这有利于我们设置世界矩阵。接下来,创建MeshContainer的派生类。

public class MeshContainerDerived : MeshContainer

{

     private Texture[] meshTexture = null;

     private int numAttr = 0;

     private int numInfl = 0;

     private BoneCombination[] bones;

     private FrameDerived[] frameMarices;

     private Matirx[] offsetMatrices;

     //public prperties

     public Texture[] GetTexture() { reture meshTexture;}

     public void SetTextures(Texture[] textures) { meshTextures = textures; }

     public BoneCombination[] GetBones() { return bones; }

     public void SetBones(BoneCombination[] b) { bones = b; }      

     public FrameDerived[] GetFrames() { return frameMatrices; }

     public void SetFrames(FrameDerived[] frames) { frameMatrices = frames; } 

     public Matrix[] GetOffsetMatrices() { return offsetMatrices; }

     public void SetOffsetMatrices(Matrix[] matrices) { offsetMatrices = matrices; }      

     public int NumberAttributes { get { return numAttr; } set { numAttr = value; } }

     public int NumberInfluences { get { return numInfl; } set { numInfl = value; } }

}

     每个mesh容器都包含了一些额外信息,包括骨骼连接表,属性的数量,influence的数量,还有编译矩阵。在渲染和动画mesh时,每一个变量都会用到。

     实现了连个派生类,可以来添加刚才没有实现的两个抽象方法了,在AllocateHierarchyDerived中添加如下代码:

public override Frame CreateFrame(string name)

{

     FrameDerived frame = new FrameDerived();

     frame.Name = name;

     frame.TransformationMatrix = Matrix.Identity;

     frame.CominedTramsformationMatrix = Matrix.Identity;            

     return frame;

}

     对要创建的每帧来说,这段代码会保存这一帧的名字,同时把变换和混合矩阵设置为单位矩阵。在后台,所有同层帧,子帧,以及mesh容器都在运行时填充,所以不必为他担心。对于创建mesh容器来说,还有很多需要完成的工作,添加如下代码:

public override MeshContainer CreateMeshContainer(string name, MeshData meshData, ExtendedMaterial[] materials,

              EffectInstance[] effectInstances, GraphicsStream adjacency, SkinInformation skinInfo)

{

     if(meshData.Mesh == null)

         throw new ArgumentException();

     if(meshData.Mesh.VertexFormat == VertexFormats.None)

              throw new ArgumentException();

     MeshContainerDerived mesh = new MeshContainerDerived();

     mesh.Name = name;

     int numFaces = meshData.Mesh.NumberFaces;

     Device dev = meshData.Mesh.Device;

     if((meshData.Mesh.VertexFormat & VertexFormats.Normal) == 0)

     {

         Mesh tempMesh = meshData.Mesh.Clone(meshData.Mesh.Options.Value,meshData.Mesh.VertexFormat | VertexFormats.Normal,dev);

         meshData.Mesh = tempMesh;

         meshData.Mesh.ComputeNormals();

     }

     mesh.SetMaterials(materials);

     mesh.SetAdjacency(adjacency);

     Texture[] meshTextures = new Texture[materials.Length];

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

     {

         if(materials[i].TextureFilename != null)

         {

              meshTextures[i] = TextureLoader.FromFile(dev,@"../../" + materials[i].TextureFilename);

         }

     }

     mesh.SetTextures(meshTextures);

     mesh.MeshData = meshData;

     if(skinInfo != null)

     {

         mesh.SkinInformation = skinInfo;

         int numBones = skinInfo.NumberBones;

         Matrix[] offsetMatrices = new Matrix[numBones];

         for(int i = 0;i<numBones;i++)

              offsetMatrices[i] = skinInfo.GetBoneOffsetMatrix(i);

         mesh.SetOffsetMatrices(offsetMatrices);

         app.GenerateSkinnedMesh(mesh);

     }

     return mesh;

}

     代码看起来有一点点多。首先要做的就是验证mesh是否为程序所支持的类型。显然,如果mesh数据结构中没有包含mesh,那么会出现一些问题,所以抛出一个异常。另外,如果mesh没有包含正确的顶点格式,则抛出另外一个异常。

     假设mesh通过验证,创建了新的mesh container。就设置mesh contain的名字、所包含的面数以及使用的device。这里虽然app成员包含device对象,但由于它是私有的,所以不能直接使用。

     下面几行代码应该很熟悉了,处理关于法线的信息。

     接下来,保存材质和邻接信息,之后会用到他们。因为材质可能包含了纹理,所以还需要创建一个纹理数组。之后,把正确的纹理放到纹理数组的成员中。

     最后,检查mesh container中是否包含任何骨骼信息。如果有,就保存骨骼,同时创建偏移矩阵数组(每条骨骼对应一个矩阵),同样,把相应的偏移矩阵保存到整个数组中。最后调用现在还没有编写的GenerateSkinnedMesh方法,然后返回这个mesh container。

     来看看GenerateSkinnedMesh是怎样的吧:

public void GenerateSkinnedMesh(MeshContainerDerived mesh)

{

     if(mesh.SkinInformation == null)

         throw new ArgumentException();

     int numInfl = 0;

     BoneCombination[] bones;

     MeshData m = mesh.MeshData;

     m.Mesh = mesh.SkinInformation.ConvertToBlendedMesh(m.Mesh,MeshFlags.Managed | MeshFlags.OptimizeVertexCache,

                   mesh.GetAdjacencyStream(),out numInfl,out bones);

mesh.NumberInfluences = numInfl;

     mesh.SetBones(bones);

     mesh.NumberAttributes = bones.Length;

     mesh.MeshData = m;

}

     如果没有骨骼信息就绝对不需要调用这个方法,所以万一那么做了,我们就抛出一个异常。把mesh数据保存到一个临时变量中,之后,使用ConvertToBlendedMesh方法创建一个包含了每个顶点混合权重以及骨骼连接表(bone combination table)的新mesh。ConvertToBlendedMesh是最根本也是最重要的实现mesh动画的方法。最后,保存mesh中influence的数量,骨骼连接表,以及属性的数量。

 

加载动画Mesh

     我们已经建立了基本的mesh层次,现在要做的就是编写代码来使用他们。在这之前,还需要对显卡作一些检查:

     这些代码大家应该很熟悉。需要注意,对这个例子来说,至少需要4个顶点混合矩阵(vertex blend Matrices)才能完全实现硬件渲染,所以我们在这里做了检查。创建了设备之后,就可以加载动画了。这里使用了一个还没有编写的方法,稍后我们就来实现它,不过在这之前,先来看看所订阅的OnDeviceReset方法:

private void OnDeviceReset(object sender, EventArgs e)

{

     Device dev = (Device)sender;

     Vector3 vEye = new Vector3(0,0, -1.8f *objectRadius);

     Vector3 vUp = new Vector3(0,1,0);

     dev.Transform.View = Matrix.LookAtLH(vEye,objectCenter,vUp);

     float aspectRatio = (float)dev.PresentationParameters.BackBufferWidth / (float)dev.PresentationParameters.BackBufferHeight;

     dev.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4,aspectRatio,objectRadius/ 64.0f ,objectRadius * 200.0f );

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

     dev.Lights[0].Direction = new Vector3( 0.0f , 0.0f , 1.0f );

     dev.Lights[0].Diffuse = Color.White;

     dev.Lights[0].Update();

     dev.Lights[0].Enabled = true;

}

     首先,设置了观察矩阵,把摄像机移动到足够远的地方,保证能看到整个模型,同时,我们的观察点指向模型的中心。同样,投影矩阵也创建了一个足够远的后裁剪平面,保证可以看到整个模型。另外,我们还添加了一个方向光来照亮模型。

     接下来就是创建动画的代码:

private void CreateAnimation(string file ,PresentParameters presentParams)

{

     AllocateHierarchyDerived alloc = new AllocateHierarchyDerived(this);

     rootFrame = Mesh.LoadHierarchyFromFile(file,MeshFlags.Managed,device,alloc,null);

     objectRadius = Frame.CalculateBoundingSphere(rootFrame.FrameHierarchy,out objectCenter);

     SepupBoneMatrices((FrameDerived)rootFrame.FrameHierarchy);

     DXUtil.Timer(DirectXTimer.Start);

}

     这里的代码很简单,基本上就是调用其它方法而已。

首先,实例化AllocateHierarchyDerived对象。接下来,使用这个对象以及其它几个参数调用LoadHierarchyFromFile方法。注意,当调用这个方法时,根据mesh中帧和mesh container的数量,将会多次调用重载的CreateFrame和CreateMeshContainer方法。之后,它将返回AnimationRootFrame对象作为帧层次树的根节点,当然,还包括animation controller,用来控制mesh的动画。

之后,使用CalculateBoundingSphere根据根节点帧计算整个帧的边界球体,同时,返回这个球体的半径和mesh的中心。

最后,在完成动画前,设置骨骼矩阵。这个方法将遍历(walk)整个帧层次树,同时也是其它操作的基础。具体代码如下:

private void SetupBoneMatrices(FrameDerived frame)

{

     if(frame.MeshContainer != null)

         SetupBoneMatrices((MeshContainerDerived)frame.MeshContainer);

     if(frame.FrameSibling != null)

         SetupBoneMatrices((FrameDerived)frame.FrameSibling);

     if(frame.FrameFirstChild != null)

         SetupBoneMatrices((FrameDerived)frame.FrameFirstChild);

}

private void SetupBoneMatrices(MeshContainerDerived mesh)

{

     if(mesh.SkinInformation != null)

     {

         int numBones = mesh.SkinInformation.NumberBones;

         FrameDerived[] frameMatrices = new FrameDerived[numBones];

         for(int i=0;i<numBones;i++)

         {

              FrameDerived frame = (FrameDerived)Frame.Find(rootFrame.FrameHierarchy,mesh.SkinInformation.GetBoneName(i));

              if(frame == null)

                   throw new ArgumentException();

              frameMatrices[i] = frame;

         }

         mesh.SetFrames(frameMatrices);

     }

}

你看,遍历整个帧层次并不是很复杂。如果有兄弟帧或者子帧,重复调用当前的方法就可以了。我们只需要为第一个子帧调用方法就可以了,因为第一个子帧会把它之后的子帧作为兄弟帧来处理(the first child will have each subsequent child listed as its sibling)。这里,根据骨骼的名字来保存每帧。

实际上接受FrameDerived参数的重载并不是很有趣,它只是遍历整个层次树,并使用MeshContainerDerived作为参数,再调用下一个重载而已。这里,创建了一个FrameDerived数组,它的每个成员对应一个骨骼。每帧都通过遍历骨骼列表来寻找名字相符的骨骼。找到之后,就保存它。

接下来更新main()方法:

static void Main ()

{

     using (Form1 frm = new Form1())

     {

         frm.Show();

         if (!frm.InitializeGraphics())

         {

              MessageBox.Show("Your card can not perform skeletal animation on " +"this file in hardware. This application will run in " +

                            "ref mode instead.");

}

         Application.Run(frm);

}

 

渲染Mesh动画

     现在所要做的就是渲染动画角色了。渲染代码实际上是很简单的:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)

{

     ProcessNextFrame();

     device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f , 0);

     device.BeginScene();

     DrawFrame((FrameDerived)rootFrame.FrameHierarchy);

     device.EndScene();

     device.Present();

     this.Invalidate();

}

     处理下一帧,clear设备,绘制根帧。看起来很简单,但实际处理了很多步骤。来看看如何处理下一帧:

private void ProcessNextFrame()

{

     elapsedTime = DXUtil.Timer(DirectXTimer.GetElapsedTime);

     Matrix worldMatrix = Matrix.Translation(objectCenter);

     device.Transform.World = worldMatrix;

     if(rootFrame.AnimationController != null)

         rootFrame.AnimationController.AdvanceTimed(elapsedTime,null);

     UpdateFrameMatrices((FrameDerived)rootFrame.FrameHierarchy,worldMatrix);

}

     首先,保存逝去时间,接下来,创建根帧的世界矩阵。变换物体的位置,更新设备。假设这个mesh包含动画,使用保存的时间来改变(advance)动画时间。最后,更新混合的变换矩阵:

private void UpdateFrameMatrices(FrameDerived frame,Matrix parentMatrix)

{

     frame.CominedTramsformationMatrix = frame.TransformationMatrix * parentMatrix;

     if(frame.FrameSibling != null)

         UpdateFrameMatrices((FrameDerived)frame.FrameSibling,parentMatrix);

     if(frame.FrameFirstChild != null)

          UpdateFrameMatrices((FrameDerived)frame.FrameFirstChild,frame.CominedTramsformationMatrix);

}

     这里,当前帧的混合变换矩阵通过帧的变换矩阵与他的父变换矩阵相乘来计算。所有兄弟帧都使用当前帧的父矩阵,而子帧则使用当前帧的混合变换矩阵作为他自己的变换矩阵。这里实际上形成了一个矩阵链,子帧需要把他的变换矩阵和所有父节点帧的矩阵相乘。

     处理完下一帧之后,就可以对它进行渲染了:

private void DrawFrame(FrameDerived frame)

{

     MeshContainerDerived mesh = (MeshContainerDerived)frame.MeshContainer;

     while(mesh != null)

     {

         DrawMeshContainer(mesh,frame);

         mesh = (MeshContainerDerived)mesh.NextContainer;

     }

     if(frame.FrameSibling != null)

         DrawFrame((FramejDerived)frame.FrameSibling);

     if(frame.FrameFirstChild != null)

         DrawFrame((FrameDerived)frame.FrameFirstChild);

}

     这里同样遍历了整个树,同时,尝试着绘制frame对象所引用的所有mesh container。这个方法将完成相当多的工作。以下是DrawMeshContain方法:

private void DrawMeshContainer(MeshContainerDerived mesh, FrameDerived frame)

{

     if(mesh.SkinInformation != null)

     {

         int attribIdPrev = -1;

         for(int iattrib = 0; iattrib<mesh.NumberAttributes;iattrib++)

         {

              int numBlend = 0;

              BoneCombination[] bones = mesh.GetBones();

              for(int i=0; i<mesh.NumberInfluences; i++)

              {

                   if(bones[iattrib].BoneId[i] != -1)

                       numBlend = i;

              }

              if(device.DeviceCaps.MaxVertexBlendMatrices >= numBlend +1)

              {

                   Matrix[] offsetMatrices = mesh.GetOffsetMatrices();

                   FrameDerived[] frameMatrices = mesh.GetFrames();

                   for(int i=0;i<mesh.NumberInfluences;i++)

                   {

                       int matrixIndex = bones[iattrib].BoneId[i];

                       if(matrixIndex != -1)

                       {

                            Matrix tempMatrix = offsetMatrices[matrixIndex]*frameMatrices[matrixIndex].CombinedTransformationMatrix;

                            device.Transform.SetWorldMatrixByIndex(i,tempMatrix);

                       }

                   }

                   device.RenderState.VertexBlend = (VertexBlend)numBlend;

                   if((attribIdPrev != bones[iattrib].AttributeId) || (attribIdPrev == -1))

                   {

                       device.Material = mesh.GetMaterials()[bones[iattrib].AttributeId].Material3D;

                       device.SetTexture(0,mesh.GetTextures()[bones[iattrib].AttributeId]);

                       attribIdPrev = bones[iattrib].AttributeId;

                   }

                   mesh.MeshData.Mesh.DrawSubset(iattrib);

              }

         }

     }

     else

     {

         ExtendedMaterial[] mtrl = mesh.GetMaterials();

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

         {

              device.Material = mtrl[iMaterial].Material3D;

              device.SetTexture(0,mesh.GetTextures()[iMaterial]);

              mesh.MeshData.Mesh.DrawSubset(iMaterial);

          }

     }

}

     这个方法看起来似乎有些可怕。但如果分开来看,其实也不是很复杂。首先检查skin information成员。如果mesh container没有骨骼信息,那么就使用以前的方法渲染它。如果有,渲染方法自然就不一样了。对mesh中的每种属性来说(比如材质、纹理等),需要进行一系列操作。首先,需要扫描骨骼连接表,同时选择mesh将使用的混合权重(blend weight)数量。示例中的文件最多使用了4个混合权重,因此,我们在创建设备时就对设备进行了检测。但是,以防更换了mesh,这里的代码还是检测了设备能力,以保证能混合包含更多矩阵的mesh。之后的代码设置了世界变换。对骨骼连接表中的每个骨骼id来说,把当前的索引世界变换矩阵(currently indexed world matrix tansform)设置为偏移矩阵和帧的混合变换矩阵的乘积。这样,Direct3D才能使用适当的世界矩阵来混合顶点。接下来,把顶点混合渲染状态设置为所希望的混合值。最后,设置纹理和材质,并且绘制图形。

使用不含骨骼的mesh动画:

     不能说一个mesh没有骨骼信息就不包含动画。如果mesh的动画只包含一系列标准的矩阵操作(比如缩放,位移,或者旋转),那么就不需要骨骼。但是,东环系统仍然会为mesh更新矩阵,所以使用普通的渲染方法就能得到所需效果。

 

使用带索引的mesh动画骨骼(Using an Indexed Mesh To Animate the Bones)

     很早以前我们就讨论过,使用索引缓冲来渲染顶点数据,可以减少所要绘制的三角形数量以及占用的内存,从而提高性能。对于复杂模型来说,就像我们使用的这个,使用带索引的mesh性能提升是很大的。另外,代码也要简单的多。

     在使用带索引的mesh之前,先做一点小小的改动,为MeshContainerDerived类加一个新成员:

     这个程序将保存骨骼矩阵的数量,当转换mesh时,我们将把他作为矩阵调色贴皮(matrix palette skinning)。因为我们不再使用标准的顶点混合来实现动画,所以更新初始化的方法,对设备能力进行一些额外检查。

if(hardware.MaxVertexBlendMatrixIndex >= 12)

     下面来看看如何创建indexed mesh吧:

public void GenerateSkinnedMesh(MeshContainerDerived mesh)

{

     if (mesh.SkinInformation == null)

         throw new ArgumentException();

     int numMaxFaceInfl;

     MeshFlags flags = MeshFlags.OptimizeVertexCache;

     MeshData m = mesh.MeshData;

     using(IndexBuffer ib = m.Mesh.IndexBuffer)

     {

         numMaxFaceInfl = mesh.SkinInformation.GetMaxFaceInfluences(ib,m.Mesh.NumberFaces);

     }

     numMaxFaceInfl = (int)Math.Min(numMaxFaceInfl,12);

     if(device.DeviceCaps.MaxVertexBlendMatrixIndex + 1 >= numMaxFaceInfl)

     {

         mesh.NumberPaletteEntries = (int)Math.Min((device.DeviceCaps.MaxVertexBlendMatrixIndex+1)/2,mesh.SkinInformation.NumberBones);

         flags |= MeshFlags.Managed;

     }

BoneCombination[] bones;

     Int numInfl;

     m.Mesh = mesh.SkinInformation.ConvertToIndexedBlendedMesh(m.Mesh,flags,mesh.GetAdjacency(),mesh.NumberPaletteEntries,out numInfl,out bones);

     mesh.SetBones(bones);

     mesh.NumberInfluences = numInfl;

     mesh.NumberAttributes = bones.Length;

     mesh.MeshData = m;

}

     首先,获取面的influence最大值。接下来我们保证这个值至少为12(因为对每个三角形中的每个顶点来说,12是4顶点混合的幻数(12 would be the magic number number of 4 vertex blends for each vertex in a trangle))。假设设备支持这种操作(在创建设备时检测了这种能力),就计算将要用到的调色板数量(它是骨骼数或所支持的最大面influence值的一半)。现在,把mesh转换为indexed blended mesh,同时把数据保存到之前不含索引的meshData中。在来看看绘制mesh的方法,为了简短一点,这里只包含了当skin information不为null时的情况:

if(mesh.SkinInformation != null)

{

     if(mesh.NumberInfluences == 1)

          device.RenderState.VertexBlend = VertexBlend.ZeroWeights;

     else

         device.RenderState.VertexBlend = (VertexBlend)(mesh.NumberInfluences - 1);

     if(mesh.NumberInfluences > 0)

         device.RenderState.IndexedVertexBlendEnable = true;

     BoneCombination[] bones = mesh.GetBones();

     for(int iAttrib=0;iAttrib< mesh.NumberAttributes;iAttrib++)

     {

         for(int iPaletteEntry = 0; iPaletteEntry<mesh.NumberPaletteEntries;++iPaletteEntry)

         {

              int iMatrixIndex = bones[iAttrib].BoneId[iPaletteEntry];

              if(iMatrixIndex != -1)

              {

                   device.Transform.SetWorldMatrixByIndex(iPaletteEntry,mesh.GetOffsetMatrices()[iMatrixIndex]*

                   mesh.GetFrames()[iMatrixIndex].CombinedTransformationMatrix);

              }

         }

     device.Material = mesh.GetMaterials()[bones[iAttrib].AttributeId].Material3D;

     device.SetTexture(0,mesh.GetTextures()[bones[iAttrib].AttributeId]);

     mesh.MeshData.Mesh.DrawSubset(iAttrib);

     }   

这个方法比以前的简单多了。首先把VertexBlend的值设置为influence-1。下一步,如果有influence(对于骨骼动画来说,这是必须的),就打开IndexedVertexBlendEnable选项。接下来的代码就和之前的差不多了。对每个调色板项,为每个索引的设置相应的世界矩阵。最后,设置材质,纹理,绘制图形。

使用indexed blended mesh,根据数据的不同,性能提升可以达到30%甚至更多。

[原创]Managed DirectX 基础使用教程(一) hangtao_me@yahoo.com.cn 本次内容:如何用Managed  DirectX 显示图片(2D),以及一些变形.上回我们已经学习了MDX的一些基本知识,现在我们就要来使用它了.准备:创建一个Windows 窗体应用程序然后在引用中右键添加引用然后选择需要的.NET项目然后在代码中写上相应的using .......现在准备工作已经做好了,那么在 阅读详情

相关推荐

使用Managed DirectX编写游戏(四) 使用Managed DirectX创建三维地形

作者:clayman使用Height Map作为输入  首先,什么是高度图(Height Map)呢?所谓高度图实际上就是一个2维数组。创建地形为什么需要高度图呢?我们这样考虑,地形实际上就是一系列高度不同的网格而已,这样数组中每个元素的索引值刚好可以用来定位不用的网格(x,y),而所储存的值就是网格的高度(z)。正是由于这个简单的映射关系,最常见的地形生成方法都使用高度图作为输入数据。同时,为了

csdn_gamedev的专栏 8824

使用C# + Managed DIrectX 制作 2D 游戏使用C# + Managed DIrectX 制作 2D 游戏

主旨:虽然目前Microsoft已经发布了XNA Game Studio,让游戏编程员能够更轻松的在Windows(XB360不在本文考虑范围内)制作游戏,不过这个XNAGS有个缺点,它要求系统必须有至少能够支援Shader Model 1.1的显示卡。这表示TNT,GeForce 1, 2, 和 GeForce 3 & 4 MX 系列的显示卡,以及较早期的 integrated display

shaily读书学习 6785

Managed DirectX Shader技术实战指南

本文还有配套的精品资源,点击获取 简介:Managed DirectX是微软开发的一套API,允许开发者利用.NET环境控制硬件图形处理。本主题探讨了在.NET中使用Managed DirectX实现复杂图形渲染效果的Shader技术,特别是顶点Shader和像素Shader的应用。Managed DirectX支持高级图形效果的Shader Model 2.0及以上版本。...

weixin_42284380的博客 1220

【翻译】managed DirectX (第三章)

使用简单的渲染技术翻译:clayman    至今为止,我们的渲染工作效率都很低。每次渲染场景时,都要分配新的顶点列表,并且所有东西存储在系统内存里。现代显卡集成了足够显存,把顶点数据存放在显存可以获得大幅的新能提升:存放在系统内存里的数据,渲染每一帧时都要拷贝到显卡,这会带来极大的损失。只有移除每帧时的这种分配才能帮助我们提高性能。  使用顶点缓冲(Using Vertex Buffers)Di

Clayman的专栏 5027

Managed DirectX9.0中文教程

Managed DirectX9.0中文教程 有需要的就下吧

Managed_DirectX9入门教程

关于managed_directx9与c#混合入编程,初学者的必读教材。

Managed DirectX +C# 开发(入门篇)(一)

  前      言以前是用VC 作计算机图形学的,大约在一年前,公司接到一个用C#+DirecX的项目,从那时起便开始学习Mangaged DirectX ,现将在开发过程中的一点总结发表出来,和同行们共同参考,共同学习,共同进步,前面是一些基础内容,算是入门篇吧,现在正将开发过程中所用到的一些算法整理中,算是中级篇吧。1:在整理过程中,有些内容为从网上找的资料,部分为翻译国外文章,正所

dandanCool的专栏 8816

Managed DirectX 9(中文版)

Managed DirectX 9(中文版)是受欢迎的directx教材,此版为中文版

Managed DirectX +C# 开发(入门篇)(八)

  第七章 顶点缓冲一、索引缓冲3D物体中的三角形经常会有许多共用顶点。如果用两个三角形组合为一个长方形,有两个点被重复使用,当要表现一个更精细更复杂的模型的时候,重复的顶点数将会变得很大。下图的立方体,仅有八个顶点,但是当用三角形列表示它的时候,所有的点都被重复使用。索引缓冲就是一块保存了顶点数据索引的缓冲。缓冲中的索引为32位或16位的整数。比如,当使用索引0,1,6来绘制

dandanCool的专栏 4038

Directx9.0教程(中文版)、3D数学基础图形与游戏开发

directx9.0教程中文版以及源码;3D数学基础图形与游戏开发

directx9中文文档(C#版)

不知道有没有,发一个上来。内容比较全,可惜实例少了些,需要参考SDK包中实例.

Managed DirectX中的DirectShow应用(简单Demo及源码)

阅读目录 介绍 准备工作 环境搭建 简单Demo 显示效果 其他 Demo下载 介绍   DirectX是Microsoft开发的基于Windows平台的一组API,它是为高速的实时动画渲染、交互式音乐和环境音效等高要求应用开发服务的。   而Managed DirectX 实质上是对DirectX进行了一个轻量级的封装,Microsoft在2003年...

223

别再死磕V1了!手把手教你用WPS Web Office V3 SDK快速集成(附Java Demo避坑指南)

本文详细介绍了如何从WPS Web Office V1迁移到V3版本,重点讲解了V3在性能、安全性和扩展性上的优势,以及Java开发者如何快速集成V3 SDK。文章包含核心功能实现、高级功能适配(如Solon框架)和常见问题排查指南,帮助开发者高效完成迁移并避免常见陷阱。

weixin_27215337的博客 335

A simple Example of using Video Textures in Managed DirectX (C#)

You may be confused how to use video textures in managed directX as some students of mine,"because Tom Miller didnt explain detail enough." Well, This article may solve your problem. hehe,Tom has don

nasky的专栏 7247

Triton推理服务器实战:如何为PaddleOCR的ONNX模型配置动态批处理和并发推理

本文详细介绍了如何使用Triton推理服务器为PaddleOCR的ONNX模型配置动态批处理和并发推理,以提升GPU利用率和推理性能。通过实战案例和参数调优指南,帮助开发者解决高并发场景下的性能瓶颈问题,实现从实验室到生产环境的高效部署。

weixin_42527160的博客 426

HDMI设计2----EDID and E-EDID

1,EDID是什么?有什么用? 图1 应用场景 如图1所示的应用场景,PC将视频信号发送 到我们的设备上,经过一系列的传输和切换,设备将视频信号发送到显示器或者投影机上,也就是说,我们的设备位于PC和显示器之间。首先,在输入端,设备需要让PC知道自己是能够接受输入视频信号的,让PC认为自己是一台显示器。如果不这样做的话,PC可能会拒绝输出任何视频信号的(VGA接口不需要EDID也能输出)。除此之外,设备还要告诉PC自己支持的显示时序。而在输出端,当设备需要自建输出时序而不是简单的复制...

Archar_Saber的博客 7988

Managed DirectX(第五章)

定义Mesh    虽然有很多时候,你需要手动创建顶点和索引数据,但更普遍的情况是从外部的资源加载已有的顶点数据,比如从一个文件。通常我们使用.X文件来保存这些信息。在前一章里,代码的大部分都用来创建几何体了。对于简单的三角形和立方体来说这似乎是完全可行的,但设想假如用相同的方式来创建拥有上万个顶点的物体将,所花费的时间和努力都将是很可怕的。     幸运的是,Managed DirectX里有一

sunkangta的专栏 1208

INCA文件刷写全流程:从ES581驱动安装到XCP协议实战(附避坑指南)

本文详细解析INCA文件刷写的全流程操作,从ES581驱动安装到XCP协议配置,提供实战经验与避坑指南。涵盖A2L文件校验、工作区优化、XCP参数调优及自动化刷写技巧,帮助工程师高效完成ECU开发中的文件刷写任务。

egg99的博客 942

很好的几个Managed Directx 例子

2014/09/25 (转载自:http://www.codesampler.com/dx9src/dx9src_2.htm)

narutojzm1的博客 1429

AI辅助需求分析与提示词生成

文章标题 《AI辅助需求分析与提示词生成》 关键词 人工智能 需求分析 提示词生成 深度学习 机器学习 强化学习 数学模型 系统架构

AI天才研究院 1212

图神经网络实战(5)——常用图数据集

Cora 和 Facebook Page-Page 是图神经网络领域中经常使用的两个基准图数据集,通常用来比较不同模型的性能。在本节中,介绍了如何使用 PyTorch Geometric 库提供的数据集类加载 Cora 和 Facebook Page-Page 数据集,并探索了两个数据集的主要特征。

盼小辉丶的博客 1万+

使用Managed DirectX创建三维地形

使用Managed DirectX创建三维地形 作者:claymanclayman_joe@yahoo.com.cn转载请注明作者,商业使用请联系我^_^使用Height Map作为输入           首先,什么是高度图(Height Map)呢?所谓高度图实际上就是一个2维数组。创建地形为什么需要高度图呢?我们这样考虑,地形实际上就是一系列高度不同的网格而已,这样数

Clayman的专栏 3898

关于 SAP Fiori Elements 开发项目里的本地 annotation.xml 文件

文件通过引用 SAP 提供的标准词汇表和本地 OData 服务的元数据,为 Fiori Elements 应用定义了一个基础的注解结构。开发者可以在此基础上添加具体的注解,以满足特定的业务需求和界面定制。文件用于定义本地注解,以补充或覆盖来自后端 OData 服务的元数据。元素内添加具体的注解,以定制 Fiori Elements 应用的界面和行为。的架构,用于在本地注解中定义实体、类型和注解。这使得在本地注解中可以引用该 OData 服务的实体和类型。等,用于控制界面元素的显示和行为。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 472

Managed DirectX +C# 开发(入门篇)(二)

 第二章 向量的运算(1)1:向量相等几何学上,有同样方向和长度的两个向量相等。数学上,我们说有同样维数和分量的向量相等。例如:如果ux = vx, uy = vy, 且 uz = vz.那么(ux, uy, uz) = (vx, vy, vz)。在代码中我们能够用“= =”判断两个向量相等。2:向量模长向量的大小是有向线段的长度。知道向量的分量,利用下面的公式就能计算出向量的

dandanCool的专栏 5319

TIA Portal (博图) -两台不同网段S7-1200通信(上)

因为不同网段意味着它们默认不在同一个广播域,并且没有网关设备来处理路由,所以标准的 TCP/IP 通信(如 `TSEND_C`/`TRCV_C`)无法直接建立连接。S7 通信 (PUT/GET): 最简单,配置好连接后,在客户端 PLC 使用 `PUT` 指令发送数据到服务器 PLC,使用 `GET` 指令从服务器 PLC 读取数据。* 在地址详细信息中,确保 本地连接端点是客户端 PLC 的接口,远程连接端点是服务器 PLC 的接口和 IP (`192.168.2.20`)。

电工 2678

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Managed DirectX(2)

Managed DirectX(2)                                       Namespaces Included Managed DirectX API包含一组相关的命名空间集,每个命名空间都包含不同的函数。公共函数通常被包含在“Microsoft . DirectX”中。在Managed DirectX中完整的命名空间列表见表一:

张云飞的专栏 1038

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

上一篇: 【翻译】Managed DirectX(第十二章 下)
下一篇: 持续郁闷中~~~~~~~
soilwork
博客等级 码龄23年 290粉丝 116原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值