调试shader

This commit is contained in:
2026-06-08 16:01:01 +08:00
parent 4add58d458
commit af6d7564f0
3 changed files with 459 additions and 4 deletions
@@ -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