Mesh Converter

From Ciliz|W4

Scope

Игровой движок W4 использует собственный формат хранения сцены. Следовательно, прежде чем использовать сторонний ассет, его необходимо преобразовать в наш формат. Сделать это можно с помощью инструмента Mesh Converter, который поставляется вместе с нашим SDK.

Mesh Converter позволяет преобразовывать ассеты из формата Autodesk FBX (.fbx). Другие форматы не поддерживаются.

Technical limitations

Конвертер может преобразовать любой меш, но стоит помнить про ограничения движка, которые завязаны на ограничения WebGL. Оптимальное сочетания работоспособности и компактности решений достигается, если skinned mesh содержит не более 28 костей и/или 8 вершин на кость.

How to use

Загрузка репозитория проекта описана в статье Quick Start. Файл конвертера W4MeshConverter.bin находится в каталоге w4framework/tools . Конвертация запускается из командной строки, как это описано на следующем примере.

Mesh Exmple

Preparing

Возьмем для примера Utah teapot. Если посмотрите файл с помощью утилиты Autodesk FBX Review, увидите примерно то же, что на рисунке.

D1b94d56-cb70-428b-ac1c-65701c690238.png

Обратите внимание на положение носика чайника при просмотре fbx, мы вернемся к этому чуть позже.

Converting

Скопируем файл модели в директорию конвертера.

Далее, для преобразования модели в формат нашего движка (asset) необходимо выполнить:

./W4MeshConverter utah-teapot.fbx teapot

В результате появится директория teapot содержащая данные ассета.

Adding to the project structure

Для работы с ассетом необходимо скопировать его директорию (в нашем случае это teapot) в директорию resources проекта. Директория resources располагается на том же уровне структуры проекта, что и sources. Таким образом, структура проекта станет подобна следующей:

.
└── W4TemplateGame
    ├── resources
    │   └── teapot
    │       ├── Animations
    │       ├── BoneInfo
    │       ├── Materials
    │       │   ├── defaultMaterial.matInst
    │       │   └── defaultSkinnedMaterial.matInst
    │       ├── Splines
    │       ├── teapot.asset
    │       └── VertexData
    │           ├── Utah Teapot Quads_0.ib
    │           └── Utah Teapot Quads.vb
    └── sources
        └── main.cpp

Code example

Загрузка:

auto asset = Asset::load(Path("resources/teapot", "teapot.asset"));

Добавление в Render (отображение на экране):

auto root = RootNode::make();
root->addChild(asset->getRoot());
Render::instance().getPass(0)->setRoot(root);

Полный код примера:

#include "W4Framework.h"

W4_USE_UNSTRICT_INTERFACE

class AssetSample : public w4::IGame
{
public:

    AssetSample() = default;
    ~AssetSample() = default; 

    void onStart() override
    {
        auto cam = Render::instance().getScreenCamera();
        cam->setWorldTranslation({0.f, 0, -50.f});

        auto asset = Asset::load(Path("resources/teapot", "teapot.asset"));

        auto root =  RootNode::make();
        root->addChild(asset->getRoot());
        Render::instance().getPass(0)->setRoot(root);
    }
};

W4_RUN(AssetSample)

Result

В результате выполнения должен отобразиться тот же чайник, но с небольшим отличием.

1b952599-fc03-428f-96e8-ae86935b46d8.png

Если сравнить рисунки, можно заметить, что нос чайника в FBX Review смотрит в одну сторону, а в результате выполнения программы в другую. Это происходит из-за различий в системах координат и поведении конвертера.

Система координат FBX Scene Axis and Unit Conversion является правосторонней (с осью Y направленной верх), тогда как Система координат W4 левосторонняя (ось Y также направлена верх).

Animated mesh Exmple

Для анимированных мешей порядок работы не сильно отличается от описанного выше.

Preparing

Анимированную модель можно найти в интернете. Возьмем, например, Rigged Hand. Просмотр с помощью Autodesk FBX Review покажет примерно следующее:

Converting

Так же как в примере выше (с обычным мешем), скопируем модель в папку конвертера и преобразуем в формат W4 Engine:

./W4MeshConverter Hand_rigged.FBX hand

В результате выполнения в текущей папке будет получена директория hand, содержащая данные ассета.

Adding to the project structure

Скопируем полученный каталог ассета hand в каталог resources нашего проекта. Примерная структура проекта после этого:

.
└── W4TemplateGame
    ├── resources
    │   └── hand
    │       ├── Animations
    │       │   └── Take 001.ssa
    │       ├── BoneInfo
    │       │   ├── bone_main_hande.skeleton
    │       │   └── Hand_rigged.skin
    │       ├── hand.asset
    │       ├── Materials
    │       │   ├── defaultMaterial.matInst
    │       │   └── defaultSkinnedMaterial.matInst
    │       ├── Splines
    │       └── VertexData
    │           ├── Hand_rigged_0.ib
    │           └── Hand_rigged.vb
    └── sources
        └── main.cpp

Code example

Загрузим asset и добавим его в дерево отрисовки:

auto asset = Asset::load(Path("resources/hand", "hand.asset"));
render::getRoot()->addChild(asset->getRoot());

Если мы попробуем сейчас собрать проект, то ничего не увидим. Потому что камера попала “внутрь” asset’a.

Возьмем камеру и передвинем её подальше (возможно, придется задать другие значения, в зависимости от размера окна браузера, размера объекта и т.п.):

auto cam = render::getScreenCamera();
cam->setWorldTranslation({0.f, 125.f, -300.f});
 

Если сейчас собрать проект, то на экране появится рука, но она не будет двигаться.

Чтобы она задвигалась, нужно запустить аниматор у соответствующего skinned mesh’a. Известно, что skinned mesh у нас один и у него одна анимация, поэтому в цикле пройдем по всему дереву asset’a и всем skinned mesh’ам в нём и будем проигрывать анимацию (в нашем примере - первую):

asset->getRoot()->traversal([&](Node& node)
{
  if(node.is<SkinnedMesh>())
  {
    auto skinned = node.as<SkinnedMesh>();
    skinned->getAnimator(0).setIsLooped(true);
    skinned->play(0);
  }
});

Полный код примера:

#include "W4Framework.h"

W4_USE_UNSTRICT_INTERFACE

class MeshAnimatedSample : public w4::IGame
{
public:
    MeshAnimatedSample() = default;

    void onStart() override
    {
        auto asset = Asset::load(Path("resources/hand", "hand.asset"));
        render::getRoot()->addChild(asset->getRoot());

        auto cam = render::getScreenCamera();
        cam->setWorldTranslation({0.f, 150.f, -300.f});

        asset->getRoot()->traversal([&](Node &node)
        {
            if (node.is<SkinnedMesh>())
            {
                auto skinned = node.as<SkinnedMesh>();
                skinned->getAnimator(0).setIsLooped(true);
                skinned->play(0);
            }
        });
    }
};

W4_RUN(MeshAnimatedSample)

Result

В результате выполнения вышеописанной программы отобразится следующая анимация:

Short manual

Basics

Конвертация utah-teapot.fbx в ассет с именем asset в текущей директории:

./W4MeshConverter utah-teapot.fbx

Конвертация utah-teapot.fbx в ассет с именем teapot в текущей директории:

./W4MeshConverter utah-teapot.fbx teapot

Конвертация utah-teapot.fbx в ассет с именем teapot, который будет сохранен в директории ~/some/dir:

./W4MeshConverter utah-teapot.fbx teapot ~/some/dir 

Common keys

Справка:

-h, --help

Вывод статистики сцены:

-v, --verbose

Пример статистики:

(DEBUG) ==================== Scene statistics ====================
(DEBUG) Nodes: 2
(DEBUG) Geometries: 1
(DEBUG) Poses: 0
(DEBUG) Materials: 1
(DEBUG) Textures: 0
(DEBUG) ====================== Axis & Scale ======================
(DEBUG) Up: +Y, Front: +ParityOdd, System: RightHanded
(DEBUG) Scale: 1.000000
(DEBUG) ================ Mesh: Red_label ================
(DEBUG) Vertex count: 404
(DEBUG) Index count: 2118
(DEBUG) All polygons are triangles: 1
(DEBUG) ==========================================================
(DEBUG) create default material...
(DEBUG) create default skinned material...

Управление размером (scale) на конвертации, где arg - значение, например (10, 1, 0.1):

--scale arg

Переворот UV.Y координаты:

--uv-flip

Setting limitations

Для всех Mesh'ей могут быть заданы ограничения на максимальное количество вершин и индексов. Для Skinned Mesh'ей могут быть заданы ограничения на максимальное количество костей и максимальное количество костей на вершину.

Модель, которая упирается в текущие ограничения, можно сконвертировать с помощью флага force, однако работоспособность данной модели не гарантируется:

-f, --force

Установка ограничения на максимальное количество вершин на объект, где arg - значение, по умолчанию не задано:

--max-vertex arg

Установка ограничения на максимальное количество индексов на объект, где arg - значение, по умолчанию не задано:

--max-index arg

Установка ограничения на максимальное количество костей для Skinned Mesh, где arg - значение, по умолчанию 28:

--max-bones arg

Установка ограничения на максимальное количество костей на вершину для Skinned Mesh, где arg - значение, по умолчанию 8:

--max-bones-per-vertex arg