• 处理模型——使Bone独立运动:模型动画


    问题

    你想独立的移动模型的每一部分。例如,你想摇低一辆车的车窗或让车轮转动。

    解决方案

    如教程4-1中解释的那样,一个模型是由可单独绘制的ModelMesh组成的。每个ModelMesh都链接到一个Bone,这些Bone互相联系,之间的位置关系是由矩阵显示的。

    每个模型都有一个root Bone,所有其他Bone对象直接或间接与它链接,图4-11显示了这种结构的一个例子。

    如果你想对图中的root Bone进行变换-例如,对这个root Bone进行缩放,那么它的所有child Bone对象(包括child Bone对象的child Bone对象) 会自动以同样的方式缩放。第二个例子,如果你想对存储在右前门Bone中的矩阵进行旋转,只有门本身,它的车窗和门锁会跟着旋转,这正是你需要的。

    工作原理

    在你对模型应用一个动画之前,你需要对模型的Bone结构有个概览,前面的教程中已经解释了如何可视化Bone结构,看到哪个ModelMeshes链接到哪个Bone对象上。

    让我们看一下在XNA Creators Club网站上找到的坦克模型的Bone结构,这个结构在前一个教程中已经写过了。你有一个root Bone,当你缩放这个Bone的矩阵时,整个坦克都会进行缩放。接下来看一下炮塔的Bone,它是root的子Bone,如果你旋转这个Bone,每个链接到这个Bone的ModelMesh和它的child Bone对象都会旋转。所以当你旋转炮塔Bone的矩阵时,炮塔,炮管,翻盖都会旋转,因为它们都是链接到炮塔上的。

    下面是你必须要记住的重点:

    • 模型中所有的可独立绘制的,可变换的部分都存储在不同的ModelMesh对象中。每个ModelMesh对象都链接到一个Bone对象。
    • 每个Bone对象存储这个Bone相对于它的parent Bone的位置,旋转和缩放信息。
    • 当你设置一个Bone的变换时,这个变换还要影响到这个Bone的所有child Bone对象。
    CopyAbsoluteBoneTransformsTo方法的必要性(额外的解释)

    上面列表中的最后一点不是很容易。在绘制每个ModelMesh时,你需要设置它的世界矩阵,因为你想让ModelMesh放置在正确的3D空间中。问题是这个世界矩阵定义的是ModelMesh在3D空间中的位置,但是,Bone矩阵包含的矩阵存储的相对于它的parent ModelMesh的位置!

    以坦克的火炮为例,火炮的Bone矩阵包含一个诸如(0,0,-2)的偏移量:相对于它的父:炮塔向前移动2个单位。

    如果你只是简单地将火炮的世界矩阵设置为火炮ModelMesh的矩阵,这会导致火炮ModelMesh被绘制到相对于3D空间的初始位置(0,0,0)偏离(0,0,-2)的地方。

    但这不是你想要的结果!你实际是想将火炮放置到相对于它的parent:炮塔偏离(0,0,-2)的位置。

    所以在绘制火炮前,你需要将它的Bone和它的parent的Bone (将两者相乘)组合起来。而且还要回溯到根节点,因为这种情况中最终矩阵还要和炮塔的parent Bone(坦克的车身)组合。通过这种方式,你获取了火炮的最终世界矩阵。

    因为这个矩阵是相对于坦克初始位置的,所以叫做火炮的绝对变换矩阵(absolute transformation matrix)。

    幸运的是,XNA提供了组合这些矩阵的功能。在绘制模型前,你需要调用模型的CopyAbsoluteBoneTransformsTo方法,这个方法会计算所有的组合并将它存储在结果的绝对矩阵数组中。这些绝对矩阵不再包含相对于parent Bone的变换信息;而只包含相对与模型的root的信息。结果是,这些包含在modelTransforms数组中的矩阵包含了坦克模型中所有ModelMesh的绝对变换信息。

    你可以使用这些矩阵作为模型中每个ModelMesh的绝对世界矩阵:

    myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); 
    foreach (ModelMesh mesh in myModel.Meshes) 
    {
        foreach (BasicEffect effect in mesh.Effects) 
        { 
            effect.EnableDefaultLighting(); 
            effect.World = modelTransforms[mesh.ParentBone.Index]; 
            effect.View = fpsCam.GetViewMatrix(); 
            effect.Projection = fpsCam.GetProjectionMatrix(); 
        }
        mesh.Draw(); 
    }

    虽然看起来变得更难了,但这样做会带来巨大的好处 。在这个例子中,只要炮塔的Bone矩阵发生旋转,炮管的(0,0,-2)平移矩阵也会跟着一起旋转。这是因为当CopyAbsoluteBoneTransformsTo方法结束后,火炮的绝对转换矩阵也会包含它的parent Bone 矩阵的旋转信息。而且,所有链接到炮塔的child ModelMesh,诸如炮管和翻盖,也会自动随着炮塔一起旋转。

    设置模型中指定的ModelMesh的动画

    知道了模型的结构后,现在可以实现模型动画了。

    在本例中,你想提升炮管。要做到这一点,使用前一个教程中的方法看一下炮管的Mesh part与哪个Bone相链接,而你将对这个Bone的矩阵施加一个旋转。

    但是,这个Bone矩阵中存储了火炮相对于炮塔的原始位置,如果你用旋转矩阵覆盖了这个矩阵,原始位置就丢失(或很难找到)!这样就无法以后对炮管施加动画了,因为你总是想从初始矩阵开始进行旋转,而这个初始矩阵的值已经丢失。

    所以在加载了模型后,你需要创建一个原始Bone矩阵的备份,存储相对于parent的位置,这要用到CopyBoneTransformsTo方法:

    Matrix[] originalTransforms = new Matrix[myModel.Bones.Count]; 
    myModel.CopyBoneTransformsTo(originalTransforms);

    注意:对每个ModelMesh,你都需要存储相对于parent ModelMesh的位置信息,所以你使用CopyBoneTransformsTo method。CopyAbsoluteBoneTransformsTo给你相对于模型初始位置的位置信息,如前面在“CopyAbsoluteBoneTransformsTo方法的必要性”一节中解释的。

    你需要将这个代码放置在LoadContent方法中。

    存储好矩阵后,你可以安全地覆盖存储在Bone对象中的矩阵了,在项目中添加一个canonRot变量:

    float canonRot = 0;

    可以让玩家在Update方法中调整这个变量:

    if (keyState.IsKeyDown(Keys.U)) 
        canonRot -= 0.05f; 
    if (keyState.IsKeyDown(Keys.D)) 
        canonRot += 0.05f;

    现在你可以使用键盘控制这个变量,将以下代码放到Draw方法中,在这行代码之前还要计算模型的绝对世界矩阵:

    Matrix newCanonMat = Matrix.CreateRotationX(canonRot) * originalTransforms[10]; 
    myModel.Bones[10].Transform = newCanonMat;

    在前面的教程中你可以在坦克模型的结构中看到,炮塔的ModelMesh链接到Bone 10。这个代码将炮管的相对于炮塔的初始位置存储在矩阵中,沿着向右向量旋转(使之上下旋转),并在模型中存储组合矩阵。当运行代码后,炮管会根据键盘输入上下旋转。

    如果你想旋转整个炮塔,你需要对炮塔的Bone矩阵做同样的事情,首先添加turretRot变量:

    float turretRot = 0; 

    然后在Update方法中添加键盘控制代码:

    if (keyState.IsKeyDown(Keys.L)) 
        turretRot += 0.05f; 
    if (keyState.IsKeyDown(Keys.R)) 
        turretRot -= 0.05f;

    在Draw方法中调整对应的Bone矩阵:

    Matrix newTurretMat = Matrix.CreateRotationY(turretRot) * originalTransforms[9];
    myModel.Bones[9].Transform = newTurretMat;

    如你在前一个教程中看到的,炮塔的Bone索引是9。首先获取初始矩阵,然后绕着Y轴旋转让炮塔左右旋转。

    注意:改变炮管矩阵和改变炮塔矩阵的顺序先后不会影响结果。在绝对矩阵中的Bone对象间的关系只有在调用CopyAbsoluteBoneTransformsTo方法时才会存储。

    如前所述,如果旋转炮塔,那么炮塔的children (本例中是炮管和翻盖)也会跟着一起旋转,这是因为炮管的Bone矩阵通过CopyAbsoluteBoneTransformsTo方法和它的child Bone矩阵组合在了一起。

    代码

    在加载模型后,请确保你存储了原始Bone矩阵:

    protected override void LoadContent() 
    {
        device = graphics.GraphicsDevice; 
        basicEffect = new BasicEffect(device, null); 
        cCross = new CoordCross(device); 
        
        myModel = Content.Load<Model>("tank"); 
        modelTransforms = new Matrix[myModel.Bones.Count]; 
        originalTransforms = new Matrix[myModel.Bones.Count]; 
        myModel.CopyBoneTransformsTo(originalTransforms); 
    }

    在update过程中,我们可以改变旋转角度:

    KeyboardState keyState = Keyboard.GetState(); 
    if (keyState.IsKeyDown(Keys.U)) 
        canonRot -= 0.05f; 
    if (keyState.IsKeyDown(Keys.D)) 
        canonRot += 0.05f; 
    if (keyState.IsKeyDown(Keys.L)) 
        turretRot += 0.05f; 
    if (keyState.IsKeyDown(Keys.R)) 
        turretRot -= 0.05f;

    最后绘制模型,你需要用旋转矩阵覆盖原始Bone矩阵并构建绝对Bone矩阵,而这些绝对矩阵必须作为ModelMesh的当前矩阵:

    protected override void Draw(GameTime gameTime) 
    {
        device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.CornflowerBlue, 1, 0);
        cCross.Draw(fpsCam.ViewMatrix, fpsCam.ProjectionMatrix); 
        
        //draw model
        Matrix newCanonMat = Matrix.CreateRotationX(canonRot) * originalTransforms[10];
        MyModel.Bones[10].Transform = newCanonMat; 
        Matrix newTurretMat = Matrix.CreateRotationY(turretRot) * originalTransforms[9]; 
        myModel.Bones[9].Transform = newTurretMat; 
        
        Matrix worldMatrix = Matrix.CreateScale(0.01f, 0.01f, 0.01f); 
        myModel.CopyAbsoluteBoneTransformsTo(modelTransforms); 
        foreach (ModelMesh mesh in myModel.Meshes) 
        {
            foreach (BasicEffect effect in mesh.Effects) 
            { 
                effect.EnableDefaultLighting(); 
                effect.World = modelTransforms[mesh.ParentBone.Index] * worldMatrix; 
                effect.View = fpsCam.ViewMatrix; 
                effect.Projection = fpsCam.ProjectionMatrix; 
            }
            mesh.Draw(); 
        } 
        base.Draw(gameTime); 
    };

    1

  • 相关阅读:
    使用Wireshark捕捉USB通信数据
    simtrace之探秘SIM卡中的世界
    极客DIY:RFID飞贼打造一款远距离渗透利器
    C118+Osmocom-bb+Openbts搭建小型基站
    天猫标的就是虚价,果然败家节啊
    为什么项目的jar包会和tomcat的jar包冲突?
    Spring-JDBC实现Contact的CRUD
    使用maven下载jar包的source和javadoc
    Spring-Context的注解实现依赖注入功能
    [html]三列居中自动伸缩的结构
  • 原文地址:https://www.cnblogs.com/AlexCheng/p/2120136.html
Copyright © 2020-2023  润新知