You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity射线删除网格三角形异常:仅删除背面三角形求助

问题描述

我用Raycast实现Mesh三角形删除功能,射线命中网格时删除对应三角形,但出现异常:仅从网格内部发射射线时,删除的是网格背面的三角形而非正面。尝试调整Mesh设置未解决,附上实现代码,求指导。

Scene视图向下翻转,Game视图显示射线发射物体位于中间:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 14:16:24