You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 03:21:12