修复统计id问题

This commit is contained in:
2026-06-08 16:23:20 +08:00
parent af6d7564f0
commit c3b9eef683
@@ -50,15 +50,29 @@ namespace XericLibrary.PostProcess
}
/// <summary>
/// 材质统计结果
/// 单行上的材质段信息
/// </summary>
[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;
}
/// <summary>
/// 材质孤岛数据结构
/// 表示一组相连的同材质像素区域
/// </summary>
public class MaterialIsland
{
public int matID;
public float location;
public float percentage;
public int pixelCount;
public List<MaterialSegment> segments = new List<MaterialSegment>();
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<MaterialStatResult> results, float startPos = 0f, float endPos = 1f)
#region 材质孤岛统计 API
private List<MaterialIsland> m_MaterialIslands = new List<MaterialIsland>();
private bool m_IslandsDirty = true;
public IReadOnlyList<MaterialIsland> 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<MaterialIsland> 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<int, List<float>> matIDToPositions = new Dictionary<int, List<float>>();
Dictionary<int, int> matIDToCount = new Dictionary<int, int>();
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<float>();
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<List<MaterialSegment>> rowSegments = new List<List<MaterialSegment>>();
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<List<MaterialIsland>> islandGroups = new List<List<MaterialIsland>>();
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<MaterialSegment> ExtractRowSegments(int rowIndex, int width)
{
List<MaterialSegment> segments = new List<MaterialSegment>();
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<MaterialStatResult> results, float startPos = 0f, float endPos = 1f)
private List<MaterialIsland> BuildIslandsForMaterial(int targetMatID, List<List<MaterialSegment>> rowSegments, int width, int height)
{
results.Clear();
if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return;
List<MaterialIsland> islands = new List<MaterialIsland>();
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<int, List<float>> matIDToPositions = new Dictionary<int, List<float>>();
Dictionary<int, int> matIDToCount = new Dictionary<int, int>();
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<MaterialIsland> connectedIslands = new List<MaterialIsland>();
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<float>();
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<MaterialStatResult> 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<int, int> matIDCount = new Dictionary<int, int>();
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<MaterialStatResult> 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<MaterialIsland> 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<int, int> matIDCount = new Dictionary<int, int>();
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<int, int> matIDCounts = new Dictionary<int, int>();
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();
}
/// <summary>
/// 重建模型绘制列表
/// </summary>
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