【从Unity物理系统开始探索游戏物理】专栏-直达

分离轴定理 Separation axis theorem(SAT)是游戏物理中用于凸多边形碰撞检测的高效算法,其核心思想是通过寻找能将两物体投影分离的轴线来判断碰撞:若存在任一轴线使投影不重叠,则物体未碰撞;若所有轴线投影均重叠,则发生碰撞.

解决的问题

SAT主要用于解决‌凸多边形间的精确碰撞检测‌问题,相比AABB(轴对齐包围盒)或圆形检测,它能处理更复杂的几何形状(如不规则多边形),且计算效率较高(O(n²)时间复杂度)。其局限性在于仅适用于凸多边形,凹多边形需先分解为凸多边形组合。

算法原理与解决步骤

  • 分离轴定义‌:若两物体未碰撞,必存在一条直线(分离轴)使其投影不重叠。
  • 关键步骤‌:
    • 计算法向量‌:遍历多边形每条边,计算其垂直向量(法向量)作为潜在分离轴。
    • 投影检测‌:将两多边形顶点投影到每条法向量上,得到投影区间。
    • 区间重叠判断‌:若任一轴上投影区间无重叠,则物体未碰撞;否则继续检测,全部通过则判定碰撞。

历史发展

SAT起源于计算几何领域,早期用于机器人路径规划中的碰撞预测。随着游戏物理引擎(如Unity的PhysX)普及,SAT因实现简单、性能适中成为Narrow Phase(精细检测)的常用算法。现代优化结合了Burst Compiler等加速技术。

C#实现示例

2D凸多边形的SAT检测

代码说明:

  • 核心逻辑‌:通过遍历所有边的法向量作为分离轴,检测投影区间重叠。

  • 性能优化‌:使用向量点积计算投影,避免复杂数学运算。

  • 扩展性‌:可结合Unity的Collider组件实现更复杂物理效果。

  • SatCollision.cs

    using UnityEngine;
    using System.Collections.Generic;
    
    public class SATCollision : MonoBehaviour {
        // 检测两个凸多边形是否碰撞
        public static bool CheckCollision(Vector2[] polyA, Vector2[] polyB) {
            // 检查多边形A的边法向量
            foreach (Vector2 edge in GetEdges(polyA)) {
                Vector2 normal = new Vector2(-edge.y, edge.x).normalized;
                if (!OverlapOnAxis(polyA, polyB, normal))
                    return false;
            }
            // 检查多边形B的边法向量
            foreach (Vector2 edge in GetEdges(polyB)) {
                Vector2 normal = new Vector2(-edge.y, edge.x).normalized;
                if (!OverlapOnAxis(polyA, polyB, normal))
                    return false;
            }
            return true;
        }
    
        // 获取多边形所有边向量
        private static List<Vector2> GetEdges(Vector2[] polygon) {
            List<Vector2> edges = new List<Vector2>();
            for (int i = 0; i < polygon.Length; i++) {
                Vector2 next = polygon[(i + 1) % polygon.Length];
                edges.Add(next - polygon[i]);
            }
            return edges;
        }
    
        // 检查两多边形在指定轴上的投影是否重叠
        private static bool OverlapOnAxis(Vector2[] polyA, Vector2[] polyB, Vector2 axis) {
            float minA = float.MaxValue, maxA = float.MinValue;
            float minB = float.MaxValue, maxB = float.MinValue;
    
            // 计算多边形A的投影区间
            foreach (Vector2 point in polyA) {
                float projection = Vector2.Dot(point, axis);
                minA = Mathf.Min(minA, projection);
                maxA = Mathf.Max(maxA, projection);
            }
    
            // 计算多边形B的投影区间
            foreach (Vector2 point in polyB) {
                float projection = Vector2.Dot(point, axis);
                minB = Mathf.Min(minB, projection);
                maxB = Mathf.Max(maxB, projection);
            }
    
            return maxA >= minB && maxB >= minA;
        }
    }
    

3D凸多边形的SAT检测

代码功能说明:

  • Sat3D扩展至3D空间,增加面法线和边叉积检测

  • 测试程序展示两个相交立方体和矩形的检测示例

  • Sat3D.cs

    public static class Sat3D
    {
        public static bool CheckCollision(Vector3[] verticesA, Vector3[] verticesB, 
            Vector3[] normalsA, Vector3[] normalsB)
        {
            // 检查物体A的面法线
            foreach (var normal in normalsA)
            {
                if (!OverlapOnAxis(verticesA, verticesB, normal))
                    return false;
            }
            
            // 检查物体B的面法线
            foreach (var normal in normalsB)
            {
                if (!OverlapOnAxis(verticesA, verticesB, normal))
                    return false;
            }
            
            // 检查所有边的叉积方向
            for (int i = 0; i < normalsA.Length; i++)
            {
                for (int j = 0; j < normalsB.Length; j++)
                {
                    Vector3 axis = Vector3.Cross(normalsA[i], normalsB[j]);
                    if (axis.X == 0 && axis.Y == 0 && axis.Z == 0) continue;
                    
                    if (!OverlapOnAxis(verticesA, verticesB, axis))
                        return false;
                }
            }
            
            return true;
        }
        
        private static bool OverlapOnAxis(Vector3[] verticesA, Vector3[] verticesB, Vector3 axis)
        {
            float minA = float.MaxValue, maxA = float.MinValue;
            float minB = float.MaxValue, maxB = float.MinValue;
            
            foreach (var vertex in verticesA)
            {
                float projection = Vector3.Dot(vertex, axis);
                minA = Math.Min(minA, projection);
                maxA = Math.Max(maxA, projection);
            }
            
            foreach (var vertex in verticesB)
            {
                float projection = Vector3.Dot(vertex, axis);
                minB = Math.Min(minB, projection);
                maxB = Math.Max(maxB, projection);
            }
            
            return maxA >= minB && maxB >= minA;
        }
    }
    
  • Program.cs

    class Program
    {
        static void Main()
        {
            // 3D测试用例
            Vector3[] cubeA = {
                new Vector3(0,0,0), new Vector3(1,0,0),
                new Vector3(1,1,0), new Vector3(0,1,0),
                new Vector3(0,0,1), new Vector3(1,0,1),
                new Vector3(1,1,1), new Vector3(0,1,1)
            };
            
            Vector3[] cubeB = {
                new Vector3(0.5f,0.5f,0.5f), new Vector3(1.5f,0.5f,0.5f),
                new Vector3(1.5f,1.5f,0.5f), new Vector3(0.5f,1.5f,0.5f),
                new Vector3(0.5f,0.5f,1.5f), new Vector3(1.5f,0.5f,1.5f),
                new Vector3(1.5f,1.5f,1.5f), new Vector3(0.5f,1.5f,1.5f)
            };
            
            Vector3[] normalsA = {
                new Vector3(1,0,0), new Vector3(0,1,0),
                new Vector3(0,0,1), new Vector3(-1,0,0),
                new Vector3(0,-1,0), new Vector3(0,0,-1)
            };
            
            bool collision3D = Sat3D.CheckCollision(cubeA, cubeB, normalsA, normalsA);
            Console.WriteLine($"3D碰撞检测结果: {collision3D}");
        }
    }
    

应用场景

  • 游戏开发‌:精确检测角色与复杂环境的碰撞。
  • 物理模拟‌:如弹球游戏中多边形物体的交互。
  • 机器人导航‌:障碍物碰撞预测

【从Unity物理系统开始探索游戏物理】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐