Unity射线删除网格三角形异常:仅删除背面三角形求助
问题描述
我用Raycast实现Mesh三角形删除功能,射线命中网格时删除对应三角形,但出现异常:仅从网格内部发射射线时,删除的是网格背面的三角形而非正面。尝试调整Mesh设置未解决,附上实现代码,求指导。
Scene视图向下翻转,Game视图显示射线发射物体位于中间:
实现代码
using System.Linq; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using System.Collections.Generic; using System.Numerics; using Vector2 = UnityEngine.Vector2; using Vector3 = UnityEngine.Vector3; using Vector4 = UnityEngine.Vector4; public class RemoveTriangle : MonoBehaviour { [Header("Game Object")][Tooltip("The game object that will be removed its triangles")] public GameObject spherePrefab; // Better to reference those already in the Inspector [Header("Mesh Files to Load")] [SerializeField][Tooltip("Drag and drop the mesh files you want to load")] private MeshFilter meshFilter; [SerializeField][Tooltip("Drag and drop the mesh files you want to load")] private MeshRenderer meshRenderer; [SerializeField][Tooltip("Drag and drop the mesh files you want to load")] private MeshCollider meshCollider; //[SerializeField] [Range(1.0f, 3.0f)] public float raycastSize = 1.0f; [Header("Origin and Target objects")] [Tooltip("Drag and drop the origin and target objects")]public Transform SelectOrigin; [Tooltip("Drag and drop the origin and target objects")]public MeshFilter SelectTarget; [Header("Raycast Distance")][Tooltip("The distance of the raycast")] [SerializeField][Range(0.1f,20.0f)]public float MaxDistance = 10f; private Mesh mesh; private void Awake() { MeshProperties(); } private void Update() { DeleteTriangle(); } void MeshProperties() {// Get the mesh from the mesh filter if (!meshFilter) meshFilter = GetComponent<MeshFilter>(); if (!meshRenderer) meshRenderer = GetComponent<MeshRenderer>(); if (!meshCollider) meshCollider = GetComponent<MeshCollider>(); mesh = meshFilter.mesh; }// MeshProperties void DeleteTriangle() {//raycast from mouse position if (Input.GetKey(KeyCode.C)) {//raycast from mouse position Ray ray = new Ray(SelectOrigin.position, SelectOrigin.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit, MaxDistance)) {//if raycast hits a triangle Debug.DrawRay(SelectOrigin.position, SelectTarget.transform.position - SelectOrigin.position, Color.red); Debug.Log("Triangle Removed at: " + hit.point + "/" + hit.triangleIndex + "/" + hit.normal); //cube.transform.position = hit.point; // Get current vertices, triangles, uvs,tangents and normals of the mesh Vector3[] vertices = mesh.vertices; Vector2[] uv = mesh.uv; Vector4[] tangents = mesh.tangents; Vector3[] normals = mesh.normals; int[] triangles = mesh.triangles; // Get the vert indices for this triangle int vertIndex_1 = triangles[hit.triangleIndex * 3 + 0]; int vertIndex_2 = triangles[hit.triangleIndex * 3 + 1]; int vertIndex_3 = triangles[hit.triangleIndex * 3 + 2]; // Get the vertices for this triangle var vert_1 = vertices[vertIndex_1]; var vert_2 = vertices[vertIndex_2]; var vert_3 = vertices[vertIndex_3]; // Get the uv for this triangle var uv_1 = uv[vertIndex_1]; var uv_2 = uv[vertIndex_2]; var uv_3 = uv[vertIndex_3]; //Get the Normals for this triangle var normal_1 = mesh.normals[vertIndex_1]; var normal_2 = mesh.normals[vertIndex_2]; var normal_3 = mesh.normals[vertIndex_3]; //Get Tangents for this triangle Vector4 tangent_1 = mesh.tangents[vertIndex_1]; Vector4 tangent_2 = mesh.tangents[vertIndex_2]; Vector4 tangent_3 = mesh.tangents[vertIndex_3]; // Get the positions for the vertices var vertPos_1 = vertices[vertIndex_1]; var vertPos_2 = vertices[vertIndex_2]; var vertPos_3 = vertices[vertIndex_3]; // Get the center of the triangle var center = (vertPos_1 + vertPos_2 + vertPos_3) / 3; // Now for all three vertices we first check if any other triangle if using it // by simply count how often the indices are used in the triangles list var verticesOccur_1 = 0; var verticesOccur_2 = 0; var verticesOccur_3 = 0; for (var i = 0; i < triangles.Length; i++) { if (triangles[i] == vertIndex_1) verticesOccur_1++; if (triangles[i] == vertIndex_2) verticesOccur_2++; if (triangles[i] == vertIndex_3) verticesOccur_3++; }//end for // Remove the vertices if (verticesOccur_1 == 1) vertices[vertIndex_1] = Vector3.zero; if (verticesOccur_2 == 1) vertices[vertIndex_2] = Vector3.zero; if (verticesOccur_3 == 1) vertices[vertIndex_3] = Vector3.zero; // Remove the uv if (verticesOccur_1 == 1) uv[vertIndex_1] = Vector2.zero; if (verticesOccur_2 == 1) uv[vertIndex_2] = Vector2.zero; if (verticesOccur_3 == 1) uv[vertIndex_3] = Vector2.zero; // Remove the normals if (verticesOccur_1 == 1) mesh.normals[vertIndex_1] = Vector3.zero; if (verticesOccur_2 == 1) mesh.normals[vertIndex_2] = Vector3.zero; if (verticesOccur_3 == 1) mesh.normals[vertIndex_3] = Vector3.zero; // Remove the tangents if (verticesOccur_1 == 1) mesh.tangents[vertIndex_1] = Vector4.zero; if (verticesOccur_2 == 1) mesh.tangents[vertIndex_2] = Vector4.zero; if (verticesOccur_3 == 1) mesh.tangents[vertIndex_3] = Vector4.zero; //fix the triangles //for (var i = 0; i < triangles.Length; i++) //{ //if (triangles[i] == vertIndex_1) triangles[i] = 0; // (triangles[i] == vertIndex_2) triangles[i] = 0; //if (triangles[i] == vertIndex_3) triangles[i] = 0; //}] // Find the intersecting triangles by checking if the ray intersects with their vertices //var intersectingTriangles = new List<int>(); /* for (var i = 0; i < triangles.Length; i += 3) { var triangleVertIndex_1 = triangles[i + 0]; var triangleVertIndex_2 = triangles[i + 1]; var triangleVertIndex_3 = triangles[i + 2]; var triangleVertPos_1 = vertices[triangleVertIndex_1]; var triangleVertPos_2 = vertices[triangleVertIndex_2]; var triangleVertPos_3 = vertices[triangleVertIndex_3]; }*/ // Remove the triangles and update the mesh triangles[hit.triangleIndex * 3] = 0; triangles[hit.triangleIndex * 3 + 1] = 0; triangles[hit.triangleIndex * 3 + 2] = 0; //update the mesh with the new vertices, uv, normals and tangents mesh.vertices = vertices; mesh.uv = uv; mesh.tangents = tangents; mesh.normals = normals; //update the tirangles mesh.triangles = triangles; // Recalculate the normals mesh.RecalculateNormals(); // Recalculate the tangents mesh.RecalculateTangents(); // Recalculate the bounds mesh.RecalculateBounds(); }//end of if else { Debug.Log("No Triangle to remove" + hit.point); }//end of if hit.triangleIndex }//end of remove triangle }//end of class }//end of class
解决方案
问题根源
Unity的MeshCollider默认只检测顶点顺时针顺序的正面,当射线从网格内部发射时,命中的是三角形背面,hit.triangleIndex返回的是背面索引,导致删除错误的三角形。同时原代码直接将三角形索引设为0的删除方式会引发网格异常,因为0号顶点可能被其他三角形引用。
修复步骤
1. 修正射线命中的正反面判断
在射线命中后,通过法线与射线方向的点积判断是否命中背面,若为背面则反转射线重新检测,获取正确的正面三角形索引:
if (Physics.Raycast(ray, out hit, MaxDistance)) { Vector3 worldNormal = hit.transform.TransformDirection(hit.normal); float dot = Vector3.Dot(ray.direction, worldNormal); // 点积为正说明射线从内部射出,命中背面 if (dot > 0) { Ray reverseRay = new Ray(hit.point, -ray.direction); if (Physics.Raycast(reverseRay, out RaycastHit reverseHit, 0.1f)) { hit.triangleIndex = reverseHit.triangleIndex; } } // 后续删除逻辑... }
2. 正确删除三角形与清理顶点
不要直接修改索引值,而是创建新数组排除目标三角形,并清理未被使用的顶点,重新映射索引:
// 统计每个顶点的使用次数 Dictionary<int, int> vertexUsage = new Dictionary<int, int>(); foreach (int idx in triangles) { if (vertexUsage.ContainsKey(idx)) vertexUsage[idx]++; else vertexUsage[idx] = 1; } // 减去要删除三角形的顶点使用次数 int triStart = hit.triangleIndex * 3; vertexUsage[triangles[triStart]]--; vertexUsage[triangles[triStart+1]]--; vertexUsage[triangles[triStart+2]]--; // 构建新顶点数组与索引映射 List<Vector3> newVertices = new List<Vector3>(); List<Vector2> newUVs = new List<Vector2>(); List<Vector3> newNormals = new List<Vector3>(); List<Vector4> newTangents = new List<Vector4>(); Dictionary<int, int> indexMap = new Dictionary<int, int>(); for (int i = 0; i < vertices.Length; i++) { if (vertexUsage.ContainsKey(i) && vertexUsage[i] > 0) { indexMap[i] = newVertices.Count; newVertices.Add(vertices[i]); newUVs.Add(uv[i]); newNormals.Add(normals[i]); newTangents.Add(tangents[i]); } } // 构建新三角形数组 List<int> newTriangles = new List<int>(); for (int i = 0; i < triangles.Length; i += 3) { if (i / 3 != hit.triangleIndex) { newTriangles.Add(indexMap[triangles[i]]); newTriangles.Add(indexMap[triangles[i+1]]); newTriangles.Add(indexMap[triangles[i+2]]); } } // 更新网格 mesh.Clear(); mesh.vertices = newVertices.ToArray(); mesh.uv = newUVs.ToArray(); mesh.normals = newNormals.ToArray(); mesh.tangents = newTangents.ToArray(); mesh.triangles = newTriangles.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateTangents(); mesh.RecalculateBounds();
3. 更新MeshCollider
修改Mesh后需要重置碰撞体,确保碰撞检测同步:
meshCollider.sharedMesh = null; meshCollider.sharedMesh = mesh;
完整修改后的DeleteTriangle方法
void DeleteTriangle() { if (Input.GetKey(KeyCode.C)) { Ray ray = new Ray(SelectOrigin.position, SelectOrigin.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit, MaxDistance)) { Debug.DrawRay(SelectOrigin.position, SelectTarget.transform.position - SelectOrigin.position, Color.red); // 处理背面命中 Vector3 worldNormal = hit.transform.TransformDirection(hit.normal); float dot = Vector3.Dot(ray.direction, worldNormal); if (dot > 0) { Ray reverseRay = new Ray(hit.point, -ray.direction); if (Physics.Raycast(reverseRay, out RaycastHit reverseHit, 0.1f)) { hit.triangleIndex = reverseHit.triangleIndex; } } Debug.Log("Triangle Removed at: " + hit.point + "/" + hit.triangleIndex + "/" + hit.normal); Vector3[] vertices = mesh.vertices; Vector2[] uv = mesh.uv; Vector3[] normals = mesh.normals; Vector4[] tangents = mesh.tangents; int[] triangles = mesh.triangles; // 统计顶点使用次数 Dictionary<int, int> vertexUsage = new Dictionary<int, int>(); foreach (int idx in triangles) { if (vertexUsage.ContainsKey(idx)) vertexUsage[idx]++; else vertexUsage[idx] = 1; } // 扣除要删除三角形的顶点使用次数 int triStartIdx = hit.triangleIndex * 3; vertexUsage[triangles[triStartIdx]]--; vertexUsage[triangles[triStartIdx+1]]--; vertexUsage[triangles[triStartIdx+2]]--; // 构建新顶点数组和索引映射 List<Vector3> newVertices = new List<Vector3>(); List<Vector2> newUVs = new List<Vector2>(); List<Vector3> newNormals = new List<Vector3>(); List<Vector4> newTangents = new List<Vector4>(); Dictionary<int, int> indexMap = new Dictionary<int, int>(); for (int i = 0; i < vertices.Length; i++) { if (vertexUsage.ContainsKey(i) && vertexUsage[i] > 0) { indexMap[i] = newVertices.Count; newVertices.Add(vertices[i]); newUVs.Add(uv[i]); newNormals.Add(normals[i]); newTangents.Add(tangents[i]); } } // 构建新三角形数组 List<int> newTriangles = new List<int>(); for (int i = 0; i < triangles.Length; i += 3) { if (i / 3 != hit.triangleIndex) { newTriangles.Add(indexMap[triangles[i]]); newTriangles.Add(indexMap[triangles[i+1]]); newTriangles.Add(indexMap[triangles[i+2]]); } } // 更新网格数据 mesh.Clear(); mesh.vertices = newVertices.ToArray(); mesh.uv = newUVs.ToArray(); mesh.normals = newNormals.ToArray(); mesh.tangents = newTangents.ToArray(); mesh.triangles = newTriangles.ToArray(); mesh.RecalculateNormals(); mesh.RecalculateTangents(); mesh.RecalculateBounds(); // 更新碰撞体 meshCollider.sharedMesh = null; meshCollider.sharedMesh = mesh; } else { Debug.Log("No Triangle to remove"); } } }
内容的提问来源于stack exchange,提问作者user20980680
相关产品推荐
相关产品推荐

