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

Unity调用Replicate API返回Vector3值异常问题求助

问题:Unity中使用JsonUtility解析Replicate API点云坐标值异常

我正在使用Replicate API在Unity中获取点云数据,API已成功返回JSON文件,其中coords字段为[N×3]格式的(X,Y,Z)点坐标数组。但使用JsonUtility.FromJson将其转换为Vector3[]后,值出现异常,变为6.852349e-43这类极小值甚至NaN,而API返回的原始值均为接近0的数值。

API返回的JSON示例:

"coords": [
    [
      -0.039118800312280655,
      -0.25740715861320496,
      0.28050559759140015
    ],
    [
      0.13176295161247253,
      0.23972494900226593,
      -0.06013050675392151
    ]
]

我尝试过使用float[,]、float[,,]、float[][]替代Vector3[],但只有Vector3[]能通过JsonUtility完成赋值,其他类型均无法正常解析。以下是我的调用脚本和响应类定义:

调用脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using SimpleJSON;

public class Prompt
{
    public string prompt;
}

public class PostResponse
{
    public string completed_at;
    public string created_at;
    public string error;
    public string id;
    public Prompt input;
    public string[] logs;
    public string metrics;
    public Output output;
    public string started_at;
    public string status;
    public string version;
}

public class ReplicateAPI : MonoBehaviour
{
    private string API_ENDPOINT = "https://api.replicate.com/v1/predictions";
    private string REPLICATE_API_TOKEN = "r8_DhOE6C7LuA7edPvLh7Io0ukwbmXCLEY450vc3";

    [SerializeField] PointEResponse response;

    public string prompt = "dog";

    [SerializeField] Material material;

    [SerializeField] MeshFilter meshFilter;

    private void Start()
    {
        StartCoroutine(Predict());
    }

    IEnumerator Predict()
    {
        string requestData = $@"{{
    ""version"": ""1a4da7adf0bc84cd786c1df41c02db3097d899f5c159f5fd5814a11117bdf02b"",
    ""input"": {{
        ""prompt"": ""{prompt}"",
        ""output_format"": ""json_file""
    }}
}}";

        UnityWebRequest request = UnityWebRequest.Put(API_ENDPOINT, requestData);
        request.method = "POST";
        request.SetRequestHeader("Authorization", "Token " + REPLICATE_API_TOKEN);
        request.SetRequestHeader("Content-Type", "application/json");

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError("Error " + request.responseCode + ": " + request.error);
            StartCoroutine(Predict());
        }
        else
        {
            Debug.Log(request.downloadHandler.text);
            PostResponse response = JsonUtility.FromJson<PostResponse>(request.downloadHandler.text);
            StartCoroutine(GetResult(response.id));
        }
    }

    IEnumerator GetResult(string id)
    {
        yield return new WaitForSeconds(5);
        UnityWebRequest request = UnityWebRequest.Get(API_ENDPOINT + "/" + id);
        request.SetRequestHeader("Authorization", "Token " + REPLICATE_API_TOKEN);
        request.SetRequestHeader("Content-Type", "application/json");

        yield return request.SendWebRequest();

        if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError("Error " + request.responseCode + ": " + request.error);
            yield break;
        }
        else
        {
            response = JsonUtility.FromJson<PointEResponse>(request.downloadHandler.text);
            Debug.Log(request.downloadHandler.text);
        }

        if (response.status != "succeeded") 
            StartCoroutine(GetResult(id));
        else
        {
            if (response.output.json_file.colors != null && response.output.json_file.colors.Length > 0)
            {
                for (int i = 0; i < response.output.json_file.colors.Length; i++)
                {
                    GameObject g = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                    g.transform.localScale = Vector3.one / 100;
                    g.transform.position = new Vector3(response.output.json_file.coords[i].x, response.output.json_file.coords[i].y, response.output.json_file.coords[i].z);
                    Material m = new Material(material);
                    m.color = new Color(response.output.json_file.colors[i].x, response.output.json_file.colors[i].y, response.output.json_file.colors[i].z);
                    g.GetComponent<MeshRenderer>().material = m;
                }
            }
            else
            {
                StartCoroutine(GetResult(id));
            }
        }
    }
}

响应类定义

using System;
using UnityEngine;

[Serializable]
public class PointEResponse
{
    public string id;
    public string version;
    public Urls urls;
    public DateTime created_at;
    public DateTime started_at;
    public DateTime completed_at;
    public string source;
    public string status;
    public Prompt input;
    public Output output;
    public object error;
    public string[] logs;
    public Metrics metrics;
}

[Serializable]
public class Output
{
    public JsonFile json_file;
}

[Serializable]
public class JsonFile
{
    public Vector3[] coords;
    public Vector3[] colors;
}

public class Urls
{
    public string get;
    public string cancel;
}

public class Metrics
{
    public float predict_time;
}

解决方案

问题根源在于Unity的JsonUtility不支持直接将二维数组(如[[x,y,z],[x,y,z]])解析为Vector3[]。JsonUtility期望的Vector3格式是带键的对象(如{"x":0,"y":0,"z":0}),而非数组形式,所以直接解析会导致内存值混乱,出现极小值或NaN。

可以通过以下两种方式解决:

方法1:自定义可序列化的三维数组包装类

创建一个可序列化的Float3类来替代数组中的每个三维元素,让JsonUtility能正确解析:

[Serializable]
public class Float3
{
    public float[] values;

    // 转换为Vector3的方法
    public Vector3 ToVector3()
    {
        if (values == null || values.Length < 3)
            return Vector3.zero;
        return new Vector3(values[0], values[1], values[2]);
    }
}

// 修改JsonFile类
[Serializable]
public class JsonFile
{
    public Float3[] coords;
    public Float3[] colors;
}

然后在解析完成后,将Float3[]转换为Vector3[]使用:

// 在GetResult的成功分支中
List<Vector3> pointCoords = new List<Vector3>();
List<Color> pointColors = new List<Color>();

foreach (var coord in response.output.json_file.coords)
{
    pointCoords.Add(coord.ToVector3());
}

foreach (var color in response.output.json_file.colors)
{
    var rgb = color.ToVector3();
    pointColors.Add(new Color(rgb.x, rgb.y, rgb.z));
}

// 之后使用pointCoords和pointColors创建点云
for (int i = 0; i < pointCoords.Count; i++)
{
    GameObject g = GameObject.CreatePrimitive(PrimitiveType.Sphere);
    g.transform.localScale = Vector3.one / 100;
    g.transform.position = pointCoords[i];
    Material m = new Material(material);
    m.color = pointColors[i];
    g.GetComponent<MeshRenderer>().material = m;
}

方法2:使用SimpleJSON解析(你已引入该库)

既然项目中已经导入了SimpleJSON,可以用它直接解析二维数组,再手动转换为Vector3:

修改GetResult中的解析逻辑:

else
{
    // 用SimpleJSON解析原始文本
    var jsonNode = JSON.Parse(request.downloadHandler.text);
    
    // 提取coords和colors数组
    var coordsArray = jsonNode["output"]["json_file"]["coords"].AsArray;
    var colorsArray = jsonNode["output"]["json_file"]["colors"].AsArray;

    List<Vector3> pointCoords = new List<Vector3>();
    List<Color> pointColors = new List<Color>();

    foreach (var coordNode in coordsArray)
    {
        var coord = coordNode.AsArray;
        pointCoords.Add(new Vector3(coord[0].AsFloat, coord[1].AsFloat, coord[2].AsFloat));
    }

    foreach (var colorNode in colorsArray)
    {
        var color = colorNode.AsArray;
        pointColors.Add(new Color(color[0].AsFloat, color[1].AsFloat, color[2].AsFloat));
    }

    // 创建点云
    for (int i = 0; i < pointCoords.Count; i++)
    {
        GameObject g = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        g.transform.localScale = Vector3.one / 100;
        g.transform.position = pointCoords[i];
        Material m = new Material(material);
        m.color = pointColors[i];
        g.GetComponent<MeshRenderer>().material = m;
    }
}

这种方法不需要修改原有的响应类,直接用SimpleJSON处理更灵活,适合解析数组格式的JSON数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:47:18