尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Unity机械臂抓取实战:碰撞检测与姿态解算协同实现稳定抓取

Unity机械臂抓取实战:碰撞检测与姿态解算协同实现稳定抓取 1. 从上一阶段的遗留问题说起为什么碰撞检测和姿态解算必须放在一起做如果你跟着这个系列的第一篇一路做下来大概率已经能让机械臂在场景里动起来了——关节能转、末端能跟着目标点走、看起来像那么回事。但真正上手抓东西的时候问题就全冒出来了夹爪明明对准了方块结果穿模而过或者末端到了位置姿态却是歪的夹爪以一个诡异的角度怼上去方块直接被弹飞。这些现象背后其实是两个独立但必须协同的子系统在打架碰撞检测负责“别穿过去”姿态解算负责“以正确的角度贴上去”。任何一个没调好抓取都会失败。这篇内容就是来解决这个阶段的典型问题的。我会把 Unity 里机械臂从“能动能走”推进到“能稳稳抓住一个物体”的完整链路拆开讲包括碰撞体的选型与配置、逆向运动学IK求解姿态时的坑、四元数插值的正确用法、以及抓取瞬间的物理参数调优。适合已经做过基础关节驱动、想让机械臂真正干活的开发者也适合做数字孪生、工业仿真、教育演示这类项目、需要机械臂和场景物体产生真实交互的朋友。整篇内容基于 Unity 2022 LTS 和通用渲染管线验证其他版本思路一致参数需要微调。我个人的习惯是机械臂控制这件事先保证碰撞不出错再谈姿态精不精准。因为碰撞穿模是“硬伤”一旦发生后面所有逻辑都白搭而姿态偏差是“软伤”可以通过迭代和补偿慢慢收敛。所以下面的顺序也是先碰撞、后姿态、最后合起来做抓取。2. 碰撞检测机械臂抓取的第一道门槛2.1 为什么机械臂的碰撞体不能直接用 Mesh Collider新手最容易犯的错就是给机械臂每个连杆直接挂Mesh Collider觉得“模型长什么样碰撞体就长什么样最准”。实测下来这是灾难。机械臂的连杆模型往往有倒角、螺纹、线缆槽这些细节Mesh Collider会把这些全部纳入碰撞计算三角面数量轻松上万。六个关节串起来每帧的碰撞检测开销直接让帧率掉到个位数。更麻烦的是Mesh Collider默认不是凸体non-convex两个非凸碰撞体之间的检测在 PhysX 里是受限的动态物体之间的碰撞经常出现“该碰不碰”的诡异现象。正确的做法是用基础几何体拼出碰撞代理。每个连杆用一到两个Box Collider或Capsule Collider近似关节处用一个Sphere Collider覆盖旋转区域。这样碰撞体面数从万级降到几十检测效率提升两个数量级而且凸体之间的碰撞结果稳定可预测。具体配置上我一般这样处理连杆主体Box Collider尺寸贴合连杆外形Center对齐连杆质心关节连接处Sphere Collider半径略大于关节外壳保证旋转时不会露出缝隙夹爪指尖Box Collider这是唯一需要精确的地方因为要和被抓物体接触注意碰撞代理不需要和视觉模型完全重合允许有 5% 到 10% 的偏差。宁可碰撞体略小一点也不要过大否则机械臂会在还没碰到物体时就触发碰撞导致 IK 求解失败。2.2 碰撞层Layer与碰撞矩阵的规划机械臂场景里通常有三类物体机械臂自身、被抓目标、环境障碍。如果全部放在 Default 层机械臂的连杆之间会互相碰撞IK 求解时关节还没到位就被自己的碰撞体挡住解算器直接报无解。所以必须做层分离。我的分层方案是这样的层名用途与其他层的碰撞关系RobotArm机械臂所有连杆和关节与 Target、Obstacle 碰撞与自身不碰撞Target被抓取的物体与 RobotArm、Obstacle 碰撞Obstacle桌面、围栏等环境与所有层碰撞Gripper夹爪指尖单独一层仅与 Target 碰撞在Edit Project Settings Physics Layer Collision Matrix里把 RobotArm 与自身的勾选去掉这一步能解决 80% 的“IK 解算无解”问题。Gripper 单独分层是为了后续做抓取判定时能精确检测“指尖是否接触到目标”而不被连杆的碰撞干扰。2.3 用 SphereCast 做抓取前的接近检测碰撞体配好之后机械臂能“不穿模”了但还需要知道“什么时候该闭合夹爪”。最直接的做法是在指尖前方做射线检测但单条Raycast太细目标稍微偏一点就漏检。我推荐用Physics.SphereCast它相当于用一个球体去扫掠容错率高得多。public class GripperSensor : MonoBehaviour { public float castRadius 0.02f; public float castDistance 0.08f; public LayerMask targetLayer; public bool DetectTarget(out RaycastHit hit) { Vector3 origin transform.position; Vector3 direction transform.forward; return Physics.SphereCast(origin, castRadius, direction, out hit, castDistance, targetLayer); } }castRadius取指尖宽度的一半左右castDistance取夹爪行程的 1.5 倍。这样当目标进入夹爪可闭合范围时SphereCast就能稳定命中。实测下来相比单射线SphereCast的漏检率从 30% 降到 5% 以内。实操心得SphereCast的起点不要放在指尖表面要稍微往内缩 1 到 2 厘米。因为如果起点就在表面球体初始位置可能已经和目标重叠PhysX 会返回距离为 0 的命中导致判定时机过早。3. 姿态解算让夹爪以正确角度贴上去3.1 逆向运动学求解姿态的基本原理机械臂的末端要到达某个位置并保持某个朝向这个过程叫逆向运动学IK。Unity 自带的AnimatorIK 主要面向人形角色对机械臂这种串联结构支持有限所以大多数项目会用两种方案一是自己写解析 IKAnalytical IK二是用Unity.Mathematics配合迭代求解如 CCD、FABRIK。解析 IK 的优点是快、精确缺点是只适用于特定构型比如六轴串联臂。以常见的六轴机械臂为例前三个关节决定末端位置后三个关节决定末端姿态这叫位置与姿态解耦。如果机械臂满足 Pieper 准则三个相邻关节轴交于一点就能用几何法直接算出所有关节角。我一般先用解析法求位置解再用四元数求姿态解。位置解的核心是余弦定理// 已知连杆长度 L1、L2目标距离 d // 求肘关节角度 float cosTheta (L1 * L1 L2 * L2 - d * d) / (2 * L1 * L2); cosTheta Mathf.Clamp(cosTheta, -1f, 1f); float elbowAngle Mathf.Acos(cosTheta) * Mathf.Rad2Deg;这里Mathf.Clamp是必须的因为浮点误差可能让cosTheta略微超出 [-1, 1]Acos会返回 NaN整个 IK 直接崩掉。这个坑我踩过不止一次后来养成习惯所有反三角函数输入都先 Clamp。3.2 四元数姿态解算为什么不用欧拉角姿态解算里最容易出问题的就是旋转表示。很多人图省事用欧拉角transform.eulerAngles new Vector3(x, y, z)结果遇到万向节死锁Gimbal Lock某个角度附近机械臂会突然翻转。机械臂抓取时末端姿态变化范围大欧拉角几乎必然出问题。四元数没有万向节死锁插值也平滑是姿态解算的正确选择。核心操作有两个一是从目标朝向构造四元数二是用Quaternion.Slerp做平滑过渡。// 根据目标法线和接近方向构造末端姿态 Quaternion targetRotation Quaternion.LookRotation(approachDir, targetNormal); // 平滑插值避免姿态突变 currentRotation Quaternion.Slerp(currentRotation, targetRotation, Time.deltaTime * rotationSpeed);Quaternion.LookRotation的第一个参数是“前方”第二个是“上方”。抓取时approachDir是夹爪接近方向通常是目标表面法线的反方向targetNormal是目标表面的法线。这样构造出来的姿态夹爪会垂直于目标表面贴上去抓取成功率最高。注意Quaternion.Slerp的第三个参数是插值系数不是速度。如果直接写Time.deltaTime * rotationSpeed当rotationSpeed较大时系数可能超过 1Slerp会 Clamp 到 1表现为瞬间到位。正确做法是用1 - Mathf.Exp(-rotationSpeed * Time.deltaTime)做指数平滑这样无论帧率如何变化收敛速度都一致。3.3 姿态解算中的坐标系转换陷阱机械臂的 IK 求解涉及多个坐标系世界坐标系、基座坐标系、各连杆局部坐标系、工具坐标系。姿态解算最容易出错的地方就是坐标系搞混。比如目标物体的法线是世界坐标系的但 IK 求解需要在基座坐标系里算中间少了一步转换结果就是夹爪朝向完全不对。我的做法是所有 IK 计算统一在基座坐标系里做。先把目标的位置和姿态从世界坐标系转到基座坐标系求解完关节角再把关节角应用到各连杆的局部旋转上。转换用Transform.InverseTransformPoint和Transform.InverseTransformDirection。Vector3 localTargetPos baseTransform.InverseTransformPoint(target.position); Vector3 localTargetNormal baseTransform.InverseTransformDirection(target.up);这样整个求解过程只在一个坐标系里逻辑清晰调试也方便。如果发现姿态不对只需要检查转换那一步不用满世界找问题。4. 抓取实现把碰撞检测和姿态解算串起来4.1 完整的抓取状态机设计机械臂抓取不是一步到位的而是一个多阶段过程。我一般用一个状态机来管理状态划分如下状态行为退出条件Idle待机等待抓取指令收到目标物体ApproachingIK 驱动末端接近目标距离目标小于阈值Aligning姿态解算调整夹爪朝向姿态误差小于 5 度Descending沿接近方向直线下降SphereCast 命中目标Grasping闭合夹爪锁定物体夹爪闭合到位Lifting抬起物体到达指定高度Done抓取完成收到释放指令每个状态只做一件事状态之间的转换条件明确。这样调试时哪个环节出问题一目了然。比如夹爪总是抓空就看Descending到Grasping的转换大概率是SphereCast的检测距离或层设置有问题。4.2 抓取瞬间的物理参数调优夹爪闭合的瞬间物理引擎会计算接触力。如果参数不对要么物体被弹飞要么夹爪穿模。关键参数有三个夹爪的Rigidbody.mass不能太大否则接触时动量过大物体被撞飞。我一般设成目标物体质量的 2 到 3 倍。接触材质的Dynamic Friction设成 0.6 到 0.8保证夹住后不打滑。Fixed Timestep默认 0.02 秒抓取瞬间可能不够精细可以临时调到 0.01 秒。另外夹爪闭合不要用Transform直接移动要用Rigidbody.MovePosition或者在关节上加Configurable Joint并设置targetPosition。直接改Transform会绕过物理引擎接触力计算不出来物体要么穿过去要么被瞬移的碰撞体弹开。// 用 Configurable Joint 驱动夹爪闭合 JointDrive drive new JointDrive { positionSpring 500f, positionDamper 50f, maximumForce 100f }; joint.xDrive drive; joint.targetPosition new Vector3(closeDistance, 0, 0);positionSpring和positionDamper的比例大概是 10:1maximumForce要足够大否则夹不紧。实测下来maximumForce至少要是物体重力的 5 倍才能保证抓取过程中物体不滑落。4.3 抓取后的物体锁定与释放物体被夹住后如果继续用物理碰撞来维持抓取会有微小的抖动和滑移。更稳的做法是抓取成功后把物体的Rigidbody设为isKinematic true并把它Parent到夹爪上。这样物体完全跟随夹爪运动没有任何物理抖动。释放时反过来先解除Parent把物体放回场景根节点恢复isKinematic false再给一个微小的初速度如果需要抛出。注意释放前要确认夹爪已经张开到足够宽度否则物体可能和夹爪碰撞体穿插恢复物理后直接被弹飞。实操心得Parent切换的瞬间物体的世界坐标会跳变。正确做法是先记录物体的世界位置和旋转切换Parent后再设回去。用transform.SetParent(parent, true)可以自动保持世界坐标不变这个true参数很多人不知道但非常关键。5. 常见问题与排查技巧实录5.1 IK 解算无解或抖动这是最高频的问题。排查顺序如下检查碰撞层矩阵确认机械臂自身层没有勾选碰撞检查目标点是否超出机械臂工作空间超出时 IK 无解是正常的检查关节角度限制Configurable Joint的angularXLimit等参数是否设得太紧检查Mathf.Acos的输入是否 Clamp浮点误差会导致 NaN如果 IK 解算结果抖动大概率是目标点在两个解之间跳变肘部上翻和下翻。解决办法是加一个解选择偏好比如始终选择肘部朝上的解或者在两个解之间做平滑过渡。5.2 夹爪穿模或抓空穿模通常是碰撞体配置问题。检查夹爪指尖的Box Collider是否覆盖了实际接触区域SphereCast的检测层是否包含目标层。抓空则多半是检测时机问题SphereCast距离太短夹爪还没到位就闭合了。把castDistance加大到夹爪行程的 2 倍试试。5.3 物体被抓取后抖动或滑落抖动一般是物理抓取靠碰撞维持导致的改用Parent锁定即可。滑落则是摩擦力不够检查接触材质的Dynamic Friction和Static Friction同时确认夹爪的maximumForce是否足够。如果物体较重还需要考虑在夹爪内侧加Physic Material把摩擦力调到 0.9 以上。5.4 姿态解算结果与预期不符先确认坐标系转换是否正确。在Aligning状态里打印出目标姿态的四元数和当前末端姿态的四元数对比Quaternion.Angle的差值。如果差值一直不收敛检查Slerp的插值系数是否被 Clamp 到 1 了。如果差值收敛但方向不对检查LookRotation的两个参数是否搞反了。问题现象可能原因排查方法IK 无解碰撞层未分离 / 超出工作空间检查 Layer Matrix打印目标距离姿态抖动解跳变 / Slerp 系数过大加解选择偏好改用指数平滑夹爪穿模碰撞体过小 / 检测层错误可视化碰撞体检查 LayerMask物体滑落摩擦力不足 / 夹持力不够调高摩擦系数和 maximumForce抓取后抖动物理抓取不稳定改用 Parent 锁定6. 一些让抓取更稳的进阶技巧6.1 用预测位置补偿 IK 延迟IK 求解和物理更新不在同一帧快速移动时末端会滞后。可以在求解前对目标位置做一阶预测predictedPos currentPos velocity * deltaTime。这样末端会提前往目标移动方向偏一点实际到达时刚好对上。实测能减少 30% 到 40% 的到位误差。6.2 夹爪接触力的自适应调整不同物体质量不同固定的夹持力要么夹不紧轻物体要么压坏重物体。可以根据物体的Rigidbody.mass动态调整maximumForcemaxForce mass * gravity * safetyFactorsafetyFactor取 3 到 5。这样轻物体用小力重物体用大力抓取更自然。6.3 抓取失败的重试机制再稳的系统也有失败的时候。加一个重试逻辑如果Grasping状态超时比如 0.5 秒还没检测到夹爪闭合到位就退回Approaching状态重新来一次。重试前把目标位置稍微偏移一点避免在同一个位置反复失败。重试次数设 2 到 3 次超过就报错避免死循环。这套东西我在几个数字孪生项目里都用过从六轴工业臂到小型桌面臂都跑得通。核心就一句话碰撞检测保证不穿模姿态解算保证贴得正物理参数保证抓得牢。三件事分开调调好再串起来比一上来就整体调效率高得多。如果卡在某个环节先把其他部分注释掉单独测那一个模块定位问题的速度会快很多。
返回列表