Adding Morph Targets(添加变形目标)
本文描述了如何使用 morph target(变形目标)动画,在UNIGINE中也称为blend shapes(混合形状)。通常,变形目标是用来改变人物的面部表情。
这篇文章展示了如何从Autodesk Maya导出带有morph目标的网格,然后将其添加到UNIGINE。
本教程中使用的模型是分布在CC0 1.0 Universal下的Spot 由 Keenan Crane。
需求#
- 假设您已经有一个混合形状的3D模型准备导出,并且该模型在UNIGINE设置的限制范围内。
- 假设您已经创造了一个世界。
另请参阅#
- ObjectMeshSkinned类函数的描述
步骤1。从玛雅导出一个网格与混合形状#
本节展示了从Autodesk Maya导出FBX格式的混合形状网格的方法。它包含一个小牛的示例网格,它有2个混合形状(变形目标)。
要导出具有混合形状的网格,请执行以下操作:
-
在Autodesk Maya中,选择要导出的混合形状网格。
在主菜单中,单击File -> Export Selection...
- 在Export Selection窗口中,选择一个文件夹来保存网格,并为FBX文件指定一个名称。在“Files of type”下拉列表中选择“FBX export”。
- 在带有导出选项的File Type Specific Options选项卡中,指定参数以导出网格。
- 在Deformed Models选项卡中,选中Blend Shapes复选框以导出混合形状。
- 单击Export Selection。
现在您有了FBX格式的网格,可以很容易地添加到您的项目中。
步骤2。在世界中添加网格#
本节将展示如何添加导出的网格动画世界,建立变形目标。
将导出的网格添加到世界中:
-
启用 Import Morph Targets 选项并导入.fbx文件。
- 将导入的文件添加到场景中。
- 保存世界。
网格的每个混合形状都是一个目标。您需要为网格的表面创建变形目标,并为这些目标设置参数来控制变形目标动画。下面的例子展示了如何从代码中创建变形目标和设置参数:
AppWorldLogic.h:
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__
#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineGame.h>
#include <UnigineMathLib.h>
using namespace Unigine;
class AppWorldLogic : public Unigine::WorldLogic
{
public:
AppWorldLogic();
~AppWorldLogic() override;
int init() override;
int update() override;
int postUpdate() override;
int updatePhysics() override;
int shutdown() override;
int save(const Unigine::StreamPtr &stream) override;
int restore(const Unigine::StreamPtr &stream) override;
private:
ObjectMeshSkinnedPtr mesh;
};
#endif // __APP_WORLD_LOGIC_H__
AppWorldLogic.cpp:
#include "AppWorldLogic.h"
#include <UnigineWorld.h>
int AppWorldLogic::init()
{
// get the node that refers to the exported FBX file and cast it to a skinned mesh
mesh = static_ptr_cast<ObjectMeshSkinned>(World::getNodeByName("spot_the_cow"));
// set the number of morph targets
mesh->setNumTargets(5, 0);
return 1;
}
int AppWorldLogic::update()
{
float time = Game::getTime() * 2.0f;
// calculate weights of targets
float k0 = sin(time * 3.0f) + 0.75f;
float k1 = cos(time * 3.0f) + 0.75f;
// set targets with parameters
mesh->setTarget(0, 1, 0, 1.0f, 0);
mesh->setTarget(1, 1, 0, -k0, 0);
mesh->setTarget(2, 1, 0, -k1, 0);
mesh->setTarget(3, 1, 1, k0, 0);
mesh->setTarget(4, 1, 2, k1, 0);
return 1;
}
int AppWorldLogic::shutdown()
{
mesh.clear();
return 1;
}
上面给出的代码获取引用FBX文件的节点,并设置参数以变形目标。让我们来澄清一些重要的事情:
- 导出的网格是通过将引用FBX资源的节点转换为ObjectMeshSkinned来获得的。节点是,通过其名称从World获得。
- setNumTargets()函数集的数量目标的表面网格。导出的网格在上面的例子中有2个目标(混合形状)。
-
通过使用setTarget()函数,每个创建的变形目标的所有参数都在update()函数中设置。每个目标都有其目标权重。权重对网格的坐标有影响:坐标乘以它们的权重。因此,所有启用的目标都乘以它们的权重,并创建新的网格:
final_xyz = target_0_xyz * weight_0 + target_1_xyz * weight_1 + ... -
因为在上面给出的代码sin()和cos()函数用于动画混合不同的目标,五创建目标:
-
mesh->setTarget(0, 1, 0, 1.0f, 0);
这个目标是没有任何插值的绑定姿势。
-
mesh->setTarget(1, 1, 0, -k0, 0); mesh->setTarget(2, 1, 0, -k1, 0);
这些目标用于插值两种动画混合。
-
mesh->setTarget(3, 1, 1, k0, 0);
这个目标用于第一个动画混合,它使用sin()函数进行插值。
-
mesh->setTarget(4, 1, 2, k1, 0);
这个目标用于第二个动画混合,它使用cos()函数进行插值。
-
分配材质网后,结果如下: