diff --git a/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs b/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs index 36e87a2..eb90341 100644 --- a/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs +++ b/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs @@ -50,15 +50,29 @@ namespace XericLibrary.PostProcess } /// - /// 材质统计结果 + /// 单行上的材质段信息 /// - [System.Serializable] - public struct MaterialStatResult + public struct MaterialSegment + { + public int rowIndex; + public int colStart; + public int colEnd; + public int matID; + public int length => colEnd - colStart + 1; + } + + /// + /// 材质孤岛数据结构 + /// 表示一组相连的同材质像素区域 + /// + public class MaterialIsland { public int matID; - public float location; - public float percentage; - public int pixelCount; + public List segments = new List(); + public int totalPixelCount; + public Vector2 centerPixelPos; + public Vector3 centerWorldPos; + public bool isValid => matID > 0 && segments.Count > 0; public Material GetMaterial() { @@ -200,265 +214,236 @@ namespace XericLibrary.PostProcess } #endregion - - #region 材质统计 API - public void GetVerticalMaterialStats(List results, float startPos = 0f, float endPos = 1f) + #region 材质孤岛统计 API + + private List m_MaterialIslands = new List(); + private bool m_IslandsDirty = true; + + public IReadOnlyList GetMaterialIslands() { - results.Clear(); - if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; + UpdateMaterialIslands(); + return m_MaterialIslands; + } - startPos = Mathf.Clamp01(startPos); - endPos = Mathf.Clamp01(endPos); - if (startPos > endPos) + public List GetMaterialIslandsByMatID(int matID) + { + UpdateMaterialIslands(); + return m_MaterialIslands.Where(island => island.matID == matID).ToList(); + } + + public int GetIslandCount() + { + UpdateMaterialIslands(); + return m_MaterialIslands.Count; + } + + public void MarkIslandsDirty() + { + m_IslandsDirty = true; + } + + private void UpdateMaterialIslands() + { + if (!m_IslandsDirty && m_MaterialIslands.Count > 0) return; + if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) { - float temp = startPos; - startPos = endPos; - endPos = temp; + m_MaterialIslands.Clear(); + m_IslandsDirty = false; + return; } - int startPixelY = Mathf.FloorToInt(startPos * (pickRTSize.y - 1)); - int endPixelY = Mathf.FloorToInt(endPos * (pickRTSize.y - 1)); + FindMaterialIslands(); + m_IslandsDirty = false; - Dictionary> matIDToPositions = new Dictionary>(); - Dictionary matIDToCount = new Dictionary(); - int totalValidPixels = 0; - - for (int y = startPixelY; y <= endPixelY; y++) + if (enableDebugLog) { - for (int x = 0; x < pickRTSize.x; x++) + Debug.Log($"[GeoLayerPicker] 材质孤岛统计完成,共 {m_MaterialIslands.Count} 个孤岛"); + foreach (var island in m_MaterialIslands) { - int index = y * pickRTSize.x + x; - if (index < 0 || index >= m_DecodedDataArray.Length) continue; - - var data = m_DecodedDataArray[index]; - if (!data.isValid) continue; - - float normalizedY = (float)y / (pickRTSize.y - 1); - - if (!matIDToPositions.ContainsKey(data.matID)) - { - matIDToPositions[data.matID] = new List(); - matIDToCount[data.matID] = 0; - } - matIDToPositions[data.matID].Add(normalizedY); - matIDToCount[data.matID]++; - totalValidPixels++; + Debug.Log($" MatID {island.matID} ({island.GetMaterialName()}): {island.totalPixelCount} 像素, 中心: {island.centerWorldPos}"); } } + } - foreach (var kvp in matIDToPositions) + private void FindMaterialIslands() + { + m_MaterialIslands.Clear(); + + int width = pickRTSize.x; + int height = pickRTSize.y; + + List> rowSegments = new List>(); + for (int y = 0; y < height; y++) { - int matID = kvp.Key; - var positions = kvp.Value; - float avgLocation = positions.Average(); - float percentage = totalValidPixels > 0 ? (float)matIDToCount[matID] / totalValidPixels * 100f : 0f; + var segments = ExtractRowSegments(y, width); + rowSegments.Add(segments); + } - results.Add(new MaterialStatResult + List> islandGroups = new List>(); + for (int matID = 1; matID <= m_IDToMaterial.Count; matID++) + { + var islands = BuildIslandsForMaterial(matID, rowSegments, width, height); + islandGroups.Add(islands); + } + + foreach (var islands in islandGroups) + { + m_MaterialIslands.AddRange(islands); + } + } + + private List ExtractRowSegments(int rowIndex, int width) + { + List segments = new List(); + if (m_DecodedDataArray == null) return segments; + + int col = 0; + while (col < width) + { + int index = rowIndex * width + col; + if (index >= m_DecodedDataArray.Length) break; + + PickedGeoData pixel = m_DecodedDataArray[index]; + + if (!pixel.isValid || pixel.matID <= 0) { - matID = matID, - location = avgLocation, - percentage = percentage, - pixelCount = matIDToCount[matID] + col++; + continue; + } + + int startCol = col; + int matID = pixel.matID; + + while (col < width) + { + int checkIndex = rowIndex * width + col; + if (checkIndex >= m_DecodedDataArray.Length) break; + + PickedGeoData checkPixel = m_DecodedDataArray[checkIndex]; + if (!checkPixel.isValid || checkPixel.matID != matID) + { + break; + } + col++; + } + + segments.Add(new MaterialSegment + { + rowIndex = rowIndex, + colStart = startCol, + colEnd = col - 1, + matID = matID }); } - results.Sort((a, b) => a.location.CompareTo(b.location)); + return segments; } - public void GetHorizontalMaterialStats(List results, float startPos = 0f, float endPos = 1f) + private List BuildIslandsForMaterial(int targetMatID, List> rowSegments, int width, int height) { - results.Clear(); - if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; + List islands = new List(); - startPos = Mathf.Clamp01(startPos); - endPos = Mathf.Clamp01(endPos); - if (startPos > endPos) + for (int y = 0; y < height; y++) { - float temp = startPos; - startPos = endPos; - endPos = temp; - } + if (y >= rowSegments.Count) break; - int startPixelX = Mathf.FloorToInt(startPos * (pickRTSize.x - 1)); - int endPixelX = Mathf.FloorToInt(endPos * (pickRTSize.x - 1)); - - Dictionary> matIDToPositions = new Dictionary>(); - Dictionary matIDToCount = new Dictionary(); - int totalValidPixels = 0; - - for (int x = startPixelX; x <= endPixelX; x++) - { - for (int y = 0; y < pickRTSize.y; y++) + foreach (var currentSegment in rowSegments[y].Where(s => s.matID == targetMatID)) { - int index = y * pickRTSize.x + x; - if (index < 0 || index >= m_DecodedDataArray.Length) continue; + List connectedIslands = new List(); - var data = m_DecodedDataArray[index]; - if (!data.isValid) continue; - - float normalizedX = (float)x / (pickRTSize.x - 1); - - if (!matIDToPositions.ContainsKey(data.matID)) + if (y > 0 && y - 1 < rowSegments.Count) { - matIDToPositions[data.matID] = new List(); - matIDToCount[data.matID] = 0; - } - matIDToPositions[data.matID].Add(normalizedX); - matIDToCount[data.matID]++; - totalValidPixels++; - } - } - - foreach (var kvp in matIDToPositions) - { - int matID = kvp.Key; - var positions = kvp.Value; - float avgLocation = positions.Average(); - float percentage = totalValidPixels > 0 ? (float)matIDToCount[matID] / totalValidPixels * 100f : 0f; - - results.Add(new MaterialStatResult - { - matID = matID, - location = avgLocation, - percentage = percentage, - pixelCount = matIDToCount[matID] - }); - } - - results.Sort((a, b) => a.location.CompareTo(b.location)); - } - - public void GetDominantMaterialByVerticalSlice(List results, int sliceCount = 10, float startPos = 0f, float endPos = 1f) - { - results.Clear(); - if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; - - sliceCount = Mathf.Max(1, sliceCount); - startPos = Mathf.Clamp01(startPos); - endPos = Mathf.Clamp01(endPos); - if (startPos > endPos) - { - float temp = startPos; - startPos = endPos; - endPos = temp; - } - - int startPixelY = Mathf.FloorToInt(startPos * (pickRTSize.y - 1)); - int endPixelY = Mathf.FloorToInt(endPos * (pickRTSize.y - 1)); - int totalHeight = endPixelY - startPixelY + 1; - if (totalHeight <= 0) return; - - float sliceHeight = (float)totalHeight / sliceCount; - - for (int sliceIdx = 0; sliceIdx < sliceCount; sliceIdx++) - { - int sliceStartY = startPixelY + Mathf.FloorToInt(sliceIdx * sliceHeight); - int sliceEndY = startPixelY + Mathf.FloorToInt((sliceIdx + 1) * sliceHeight) - 1; - sliceEndY = Mathf.Min(sliceEndY, endPixelY); - - Dictionary matIDCount = new Dictionary(); - int sliceTotal = 0; - - for (int y = sliceStartY; y <= sliceEndY; y++) - { - for (int x = 0; x < pickRTSize.x; x++) - { - int index = y * pickRTSize.x + x; - if (index < 0 || index >= m_DecodedDataArray.Length) continue; - - var data = m_DecodedDataArray[index]; - if (!data.isValid) continue; - - if (!matIDCount.ContainsKey(data.matID)) + foreach (var island in islands) { - matIDCount[data.matID] = 0; + foreach (var existingSegment in island.segments) + { + if (existingSegment.rowIndex == y - 1) + { + if (SegmentsOverlap(currentSegment, existingSegment)) + { + connectedIslands.Add(island); + break; + } + } + } } - matIDCount[data.matID]++; - sliceTotal++; + } + + if (connectedIslands.Count == 0) + { + MaterialIsland newIsland = new MaterialIsland + { + matID = targetMatID + }; + newIsland.segments.Add(currentSegment); + islands.Add(newIsland); + } + else if (connectedIslands.Count == 1) + { + connectedIslands[0].segments.Add(currentSegment); + } + else + { + MaterialIsland mergedIsland = new MaterialIsland + { + matID = targetMatID + }; + + foreach (var islandToMerge in connectedIslands) + { + mergedIsland.segments.AddRange(islandToMerge.segments); + islands.Remove(islandToMerge); + } + mergedIsland.segments.Add(currentSegment); + islands.Add(mergedIsland); } } - - if (sliceTotal > 0) - { - int dominantMatID = matIDCount.Aggregate((l, r) => l.Value > r.Value ? l : r).Key; - float sliceCenterY = (sliceStartY + sliceEndY) / 2f / (pickRTSize.y - 1); - float percentage = (float)matIDCount[dominantMatID] / sliceTotal * 100f; - - results.Add(new MaterialStatResult - { - matID = dominantMatID, - location = sliceCenterY, - percentage = percentage, - pixelCount = matIDCount[dominantMatID] - }); - } } + + CalculateIslandProperties(islands, width, height); + + return islands; } - public void GetDominantMaterialByHorizontalSlice(List results, int sliceCount = 10, float startPos = 0f, float endPos = 1f) + private bool SegmentsOverlap(MaterialSegment a, MaterialSegment b) { - results.Clear(); - if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; + return !(a.colEnd < b.colStart - 1 || a.colStart > b.colEnd + 1); + } - sliceCount = Mathf.Max(1, sliceCount); - startPos = Mathf.Clamp01(startPos); - endPos = Mathf.Clamp01(endPos); - if (startPos > endPos) + private void CalculateIslandProperties(List islands, int width, int height) + { + foreach (var island in islands) { - float temp = startPos; - startPos = endPos; - endPos = temp; - } + island.totalPixelCount = island.segments.Sum(s => s.length); - int startPixelX = Mathf.FloorToInt(startPos * (pickRTSize.x - 1)); - int endPixelX = Mathf.FloorToInt(endPos * (pickRTSize.x - 1)); - int totalWidth = endPixelX - startPixelX + 1; - if (totalWidth <= 0) return; - - float sliceWidth = (float)totalWidth / sliceCount; - - for (int sliceIdx = 0; sliceIdx < sliceCount; sliceIdx++) - { - int sliceStartX = startPixelX + Mathf.FloorToInt(sliceIdx * sliceWidth); - int sliceEndX = startPixelX + Mathf.FloorToInt((sliceIdx + 1) * sliceWidth) - 1; - sliceEndX = Mathf.Min(sliceEndX, endPixelX); - - Dictionary matIDCount = new Dictionary(); - int sliceTotal = 0; - - for (int x = sliceStartX; x <= sliceEndX; x++) + if (island.segments.Count == 0) { - for (int y = 0; y < pickRTSize.y; y++) - { - int index = y * pickRTSize.x + x; - if (index < 0 || index >= m_DecodedDataArray.Length) continue; - - var data = m_DecodedDataArray[index]; - if (!data.isValid) continue; - - if (!matIDCount.ContainsKey(data.matID)) - { - matIDCount[data.matID] = 0; - } - matIDCount[data.matID]++; - sliceTotal++; - } + island.centerPixelPos = Vector2.zero; + island.centerWorldPos = Vector3.zero; + continue; } - if (sliceTotal > 0) - { - int dominantMatID = matIDCount.Aggregate((l, r) => l.Value > r.Value ? l : r).Key; - float sliceCenterX = (sliceStartX + sliceEndX) / 2f / (pickRTSize.x - 1); - float percentage = (float)matIDCount[dominantMatID] / sliceTotal * 100f; + int minRow = island.segments.Min(s => s.rowIndex); + int maxRow = island.segments.Max(s => s.rowIndex); + float avgRow = (minRow + maxRow) * 0.5f; - results.Add(new MaterialStatResult - { - matID = dominantMatID, - location = sliceCenterX, - percentage = percentage, - pixelCount = matIDCount[dominantMatID] - }); + MaterialSegment longestSegment = island.segments.OrderByDescending(s => s.length).First(); + float avgCol = (longestSegment.colStart + longestSegment.colEnd) * 0.5f; + + island.centerPixelPos = new Vector2(avgCol, avgRow); + + int centerX = Mathf.Clamp(Mathf.RoundToInt(avgCol), 0, width - 1); + int centerY = Mathf.Clamp(Mathf.RoundToInt(avgRow), 0, height - 1); + int centerIndex = centerY * width + centerX; + + if (centerIndex >= 0 && centerIndex < m_DecodedDataArray.Length) + { + island.centerWorldPos = m_DecodedDataArray[centerIndex].worldPos; + } + else + { + island.centerWorldPos = Vector3.zero; } } } @@ -517,7 +502,13 @@ namespace XericLibrary.PostProcess // 手动设置渲染目标并清理,避免 URP 隐式状态干扰 cmd.SetRenderTarget(passRTHandle); - cmd.ClearRenderTarget(true, true, Color.clear); + cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0)); + + if (drawDebugOverlay) + { + Debug.Log($"[GeoLayerPicker] 开始绘制 {passDrawList.Count} 个SubMesh"); + Debug.Log($"[GeoLayerPicker] 渲染目标: {passRTHandle.rt.name}, 尺寸: {passRTHandle.rt.width}x{passRTHandle.rt.height}"); + } Camera cam = renderingData.cameraData.camera; Matrix4x4 viewMatrix = cam.worldToCameraMatrix; @@ -542,13 +533,23 @@ namespace XericLibrary.PostProcess cmd.SetViewProjectionMatrices(viewMatrix, projMatrix); cmd.SetViewport(new Rect(0, 0, passRTHandle.rt.width, passRTHandle.rt.height)); + MaterialPropertyBlock drawMpb = new MaterialPropertyBlock(); + for (int i = 0; i < passDrawList.Count; i++) { var data = passDrawList[i]; - cmd.SetGlobalInt(m_MatIDProperty, data.uniqueMatID); - cmd.DrawMesh(data.mesh, data.matrix, passMaterial, data.subMeshIndex); + + drawMpb.SetInt(m_MatIDProperty, data.uniqueMatID); + cmd.DrawMesh(data.mesh, data.matrix, passMaterial, data.subMeshIndex, 0, drawMpb); + + if (GeoLayerPickerRenderPassFeature.Instance != null && + GeoLayerPickerRenderPassFeature.Instance.enableDebugLog && + (i == 0 || i == passDrawList.Count - 1)) + { + Debug.Log($"[GeoLayerPicker] 绘制第{i}个SubMesh, MatID={data.uniqueMatID}, 材质={data.material?.name ?? "null"}"); + } } - + cmd.SetViewProjectionMatrices(viewMatrix, renderingData.cameraData.GetProjectionMatrix()); // 如果本帧需要回读,将数据安全地 Blit 到隔离缓冲区 @@ -576,13 +577,13 @@ namespace XericLibrary.PostProcess CommandBuffer debugCmd = CommandBufferPool.Get("GeoPickerDebugOverlay"); // 【核心 1】:使用 MaterialPropertyBlock 隔离参数 - MaterialPropertyBlock mpb = new MaterialPropertyBlock(); + MaterialPropertyBlock debugMpb = new MaterialPropertyBlock(); // 【修正 1】:直接使用字符串名称和 RTHandle 对象本身(RTHandle 隐式转为 Texture) // 彻底避开 nameID (RenderTargetIdentifier) 的类型转换坑! - mpb.SetTexture(DebugMainTex, passRTHandle); + debugMpb.SetTexture(DebugMainTex, passRTHandle); - mpb.SetVector(DebugRectProp, new Vector4( + debugMpb.SetVector(DebugRectProp, new Vector4( feature.sampleRect.x, feature.sampleRect.y, feature.sampleRect.width, @@ -600,7 +601,7 @@ namespace XericLibrary.PostProcess MeshTopology.Triangles, // 4. topology 3, // 5. vertexCount (全屏三角形) 1, // 6. instanceCount (必须显式写 1!) - mpb // 7. MaterialPropertyBlock + debugMpb // 7. MaterialPropertyBlock ); context.ExecuteCommandBuffer(debugCmd); @@ -757,10 +758,17 @@ namespace XericLibrary.PostProcess if (pixel.a > 0.5f) { + int decodedMatID = Mathf.RoundToInt(pixel.a); + + if (enableDebugLog && i == 0) + { + Debug.Log($"[GeoLayerPicker] 像素({x},{y}), alpha={pixel.a:F4}, 解码MatID={decodedMatID}"); + } + m_DecodedDataArray[i] = new PickedGeoData { isValid = true, - matID = Mathf.RoundToInt(pixel.a), + matID = decodedMatID, worldPos = new Vector3(pixel.r, pixel.g, pixel.b) }; m_ValidDataCount++; @@ -774,9 +782,34 @@ namespace XericLibrary.PostProcess if (enableDebugLog) { Debug.Log($"[GeoLayerPicker] 回读完成!总像素: {count}, 有效拾取像素: {m_ValidDataCount}"); + + Dictionary matIDCounts = new Dictionary(); + foreach (var data in m_DecodedDataArray) + { + if (data.isValid) + { + if (!matIDCounts.ContainsKey(data.matID)) + { + matIDCounts[data.matID] = 0; + } + matIDCounts[data.matID]++; + } + } + + Debug.Log("[GeoLayerPicker] 材质ID分布:"); + foreach (var kvp in matIDCounts) + { + var mat = GetMaterialByID(kvp.Key); + Debug.Log($" MatID={kvp.Key} ({mat?.name ?? "未知"}): {kvp.Value} 像素"); + } } + + MarkIslandsDirty(); } + /// + /// 重建模型绘制列表 + /// private void RebuildDrawList() { m_DrawList.Clear(); @@ -799,19 +832,17 @@ namespace XericLibrary.PostProcess material = materials[sub]; } - int matID; - if (material != null) + if (material == null) { - if (!m_MaterialToID.TryGetValue(material, out matID)) - { - matID = globalIDCounter++; - m_MaterialToID[material] = matID; - m_IDToMaterial[matID] = material; - } + continue; } - else + + int matID; + if (!m_MaterialToID.TryGetValue(material, out matID)) { matID = globalIDCounter++; + m_MaterialToID[material] = matID; + m_IDToMaterial[matID] = material; } m_DrawList.Add(new SubMeshDrawData