Android平台ASSIMP加载OBJ模型丢失纹理,求ReadFile使用方案
解决方案:Android平台ASSIMP加载OBJ纹理缺失问题
方案一:拷贝资源到私有目录后使用ReadFile
Android的Assets目录属于APK内部资源,无法被ASSIMP的ReadFile直接访问,需先将OBJ、MTL及纹理文件拷贝到应用私有存储目录,再用该目录路径调用ReadFile。
1. 实现Assets文件拷贝函数
#include <android/asset_manager.h> #include <android/asset_manager_jni.h> #include <fstream> #include <string> bool copyAssetToFilesDir(AAssetManager* assetManager, const char* assetPath, const char* destPath) { AAsset* asset = AAssetManager_open(assetManager, assetPath, AASSET_MODE_STREAMING); if (!asset) return false; std::ofstream outFile(destPath, std::ios::binary); if (!outFile.is_open()) { AAsset_close(asset); return false; } char buffer[1024]; int bytesRead; while ((bytesRead = AAsset_read(asset, buffer, sizeof(buffer))) > 0) { outFile.write(buffer, bytesRead); } AAsset_close(asset); outFile.close(); return true; }
2. 加载模型流程
#include <jni.h> #include <string> void Model::loadModelFromFilesDir(JNIEnv* env, jobject contextObj, const char* objAssetPath) { // 获取应用私有目录路径 jclass contextClass = env->FindClass("android/content/Context"); jmethodID getFilesDirMethod = env->GetMethodID(contextClass, "getFilesDir", "()Ljava/io/File;"); jobject filesDirObj = env->CallObjectMethod(contextObj, getFilesDirMethod); jclass fileClass = env->FindClass("java/io/File"); jmethodID getAbsolutePathMethod = env->GetMethodID(fileClass, "getAbsolutePath", "()Ljava/lang/String;"); jstring filesDirPathStr = (jstring)env->CallObjectMethod(filesDirObj, getAbsolutePathMethod); const char* filesDirPath = env->GetStringUTFChars(filesDirPathStr, nullptr); // 拷贝OBJ文件到私有目录 std::string destObjPath = std::string(filesDirPath) + "/" + objAssetPath; AAssetManager* assetManager = AAssetManager_fromJava(env, contextObj); if (!copyAssetToFilesDir(assetManager, objAssetPath, destObjPath.c_str())) { Log::Message("Failed to copy obj file", LOG_ERROR); goto cleanup; } // 拷贝关联的MTL文件(假设MTL与OBJ同名,仅后缀不同) std::string mtlAssetPath = objAssetPath; size_t dotPos = mtlAssetPath.find_last_of('.'); if (dotPos != std::string::npos) { mtlAssetPath.replace(dotPos, std::string::npos, ".mtl"); } std::string destMtlPath = std::string(filesDirPath) + "/" + mtlAssetPath; copyAssetToFilesDir(assetManager, mtlAssetPath.c_str(), destMtlPath.c_str()); // 拷贝纹理文件(需根据MTL中定义的纹理路径,提前将对应Assets文件拷贝到同一目录) // 调用ASSIMP加载模型 Assimp::Importer importer; const aiScene* scene = importer.ReadFile(destObjPath.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { Log::Message(strcat("ERROR::ASSIMP::", importer.GetErrorString()), LOG_ERROR); goto cleanup; } processNode(scene->mRootNode, scene); cleanup: env->ReleaseStringUTFChars(filesDirPathStr, filesDirPath); env->DeleteLocalRef(filesDirObj); env->DeleteLocalRef(fileClass); env->DeleteLocalRef(contextClass); }
方案二:自定义MemoryIOWrapper实现内存多文件加载
通过实现ASSIMP的IOSystem和IOStream接口,将OBJ、MTL及纹理文件全部加载到内存缓存,让ASSIMP从内存中读取依赖文件,无需磁盘拷贝。
1. 实现自定义内存IO系统
#include <assimp/IOSystem.hpp> #include <assimp/IOStream.hpp> #include <unordered_map> #include <cstring> struct MemoryFile { const void* data; size_t length; }; class MemoryIOSystem : public Assimp::IOSystem { private: std::unordered_map<std::string, MemoryFile> m_fileCache; public: void addFile(const std::string& path, const void* data, size_t length) { m_fileCache[path] = {data, length}; } Assimp::IOStream* Open(const char* fileName, const char*) override { auto it = m_fileCache.find(fileName); if (it == m_fileCache.end()) return nullptr; return new MemoryIOStream(it->second.data, it->second.length); } void Close(Assimp::IOStream* stream) override { delete stream; } bool Exists(const char* fileName) const override { return m_fileCache.find(fileName) != m_fileCache.end(); } }; class MemoryIOStream : public Assimp::IOStream { private: const void* m_data; size_t m_length; size_t m_position; public: MemoryIOStream(const void* data, size_t length) : m_data(data), m_length(length), m_position(0) {} size_t Read(void* pvBuffer, size_t pSize, size_t pCount) override { size_t available = m_length - m_position; size_t readSize = pSize * pCount; if (readSize > available) readSize = available; memcpy(pvBuffer, (const char*)m_data + m_position, readSize); m_position += readSize; return readSize / pSize; } size_t Write(const void*, size_t, size_t) override { return 0; } aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override { switch (pOrigin) { case aiOrigin_SET: m_position = pOffset; break; case aiOrigin_CUR: m_position += pOffset; break; case aiOrigin_END: m_position = m_length - pOffset; break; } m_position = std::min(m_position, m_length); return aiReturn_SUCCESS; } size_t Tell() const override { return m_position; } size_t FileSize() const override { return m_length; } void Flush() override {} };
2. 使用自定义IO系统加载模型
#include <unordered_map> void Model::loadModelWithMemoryIOSystem( const void* objData, size_t objLength, const void* mtlData, size_t mtlLength, const std::unordered_map<std::string, std::pair<const void*, size_t>>& textures ) { Assimp::Importer importer; MemoryIOSystem* ioSystem = new MemoryIOSystem(); // 添加文件到内存缓存,路径需与OBJ/MTL中引用的路径完全一致 ioSystem->addFile("model.obj", objData, objLength); ioSystem->addFile("model.mtl", mtlData, mtlLength); for (const auto& tex : textures) { ioSystem->addFile(tex.first, tex.second.first, tex.second.second); } importer.SetIOHandler(ioSystem); const aiScene* scene = importer.ReadFile("model.obj", aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { Log::Message(strcat("ERROR::ASSIMP::", importer.GetErrorString()), LOG_ERROR); return; } processNode(scene->mRootNode, scene); }
注意事项
- 方案一中,需保证拷贝后的文件目录结构与Assets内一致,确保ASSIMP能通过相对路径找到MTL和纹理文件。
- 方案二中,内存缓存的文件路径必须与OBJ、MTL中定义的引用路径完全匹配,否则ASSIMP无法找到依赖文件。
- 两种方案仅解决ASSIMP读取模型数据及依赖文件的问题,纹理的实际渲染需自行通过OpenGL等API加载。
内容的提问来源于stack exchange,提问作者Daoming Dong
相关产品推荐
相关产品推荐

