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中的显示效果与原图相差甚远:
问题原因与解决方案
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
相关产品推荐
相关产品推荐

