正確なバージョンは把握してないのですが、v3.1あたりからCamera3D Testが追加され3Dモデルの表示テストができるようになってます。
これにunityちゃんfbxデータをc3bにして表示しようとすると、表示自体はできるのですが数点問題が発生したので、その対処方法のメモ。上手くいくと以下な感じで表示できます。
##unityのサイトからunityちゃんのデータを入手
今回はSD版のunityちゃんデータを利用します。
http://unity-chan.com/contents/guideline/?id=SDUnityChan
##unitypackageから以下を参考にfbxを取り出す
落としたデータはunitypackageなので、以下を参考にfbxを取り出します。
http://unrealengine.hatenablog.com/entry/2014/04/15/184432
複数のfbxが含まれますが、SD_unitychan_generic.fbxを利用します。
##fbx-convにてc3bに変換
fbx-convはcocos2d-xに付属しており、tools\fbx-conv\win32以下にあります。(mac版は未検証です)
fbx-conv.exe -f SD_unitychan_generic.fbx
テクスチャのuvが反転しているので、-fオプションを指定します。
変換時にWARNINGが表示されますが、これでSD_unitychan_generic.c3bができるはず。
##Sprite3DTestを改変する
Node: Camera3D Testでunityちゃんが表示されるようにSprite3DTestへ手を加えます。
Sprite3DTestのc3b読み込み部分を修正。
addNewSpriteWithCoords( Vec3(0,0,0),"Sprite3DTest/SD_unitychan_generic.c3b",false,0.2f,true);
SD_unitychan_generic.c3bファイルと、fbxで利用されているtgaファイルをcpp-tests\Resources\Sprite3DTestへコピー。
これでSD_unitychan_generic.c3b自体が表示されるようになります。
が、以下のようになっているはずです。
##unityちゃんfbxをfbx-convする際の問題点
問題としては2点あります。
顔のモデルがスキニングされておらず、表示位置がおかしくなる
顔のモデルである_faceノードがCharacter1_Headの子になっていますが、スキニングされていないため、原点位置に表示されます。
Cgfxシェーダーが利用されている箇所が赤くなる
cocos2d-xはcgfxには対応していないため、fbx-conv時にdiffuse(1,0,0)のマテリアルに置き換わります。
fbx-conv時に表示されている
WARNING: [nol_mat] Material doesn't extend FbxSurfaceLambert, replaced with RED diffuse
は、このマテリアル置き換えを指しています。
問題点への対処方法
前述2つの問題ですが、fbx-convを改変して対処できます。
fbx-convのビルドは以下を参考に。
http://qiita.com/makanai/items/ae7776aef0147c30cc7e
スキニングされていないmodelについて、強制的にスキニングする
顔のモデルがスキニングされておらず、表示位置がおかしくなる問題について、
スキニングされていないmodelは強制的にスキニングを行って対処します。
void prefetchMeshes() {
{
int cnt = scene->GetGeometryCount();
FbxGeometryConverter converter(manager);
std::vector<FbxGeometry *> triangulate;
for (int i = 0; i < scene->GetGeometryCount(); ++i) {
FbxGeometry * geometry = scene->GetGeometry(i);
if (!geometry->Is<FbxMesh>() || !((FbxMesh*)geometry)->IsTriangleMesh())
triangulate.push_back(geometry);
}
for (std::vector<FbxGeometry *>::iterator it = triangulate.begin(); it != triangulate.end(); ++it)
{
log->status(log::sSourceConvertFbxTriangulate, getGeometryName(*it), (*it)->GetClassId().GetName());
FbxNodeAttribute * const attr = converter.Triangulate(*it, true);
}
}
int cnt = scene->GetGeometryCount();
for (int i = 0; i < cnt; ++i) {
FbxGeometry * geometry = scene->GetGeometry(i);
if (fbxMeshMap.find(geometry) == fbxMeshMap.end()) {
if (!geometry->Is<FbxMesh>()) {
log->warning(log::wSourceConvertFbxCantTriangulate, geometry->GetClassId().GetName());
continue;
}
FbxMesh *mesh = (FbxMesh*)geometry;
int indexCount = (mesh->GetPolygonCount() * 3);
log->verbose(log::iSourceConvertFbxMeshInfo, getGeometryName(mesh), mesh->GetPolygonCount(), indexCount, mesh->GetControlPointsCount());
if (indexCount > settings->maxIndexCount)
log->warning(log::wSourceConvertFbxExceedsIndices, indexCount, settings->maxIndexCount);
if (mesh->GetElementMaterialCount() <= 0) {
log->error(log::wSourceConvertFbxNoMaterial, getGeometryName(mesh));
continue;
}
//スキニングされてない場合、親ノードへ強制的にスキニングする
if (mesh->GetDeformerCount(FbxDeformer::eSkin)==0){
std::function<FbxNode*(FbxNode* node, FbxGeometry* geo)> findGeometry = [&findGeometry](FbxNode* node, FbxGeometry* geo)->FbxNode*{
if (node->GetGeometry() == geo)return node;
for (int i = 0; i < node->GetChildCount(); i++){
auto n = findGeometry(node->GetChild(i), geo);
if (n)return n;
}
return nullptr;
};
FbxNode* nChild = findGeometry(scene->GetRootNode(), geometry);
FbxNode* n = nChild->GetParent();
n->RemoveChild(nChild);
scene->GetRootNode()->AddChild(nChild);
FbxAnimEvaluator* mySceneEvaluator = scene->GetAnimationEvaluator();
auto mBase = mySceneEvaluator->GetNodeGlobalTransform(n);
auto m = mBase.Inverse();
FbxCluster *lClusterToRoot = FbxCluster::Create(scene, "");
lClusterToRoot->SetLink(n);
lClusterToRoot->SetTransformLinkMatrix(mBase);
lClusterToRoot->SetLinkMode(FbxCluster::eTotalOne);
for (int j = 0; j < mesh->GetControlPointsCount(); ++j){
lClusterToRoot->AddControlPointIndex(j, 1.0f);
}
FbxSkin* lSkin = FbxSkin::Create(scene, "");
lSkin->AddCluster(lClusterToRoot);
mesh->AddDeformer(lSkin);
}
FbxMeshInfo * const info = new FbxMeshInfo(log, mesh, settings->packColors, settings->maxVertexBonesCount, settings->forceMaxVertexBoneCount, settings->maxNodePartBonesCount);
meshInfos.push_back(info);
fbxMeshMap[mesh] = info;
if (info->bonesOverflow)
log->warning(log::wSourceConvertFbxExceedsBones);
}
else {
log->warning(log::wSourceConvertFbxDuplicateMesh, getGeometryName(geometry));
}
}
}
cgfx内のテクスチャ名をdiffuseに使用する
Cgfxシェーダー利用時にマテリアル置き換えしている箇所を改変し、Cgfx中のテクスチャをdiffuseとしてアサインするようにします。
unityちゃんではDiffuseTextureというテクスチャがdiffuseに該当しているので、それを利用します。
Material *createMaterial(FbxSurfaceMaterial * const &material) {
Material * const result = new Material();
result->source = material;
result->id = material->GetName();
if ((!material->Is<FbxSurfaceLambert>()) || GetImplementation(material, FBXSDK_IMPLEMENTATION_HLSL) || GetImplementation(material, FBXSDK_IMPLEMENTATION_CGFX)) {
if (!material->Is<FbxSurfaceLambert>()){
log->warning(log::wSourceConvertFbxMaterialUnknown, result->id.c_str());
float f[3] = { 1, 0, 0 };
result->diffuse.set(f);
}
if (GetImplementation(material, FBXSDK_IMPLEMENTATION_HLSL)){
log->warning(log::wSourceConvertFbxMaterialHLSL, result->id.c_str());
float f[3] = { 1, 0, 0 };
result->diffuse.set(f);
}
if (GetImplementation(material, FBXSDK_IMPLEMENTATION_CGFX)){
log->warning(log::wSourceConvertFbxMaterialCgFX, result->id.c_str());
float f[3] = { 1, 1, 1 };
result->diffuse.set(f);
//cgfxからdiffuseテクスチャ名を探す
auto lImplementation = GetImplementation(material, FBXSDK_IMPLEMENTATION_CGFX);
const FbxBindingTable* lRootTable = lImplementation->GetRootTable();
FbxString lFileName = lRootTable->DescAbsoluteURL.Get();
FbxString lTechniqueName = lRootTable->DescTAG.Get();
const FbxBindingTable* lTable = lImplementation->GetRootTable();
size_t lEntryNum = lTable->GetEntryCount();
for (int i = 0; i < (int)lEntryNum; ++i)
{
const FbxBindingTableEntry& lEntry = lTable->GetEntry(i);
const char* lEntrySrcType = lEntry.GetEntryType(true);
FbxProperty lFbxProp;
FbxString lTest = lEntry.GetSource();
if (lTest.Find("DiffuseTexture") < 0)continue;
if (strcmp(FbxPropertyEntryView::sEntryType, lEntrySrcType) == 0)
{
lFbxProp = material->FindPropertyHierarchical(lEntry.GetSource());
if (!lFbxProp.IsValid())
{
lFbxProp = material->RootProperty.FindHierarchical(lEntry.GetSource());
}
}
else if (strcmp(FbxConstantEntryView::sEntryType, lEntrySrcType) == 0)
{
lFbxProp = lImplementation->GetConstants().FindHierarchical(lEntry.GetSource());
}
if (lFbxProp.IsValid())
{
if (lFbxProp.GetSrcObjectCount<FbxTexture>() > 0)
{
for (int j = 0; j < lFbxProp.GetSrcObjectCount<FbxFileTexture>(); ++j)
{
FbxFileTexture *lTex = lFbxProp.GetSrcObject<FbxFileTexture>(j);
add_if_not_null(result->textures, createTexture(lTex, Material::Texture::Diffuse));
}
}
}
}
}
return result;
}
あとはfbx-convをビルドし、出来上がったexeで変換すれば問題が解決するはずです。
またフェイシャル以外のアニメーションはそのまま利用できたので、必要ならunitypackage付属のアニメーションを変換して利用すると良いかと思います。