Создание игр » Featured, Toolset » Экспорт из 3d max – 3d max sdk
Экспорт из 3d max – 3d max sdk
Экспорт из 3d max можно сделать и на Си++. В прошлом уроке Экспорт из 3d max за 5 минут – max script я рассказал Вам, как можно сделать экспорт из 3d max с помощью maxscript, теперь же хочу объяснить как сделать примерно то же самое, но с помощью 3d max SDK. Для создания этого плагина экспорта из 3d max Вам, в первую очередь, нужно будет установить 3d max SDK, идущий в комплекте дистрибутива самого 3d Studio MAX. Не забудьте так же прописать пути к хидерам и библиотекам этого SDK в Visual Studio. В отличие от прошлого урока по экспорту из 3d max, на этот раз мы будем делать экспорт в бинарный формат – такие данные занимают меньше места и грузятся заметно быстрее.
Создание плагина экспорта из 3d max
В составе 3D Studio MAX SDK есть плагин для Visual Studio, который позволяет создавать новые плагины (в т.ч. и экспорта/импорта) для 3D MAX через специальный визард. Установка этого плагина совсем не сложная и описана в
После того, как вы установили этот плагин, просто запускайте ваш Visual Studio и создавайте новый проект “3ds Max Plugin Wizard”, задайте вашему проекту имя, выберите тип плагина “File Export 3DXI”, заполните поля в которых указывается имя плагина, желаемый класс, категория и описание. В итоге Вы получите готовый каркас для плагина экспорта из 3d max. Если же визард по каким-то причинам не заработает – думаю, единственное, что вам остаётся, это взять проект с моими исходниками плагина и просто скомпилировать его.
Классы экспорта
Плагин-визард по умолчанию создаст в проекте два класса. У меня они называются ExportToGame, он унаследован от SceneExport и ExportToGameClassDesc – унаследован от ClassDesc2. Класс ExportToGame является, собственно, самим плагином экспорта из 3d max, а ExportToGameClassDesc это класс, описывающий наш экспорт-плагин для 3d MAX – он указывает какое расширение файлов будет у наших файлов, задаёт описание, категорию и т.д., в общем, несёт чисто информативный характер.
Процедура экспорта из 3d max
Я не буду цитировать сюда всех исходники экспортера, которые сделал для меня плагин-визард. Расскажу лишь о том, как этот экспорт вообще работает в 3d studio max. Я так же опущу описания всяких возможных опций и т.д. – если Вам они нужны, просто почитайте справку по 3d max SDK.
Основная процедура экспорта, это, в моём случае ExportToGame::DoExport:
int ExportToGame::DoExport(const TCHAR* name,ExpInterface* ei,Interface* i, BOOL suppressPrompts, DWORD options) { // This is where the file export operation occur. if(!suppressPrompts) DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_PANEL), GetActiveWindow(), ExportToGameOptionsDlgProc, (LPARAM)this); // Construct a tab with all this scene's nodes. // We could also only take the node currently selected, etc. INodeTab lNodes; GetSceneNodes(lNodes); // Initialise 3DXI (formerly IGame) object // For more information, please see // 3ds Max SDK topic PG: Programming with 3DXI. IGameScene * pIgame = GetIGameInterface(); IGameConversionManager* cm = GetConversionManager(); cm->SetCoordSystem( IGameConversionManager::IGAME_D3D ); pIgame->InitialiseIGame(lNodes); // получаем список мешей Tab<IGameNode*> meshes = pIgame-> GetIGameNodeByType( IGameObject::IGAME_MESH ); // если мешей нет или их много if (meshes.Count()!=1) return FALSE; // не будем делать экспорт |
Тут почти весь код сгенерил тот самый визард. А я лучше расскажу вам о том, что “нагенерил” я сам )
Для того, что бы сделать экспорт, я создал отдельный класс, который назвал MyTriMesh – этот класс при экспорте будет промежуточным буфером между 3d max и файлом. Я решил сразу сделать возможность экспортировать модели, на которых лежит несколько материалов, наложенных с помощью MutiSubObject-материала (это стандартный метод использования нескольких материалов на одной модели для 3d max). Потому в итоге я пришёл к тому, что не надо хранить все вертексы и индексы в одном месте, а решил разбить модель на несколько частей – саб-мешей, каждый из которых будет хранить информацию по вертексами и граням с определённым MaterialID. Соответственно, сам MyTriMesh хранит в себе лишь список SubMesh’ей, в которых и хранится непосредственно информация о модели:
// структура для хранения вертекса struct MyVertex { float x, y, z; // pos float tx, ty, tz; // tangent float bx, by, bz; // binormal float nx, ny, nz; // normal float tu1, tv1, tu2, tv2; // texcoords DWORD color; // vertex color }; bool operator==(const MyVertex& a, const MyVertex& v); class SubMesh { //! Идентификатор материала данного сабмеша DWORD dwMatID; //! Вертексы сабмеша std::vector<MyVertex> m_vertices; //! Индексы (для сборки граней) сабмеша std::vector<WORD> m_indices; //! Функция оптимизации вертексов под кэш видео-карты void OptimizeVertices(); //! Функция оптимизации граней (индексов) void OptimizeFaces(); //! Добавление нового вертекса и возвращение его индекса WORD AddNewVertex(const MyVertex& v); public: SubMesh(); SubMesh(DWORD iMatID); ~SubMesh(void); //! Добавление нового вертекса в сабмеш void AddVertex(const MyVertex& v); //! Оптимизация сабмеша void Optimize(); //! Запись сабмеша в указанный поток bool Save(std::ostream& out); }; struct MeshHeader { DWORD flags[16]; }; class MyTriMesh { //! Сабмеши этой модели. Отдельный сабмеш для каждого материала std::vector<SubMesh*> vecSubMeshes; public: MyTriMesh(); ~MyTriMesh(); //! Функция "очистки" модели void Clear(); //! Функция для поиска сабмеша (либо создания нового) по заданному ID SubMesh* AddOrFindSubMeshByMat(unsigned int iMatID); //! Оптимизация модели (всех сабмешей) void Optimize(); //! Запись модели в указанный поток bool Save(std::ostream& out); }; |
Комментариев сделал очень много – я думаю, они говорят обо всём, что Вам нужно знать в данном случае.
Ну а теперь код самого экспорта (продолжение функции ):
ofstream out(name, std::ios::binary); for (int i=0; i<meshes.Count(); i++) { IGameMesh* mesh = static_cast<IGameMesh*>(meshes[i]->GetIGameObject()); if (mesh->InitializeData()) { int iFacesCount = mesh->GetNumberOfFaces(); for (int iFace=0; iFace<iFacesCount; iFace++) { FaceEx* pFace = mesh->GetFace(iFace); for (int i=0; i<3; i++) { Point3 pos = mesh->GetVertex(pFace->vert[i], true); int iIndexTB = mesh->GetFaceVertexTangentBinormal(iFace, i); Point3 tangent = mesh->GetTangent(iIndexTB); Point3 binormal = mesh->GetBinormal(iIndexTB); Point3 normal = mesh->GetNormal(pFace->norm[i], true); Point3 color = mesh->GetColorVertex(pFace->color[i]); int iTexIndex = mesh->GetFaceTextureVertex(iFace, i, 1); Point2 tc1 = mesh->GetTexVertex(iTexIndex); iTexIndex = mesh->GetFaceTextureVertex(iFace, i, 2); Point2 tc2 = mesh->GetTexVertex(iTexIndex); // формируем вертекс MyVertex v; v.x = pos.x; v.y = pos.y; v.z = pos.z; v.nx = normal.x; v.ny = normal.y, v.nz = normal.z; v.tx = tangent.x; v.ty = tangent.y; v.tz = tangent.z; v.bx = binormal.x; v.by = binormal.y; v.bz = binormal.z; v.tu1 = tc1.x; v.tu1 = tc1.y; v.tu2 = tc2.x; v.tu2 = tc2.y; v.color = ((int(color.x*0xff)&0xff)<<16) | ((int(color.y*0xff)&0xff)<<8) | (int(color.z*0xff)&0xff); // добавляем вертекс в "модель" SubMesh* pSub = m_mesh.AddOrFindSubMeshByMat( mesh->GetFaceMaterialID(iFace)); pSub->AddVertex(v); } } } } m_mesh.Save(out); m_mesh.Clear(); |
Что тут происходит, в общем-то, тоже абсолютно очевидно, но я всё равно поясню:
- Мы в цикле перебираем все модели (хотя у нас она всего одна, но я сделал “задел на будущее”)
- Для каждой модели “инициализируем” её – без этого прочитать данные модели не получится
- Получаем количество граней в модели и пробегаемся по всем граням
- Для каждой грани перебираем все три её вертекса ( вертексы 0, 1, 2 )
- Для каждого вертекса с помощью разных функций получаем данные, которые нам и надо экспортировать из 3d max
- По идентификатору материала находим, либо добавляем новый сабмеш в экспортируемую модель
- Добавляем в этот сабмеш собранный вертекс
- Когда цикл закончен – записываем модель в файл и отчищаем её
Собственно, вот и весь экспорт. О том, как избавить модель от “лишних” данных, вроде дублирующихся вертексов, как правильно сделать индексы для модели – я уже говорил вам в прошлых уроках. Потому я просто приведу код реализации классов SubMesh и MyTriMesh:
bool operator==(const MyVertex& a, const MyVertex& v) { return (a.x == v.x) && (a.y== v.y) && (a.z == v.z) && (a.tx == v.tx) && (a.ty == v.ty) && (a.tz == v.tz) && (a.bx == v.bx) && (a.by == v.by) && (a.bz == v.bz) && (a.nx == v.nx) && (a.ny == v.ny) && (a.nz == v.nz) && (a.tu1 == v.tu1) && (a.tv1 == v.tv1) && (a.tu2 == v.tu2) && (a.tv2 == v.tv2) && (a.color == v.color); } SubMesh::SubMesh() { dwMatID = 0; } SubMesh::SubMesh(DWORD iMatID) { dwMatID = iMatID; } SubMesh::~SubMesh(void) { } void SubMesh::OptimizeVertices() { const unsigned int numFaces = (unsigned int )m_indices.size()/3; const unsigned int numVerts = (unsigned int)m_vertices.size(); DWORD * pdwRemap = new DWORD [numVerts]; D3DXOptimizeVertices( &m_indices[0], numFaces, numVerts, FALSE, pdwRemap ); // make a copy std::vector<MyVertex> copyVB; copyVB = m_vertices; // remap m_vertices for( unsigned int i = 0; i < numVerts; ++i ) m_vertices[pdwRemap[i]] = copyVB[i]; // memcpy(&m_vertices[pdwRemap[i]], ©VB[i], sizeof(MeshVert)); // remap triangles for( unsigned int i = 0; i < numFaces; ++i ) for( int j = 0; j < 3; ++j ) m_indices[i*3+j] = (WORD)pdwRemap[ m_indices[i*3+j] ]; delete[] pdwRemap; } void SubMesh::OptimizeFaces() { unsigned short * pIB = (unsigned short *)&m_indices[0]; const unsigned int numFaces = unsigned int(m_indices.size())/3; const unsigned int numVerts = unsigned int(m_vertices.size()); DWORD * pdwRemap = new DWORD[numFaces]; D3DXOptimizeFaces( pIB, numFaces, numVerts, FALSE, pdwRemap ); unsigned short * pCopyIB = new unsigned short[numFaces*3]; memcpy( pCopyIB, pIB, numFaces*6 ); for( unsigned int i = 0; i < numFaces; ++i ) { int newFace = (int)pdwRemap[i]; for( int j = 0; j < 3; ++j ) pIB[newFace*3+j] = pCopyIB[i*3+j]; } delete[] pCopyIB; delete[] pdwRemap; } void SubMesh::Optimize() { OptimizeFaces(); OptimizeVertices(); } bool SubMesh::Save(std::ostream& out) { WORD iVertCount = (WORD)m_vertices.size(); DWORD iIdxCount = (DWORD)m_indices.size(); // это дефайн что бы было проще писать в файл #define _write(data) out.write((const char*)&data, sizeof(data)) _write(dwMatID); _write(iVertCount); _write(iIdxCount); for (WORD i=0; i<iVertCount; i++) { _write(m_vertices[i].x); _write(m_vertices[i].y); _write(m_vertices[i].z); _write(m_vertices[i].tx); _write(m_vertices[i].ty); _write(m_vertices[i].tz); _write(m_vertices[i].bx); _write(m_vertices[i].by); _write(m_vertices[i].bz); _write(m_vertices[i].nx); _write(m_vertices[i].ny); _write(m_vertices[i].nz); _write(m_vertices[i].tu1); _write(m_vertices[i].tv1); _write(m_vertices[i].tu2); _write(m_vertices[i].tv2); _write(m_vertices[i].color); } out.write((const char*)&m_indices[0], sizeof(WORD)*m_indices.size()); return true; } WORD SubMesh::AddNewVertex(const MyVertex& v) { // ищем такой же вертекс for (size_t i=0; i<m_vertices.size(); i++) if (v == m_vertices[i]) return WORD(i); // не нашли - добавляем новый m_vertices.push_back(v); return WORD(m_vertices.size()-1); } void SubMesh::AddVertex(const MyVertex& v) { // добавляем вертекс и индекс m_indices.push_back(AddNewVertex(v)); } MyTriMesh::MyTriMesh() { } MyTriMesh::~MyTriMesh() { Clear(); } void MyTriMesh::Clear() { const unsigned int iCount = (int)vecSubMeshes.size(); for (unsigned int i=0; i<iCount; i++) if (vecSubMeshes[i]!=NULL) delete vecSubMeshes[i]; vecSubMeshes.clear(); } void MyTriMesh::Optimize() { const unsigned int iCount = (unsigned int)vecSubMeshes.size(); for (unsigned int i=0; i<iCount; i++) if (vecSubMeshes[i]!=NULL) vecSubMeshes[i]->Optimize(); } SubMesh* MyTriMesh::AddOrFindSubMeshByMat(unsigned int iMatID) { while(vecSubMeshes.size()<iMatID+1) vecSubMeshes.push_back(NULL); if (vecSubMeshes[iMatID]==NULL) vecSubMeshes[iMatID] = new SubMesh(iMatID); return vecSubMeshes[iMatID]; } bool MyTriMesh::Save(std::ostream& out) { unsigned int subMeshesCount = 0; const unsigned int iCount = (unsigned int)vecSubMeshes.size(); for (unsigned int i=0; i<iCount; i++) if (vecSubMeshes[i]!=NULL) subMeshesCount++; MeshHeader meshHeader; ZeroMemory(&meshHeader, sizeof(MeshHeader)); out.write((const char*)&meshHeader, sizeof(MeshHeader)); out.write((const char*)&subMeshesCount, sizeof(unsigned int)); for (unsigned int i=0; i<iCount; i++) if (vecSubMeshes[i]!=NULL) if (!vecSubMeshes[i]->Save(out)) return false; return true; } |
Стурктуру meshHeader я сделал про запас. Пока в ней содержатся только нули, но в будущем, когда мы будем развивать движок, мы сможем записывать в неё какую-то полезную информацию – например, верисию формата файла, флаги того, какие доп. данные содержатся в файле и прочее.
На этом об экспорте пока всё. Я сам ещё не проверил, работает ли он, т.к. 3d max под рукой сейчас нет. Но Вы можете сделать это самостоятельно )))
Скачать проект с исходным кодом
Как всегда – жду ваших комментариев!
Супер Спасибо
L-ee-X, да не за что )
Получилось разобраться что к чему? Понятно написал? А то у меня некоторые сомнения есть по поводу того, что эта статья получилась достаточно понятной и простой…
Лично я ничего не могу сказать, просто потому что не пользуюсь 3d max и его у меня нет
Понятно
Залил обновлённую версию плагина – теперь экспортится только текущий выбранный объект, так удобнее работать. Кроме того, сделал ещё несколько небольших правок кода.
Отличная статья! Читал про экспорт с помощью max script – там все по-минимуму описано было, а здесь все как надо, с тангентами и бинормалями. Тут, насколько я понял, экспортятся текс. координаты из первого и второго каналов.
Интересно было бы увидеть апдейт, где экспортится N каналов (типа сколько в меше текс. координат используется – столько и экспортится).
Everard, рад, что Вам понравилось.
Доработать экспортер соответствующим образом, для экспорта N’ного количества текстурных каналов – задача не сложная. Думаю, будет лучше, если каждый из читателей блога попробует решить её сам – уверяю Вас, решённая самостоятельно задача приносит в сотни раз больше удовлетворения, чем решённая кем-то другим
А Вы не могли бы про создание плагина под ArchiCad написать?
Заранее спасибо.
К сожалению, не могу. Никогда не занимался этим.
Попытался собрать плагин из ваших исходников, получил error 2019 unresolved external symbol для D3DXOptimizeVertices и D3DXOptimizeFaces. Студия 2010 про, DXSDK Jun10, в проекте указал пути к max sdk и dx sdk, в функций переход к определению работает. Не понимаю, что не хватает? (не программист, моделлер, просто достало ждать, когда заэкспортится скриптом, хочу плагин – собрать ваш и потихоньку переписывать для соответствия нашему формату)