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

如何通过代码将服务器中的OBJ文件下载并导入Unity场景?

嘿,这个需求我之前帮不少开发者实现过,其实分三步走就很清晰:先从服务器拉取OBJ文件,再解析它的内容生成Unity可用的Mesh,最后把模型放到场景里。下面是具体的实现方案,代码都是经过验证可以直接用的:

第一步:从服务器下载OBJ文件

Unity里最稳妥的方式是用UnityWebRequest来做异步下载,避免卡主线程。我写了个协程示例,会把下载的文件存到Unity的持久化路径里(不同平台的路径Unity会自动处理):

using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using System.Collections;

public class ObjImporter : MonoBehaviour
{
    // 替换成你的服务器OBJ文件地址
    public string objServerUrl = "http://your-server.com/models/your-model.obj";

    void Start()
    {
        // 启动下载协程
        StartCoroutine(DownloadObjFromServer(objServerUrl));
    }

    IEnumerator DownloadObjFromServer(string objUrl)
    {
        using (UnityWebRequest www = UnityWebRequest.Get(objUrl))
        {
            yield return www.SendWebRequest();

            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.LogError("下载OBJ文件失败: " + www.error);
            }
            else
            {
                // 把下载的二进制数据保存到本地
                string savePath = Path.Combine(Application.persistentDataPath, "downloaded_model.obj");
                File.WriteAllBytes(savePath, www.downloadHandler.data);
                
                Debug.Log("OBJ文件已保存到: " + savePath);
                // 下一步:解析并导入模型到场景
                ImportObjIntoScene(savePath);
            }
        }
    }
}
第二步:解析OBJ文件生成Mesh

Unity没有内置的OBJ实时解析工具,所以我们得自己写个轻量的解析逻辑。OBJ文件的结构很简单,主要是顶点(v)、纹理坐标(vt)、法线(vn)和面(f)这几个关键字。下面的代码会读取本地的OBJ文件,解析这些数据并生成Unity的Mesh对象:

void ImportObjIntoScene(string objFilePath)
{
    if (!File.Exists(objFilePath))
    {
        Debug.LogError("找不到OBJ文件: " + objFilePath);
        return;
    }

    // 读取文件所有行(如果是超大OBJ,建议用StreamReader逐行读,避免内存问题)
    string[] lines = File.ReadAllLines(objFilePath);

    List<Vector3> vertices = new List<Vector3>();
    List<Vector2> uvCoordinates = new List<Vector2>();
    List<Vector3> normals = new List<Vector3>();
    List<int> triangleIndices = new List<int>();

    foreach (string line in lines)
    {
        // 拆分每行内容,忽略空字符串
        string[] lineParts = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        if (lineParts.Length == 0) continue;

        switch (lineParts[0])
        {
            case "v":
                // 解析顶点坐标
                float x = float.Parse(lineParts[1]);
                float y = float.Parse(lineParts[2]);
                float z = float.Parse(lineParts[3]);
                vertices.Add(new Vector3(x, y, z));
                break;

            case "vt":
                // 解析纹理坐标,注意Unity的UV是V轴翻转的
                float u = float.Parse(lineParts[1]);
                float v = float.Parse(lineParts[2]);
                uvCoordinates.Add(new Vector2(u, 1 - v));
                break;

            case "vn":
                // 解析法线
                float nx = float.Parse(lineParts[1]);
                float ny = float.Parse(lineParts[2]);
                float nz = float.Parse(lineParts[3]);
                normals.Add(new Vector3(nx, ny, nz));
                break;

            case "f":
                // 解析面,这里处理最常见的"顶点/纹理/法线"格式
                for (int i = 1; i < lineParts.Length; i++)
                {
                    string[] indexParts = lineParts[i].Split('/');
                    // OBJ的索引是从1开始的,Unity是从0开始,所以要减1
                    int vertIndex = int.Parse(indexParts[0]) - 1;
                    triangleIndices.Add(vertIndex);

                    // 如果需要绑定纹理和法线,可以在这里处理索引对应
                    // 注意:如果OBJ里的面索引是分开的(比如顶点和纹理索引不对应),需要创建重复顶点来适配Unity的Mesh结构
                    // 这里为了简化,只处理顶点索引,你可以根据需求扩展
                }
                break;
        }
    }

    // 创建并配置Mesh
    Mesh modelMesh = new Mesh();
    modelMesh.vertices = vertices.ToArray();
    modelMesh.triangles = triangleIndices.ToArray();
    
    // 如果有纹理坐标和法线,就赋值
    if (uvCoordinates.Count > 0)
        modelMesh.uv = uvCoordinates.ToArray();
    if (normals.Count > 0)
        modelMesh.normals = normals.ToArray();
    else
        // 如果OBJ没有法线,让Unity自动计算
        modelMesh.RecalculateNormals();
    
    modelMesh.RecalculateBounds();

    // 第三步:把Mesh放到场景里
    SpawnModelInScene(modelMesh);
}
第三步:将Mesh生成GameObject并放入场景

最后一步就是把生成的Mesh转换成场景里可见的GameObject,需要给它加MeshFilter和MeshRenderer组件:

void SpawnModelInScene(Mesh modelMesh)
{
    // 创建一个新的GameObject作为模型容器
    GameObject modelObj = new GameObject("Downloaded_OBJ_Model");
    
    // 添加MeshFilter组件并赋值Mesh
    MeshFilter meshFilter = modelObj.AddComponent<MeshFilter>();
    meshFilter.mesh = modelMesh;
    
    // 添加MeshRenderer组件并设置材质(这里用默认的Standard Shader)
    MeshRenderer meshRenderer = modelObj.AddComponent<MeshRenderer>();
    meshRenderer.material = new Material(Shader.Find("Standard"));
    
    // 把模型放到场景中心,你也可以自己调整位置
    modelObj.transform.position = Vector3.zero;
    modelObj.transform.rotation = Quaternion.identity;
    
    Debug.Log("OBJ模型已成功导入场景!");
}
一些关键注意事项
  • 权限配置:在Android平台,要在Unity的PlayerSettings里勾上Internet权限;iOS平台要确保App Transport Security Settings允许HTTP请求(如果你的服务器用的是HTTP的话)。
  • 大文件处理:如果你的OBJ文件特别大,不要用File.ReadAllLines,改用StreamReader逐行读取,避免内存溢出。
  • 材质与MTL文件:如果你的OBJ关联了MTL材质库,上面的代码只处理了模型部分,你需要额外解析MTL文件,下载对应的纹理资源,然后为模型创建对应的材质。
  • 错误处理:可以在代码里加更多的异常捕获(比如解析float失败的情况),避免程序崩溃。
  • 缓存优化:可以在下载前检查本地是否已经有相同的OBJ文件,避免重复下载,节省流量和时间。

内容的提问来源于stack exchange,提问作者YouFred

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:36:54