111
This commit is contained in:
259
Assets/Scripts/Test/Light/New/MarchingSquaresProcessor.cs
Normal file
259
Assets/Scripts/Test/Light/New/MarchingSquaresProcessor.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[System.Serializable]
|
||||
public class MarchingSquaresProcessor
|
||||
{
|
||||
[Header("Marching Squares设置")]
|
||||
public float isoLevel = 0.5f;
|
||||
public bool smoothEdges = true;
|
||||
public float smoothness = 0.1f;
|
||||
|
||||
// Marching Squares查找表
|
||||
private static readonly int[,] transitionTable = new int[,]
|
||||
{
|
||||
{-1, -1, -1, -1}, // 0: 空单元格
|
||||
{ 0, 3, -1, -1}, // 1: 只有左下角
|
||||
{ 1, 0, -1, -1}, // 2: 只有右下角
|
||||
{ 1, 3, -1, -1}, // 3: 下边
|
||||
{ 2, 1, -1, -1}, // 4: 只有右上角
|
||||
{ 0, 3, 2, 1}, // 5: S形状
|
||||
{ 2, 0, -1, -1}, // 6: 右边
|
||||
{ 2, 3, -1, -1}, // 7: 右下三角
|
||||
{ 3, 2, -1, -1}, // 8: 只有左上角
|
||||
{ 0, 2, -1, -1}, // 9: 左边
|
||||
{ 0, 1, 3, 2}, // 10: S形状(反转)
|
||||
{ 1, 2, -1, -1}, // 11: 左下三角
|
||||
{ 3, 1, -1, -1}, // 12: 上边
|
||||
{ 3, 0, -1, -1}, // 13: 右上三角
|
||||
{ 1, 3, -1, -1}, // 14: 左上三角
|
||||
{-1, -1, -1, -1} // 15: 全满
|
||||
};
|
||||
|
||||
public List<List<Vector2>> ExtractContours(DensityGrid grid)
|
||||
{
|
||||
var contours = new List<List<Vector2>>();
|
||||
bool[,] visited = new bool[grid.Width, grid.Height];
|
||||
|
||||
for (int x = 0; x < grid.Width - 1; x++)
|
||||
{
|
||||
for (int y = 0; y < grid.Height - 1; y++)
|
||||
{
|
||||
if (!visited[x, y] && IsBoundaryCell(grid, x, y))
|
||||
{
|
||||
var contour = MarchCell(grid, x, y, visited);
|
||||
if (contour != null && contour.Count >= 3)
|
||||
{
|
||||
if (smoothEdges)
|
||||
contour = SmoothContour(contour);
|
||||
contours.Add(contour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return contours;
|
||||
}
|
||||
|
||||
List<Vector2> MarchCell(DensityGrid grid, int startX, int startY, bool[,] visited)
|
||||
{
|
||||
var contour = new List<Vector2>();
|
||||
int x = startX, y = startY;
|
||||
int step = 0;
|
||||
int maxSteps = grid.Width * grid.Height; // 防止无限循环
|
||||
|
||||
do
|
||||
{
|
||||
// 边界检查
|
||||
if (x < 0 || x >= grid.Width - 1 || y < 0 || y >= grid.Height - 1)
|
||||
break;
|
||||
|
||||
if (visited[x, y])
|
||||
break;
|
||||
|
||||
visited[x, y] = true;
|
||||
|
||||
int config = GetCellConfiguration(grid, x, y);
|
||||
var edgePoints = GetEdgePoints(grid, x, y, config);
|
||||
|
||||
// 只添加有效的点
|
||||
foreach (var point in edgePoints)
|
||||
{
|
||||
if (!contour.Contains(point)) // 简单的去重
|
||||
contour.Add(point);
|
||||
}
|
||||
|
||||
// 移动到下一个单元格
|
||||
if (!MoveToNextCell(config, ref x, ref y))
|
||||
break;
|
||||
|
||||
step++;
|
||||
|
||||
} while ((x != startX || y != startY) && step < maxSteps);
|
||||
|
||||
return contour;
|
||||
}
|
||||
|
||||
int GetCellConfiguration(DensityGrid grid, int x, int y)
|
||||
{
|
||||
int config = 0;
|
||||
|
||||
// 安全地获取值,处理边界情况
|
||||
float a = GetSafeValue(grid, x, y);
|
||||
float b = GetSafeValue(grid, x + 1, y);
|
||||
float c = GetSafeValue(grid, x + 1, y + 1);
|
||||
float d = GetSafeValue(grid, x, y + 1);
|
||||
|
||||
if (a >= isoLevel) config |= 1;
|
||||
if (b >= isoLevel) config |= 2;
|
||||
if (c >= isoLevel) config |= 4;
|
||||
if (d >= isoLevel) config |= 8;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
float GetSafeValue(DensityGrid grid, int x, int y)
|
||||
{
|
||||
if (x < 0 || x >= grid.Width || y < 0 || y >= grid.Height)
|
||||
return 0f;
|
||||
return grid.GetValue(x, y);
|
||||
}
|
||||
|
||||
Vector2[] GetEdgePoints(DensityGrid grid, int x, int y, int config)
|
||||
{
|
||||
var points = new List<Vector2>();
|
||||
|
||||
// 根据配置添加边点
|
||||
switch (config)
|
||||
{
|
||||
case 1:
|
||||
case 14:
|
||||
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
|
||||
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
|
||||
break;
|
||||
case 2:
|
||||
case 13:
|
||||
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
|
||||
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
|
||||
break;
|
||||
case 3:
|
||||
case 12:
|
||||
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
|
||||
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
|
||||
break;
|
||||
case 4:
|
||||
case 11:
|
||||
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
|
||||
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
|
||||
break;
|
||||
case 5:
|
||||
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
|
||||
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
|
||||
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
|
||||
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
|
||||
break;
|
||||
case 6:
|
||||
case 9:
|
||||
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
|
||||
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
|
||||
break;
|
||||
case 7:
|
||||
case 8:
|
||||
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
|
||||
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
|
||||
break;
|
||||
case 10:
|
||||
points.Add(InterpolateEdge(grid, x, y, x, y + 1)); // 左边
|
||||
points.Add(InterpolateEdge(grid, x, y, x + 1, y)); // 底边
|
||||
points.Add(InterpolateEdge(grid, x + 1, y, x + 1, y + 1)); // 右边
|
||||
points.Add(InterpolateEdge(grid, x, y + 1, x + 1, y + 1)); // 顶边
|
||||
break;
|
||||
}
|
||||
|
||||
return points.ToArray();
|
||||
}
|
||||
|
||||
Vector2 InterpolateEdge(DensityGrid grid, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
// 安全获取值
|
||||
float v1 = GetSafeValue(grid, x1, y1);
|
||||
float v2 = GetSafeValue(grid, x2, y2);
|
||||
|
||||
if (Mathf.Abs(v1 - v2) < 0.001f)
|
||||
return grid.GridToLocalPosition(x1, y1);
|
||||
|
||||
float t = (isoLevel - v1) / (v2 - v1);
|
||||
t = Mathf.Clamp01(t);
|
||||
|
||||
Vector2 p1 = grid.GridToLocalPosition(x1, y1);
|
||||
Vector2 p2 = grid.GridToLocalPosition(x2, y2);
|
||||
|
||||
return Vector2.Lerp(p1, p2, t);
|
||||
}
|
||||
|
||||
bool MoveToNextCell(int config, ref int x, ref int y)
|
||||
{
|
||||
// 简化的移动逻辑 - 根据配置决定下一个单元格
|
||||
switch (config)
|
||||
{
|
||||
case 1:
|
||||
case 3:
|
||||
case 7:
|
||||
case 5:
|
||||
y++; break; // 向上移动
|
||||
case 2:
|
||||
case 6:
|
||||
case 10:
|
||||
case 14:
|
||||
x++; break; // 向右移动
|
||||
case 4:
|
||||
case 12:
|
||||
case 13:
|
||||
case 15:
|
||||
y--; break; // 向下移动
|
||||
case 8:
|
||||
case 9:
|
||||
case 11:
|
||||
x--; break; // 向左移动
|
||||
default:
|
||||
return false; // 无法移动
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
List<Vector2> SmoothContour(List<Vector2> contour)
|
||||
{
|
||||
if (contour.Count < 4) return contour;
|
||||
|
||||
var smoothed = new List<Vector2>();
|
||||
for (int i = 0; i < contour.Count; i++)
|
||||
{
|
||||
Vector2 prev = contour[(i - 1 + contour.Count) % contour.Count];
|
||||
Vector2 current = contour[i];
|
||||
Vector2 next = contour[(i + 1) % contour.Count];
|
||||
|
||||
Vector2 smoothedPoint = (prev + current * 2f + next) / 4f;
|
||||
smoothed.Add(smoothedPoint);
|
||||
}
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
bool IsBoundaryCell(DensityGrid grid, int x, int y)
|
||||
{
|
||||
// 安全检查
|
||||
if (x < 0 || x >= grid.Width - 1 || y < 0 || y >= grid.Height - 1)
|
||||
return false;
|
||||
|
||||
float value = grid.GetValue(x, y);
|
||||
return value >= isoLevel && value < 1.0f; // 边界单元格
|
||||
}
|
||||
|
||||
public void DrawGridGizmos(Transform wall)
|
||||
{
|
||||
// 调试可视化代码可以留空或简化
|
||||
#if UNITY_EDITOR
|
||||
// 可选:添加网格可视化
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2a4c434153f3614d8381195368e2fec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
54
Assets/Scripts/Test/Light/New/ShadowCollider.cs
Normal file
54
Assets/Scripts/Test/Light/New/ShadowCollider.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ShadowCollider : MonoBehaviour
|
||||
{
|
||||
[Header("阴影碰撞器")]
|
||||
public GameObject sourceObject;
|
||||
public PolygonCollider2D polygonCollider;
|
||||
|
||||
[Header("调试")]
|
||||
public bool showGizmos = true;
|
||||
public Color gizmoColor = new Color(1, 0, 0, 0.3f);
|
||||
|
||||
public void Initialize(List<List<Vector2>> contours)
|
||||
{
|
||||
polygonCollider = GetComponent<PolygonCollider2D>();
|
||||
if (polygonCollider == null)
|
||||
polygonCollider = gameObject.AddComponent<PolygonCollider2D>();
|
||||
|
||||
UpdateCollider(contours);
|
||||
}
|
||||
|
||||
public void UpdateCollider(List<List<Vector2>> contours)
|
||||
{
|
||||
if (polygonCollider == null) return;
|
||||
|
||||
polygonCollider.pathCount = contours.Count;
|
||||
for (int i = 0; i < contours.Count; i++)
|
||||
{
|
||||
polygonCollider.SetPath(i, contours[i].ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (!showGizmos || polygonCollider == null) return;
|
||||
|
||||
Gizmos.color = gizmoColor;
|
||||
Vector3 offset = transform.forward * 0.01f;
|
||||
|
||||
for (int pathIndex = 0; pathIndex < polygonCollider.pathCount; pathIndex++)
|
||||
{
|
||||
var path = polygonCollider.GetPath(pathIndex);
|
||||
for (int i = 0; i < path.Length; i++)
|
||||
{
|
||||
Vector3 point1 = transform.TransformPoint(path[i]);
|
||||
Vector3 point2 = transform.TransformPoint(path[(i + 1) % path.Length]);
|
||||
Gizmos.DrawLine(point1 + offset, point2 + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
11
Assets/Scripts/Test/Light/New/ShadowCollider.cs.meta
Normal file
11
Assets/Scripts/Test/Light/New/ShadowCollider.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a96ca5e4a935984392a295d8855f0f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
183
Assets/Scripts/Test/Light/New/ShadowColliderSystem.cs
Normal file
183
Assets/Scripts/Test/Light/New/ShadowColliderSystem.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public class ShadowColliderSystem : MonoBehaviour
|
||||
{
|
||||
[Header("系统设置")]
|
||||
public Light shadowLight;
|
||||
public Transform shadowWall;
|
||||
public string shadowLayerName = "Shadow";
|
||||
public LayerMask obstacleLayerMask = -1;
|
||||
|
||||
[Header("生成设置")]
|
||||
public bool generateOnStart = true;
|
||||
public bool updateInRealTime = false;
|
||||
public float updateInterval = 0.1f;
|
||||
|
||||
[Header("阴影碰撞器")]
|
||||
public List<ShadowCollider> shadowColliders = new List<ShadowCollider>();
|
||||
|
||||
private ShadowProjector projector;
|
||||
private ShadowMeshGenerator meshGenerator;
|
||||
private MarchingSquaresProcessor marchingSquares;
|
||||
private float updateTimer;
|
||||
|
||||
void Start()
|
||||
{
|
||||
InitializeComponents();
|
||||
|
||||
if (generateOnStart)
|
||||
{
|
||||
GenerateAllShadowColliders();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (updateInRealTime)
|
||||
{
|
||||
updateTimer += Time.deltaTime;
|
||||
if (updateTimer >= updateInterval)
|
||||
{
|
||||
UpdateAllShadowColliders();
|
||||
updateTimer = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeComponents()
|
||||
{
|
||||
// 自动查找必要的组件
|
||||
if (shadowLight == null) shadowLight = ShadowUtils.FindMainLight();
|
||||
if (shadowWall == null) shadowWall = ShadowUtils.FindMainWall();
|
||||
|
||||
// 初始化子系统
|
||||
projector = new ShadowProjector(shadowLight, shadowWall, obstacleLayerMask);
|
||||
meshGenerator = new ShadowMeshGenerator();
|
||||
marchingSquares = new MarchingSquaresProcessor();
|
||||
}
|
||||
|
||||
[ContextMenu("生成所有阴影碰撞器")]
|
||||
public void GenerateAllShadowColliders()
|
||||
{
|
||||
ClearAllShadowColliders();
|
||||
|
||||
var shadowObjects = ShadowUtils.FindObjectsInLayer(shadowLayerName);
|
||||
foreach (GameObject obj in shadowObjects)
|
||||
{
|
||||
GenerateShadowCollider(obj);
|
||||
}
|
||||
|
||||
Debug.Log($"生成了 {shadowColliders.Count} 个阴影碰撞器");
|
||||
}
|
||||
|
||||
[ContextMenu("更新所有阴影碰撞器")]
|
||||
public void UpdateAllShadowColliders()
|
||||
{
|
||||
foreach (var shadowCollider in shadowColliders)
|
||||
{
|
||||
if (shadowCollider != null)
|
||||
{
|
||||
UpdateShadowCollider(shadowCollider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateShadowCollider(GameObject shadowCaster)
|
||||
{
|
||||
if (!IsValidShadowCaster(shadowCaster)) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 投影网格到墙面
|
||||
var projectedMesh = projector.ProjectMeshToWall(shadowCaster);
|
||||
if (!projectedMesh.IsValid)
|
||||
{
|
||||
Debug.LogWarning($"无法为 {shadowCaster.name} 生成有效投影网格");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 生成阴影密度网格
|
||||
var densityGrid = meshGenerator.GenerateDensityGrid(projectedMesh, shadowWall);
|
||||
|
||||
// 3. 使用Marching Squares提取轮廓
|
||||
var contours = marchingSquares.ExtractContours(densityGrid);
|
||||
if (contours == null || contours.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"无法为 {shadowCaster.name} 提取轮廓");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 创建阴影碰撞器对象
|
||||
var shadowCollider = CreateShadowColliderObject(shadowCaster.name, contours);
|
||||
shadowCollider.sourceObject = shadowCaster;
|
||||
shadowColliders.Add(shadowCollider);
|
||||
|
||||
Debug.Log($"成功为 {shadowCaster.name} 生成阴影碰撞器,包含 {contours.Count} 个轮廓");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"为 {shadowCaster.name} 生成阴影碰撞器时出错: {e.Message}");
|
||||
}
|
||||
}
|
||||
public void UpdateShadowCollider(ShadowCollider shadowCollider)
|
||||
{
|
||||
if (shadowCollider.sourceObject == null) return;
|
||||
|
||||
var projectedMesh = projector.ProjectMeshToWall(shadowCollider.sourceObject);
|
||||
var densityGrid = meshGenerator.GenerateDensityGrid(projectedMesh, shadowWall);
|
||||
var contours = marchingSquares.ExtractContours(densityGrid);
|
||||
|
||||
shadowCollider.UpdateCollider(contours);
|
||||
}
|
||||
|
||||
[ContextMenu("清空所有阴影碰撞器")]
|
||||
public void ClearAllShadowColliders()
|
||||
{
|
||||
foreach (var collider in shadowColliders)
|
||||
{
|
||||
if (collider != null && collider.gameObject != null)
|
||||
{
|
||||
DestroyImmediate(collider.gameObject);
|
||||
}
|
||||
}
|
||||
shadowColliders.Clear();
|
||||
}
|
||||
|
||||
bool IsValidShadowCaster(GameObject obj)
|
||||
{
|
||||
return obj != null &&
|
||||
obj.activeInHierarchy &&
|
||||
obj.GetComponent<MeshFilter>() != null;
|
||||
}
|
||||
|
||||
ShadowCollider CreateShadowColliderObject(string name, List<List<Vector2>> contours)
|
||||
{
|
||||
GameObject colliderObj = new GameObject($"{name}_ShadowCollider");
|
||||
colliderObj.transform.SetParent(shadowWall);
|
||||
colliderObj.transform.localPosition = Vector3.zero;
|
||||
colliderObj.transform.localRotation = Quaternion.identity;
|
||||
|
||||
var shadowCollider = colliderObj.AddComponent<ShadowCollider>();
|
||||
shadowCollider.Initialize(contours);
|
||||
|
||||
return shadowCollider;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
ClearAllShadowColliders();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (marchingSquares != null && shadowWall != null)
|
||||
{
|
||||
marchingSquares.DrawGridGizmos(shadowWall);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
11
Assets/Scripts/Test/Light/New/ShadowColliderSystem.cs.meta
Normal file
11
Assets/Scripts/Test/Light/New/ShadowColliderSystem.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 836b4625937ba9d45be59618cd2a6a15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
123
Assets/Scripts/Test/Light/New/ShadowMeshGenerator.cs
Normal file
123
Assets/Scripts/Test/Light/New/ShadowMeshGenerator.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[System.Serializable]
|
||||
public class ShadowMeshGenerator
|
||||
{
|
||||
[Header("网格设置")]
|
||||
public int gridResolution = 64;
|
||||
public float gridSize = 1.0f;
|
||||
public float influenceRadius = 0.5f;
|
||||
|
||||
public DensityGrid GenerateDensityGrid(ProjectedMesh mesh, Transform wall)
|
||||
{
|
||||
var bounds = CalculateMeshBounds(mesh);
|
||||
var grid = new DensityGrid(bounds, gridResolution, gridSize);
|
||||
|
||||
// 为每个网格点计算密度
|
||||
for (int x = 0; x < grid.Width; x++)
|
||||
{
|
||||
for (int y = 0; y < grid.Height; y++)
|
||||
{
|
||||
Vector2 gridPos = grid.GridToLocalPosition(x, y);
|
||||
float density = CalculateDensityAtPoint(gridPos, mesh);
|
||||
grid.SetValue(x, y, density);
|
||||
}
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
float CalculateDensityAtPoint(Vector2 point, ProjectedMesh mesh)
|
||||
{
|
||||
float totalDensity = 0f;
|
||||
int samples = 0;
|
||||
|
||||
// 采样网格点周围的顶点影响
|
||||
foreach (var vertex in mesh.vertices)
|
||||
{
|
||||
Vector2 vertex2D = new Vector2(vertex.x, vertex.y);
|
||||
float distance = Vector2.Distance(point, vertex2D);
|
||||
|
||||
if (distance < influenceRadius)
|
||||
{
|
||||
float influence = 1f - (distance / influenceRadius);
|
||||
totalDensity += influence;
|
||||
samples++;
|
||||
}
|
||||
}
|
||||
|
||||
return samples > 0 ? totalDensity / samples : 0f;
|
||||
}
|
||||
|
||||
Bounds CalculateMeshBounds(ProjectedMesh mesh)
|
||||
{
|
||||
if (mesh.vertices.Count == 0)
|
||||
return new Bounds(Vector3.zero, Vector3.one);
|
||||
|
||||
Vector3 min = mesh.vertices[0];
|
||||
Vector3 max = mesh.vertices[0];
|
||||
|
||||
foreach (var vertex in mesh.vertices)
|
||||
{
|
||||
min = Vector3.Min(min, vertex);
|
||||
max = Vector3.Max(max, vertex);
|
||||
}
|
||||
|
||||
return new Bounds((min + max) * 0.5f, max - min);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class DensityGrid
|
||||
{
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public float CellSize { get; private set; }
|
||||
public Bounds WorldBounds { get; private set; }
|
||||
|
||||
private float[,] values;
|
||||
private Vector3 gridOrigin;
|
||||
|
||||
public DensityGrid(Bounds bounds, int resolution, float cellSize)
|
||||
{
|
||||
WorldBounds = bounds;
|
||||
CellSize = cellSize;
|
||||
Width = resolution;
|
||||
Height = resolution;
|
||||
|
||||
gridOrigin = bounds.min;
|
||||
values = new float[Width, Height];
|
||||
}
|
||||
|
||||
public void SetValue(int x, int y, float value)
|
||||
{
|
||||
if (x >= 0 && x < Width && y >= 0 && y < Height)
|
||||
{
|
||||
values[x, y] = Mathf.Clamp01(value);
|
||||
}
|
||||
}
|
||||
|
||||
public float GetValue(int x, int y)
|
||||
{
|
||||
if (x >= 0 && x < Width && y >= 0 && y < Height)
|
||||
{
|
||||
return values[x, y];
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public Vector2 GridToLocalPosition(int x, int y)
|
||||
{
|
||||
return new Vector2(
|
||||
x * CellSize + gridOrigin.x,
|
||||
y * CellSize + gridOrigin.y
|
||||
);
|
||||
}
|
||||
|
||||
public Vector3 GridToWorldPosition(int x, int y, Transform wall)
|
||||
{
|
||||
Vector2 localPos = GridToLocalPosition(x, y);
|
||||
return wall.TransformPoint(new Vector3(localPos.x, localPos.y, 0));
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Test/Light/New/ShadowMeshGenerator.cs.meta
Normal file
11
Assets/Scripts/Test/Light/New/ShadowMeshGenerator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85170fe0e92d3604fbf662a3620570e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
136
Assets/Scripts/Test/Light/New/ShadowProjector.cs
Normal file
136
Assets/Scripts/Test/Light/New/ShadowProjector.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[System.Serializable]
|
||||
public class ShadowProjector
|
||||
{
|
||||
public Light lightSource;
|
||||
public Transform projectionWall;
|
||||
public LayerMask obstacleMask;
|
||||
|
||||
private Vector3 wallNormal;
|
||||
private Vector3 wallPosition;
|
||||
|
||||
public ShadowProjector(Light light, Transform wall, LayerMask obstacleMask)
|
||||
{
|
||||
this.lightSource = light;
|
||||
this.projectionWall = wall;
|
||||
this.obstacleMask = obstacleMask;
|
||||
CalculateWallPlane();
|
||||
}
|
||||
|
||||
public ProjectedMesh ProjectMeshToWall(GameObject shadowCaster)
|
||||
{
|
||||
var meshFilter = shadowCaster.GetComponent<MeshFilter>();
|
||||
if (meshFilter == null) return new ProjectedMesh();
|
||||
|
||||
var mesh = meshFilter.mesh;
|
||||
var vertices = mesh.vertices;
|
||||
var triangles = mesh.triangles;
|
||||
|
||||
List<Vector3> projectedVertices = new List<Vector3>();
|
||||
List<int> projectedTriangles = new List<int>();
|
||||
|
||||
// 投影每个顶点
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
Vector3 worldVertex = shadowCaster.transform.TransformPoint(vertices[i]);
|
||||
Vector3 projectedVertex = ProjectPoint(worldVertex);
|
||||
|
||||
if (!IsObstructed(worldVertex, projectedVertex))
|
||||
{
|
||||
projectedVertices.Add(projectedVertex);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新构建三角形(简化版本)
|
||||
// 实际应该使用更复杂的三角化算法
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
if (IsValidTriangle(projectedVertices, triangles, i))
|
||||
{
|
||||
projectedTriangles.Add(triangles[i]);
|
||||
projectedTriangles.Add(triangles[i + 1]);
|
||||
projectedTriangles.Add(triangles[i + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ProjectedMesh
|
||||
{
|
||||
vertices = projectedVertices,
|
||||
triangles = projectedTriangles.ToArray(),
|
||||
sourceObject = shadowCaster
|
||||
};
|
||||
}
|
||||
|
||||
Vector3 ProjectPoint(Vector3 worldPoint)
|
||||
{
|
||||
Vector3 lightDirection = GetLightDirection(worldPoint);
|
||||
return IntersectWithWall(worldPoint, lightDirection);
|
||||
}
|
||||
|
||||
Vector3 GetLightDirection(Vector3 targetPoint)
|
||||
{
|
||||
if (lightSource.type == LightType.Directional)
|
||||
{
|
||||
return -lightSource.transform.forward;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (targetPoint - lightSource.transform.position).normalized;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 IntersectWithWall(Vector3 point, Vector3 direction)
|
||||
{
|
||||
float denominator = Vector3.Dot(direction, wallNormal);
|
||||
|
||||
if (Mathf.Abs(denominator) < 0.0001f)
|
||||
return point;
|
||||
|
||||
float t = Vector3.Dot(wallPosition - point, wallNormal) / denominator;
|
||||
return point + direction * t;
|
||||
}
|
||||
|
||||
bool IsObstructed(Vector3 start, Vector3 end)
|
||||
{
|
||||
Vector3 direction = (end - start).normalized;
|
||||
float distance = Vector3.Distance(start, end);
|
||||
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(start, direction, out hit, distance, obstacleMask))
|
||||
{
|
||||
return hit.collider.gameObject != projectionWall.gameObject;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsValidTriangle(List<Vector3> vertices, int[] triangles, int startIndex)
|
||||
{
|
||||
int i1 = triangles[startIndex];
|
||||
int i2 = triangles[startIndex + 1];
|
||||
int i3 = triangles[startIndex + 2];
|
||||
|
||||
return i1 < vertices.Count && i2 < vertices.Count && i3 < vertices.Count;
|
||||
}
|
||||
|
||||
void CalculateWallPlane()
|
||||
{
|
||||
if (projectionWall != null)
|
||||
{
|
||||
wallPosition = projectionWall.position;
|
||||
wallNormal = -projectionWall.forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct ProjectedMesh
|
||||
{
|
||||
public List<Vector3> vertices;
|
||||
public int[] triangles;
|
||||
public GameObject sourceObject;
|
||||
|
||||
public bool IsValid => vertices != null && vertices.Count >= 3;
|
||||
}
|
||||
11
Assets/Scripts/Test/Light/New/ShadowProjector.cs.meta
Normal file
11
Assets/Scripts/Test/Light/New/ShadowProjector.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 462c6c18ec968ae4badc890a169194e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/Scripts/Test/Light/New/ShadowUtils.cs
Normal file
42
Assets/Scripts/Test/Light/New/ShadowUtils.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
public static class ShadowUtils
|
||||
{
|
||||
public static Light FindMainLight()
|
||||
{
|
||||
Light[] lights = Object.FindObjectsOfType<Light>();
|
||||
return lights.FirstOrDefault(l => l.type == LightType.Directional && l.enabled) ??
|
||||
lights.FirstOrDefault(l => l.type == LightType.Spot && l.enabled);
|
||||
}
|
||||
|
||||
public static Transform FindMainWall()
|
||||
{
|
||||
var walls = Object.FindObjectsOfType<Collider>()
|
||||
.Where(c => c is BoxCollider)
|
||||
.OrderByDescending(c =>
|
||||
{
|
||||
var box = c as BoxCollider;
|
||||
var size = Vector3.Scale(box.size, c.transform.lossyScale);
|
||||
return size.x * size.y;
|
||||
});
|
||||
|
||||
return walls.FirstOrDefault()?.transform;
|
||||
}
|
||||
|
||||
public static List<GameObject> FindObjectsInLayer(string layerName)
|
||||
{
|
||||
int layer = LayerMask.NameToLayer(layerName);
|
||||
return Object.FindObjectsOfType<GameObject>()
|
||||
.Where(obj => obj.activeInHierarchy && obj.layer == layer)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static List<GameObject> FindObjectsInLayer(int layer)
|
||||
{
|
||||
return Object.FindObjectsOfType<GameObject>()
|
||||
.Where(obj => obj.activeInHierarchy && obj.layer == layer)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Test/Light/New/ShadowUtils.cs.meta
Normal file
11
Assets/Scripts/Test/Light/New/ShadowUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5f9eb8ac9da6a8488b456411e1e7370
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user