diff --git a/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs b/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs index 2d40943..36e87a2 100644 --- a/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs +++ b/Runtime/MeshDetection_URP/GeoLayerPickerRenderPassFeature.cs @@ -2,6 +2,7 @@ using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using System.Collections.Generic; +using System.Linq; using Unity.Collections; using UnityEngine.Experimental.Rendering; using XericLibrary.Runtime.MacroLibrary; @@ -19,6 +20,7 @@ namespace XericLibrary.PostProcess public Matrix4x4 matrix; public int subMeshIndex; public int uniqueMatID; + public Material material; } /// @@ -30,6 +32,48 @@ namespace XericLibrary.PostProcess public int matID; public Vector3 worldPos; public bool isValid; + + public Material GetMaterial() + { + if (GeoLayerPickerRenderPassFeature.Instance != null) + { + return GeoLayerPickerRenderPassFeature.Instance.GetMaterialByID(matID); + } + return null; + } + + public string GetMaterialName() + { + var mat = GetMaterial(); + return mat != null ? mat.name : "Unknown"; + } + } + + /// + /// 材质统计结果 + /// + [System.Serializable] + public struct MaterialStatResult + { + public int matID; + public float location; + public float percentage; + public int pixelCount; + + public Material GetMaterial() + { + if (GeoLayerPickerRenderPassFeature.Instance != null) + { + return GeoLayerPickerRenderPassFeature.Instance.GetMaterialByID(matID); + } + return null; + } + + public string GetMaterialName() + { + var mat = GetMaterial(); + return mat != null ? mat.name : "Unknown"; + } } /// @@ -43,8 +87,31 @@ namespace XericLibrary.PostProcess private readonly List m_GeoMeshes = new List(); private readonly List m_DrawList = new List(); + private readonly Dictionary m_MaterialToID = new Dictionary(); + private readonly Dictionary m_IDToMaterial = new Dictionary(); private bool m_IsDirty = true; + public IReadOnlyDictionary MaterialToID => m_MaterialToID; + public IReadOnlyDictionary IDToMaterial => m_IDToMaterial; + + public Material GetMaterialByID(int matID) + { + if (m_IDToMaterial.TryGetValue(matID, out var material)) + { + return material; + } + return null; + } + + public int GetIDByMaterial(Material material) + { + if (material != null && m_MaterialToID.TryGetValue(material, out var id)) + { + return id; + } + return -1; + } + public void AddGeoMesh(MeshFilter meshFilter) { if (meshFilter != null && !m_GeoMeshes.Contains(meshFilter)) @@ -132,6 +199,270 @@ namespace XericLibrary.PostProcess } } + #endregion + + #region 材质统计 API + + public void GetVerticalMaterialStats(List results, float startPos = 0f, float endPos = 1f) + { + results.Clear(); + if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; + + 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)); + + Dictionary> matIDToPositions = new Dictionary>(); + Dictionary matIDToCount = new Dictionary(); + int totalValidPixels = 0; + + for (int y = startPixelY; y <= endPixelY; 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; + + 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++; + } + } + + 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 GetHorizontalMaterialStats(List results, float startPos = 0f, float endPos = 1f) + { + results.Clear(); + if (m_DecodedDataArray == null || m_DecodedDataArray.Length == 0) return; + + startPos = Mathf.Clamp01(startPos); + endPos = Mathf.Clamp01(endPos); + if (startPos > endPos) + { + float temp = startPos; + startPos = endPos; + endPos = temp; + } + + 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++) + { + int index = y * pickRTSize.x + x; + if (index < 0 || index >= m_DecodedDataArray.Length) continue; + + var data = m_DecodedDataArray[index]; + if (!data.isValid) continue; + + float normalizedX = (float)x / (pickRTSize.x - 1); + + if (!matIDToPositions.ContainsKey(data.matID)) + { + 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)) + { + matIDCount[data.matID] = 0; + } + matIDCount[data.matID]++; + sliceTotal++; + } + } + + 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] + }); + } + } + } + + public void GetDominantMaterialByHorizontalSlice(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 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++) + { + 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++; + } + } + + 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; + + results.Add(new MaterialStatResult + { + matID = dominantMatID, + location = sliceCenterX, + percentage = percentage, + pixelCount = matIDCount[dominantMatID] + }); + } + } + } + #endregion #region Inspector 配置参数 @@ -328,14 +659,14 @@ namespace XericLibrary.PostProcess // 初始化调试材质 if (m_DebugOverlayMaterial == null) { - Shader overlayShader = Shader.Find("Hidden/XericLibrary/ScreenOverrideRect"); + Shader overlayShader = Shader.Find("Hidden/XericLibrary/GeoLayerPicker/ScreenOverrideRect"); if (overlayShader != null) { m_DebugOverlayMaterial = CoreUtils.CreateEngineMaterial(overlayShader); } else { - Debug.LogError("[GeoLayerPicker] 找不到调试 Shader: Hidden/XericLibrary/Debug/GeoPickerOverlay"); + Debug.LogError("[GeoLayerPicker] 找不到调试 Shader: Hidden/XericLibrary/GeoLayerPicker/ScreenOverrideRect"); } } } @@ -382,7 +713,7 @@ namespace XericLibrary.PostProcess // 配置调试覆盖图参数 m_ScriptablePass.drawDebugOverlay = enableDebugLog; // 由 Inspector 开关控制 m_ScriptablePass.debugOverlayMaterial = m_DebugOverlayMaterial; - // m_ScriptablePass.cameraColorTarget = renderer.cameraColorTargetHandle; // 获取当前屏幕目标 + // m_ScriptablePass.cameraColorTarget = renderer.cameraColorTargetHandle; // 当前是注册阶段无法获取目标 renderer.EnqueuePass(m_ScriptablePass); } @@ -449,22 +780,59 @@ namespace XericLibrary.PostProcess private void RebuildDrawList() { m_DrawList.Clear(); + m_MaterialToID.Clear(); + m_IDToMaterial.Clear(); int globalIDCounter = 1; foreach (var mf in m_GeoMeshes) { if (mf == null || mf.sharedMesh == null) continue; + + var renderer = mf.GetComponent(); + Material[] materials = renderer != null ? renderer.sharedMaterials : null; + for (int sub = 0; sub < mf.sharedMesh.subMeshCount; sub++) { + Material material = null; + if (materials != null && sub < materials.Length) + { + material = materials[sub]; + } + + int matID; + if (material != null) + { + if (!m_MaterialToID.TryGetValue(material, out matID)) + { + matID = globalIDCounter++; + m_MaterialToID[material] = matID; + m_IDToMaterial[matID] = material; + } + } + else + { + matID = globalIDCounter++; + } + m_DrawList.Add(new SubMeshDrawData { mesh = mf.sharedMesh, matrix = mf.transform.localToWorldMatrix, subMeshIndex = sub, - uniqueMatID = globalIDCounter++ + uniqueMatID = matID, + material = material }); } } + + if (enableDebugLog) + { + Debug.Log($"[GeoLayerPicker] 材质映射重建完成,共 {m_MaterialToID.Count} 个材质"); + foreach (var kvp in m_MaterialToID) + { + Debug.Log($" ID {kvp.Value}: {kvp.Key.name}"); + } + } } protected override void Dispose(bool disposing) diff --git a/Runtime/MeshDetection_URP/ScreenOverrideRect.shader b/Runtime/MeshDetection_URP/ScreenOverrideRect.shader new file mode 100644 index 0000000..fb7953d --- /dev/null +++ b/Runtime/MeshDetection_URP/ScreenOverrideRect.shader @@ -0,0 +1,84 @@ +Shader "Hidden/XericLibrary/GeoLayerPicker/ScreenOverrideRect" +{ + SubShader + { + Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline" } + LOD 100 + + Pass + { + Name "GeoPickerDebugOverlay" + ZWrite Off + ZTest Always + Cull Off + Blend SrcAlpha OneMinusSrcAlpha + + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" // 引入 Blitter 的标准顶点结构 + + // 在 HLSL 中声明变量(不需要在 Properties 中声明) + TEXTURE2D_X(_MainTex); // 使用 URP 标准的跨平台纹理宏 + SAMPLER(sampler_MainTex); + float4 _DebugRect; + + // 使用 Blitter 的标准片元输入结构 (包含完美的屏幕 UV) + struct v2f + { + float4 positionCS : SV_POSITION; + float2 texcoord : TEXCOORD0; + }; + + // 【核心】:使用 Blitter 的标准顶点着色器,它会自动生成完美的全屏三角形和 UV + v2f vert(Attributes input) + { + v2f output; + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + output.positionCS = GetFullScreenTriangleVertexPosition(input.vertexID); + output.texcoord = GetFullScreenTriangleTexCoord(input.vertexID); + return output; + } + + float4 frag (v2f i) : SV_Target + { + float2 rectMin = _DebugRect.xy; + float2 rectMax = rectMin + _DebugRect.zw; + + // 1. 裁剪区域判断 (使用 Blitter 提供的完美屏幕 UV) + if (i.texcoord.x < rectMin.x || i.texcoord.x > rectMax.x || + i.texcoord.y < rectMin.y || i.texcoord.y > rectMax.y) + { + return float4(0, 0, 0, 0); + } + + // 2. UV 映射 + float2 localUV = (i.texcoord - rectMin) / _DebugRect.zw; + + // 处理 Y 轴翻转 (Blitter 的 UV 原点在左下角,而 RT 渲染原点在左上角) + #if UNITY_UV_STARTS_AT_TOP + localUV.y = 1.0 - localUV.y; + #else + // 如果平台原点在左上,不需要翻转 + #endif + + // 3. 采样 HDR 纹理 (使用 URP 标准采样宏) + float4 col = SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, localUV); + + // 4. 数据可视化 + if (col.a < 0.5) + { + return float4(0.0, 0.0, 0.0, 0.3); // 未拾取区域半透黑 + } + + // 将世界坐标映射为可见颜色 + float3 debugColor = frac(col.rgb * 0.1) ^ col.a; + return float4(debugColor, 0.9); + } + ENDHLSL + } + } +} \ No newline at end of file diff --git a/Runtime/MeshDetection_URP/ScreenOverrideRect.shader.meta b/Runtime/MeshDetection_URP/ScreenOverrideRect.shader.meta new file mode 100644 index 0000000..c560b78 --- /dev/null +++ b/Runtime/MeshDetection_URP/ScreenOverrideRect.shader.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bb4d6709ee2e40e4a9d9ec614a6491d9 +timeCreated: 1780885782 \ No newline at end of file