前回は draco のビルドと、サンプルのモデルデータをとりあえず読み込んで描画を行いました。今回は、モデルデータをコンバートする部分と、別のモデルデータの読み込みについて確認したいと思います。
コンバート
現時点において Draco をビルドすると ply, obj ファイルから drc ファイルを生成するコンバーターが付属しています。
使い方は以下のようになっていました。
draco_encoder.exe -i (入力ファイル) -o (出力ファイル)
MMDのモデルから変換する
MMDのモデル形式(PMD,PMX)から obj 形式を経由して変換してみました。PMDEditor で、 obj 形式でエクスポートすることが出来るようになっており、出力したファイルを draco_encoder に入力します.
モデルの読み込みの改良
今回使用していくモデルは、 あにまさ式ミク です。先の変換によって miku.drc に変換しています。複数のマテリアルが使用されていますし、頂点データも位置情報だけではないモデルのため、前回の描画では実装が不十分です。今回はその辺りの改良と入れたいと思います。
頂点属性
前回は位置情報だけだったので他の属性についても取得するようにします。属性は以下のような感じでIDが取れるのでこれが -1 を返してこないようであればその属性が存在するということになってるようです。IDが取れれば、そこからデータの取り出しを行います。取り出したデータを適当な頂点データ構造体に詰めて、前回同様バッファにいれていくようにしました。
int pos_attr_id = mesh->GetNamedAttributeId(GeometryAttribute::POSITION); int col_attr_id = mesh->GetNamedAttributeId(GeometryAttribute::COLOR); int nrm_attr_id = mesh->GetNamedAttributeId(GeometryAttribute::NORMAL); int tex_attr_id = mesh->GetNamedAttributeId(GeometryAttribute::TEX_COORD); for (draco::PointIndex v(0); v < mesh->num_points(); ++v) { const auto* const pos_attr = mesh->attribute(pos_attr_id); const auto* const nrm_attr = mesh->attribute(nrm_attr_id); const auto* const tex_attr = mesh->attribute(tex_attr_id); auto pPosision = pos_attr->GetAddress(pos_attr->mapped_index(v)); auto position_stride = pos_attr->byte_stride(); auto pPosition2 = pos_attr->GetAddressOfMappedIndex(v); auto pNormal = nrm_attr->GetAddress(nrm_attr->mapped_index(v)); auto normal_stride = nrm_attr->byte_stride(); auto pNormal2 = nrm_attr->GetAddressOfMappedIndex(v); std::arraypos, nrm; pos_attr->GetMappedValue(v, &pos); nrm_attr->GetMappedValue(v, &nrm); Vertex vtx; vtx.Position.x = pos[0]; vtx.Position.y = pos[1]; vtx.Position.z = pos[2]; vtx.Normal.x = nrm[0]; vtx.Normal.y = nrm[1]; vtx.Normal.z = nrm[2]; vertices.push_back(vtx); }
これで頂点バッファの準備は出来ました。
マテリアルについて
各ポリゴンは各マテリアルが関連づけられています。どのマテリアルを使うのかによってポリゴン情報を振り分ける必要があります。振り分けた後、それぞれで使用する頂点インデックスを並べてインデックスバッファ作成、描画バッチの作成を行います。
各ポリゴンをマテリアルごとに振り分けているコードは以下のようになっています。ポリゴンは3頂点で構築されますが、マテリアルは共有なので最初のものをチェックして済ませています。
std::unordered_map> materialMap; for (draco::FaceIndex i(0); i < mesh->num_faces(); ++i) { int material_id = 0; const draco::PointIndex vert_index = mesh->face(i)[0]; const draco::AttributeValueIndex index_id(material_attr->mapped_index(vert_index)); material_attr->ConvertValue (index_id, &material_id); materialMap[material_id].push_back(i); }
各マテリアル単位でポリゴンが求まったのでそこから取り出すコードです。描画用のバッチも準備しています。
for (auto& v : materialMap) { for (auto& f : v.second) { auto& face = mesh->face(f); indices.push_back(face[0].value()); indices.push_back(face[1].value()); indices.push_back(face[2].value()); } DrawBatch batch; batch.startIndex = startIndex; batch.indexCount = v.second.size()*3; drawBatches.push_back(batch); startIndex += batch.indexCount; }
これで各マテリアル単位での描画バッチが作成されました。
描画
各マテリアル単位で描画単位を分けることが出来たので、適当にマテリアルごとに色分けしてみました。その結果が以下のようになりました。うまく行っているようですね!
マテリアルごとにデータを構築出来たので、マテリアルの中身をと思ったのですが実は Draco の形式には含まれていないようです。GetAttributeMetadataByStringEntry() でマテリアルのデータにアクセスできるように思えたのですが、マテリアルの名前が取れるだけのものでした。これを用いてIDと名前の対応ができるので、別にマテリアル情報を保持・参照しなさいということでしょうか。
まとめ
draco形式のモデルを読んで、マテリアルごとに描画の単位を整理するところまでを確認しました。残念ながら標準で添付されているツールで生成される draco 形式ではマテリアルの情報は保持していないので、ここまでのようです。それと、頂点属性もブレンドインデックスやウェイトについても標準で用意されていないため、スキニングモデルへの対応は(標準ツールでは)なさそうです。
注意すべきは draco はモデル形式ではありません。本来の役割は頂点属性データの中身そのものを圧縮して少なくすることです。確かに今回のデータでは miku.obj は 1.28MB あったものが 49 KB にまで小さくなりました。obj形式はテキストのため膨れあがっていることを考えて、元の PMD モデルと比較してみます。元の PMD ファイルは 464KB でした。これが 49KB にまで減ったので、確かに 10分の1程度にまで頂点データを圧縮できているようです。このレートには驚きました。
draco は WebGL でモデルデータの読み込むことについて多大な貢献を果たしそうです。前回と今回とでやったデスクトップアプリで DirectX11 で使うといったことについてはメインターゲットではないと思うので、この記事を見て使ってみようと思った人はご注意ください。