336 lines
11 KiB
C#
336 lines
11 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
|
||
public class MeshShadowProjector : MonoBehaviour
|
||
{
|
||
[Header("投影设置")]
|
||
public Light shadowLight;
|
||
public Transform shadowWall;
|
||
public string shadowLayerName = "Shadow";
|
||
|
||
[Header("Mesh设置")]
|
||
public bool keepOriginalMesh = true; // 是否保留原始mesh
|
||
public float shadowIntensity = 0.5f; // 阴影强度
|
||
|
||
private List<GameObject> modifiedObjects = new List<GameObject>();
|
||
private Vector3 wallNormal;
|
||
private Vector3 wallPosition;
|
||
|
||
void Start()
|
||
{
|
||
if (shadowLight == null) shadowLight = FindObjectOfType<Light>();
|
||
if (shadowWall == null) shadowWall = FindMainWall();
|
||
|
||
CalculateWallPlane();
|
||
GenerateAllShadowMeshes();
|
||
}
|
||
|
||
[ContextMenu("生成所有阴影Mesh")]
|
||
public void GenerateAllShadowMeshes()
|
||
{
|
||
ClearAllShadowMeshes();
|
||
|
||
var shadowObjects = FindObjectsInLayer(shadowLayerName);
|
||
foreach (GameObject obj in shadowObjects)
|
||
{
|
||
GenerateShadowMesh(obj);
|
||
}
|
||
|
||
Debug.Log($"为 {modifiedObjects.Count} 个物体生成了阴影Mesh");
|
||
}
|
||
|
||
void GenerateShadowMesh(GameObject shadowCaster)
|
||
{
|
||
MeshFilter meshFilter = shadowCaster.GetComponent<MeshFilter>();
|
||
if (meshFilter == null) return;
|
||
|
||
// 获取原始mesh
|
||
Mesh originalMesh = meshFilter.mesh;
|
||
|
||
// 创建新的mesh(组合原始mesh + 投影mesh)
|
||
Mesh combinedMesh = CreateCombinedMesh(originalMesh, shadowCaster.transform);
|
||
|
||
// 应用新mesh
|
||
if (keepOriginalMesh)
|
||
{
|
||
// 创建新物体来存放阴影mesh
|
||
CreateShadowObject(shadowCaster, combinedMesh);
|
||
}
|
||
else
|
||
{
|
||
// 直接修改原物体
|
||
meshFilter.mesh = combinedMesh;
|
||
UpdateMeshCollider(shadowCaster, combinedMesh);
|
||
modifiedObjects.Add(shadowCaster);
|
||
}
|
||
}
|
||
|
||
Mesh CreateCombinedMesh(Mesh originalMesh, Transform objectTransform)
|
||
{
|
||
// 创建新mesh
|
||
Mesh newMesh = new Mesh();
|
||
|
||
List<Vector3> vertices = new List<Vector3>();
|
||
List<int> triangles = new List<int>();
|
||
List<Vector3> normals = new List<Vector3>();
|
||
List<Vector2> uv = new List<Vector2>();
|
||
|
||
// 1. 添加原始mesh的顶点和三角形
|
||
vertices.AddRange(originalMesh.vertices);
|
||
triangles.AddRange(originalMesh.triangles);
|
||
|
||
// 复制原始法线和UV
|
||
if (originalMesh.normals.Length == originalMesh.vertices.Length)
|
||
normals.AddRange(originalMesh.normals);
|
||
else
|
||
for (int i = 0; i < originalMesh.vertices.Length; i++)
|
||
normals.Add(Vector3.up);
|
||
|
||
if (originalMesh.uv.Length == originalMesh.vertices.Length)
|
||
uv.AddRange(originalMesh.uv);
|
||
else
|
||
for (int i = 0; i < originalMesh.vertices.Length; i++)
|
||
uv.Add(Vector2.zero);
|
||
|
||
// 2. 添加投影到墙面的顶点
|
||
int originalVertexCount = vertices.Count;
|
||
List<Vector3> projectedVertices = new List<Vector3>();
|
||
List<int> projectedTriangles = new List<int>();
|
||
|
||
// 投影每个顶点到墙面
|
||
foreach (Vector3 localVertex in originalMesh.vertices)
|
||
{
|
||
Vector3 worldVertex = objectTransform.TransformPoint(localVertex);
|
||
Vector3 projectedWorld = ProjectPointToWall(worldVertex);
|
||
Vector3 projectedLocal = objectTransform.InverseTransformPoint(projectedWorld);
|
||
|
||
projectedVertices.Add(projectedLocal);
|
||
}
|
||
|
||
// 添加投影顶点
|
||
vertices.AddRange(projectedVertices);
|
||
|
||
// 为投影顶点添加法线和UV
|
||
for (int i = 0; i < projectedVertices.Count; i++)
|
||
{
|
||
normals.Add(-objectTransform.forward); // 面向墙面
|
||
uv.Add(new Vector2(1, 1)); // 使用不同的UV区域
|
||
}
|
||
|
||
// 3. 创建连接原始mesh和投影mesh的侧面三角形
|
||
for (int i = 0; i < originalMesh.triangles.Length; i += 3)
|
||
{
|
||
int v1 = originalMesh.triangles[i];
|
||
int v2 = originalMesh.triangles[i + 1];
|
||
int v3 = originalMesh.triangles[i + 2];
|
||
|
||
// 对应的投影顶点索引
|
||
int p1 = v1 + originalVertexCount;
|
||
int p2 = v2 + originalVertexCount;
|
||
int p3 = v3 + originalVertexCount;
|
||
|
||
// 创建侧面四边形(两个三角形)
|
||
// 三角形1
|
||
projectedTriangles.Add(v1);
|
||
projectedTriangles.Add(p1);
|
||
projectedTriangles.Add(v2);
|
||
|
||
projectedTriangles.Add(v2);
|
||
projectedTriangles.Add(p1);
|
||
projectedTriangles.Add(p2);
|
||
|
||
// 三角形2
|
||
projectedTriangles.Add(v2);
|
||
projectedTriangles.Add(p2);
|
||
projectedTriangles.Add(v3);
|
||
|
||
projectedTriangles.Add(v3);
|
||
projectedTriangles.Add(p2);
|
||
projectedTriangles.Add(p3);
|
||
|
||
// 三角形3
|
||
projectedTriangles.Add(v3);
|
||
projectedTriangles.Add(p3);
|
||
projectedTriangles.Add(v1);
|
||
|
||
projectedTriangles.Add(v1);
|
||
projectedTriangles.Add(p3);
|
||
projectedTriangles.Add(p1);
|
||
}
|
||
|
||
// 4. 添加投影面的三角形(反转原始三角形)
|
||
int[] originalTriangles = originalMesh.triangles;
|
||
for (int i = 0; i < originalTriangles.Length; i += 3)
|
||
{
|
||
// 反转 winding order
|
||
projectedTriangles.Add(originalTriangles[i + 2] + originalVertexCount);
|
||
projectedTriangles.Add(originalTriangles[i + 1] + originalVertexCount);
|
||
projectedTriangles.Add(originalTriangles[i] + originalVertexCount);
|
||
}
|
||
|
||
// 5. 合并所有三角形
|
||
triangles.AddRange(projectedTriangles);
|
||
|
||
// 6. 应用到新mesh
|
||
newMesh.vertices = vertices.ToArray();
|
||
newMesh.triangles = triangles.ToArray();
|
||
newMesh.normals = normals.ToArray();
|
||
newMesh.uv = uv.ToArray();
|
||
|
||
newMesh.RecalculateBounds();
|
||
newMesh.RecalculateTangents();
|
||
|
||
return newMesh;
|
||
}
|
||
|
||
void CreateShadowObject(GameObject original, Mesh shadowMesh)
|
||
{
|
||
GameObject shadowObject = new GameObject($"{original.name}_ShadowMesh");
|
||
shadowObject.transform.SetParent(original.transform);
|
||
shadowObject.transform.localPosition = Vector3.zero;
|
||
shadowObject.transform.localRotation = Quaternion.identity;
|
||
shadowObject.transform.localScale = Vector3.one;
|
||
|
||
// 添加Mesh Filter
|
||
MeshFilter meshFilter = shadowObject.AddComponent<MeshFilter>();
|
||
meshFilter.mesh = shadowMesh;
|
||
|
||
// 添加Mesh Collider
|
||
MeshCollider meshCollider = shadowObject.AddComponent<MeshCollider>();
|
||
meshCollider.sharedMesh = shadowMesh;
|
||
meshCollider.convex = false; // 对于复杂mesh使用非凸体
|
||
|
||
// 可选:添加材质用于可视化
|
||
MeshRenderer renderer = shadowObject.AddComponent<MeshRenderer>();
|
||
renderer.material = original.GetComponent<MeshRenderer>().material;
|
||
|
||
modifiedObjects.Add(shadowObject);
|
||
}
|
||
|
||
void UpdateMeshCollider(GameObject obj, Mesh mesh)
|
||
{
|
||
MeshCollider meshCollider = obj.GetComponent<MeshCollider>();
|
||
if (meshCollider == null)
|
||
meshCollider = obj.AddComponent<MeshCollider>();
|
||
|
||
meshCollider.sharedMesh = mesh;
|
||
meshCollider.convex = false;
|
||
}
|
||
|
||
Material CreateShadowMaterial()
|
||
{
|
||
Material material = new Material(Shader.Find("Standard"));
|
||
material.color = new Color(0, 0, 0, shadowIntensity);
|
||
material.SetFloat("_Mode", 2); // Fade mode
|
||
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
|
||
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
|
||
material.SetInt("_ZWrite", 0);
|
||
material.DisableKeyword("_ALPHATEST_ON");
|
||
material.EnableKeyword("_ALPHABLEND_ON");
|
||
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||
material.renderQueue = 3000;
|
||
return material;
|
||
}
|
||
|
||
Vector3 ProjectPointToWall(Vector3 worldPoint)
|
||
{
|
||
Vector3 lightDirection = GetLightDirection(worldPoint);
|
||
return IntersectWithWall(worldPoint, lightDirection);
|
||
}
|
||
|
||
Vector3 GetLightDirection(Vector3 targetPoint)
|
||
{
|
||
if (shadowLight.type == LightType.Directional)
|
||
{
|
||
return -shadowLight.transform.forward;
|
||
}
|
||
else
|
||
{
|
||
return (targetPoint - shadowLight.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;
|
||
}
|
||
|
||
void CalculateWallPlane()
|
||
{
|
||
if (shadowWall != null)
|
||
{
|
||
wallPosition = shadowWall.position;
|
||
wallNormal = -shadowWall.forward;
|
||
}
|
||
}
|
||
|
||
Transform FindMainWall()
|
||
{
|
||
BoxCollider[] colliders = FindObjectsOfType<BoxCollider>();
|
||
return colliders.Length > 0 ? colliders[0].transform : transform;
|
||
}
|
||
|
||
List<GameObject> FindObjectsInLayer(string layerName)
|
||
{
|
||
int layer = LayerMask.NameToLayer(layerName);
|
||
List<GameObject> result = new List<GameObject>();
|
||
|
||
foreach (GameObject obj in FindObjectsOfType<GameObject>())
|
||
{
|
||
if (obj.activeInHierarchy && obj.layer == layer)
|
||
{
|
||
result.Add(obj);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
[ContextMenu("清空所有阴影Mesh")]
|
||
void ClearAllShadowMeshes()
|
||
{
|
||
foreach (GameObject obj in modifiedObjects)
|
||
{
|
||
if (obj != null)
|
||
{
|
||
if (obj.name.EndsWith("_ShadowMesh"))
|
||
DestroyImmediate(obj);
|
||
else
|
||
ResetOriginalMesh(obj);
|
||
}
|
||
}
|
||
modifiedObjects.Clear();
|
||
}
|
||
|
||
void ResetOriginalMesh(GameObject obj)
|
||
{
|
||
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
|
||
if (meshFilter != null && meshFilter.mesh != null)
|
||
{
|
||
// 重新加载原始mesh
|
||
Mesh originalMesh = Instantiate(Resources.Load<Mesh>(obj.name)) ?? CreateSimpleMesh();
|
||
meshFilter.mesh = originalMesh;
|
||
|
||
MeshCollider collider = obj.GetComponent<MeshCollider>();
|
||
if (collider != null)
|
||
collider.sharedMesh = originalMesh;
|
||
}
|
||
}
|
||
|
||
Mesh CreateSimpleMesh()
|
||
{
|
||
// 创建默认的立方体mesh
|
||
return GameObject.CreatePrimitive(PrimitiveType.Cube).GetComponent<MeshFilter>().sharedMesh;
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
ClearAllShadowMeshes();
|
||
}
|
||
} |