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

Unity中如何读取图片像素并精准还原平滑蓝线至世界空间?

问题:从PNG提取蓝线并通过LineRenderer还原失败

我将一张PNG图片拖入Unity资源库,已将图片设置为2D格式并开启读写权限。

原图中的蓝线:
图片中的蓝线

我希望仅提取这条蓝线,因此尝试筛选蓝色像素以排除左上角的“Step 1”文字。

以下是编写的Unity脚本:

using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class BlueLineAnimator : MonoBehaviour
{
    public Texture2D image;
    public float spacing = 0.01f;
    public float maxDistance = 0.2f; // Filter out distant jumps
    public float lineWidth = 0.05f;
    private LineRenderer lineRenderer;

    void Start()
    {
        lineRenderer = gameObject.AddComponent<LineRenderer>();
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
        lineRenderer.startColor = Color.blue;
        lineRenderer.endColor = Color.blue;
        lineRenderer.startWidth = lineWidth;
        lineRenderer.endWidth = lineWidth;
        lineRenderer.useWorldSpace = true;
        lineRenderer.numCornerVertices = 5;
        lineRenderer.numCapVertices = 5;
        DrawBlueLine();
    }

    void DrawBlueLine()
    {
        List<Vector3> points = new();
        for (int y = 0; y < image.height; y++)
        {
            for (int x = 0; x < image.width; x++)
            {
                Color pixel = image.GetPixel(x, y);
                if (IsBlue(pixel))
                {
                    Vector3 pos = new Vector3(x * spacing, y * spacing, 0);
                    points.Add(pos);
                }
            }
        }

        // Sort like in WinForms: top to bottom, then left to right
        points = points.OrderBy(p => p.y).ThenBy(p => p.x).ToList();

        // Filter out noisy jumps
        List<Vector3> filtered = new();
        filtered.Add(points[0]);
        for (int i = 1; i < points.Count; i++)
        {
            if (Vector3.Distance(points[i], points[i - 1]) < maxDistance)
                filtered.Add(points[i]);
        }

        lineRenderer.positionCount = filtered.Count;
        lineRenderer.SetPositions(filtered.ToArray());
    }

    bool IsBlue(Color c)
    {
        return c.b > 0.7f && c.r < 0.4f && c.g < 0.4f;
    }
}

期望通过LineRenderer在世界空间中还原出与原图一致的平滑蓝线,但最终Unity中的显示效果与原图相差甚远:
Unity中的绘制效果


问题原因与解决方案

1. 核心问题:排序逻辑错误

当前按y(从上到下)再x排序,会把连续蓝线的横向、纵向像素打乱顺序,LineRenderer连接时必然出现乱线。蓝线是连续路径,必须按路径遍历顺序收集点,而非单纯坐标排序。

2. 优化蓝色像素筛选

增加透明像素排除逻辑,缩小颜色范围提升准确性:

bool IsBlue(Color c)
{
    // 排除透明像素,严格匹配深蓝色范围
    if (c.a < 0.1f) return false;
    return c.b > 0.8f && c.r < 0.2f && c.g < 0.2f;
}

3. 替换为路径追踪式点收集

用广度优先遍历从起点开始,按相邻像素顺序收集点,保证路径连续性:
修改DrawBlueLine方法:

void DrawBlueLine()
{
    List<Vector3> points = new();
    bool[,] visited = new bool[image.width, image.height];

    // 找到第一个蓝色像素作为起点
    Vector2Int start = Vector2Int.zero;
    bool foundStart = false;
    for (int y = 0; y < image.height; y++)
    {
        for (int x = 0; x < image.width; x++)
        {
            if (IsBlue(image.GetPixel(x, y)))
            {
                start = new Vector2Int(x, y);
                foundStart = true;
                break;
            }
        }
        if (foundStart) break;
    }

    if (!foundStart) return;

    // 8方向遍历相邻像素
    Queue<Vector2Int> queue = new Queue<Vector2Int>();
    queue.Enqueue(start);
    visited[start.x, start.y] = true;

    Vector2Int[] directions = new Vector2Int[]
    {
        Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right,
        new Vector2Int(1,1), new Vector2Int(1,-1), new Vector2Int(-1,1), new Vector2Int(-1,-1)
    };

    while (queue.Count > 0)
    {
        Vector2Int current = queue.Dequeue();
        points.Add(new Vector3(current.x * spacing, current.y * spacing, 0));

        foreach (var dir in directions)
        {
            Vector2Int next = current + dir;
            // 检查边界、未访问、蓝色像素
            if (next.x >= 0 && next.x < image.width && next.y >= 0 && next.y < image.height
                && !visited[next.x, next.y] && IsBlue(image.GetPixel(next.x, next.y)))
            {
                visited[next.x, next.y] = true;
                queue.Enqueue(next);
            }
        }
    }

    // 可选:用道格拉斯-普克算法简化路径,减少冗余点
    points = SimplifyPath(points, 0.01f);

    lineRenderer.positionCount = points.Count;
    lineRenderer.SetPositions(points.ToArray());
}

// 道格拉斯-普克路径简化算法
List<Vector3> SimplifyPath(List<Vector3> points, float tolerance)
{
    if (points.Count <= 2) return points;

    float maxDistance = 0;
    int index = 0;
    int end = points.Count - 1;

    for (int i = 1; i < end; i++)
    {
        float distance = DistanceFromPointToLine(points[i], points[0], points[end]);
        if (distance > maxDistance)
        {
            maxDistance = distance;
            index = i;
        }
    }

    List<Vector3> simplified = new List<Vector3>();
    if (maxDistance > tolerance)
    {
        List<Vector3> left = SimplifyPath(points.GetRange(0, index + 1), tolerance);
        List<Vector3> right = SimplifyPath(points.GetRange(index, end - index + 1), tolerance);
        simplified.AddRange(left.Take(left.Count - 1));
        simplified.AddRange(right);
    }
    else
    {
        simplified.Add(points[0]);
        simplified.Add(points[end]);
    }

    return simplified;
}

// 计算点到线段的垂直距离
float DistanceFromPointToLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
{
    Vector3 lineDir = lineEnd - lineStart;
    float lineLength = lineDir.magnitude;
    if (lineLength == 0) return Vector3.Distance(point, lineStart);

    float t = Mathf.Clamp01(Vector3.Dot(point - lineStart, lineDir) / lineLength);
    Vector3 closestPoint = lineStart + t * lineDir;
    return Vector3.Distance(point, closestPoint);
}

4. 额外优化

  • 调整spacing参数适配场景比例,避免线条过大/过小;
  • 若蓝线是闭合路径,开启lineRenderer.loop;
  • 提高numCornerVertices和numCapVertices数值,进一步提升线条平滑度。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 20:05:57