using UnityEngine; using System.Collections.Generic; using System.Linq; public class MarchingSquaresShadowCollider : MonoBehaviour { [Header("References")] public Camera shadowCamera; public RenderTexture shadowRT; public PolygonCollider2D polygonCollider; [Header("Settings")] [Range(0f, 1f)] public float shadowThreshold = 0.5f; public int gridResolution = 40; public float minContourArea = 50f; public float maxGapDistance = 20f; private Texture2D processedTexture; private List> separateContours = new List>(); void Start() { if (polygonCollider == null) polygonCollider = GetComponent(); processedTexture = new Texture2D(shadowRT.width, shadowRT.height, TextureFormat.RGBA32, false); GenerateCollider(); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { GenerateCollider(); } } [ContextMenu("Generate Collider")] public void GenerateCollider() { if (shadowCamera == null || shadowRT == null) return; // 渲染并读取纹理 shadowCamera.Render(); RenderTexture.active = shadowRT; processedTexture.ReadPixels(new Rect(0, 0, shadowRT.width, shadowRT.height), 0, 0); processedTexture.Apply(); RenderTexture.active = null; // 找到所有独立的轮廓 separateContours = FindSeparateContours(); // 设置多边形碰撞器路径 if (separateContours.Count > 0) { polygonCollider.pathCount = separateContours.Count; for (int i = 0; i < separateContours.Count; i++) { List worldPoints = ConvertToWorldCoordinates(separateContours[i]); polygonCollider.SetPath(i, worldPoints); } Debug.Log($"Created {separateContours.Count} separate collider paths"); } else { polygonCollider.pathCount = 0; Debug.LogWarning("No contours found"); } } List> FindSeparateContours() { List allEdgePoints = ExtractEdgePoints(); List> contours = new List>(); // 使用区域生长算法找到独立的轮廓 List unassignedPoints = new List(allEdgePoints); while (unassignedPoints.Count > 0) { List currentContour = new List(); Queue pointsToProcess = new Queue(); // 从第一个未分配的点开始 pointsToProcess.Enqueue(unassignedPoints[0]); unassignedPoints.RemoveAt(0); while (pointsToProcess.Count > 0) { Vector2 current = pointsToProcess.Dequeue(); currentContour.Add(current); // 查找附近的点 for (int i = unassignedPoints.Count - 1; i >= 0; i--) { if (Vector2.Distance(current, unassignedPoints[i]) <= maxGapDistance) { pointsToProcess.Enqueue(unassignedPoints[i]); unassignedPoints.RemoveAt(i); } } } // 连接轮廓点并过滤小区域 if (currentContour.Count > 3) { List connectedContour = ConnectContourPoints(currentContour); float area = CalculateContourArea(connectedContour); if (area >= minContourArea) { contours.Add(connectedContour); } } } Debug.Log($"Found {contours.Count} separate contours"); return contours; } List ExtractEdgePoints() { List points = new List(); int width = processedTexture.width; int height = processedTexture.height; int step = Mathf.Max(width, height) / gridResolution; // 扫描整个纹理寻找阴影边缘 for (int x = step; x < width - step; x += step) { for (int y = step; y < height - step; y += step) { if (IsEdgePoint(x, y, step)) { points.Add(new Vector2(x, y)); } } } Debug.Log($"Found {points.Count} edge points"); return points; } bool IsEdgePoint(int x, int y, int step) { Color center = processedTexture.GetPixel(x, y); bool isShadow = center.grayscale < shadowThreshold; if (!isShadow) return false; // 检查8邻域是否有非阴影点 for (int dx = -step; dx <= step; dx += step) { for (int dy = -step; dy <= step; dy += step) { if (dx == 0 && dy == 0) continue; int nx = x + dx; int ny = y + dy; if (nx >= 0 && nx < processedTexture.width && ny >= 0 && ny < processedTexture.height) { Color neighbor = processedTexture.GetPixel(nx, ny); if (neighbor.grayscale >= shadowThreshold) { return true; } } } } return false; } List ConnectContourPoints(List points) { if (points.Count < 3) return points; List connected = new List(); List remaining = new List(points); // 找到最左边的点作为起点 Vector2 start = remaining.OrderBy(p => p.x).First(); connected.Add(start); remaining.Remove(start); Vector2 current = start; // 按顺时针方向连接点 while (remaining.Count > 0) { // 找到与当前点角度变化最小的点 float bestAngle = float.MaxValue; Vector2 bestPoint = remaining[0]; int bestIndex = 0; Vector2 prevDir = connected.Count > 1 ? (current - connected[connected.Count - 2]).normalized : Vector2.right; for (int i = 0; i < remaining.Count; i++) { Vector2 toNext = (remaining[i] - current).normalized; float angle = Vector2.SignedAngle(prevDir, toNext); // 优先选择向右转的点(顺时针) if (angle < bestAngle && angle >= 0) { bestAngle = angle; bestPoint = remaining[i]; bestIndex = i; } } // 如果没有找到合适的点,选择最近的点 if (bestAngle == float.MaxValue) { bestPoint = remaining.OrderBy(p => Vector2.Distance(current, p)).First(); bestIndex = remaining.IndexOf(bestPoint); } connected.Add(bestPoint); remaining.RemoveAt(bestIndex); current = bestPoint; } // 确保轮廓闭合 if (Vector2.Distance(connected[0], connected[connected.Count - 1]) > maxGapDistance) { connected.Add(connected[0]); } return connected; } float CalculateContourArea(List points) { if (points.Count < 3) return 0f; float area = 0f; int n = points.Count; for (int i = 0; i < n; i++) { Vector2 current = points[i]; Vector2 next = points[(i + 1) % n]; area += (current.x * next.y) - (next.x * current.y); } return Mathf.Abs(area / 2f); } List ConvertToWorldCoordinates(List texturePoints) { List worldPoints = new List(); foreach (Vector2 texCoord in texturePoints) { Vector3 viewportPos = new Vector3( texCoord.x / processedTexture.width, texCoord.y / processedTexture.height, shadowCamera.nearClipPlane ); Vector3 worldPos = shadowCamera.ViewportToWorldPoint(viewportPos); worldPoints.Add(new Vector2(worldPos.x, worldPos.y)); } return worldPoints; } // 调试可视化 void OnDrawGizmosSelected() { if (separateContours == null) return; Color[] colors = { Color.red, Color.green, Color.blue, Color.yellow, Color.cyan, Color.magenta }; for (int i = 0; i < separateContours.Count; i++) { Gizmos.color = colors[i % colors.Length]; List contour = separateContours[i]; foreach (Vector2 point in contour) { Vector3 viewportPos = new Vector3( point.x / processedTexture.width, point.y / processedTexture.height, shadowCamera.nearClipPlane ); Vector3 worldPos = shadowCamera.ViewportToWorldPoint(viewportPos); Gizmos.DrawSphere(worldPos, 0.05f); } // 绘制轮廓线 if (contour.Count > 1) { for (int j = 0; j < contour.Count; j++) { Vector2 start = contour[j]; Vector2 end = contour[(j + 1) % contour.Count]; Vector3 startWorld = shadowCamera.ViewportToWorldPoint(new Vector3( start.x / processedTexture.width, start.y / processedTexture.height, shadowCamera.nearClipPlane )); Vector3 endWorld = shadowCamera.ViewportToWorldPoint(new Vector3( end.x / processedTexture.width, end.y / processedTexture.height, shadowCamera.nearClipPlane )); Gizmos.DrawLine(startWorld, endWorld); } } } } [ContextMenu("Debug Contour Info")] void DebugContourInfo() { shadowCamera.Render(); RenderTexture.active = shadowRT; processedTexture.ReadPixels(new Rect(0, 0, shadowRT.width, shadowRT.height), 0, 0); processedTexture.Apply(); RenderTexture.active = null; List> contours = FindSeparateContours(); for (int i = 0; i < contours.Count; i++) { float area = CalculateContourArea(contours[i]); Debug.Log($"Contour {i}: {contours[i].Count} points, area: {area}"); } } }