调试shader
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 材质统计结果
|
||||
/// </summary>
|
||||
[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";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -43,8 +87,31 @@ namespace XericLibrary.PostProcess
|
||||
|
||||
private readonly List<MeshFilter> m_GeoMeshes = new List<MeshFilter>();
|
||||
private readonly List<SubMeshDrawData> m_DrawList = new List<SubMeshDrawData>();
|
||||
private readonly Dictionary<Material, int> m_MaterialToID = new Dictionary<Material, int>();
|
||||
private readonly Dictionary<int, Material> m_IDToMaterial = new Dictionary<int, Material>();
|
||||
private bool m_IsDirty = true;
|
||||
|
||||
public IReadOnlyDictionary<Material, int> MaterialToID => m_MaterialToID;
|
||||
public IReadOnlyDictionary<int, Material> 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<MaterialStatResult> 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<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++)
|
||||
{
|
||||
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<float>();
|
||||
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<MaterialStatResult> 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<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++)
|
||||
{
|
||||
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<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))
|
||||
{
|
||||
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<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 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++)
|
||||
{
|
||||
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<MeshRenderer>();
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb4d6709ee2e40e4a9d9ec614a6491d9
|
||||
timeCreated: 1780885782
|
||||
Reference in New Issue
Block a user