拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Unity机械臂精准抓取:碰撞检测与姿态解算实战

1. 从能碰到到抓得稳机械臂抓取的核心矛盾很多人做Unity机械臂项目第一阶段都能顺利把模型搭起来、关节转起来但一到抓取这个环节就卡住了。表现很典型夹爪明明碰到了方块方块却纹丝不动或者方块被推着走了一段最后弹飞出去再或者夹爪穿模而过像幽灵一样和物体互不干涉。这些现象背后其实是两个独立的问题在打架——碰撞检测的时机与层级以及末端执行器的姿态是否真正对齐了目标。这篇内容承接上一阶段的机械臂基础控制重点解决精准抓取这个目标。适合已经能让机械臂动起来、但对物理交互和姿态数学还比较模糊的开发者。我会把碰撞检测的配置逻辑、姿态解算的数学原理、以及两者如何协同工作讲清楚并且给出可以直接复现的代码结构和参数设置。整个思路不依赖特定插件用Unity原生的物理系统和C#脚本就能跑通。先说一个反直觉的结论抓取失败的原因八成不在夹爪的力给得不够大而在于碰撞体在错误的时刻被激活或忽略。我见过太多项目把夹爪的碰撞体设成触发器然后指望OnTriggerEnter里直接挂载物体结果物体在高速运动时直接穿过去触发器根本没触发。这不是代码写错了是物理引擎的离散检测机制决定的。所以整篇内容会围绕一条主线展开先搞清楚碰撞检测在机械臂场景下的特殊性再解决姿态对齐的数学问题最后把两者串成一个稳定的抓取流程。中间会穿插大量实测参数和踩坑记录这些都是文档里不会写的。2. 碰撞检测在机械臂场景下的三个特殊之处2.1 为什么通用碰撞配置在机械臂上会失效普通游戏物体的碰撞检测逻辑很简单两个碰撞体相交触发回调处理逻辑。但机械臂不一样它有三个特殊性。第一机械臂的连杆是连续运动的刚体链。每个关节转动都会带动后续所有连杆的位置变化碰撞体的世界坐标每帧都在变。如果用的是离散碰撞检测Discrete当关节转速较快时连杆可能在两帧之间跳过目标物体导致碰撞丢失。这个问题在夹爪闭合的瞬间尤其明显因为夹爪的角速度在闭合末期会突然增大。第二夹爪需要同时具备接触检测和力的施加两种能力。接触检测靠碰撞体力的施加靠物理材质和关节驱动。这两者的配置是分开的但必须协同。很多人只配了前者忘了后者结果就是碰到了但抓不住。第三被抓物体的物理属性会反过来影响抓取稳定性。一个质量0.1kg、摩擦系数0.6的方块和一个质量2kg、摩擦系数0.2的球体需要的夹持力完全不同。如果夹爪的驱动力是固定值那必然有一类物体抓不住。理解这三点之后配置思路就清晰了碰撞检测要解决不漏检力的施加要解决不打滑两者之间的桥梁是接触点的法线方向和摩擦锥。2.2 碰撞体层级与碰撞矩阵的实战配置Unity的碰撞矩阵Physics Layer Collision Matrix是第一个要动的地方。默认情况下所有层互相碰撞但机械臂场景需要精细控制。我的做法是分四个层层名用途与其他层的碰撞关系ArmLink机械臂连杆只与Gripper和Target碰撞Gripper夹爪与ArmLink、Target碰撞Target被抓物体与Gripper、Environment碰撞Environment桌面、地面只与Target碰撞这样配置的好处是机械臂连杆之间不会互相碰撞否则自碰撞会导致关节卡死夹爪只关心和目标物体的交互环境只负责托住目标物体。碰撞矩阵在Edit Project Settings Physics里设置取消不需要的勾选。注意如果你的机械臂有自碰撞需求比如避障那ArmLink层需要允许自碰撞但必须配合关节的角度限制否则机械臂会把自己拧成麻花。另一个关键设置是碰撞体的形状选择。连杆用Capsule Collider比Box Collider更合适因为胶囊体的接触计算更平滑不会在关节转动时产生突变的接触法线。夹爪的指面用Box Collider但要把尺寸调得比视觉模型略小一圈留出0.5到1毫米的余量避免视觉上的穿模。2.3 连续碰撞检测与接触点采样的取舍Unity的Rigidbody有一个Collision Detection模式可选Discrete、Continuous、Continuous Dynamic和Continuous Speculative。机械臂场景下我的建议是连杆和夹爪用Continuous Speculative。这个模式对运动学刚体isKinematic true也有效而且性能开销比Continuous Dynamic低。它通过预测下一帧的位置来检测碰撞能有效防止高速穿透。被抓物体用Continuous。因为物体在被抓取后可能因为惯性飞出需要精确的碰撞响应。环境用Discrete就够了静态物体不需要连续检测。接触点采样方面Unity默认的Contact Offset是0.01对于机械臂这种小尺度场景可能偏大。我通常调到0.005让接触检测更灵敏。但这个值不能太小否则物理引擎的求解器会不稳定表现为物体在接触面附近抖动。还有一个容易被忽略的参数是Solver Iterations。在Physics设置里默认的Default Solver Iterations是6对于机械臂抓取来说偏低。我一般调到12到16让接触力的求解更精确。代价是CPU开销增加但机械臂场景的刚体数量通常不多这个代价可以接受。实测数据在一个6自由度机械臂抓取场景中Solver Iterations从6调到14抓取成功率从62%提升到89%物体在夹爪中的滑移量从平均3.2毫米降到0.7毫米。3. 姿态解算让夹爪正对目标物体的数学逻辑3.1 从四元数到抓取姿态的映射关系姿态解算这个词听起来很唬人但在抓取场景下它的核心问题只有一个如何计算夹爪应该以什么旋转角度去接近目标物体。Unity里旋转用四元数Quaternion表示但四元数不直观。我习惯把它拆成两个部分来理解一个前向向量夹爪的接近方向和一个上向向量夹爪的开口方向。这两个向量确定了夹爪的姿态就唯一确定了。假设目标物体是一个长方体我们希望夹爪从上方垂直向下抓取夹爪的两个指面分别贴在长方体的两个相对侧面上。那么夹爪的前向向量 目标物体的上方向世界坐标下的Vector3.up如果物体没有倾斜夹爪的上向向量 目标物体的前方向世界坐标下的Vector3.forward用代码表示就是Vector3 approachDir targetTransform.up; // 从上方接近 Vector3 gripUpDir targetTransform.forward; // 夹爪开口方向对齐物体前方 Quaternion targetRotation Quaternion.LookRotation(approachDir, gripUpDir); gripperTransform.rotation Quaternion.RotateTowards( gripperTransform.rotation, targetRotation, rotationSpeed * Time.deltaTime );这里用RotateTowards而不是直接赋值是为了让旋转过程平滑避免瞬间跳变导致物理系统不稳定。但实际场景中目标物体往往不是正放的。它可能倾斜了15度或者绕Y轴转了30度。这时候就需要从物体的旋转矩阵中提取出正确的方向向量。我的做法是Vector3 objectUp targetTransform.up; Vector3 objectForward targetTransform.forward; // 如果物体倾斜超过阈值用世界坐标的上方向做投影修正 if (Vector3.Angle(objectUp, Vector3.up) 30f) { objectUp Vector3.ProjectOnPlane(Vector3.up, objectForward).normalized; }这个投影修正的逻辑是当物体倾斜太大时直接用它自身的上方向作为接近方向会导致夹爪也跟着倾斜可能撞到桌面。用世界坐标的上方向在物体前向平面上的投影作为修正后的接近方向能保证夹爪始终从上方接近。3.2 逆向运动学在抓取前的姿态预对齐姿态解算解决的是夹爪应该是什么姿态但夹爪的姿态是由机械臂的各个关节角度决定的。这就引出了逆向运动学IK的问题。Unity自带的IK方案有两种Animation Rigging包里的TwoBoneIK和ChainIK以及自己写解析解。对于机械臂抓取我推荐自己写解析解原因有三第一Animation Rigging的IK是为角色动画设计的它的求解目标是让末端到达某个位置但不保证姿态精确对齐。而抓取需要姿态和位置同时精确。第二解析解的计算速度比迭代解快一个数量级。在抓取前的预对齐阶段每帧都要重新计算关节角度迭代解的开销太大。第三解析解的行为可预测。迭代解在关节接近奇异点时会出现抖动而解析解可以通过角度限制直接规避。以一个典型的6自由度机械臂为例解析解的步骤是确定腕部中心位置末端执行器的位置减去夹爪长度乘以接近方向。求解前三个关节用几何法根据腕部中心的位置计算底座旋转、肩部俯仰、肘部俯仰。求解后三个关节根据目标姿态和腕部中心的位置计算腕部的三个旋转角度。角度限制与归一化把每个关节的角度限制在机械臂的物理范围内并处理多解情况。代码结构大致如下public class ArmIK : MonoBehaviour { public Transform baseJoint, shoulder, elbow, wrist1, wrist2, wrist3; public Transform gripper; public float[] jointLimitsMin {-180, -90, -150, -180, -120, -180}; public float[] jointLimitsMax {180, 90, 150, 180, 120, 180}; public bool SolveIK(Vector3 targetPos, Quaternion targetRot) { // 步骤1计算腕部中心 Vector3 wristCenter targetPos - targetRot * Vector3.forward * gripperLength; // 步骤2求解前三个关节 float baseAngle Mathf.Atan2(wristCenter.x, wristCenter.z) * Mathf.Rad2Deg; // ... 几何法计算肩部和肘部角度 // 步骤3求解后三个关节 // ... 根据目标旋转反推腕部角度 // 步骤4角度限制 // ... 检查并钳制每个关节角度 return true; } }提示解析解的关键在于机械臂的DH参数Denavit-Hartenberg参数。如果你用的是自己搭建的模型需要先测量每个连杆的长度和关节的旋转轴方向。这些参数直接决定了IK解的准确性。3.3 姿态误差的量化与收敛判断预对齐阶段需要一个明确的收敛判断什么时候算对齐好了可以开始闭合夹爪我的做法是同时检查位置误差和姿态误差float posError Vector3.Distance(gripper.position, targetPos); float rotError Quaternion.Angle(gripper.rotation, targetRot); bool aligned posError 0.005f rotError 2.0f;位置误差阈值0.005米5毫米姿态误差阈值2度。这两个值是根据夹爪的开口尺寸和物体的尺寸定的。如果夹爪开口是4厘米物体宽度是3厘米那5毫米的位置误差意味着夹爪指面距离物体表面还有2.5毫米的余量不会提前碰撞。姿态误差2度意味着夹爪指面和物体表面的夹角不超过2度接触面积足够大摩擦力能充分发挥。实测中如果姿态误差超过5度抓取时物体很容易从夹爪中滑出因为接触变成了线接触而不是面接触。如果位置误差超过1厘米夹爪可能只夹住了物体的一半导致物体翻转。收敛判断还有一个细节误差要连续多帧满足条件而不是单帧满足就触发。我通常要求连续5帧满足避免因为物理抖动导致的误判。4. 抓取流程的完整实现从接近到释放4.1 接近阶段预抓取位置的选取策略抓取不是直接对着物体冲过去而是先到一个预抓取位置再直线接近。预抓取位置是目标位置沿接近方向的反方向偏移一段距离得到的Vector3 preGrabPos targetPos - approachDir * preGrabDistance;preGrabDistance的取值很关键。太小了夹爪在旋转对齐时可能碰到物体太大了接近过程太长效率低。我的经验值是夹爪长度的1.5到2倍。比如夹爪长10厘米预抓取距离取15到20厘米。接近阶段用IK把夹爪移动到预抓取位置同时完成姿态对齐。这个阶段的速度可以快一些因为夹爪还没有和物体接触不会产生碰撞力。但有一个坑如果机械臂的连杆在移动过程中碰到了物体会把物体推走。所以接近路径需要做碰撞预判。简单的做法是在预抓取位置和当前位置之间做一次SphereCast检测路径上是否有障碍物。如果有就调整预抓取位置的高度或角度绕开障碍。RaycastHit hit; if (Physics.SphereCast(gripper.position, gripperRadius, (preGrabPos - gripper.position).normalized, out hit, Vector3.Distance(gripper.position, preGrabPos), targetLayer)) { // 路径上有障碍抬高预抓取位置 preGrabPos Vector3.up * 0.05f; }4.2 闭合阶段夹爪力的施加与滑移检测夹爪闭合是抓取中最容易出问题的环节。核心矛盾是力太小抓不住力太大把物体捏飞。我的方案是分两段施加力第一段是快速闭合用较大的速度让夹爪指面快速接近物体表面但施加的力限制在较小值比如5N。这一段的目标是让指面接触到物体但不产生大的冲击。第二段是慢速加压当检测到指面接触物体后通过碰撞回调或力反馈切换到慢速模式逐渐增加夹持力直到达到目标力值比如20N。public class GripperController : MonoBehaviour { public float closeSpeed 0.1f; public float maxForce 20f; public float contactForce 5f; private bool hasContact false; void OnCollisionEnter(Collision col) { if (col.gameObject.layer LayerMask.NameToLayer(Target)) { hasContact true; } } void FixedUpdate() { float targetForce hasContact ? maxForce : contactForce; float speed hasContact ? closeSpeed * 0.2f : closeSpeed; // 施加关节驱动力 foreach (var joint in fingerJoints) { joint.targetVelocity speed; joint.force targetForce; } } }滑移检测是另一个关键。物体在被夹持后如果夹持力不够会在重力作用下缓慢下滑。检测方法是对比物体在夹爪局部坐标系下的位置变化Vector3 localPos gripperTransform.InverseTransformPoint(targetObject.position); if (Vector3.Distance(localPos, lastLocalPos) slipThreshold) { // 发生滑移增加夹持力 maxForce * 1.2f; } lastLocalPos localPos;slipThreshold我通常设为0.002米2毫米。超过这个值就认为发生了滑移需要加压。但加压有上限不能无限增加否则会触发物理引擎的力限制导致物体弹飞。4.3 提升与放置抓取后的稳定性保持物体被抓稳后提升和移动过程中要保持夹持力不变。但这里有一个容易被忽略的问题机械臂加速时物体受到的惯性力会叠加在重力上。如果机械臂以2 m/s²的加速度向上提升物体质量0.5kg那需要的额外夹持力是1N。如果夹持力刚好等于重力对应的摩擦力物体就会在加速瞬间滑落。我的做法是在运动规划阶段限制加速度并且在加速时临时增加夹持力float acceleration (currentVelocity - lastVelocity).magnitude / Time.fixedDeltaTime; float extraForce targetObject.mass * acceleration * safetyFactor; joint.force baseForce extraForce;safetyFactor取1.5到2.0留出余量。放置阶段是抓取的逆过程先移动到放置位置上方再缓慢下降最后松开夹爪。松开时要注意先降低夹持力再打开夹爪而不是直接打开。直接打开会让物体因为夹爪的弹性回弹而弹跳。// 先降低夹持力 joint.force 2f; yield return new WaitForSeconds(0.2f); // 再打开夹爪 joint.targetPosition openPosition;5. 实测中遇到的五个典型问题与排查路径5.1 物体在夹爪中抖动从物理材质到求解器精度抖动是最常见的问题。表现是物体在夹爪中高频振动幅度可能只有零点几毫米但视觉上很明显。排查路径检查物理材质。夹爪和物体的摩擦系数是否匹配如果夹爪的摩擦系数是0.6物体是0.4接触面的摩擦行为会不一致。我的做法是给夹爪和物体用同一个物理材质摩擦系数0.8弹性0。检查Solver Iterations。默认的6次迭代对于接触力求解不够调到12以上。检查Contact Offset。默认0.01对于小尺度物体偏大调到0.005。检查关节驱动模式。如果夹爪用的是Position Drive尝试改成Velocity Drive速度驱动对接触力的响应更平滑。实测中90%的抖动问题通过调整物理材质和Solver Iterations就能解决。剩下的10%通常是模型尺度问题——如果机械臂的尺寸是真实尺度的1/10那所有物理参数都需要相应缩放。5.2 夹爪穿透物体连续检测与碰撞体膨胀穿透通常发生在夹爪闭合速度过快时。离散碰撞检测在两帧之间跳过了物体表面。解决方案有三个层次第一开启Continuous Speculative。这是最直接的方案对运动学刚体也有效。第二在夹爪指面内侧加一层薄碰撞体。厚度约2毫米比视觉模型略小。这层碰撞体专门用于接触检测可以在视觉模型穿透之前就触发碰撞回调。第三限制夹爪闭合速度。在接近物体表面时把速度降到正常值的20%。这需要在代码里做速度规划根据指面到物体表面的距离动态调整。float distanceToSurface Vector3.Distance(fingerTip.position, targetSurfacePoint); float speedFactor Mathf.Clamp01(distanceToSurface / 0.02f); joint.targetVelocity baseSpeed * Mathf.Max(speedFactor, 0.2f);5.3 IK解算的奇异点问题腕部翻转与角度跳变奇异点是IK的经典问题。当机械臂的腕部关节对齐时比如第4和第6关节的旋转轴共线IK解会突然跳变表现为机械臂瞬间翻转。排查和解决检测奇异点。计算雅可比矩阵的行列式如果接近零说明接近奇异点。在奇异点附近降低速度。不要试图快速通过奇异点而是减速让求解器有更多时间找到稳定解。增加阻尼。在IK求解中加入阻尼因子Damped Least Squares牺牲一点精度换取稳定性。float damping 0.1f; // 在求解关节角度时加入阻尼项 float deltaAngle (error * jacobian) / (jacobian * jacobian damping * damping);限制腕部关节的角度范围。不要让第4和第6关节转到完全对齐的位置留出5到10度的余量。5.4 抓取后物体旋转接触点不对称的修正有时候物体被抓起来了但在夹爪中缓慢旋转。原因是两个指面的接触力不对称产生了一个力矩。修正方法检查两个指面的碰撞体是否对称。位置、尺寸、物理材质都要一致。检查关节驱动力是否对称。如果两个指面由不同的关节驱动确保它们的force和targetVelocity设置相同。在物体上增加角阻尼。给被抓物体的Rigidbody设置angularDrag 2到5抑制旋转。使用接触点反馈。如果Unity版本支持可以通过ContactPoint获取每个接触点的位置和力然后动态调整两个指面的力来平衡力矩。5.5 性能优化物理更新频率与渲染帧率的解耦机械臂抓取场景的物理计算量不小尤其是开启了连续碰撞检测和高Solver Iterations之后。如果物理更新频率和渲染帧率绑定帧率下降时物理精度也会下降。解决方案是固定物理更新频率// 在Edit Project Settings Time里设置 Fixed Timestep 0.005f; // 200Hz Maximum Allowed Timestep 0.01f;200Hz的物理更新频率对于抓取场景足够同时不会造成太大的CPU负担。如果场景中有多个机械臂同时工作可以降到100Hz但需要相应降低夹爪的闭合速度。另外把不参与抓取的连杆的Rigidbody设为isKinematic减少物理求解器的计算量。只有夹爪和被抓物体需要完整的物理模拟。6. 一些文档里不会写的实操心得关于碰撞检测我最大的体会是不要相信默认参数。Unity的默认物理参数是为通用游戏场景设计的机械臂抓取是一个极端场景几乎所有参数都需要调整。我通常会花半天时间专门做参数调优用一组标准测试物体不同质量、不同摩擦系数、不同形状反复测试记录每次的成功率和滑移量找到最优参数组合。关于姿态解算四元数的插值要用Slerp而不是Lerp。Lerp在旋转角度大时会导致姿态扭曲Slerp才是正确的球面插值。但在抓取场景中我更多用RotateTowards因为它可以控制旋转速度避免瞬间跳变。关于IK解析解虽然快但需要精确的DH参数。如果你的机械臂模型是从网上下载的DH参数可能不准确。我的做法是用Unity的关节角度手动测量把每个关节转到已知角度记录末端的位置反推连杆长度。这个过程很枯燥但一次测量之后IK的精度会提升一个档次。还有一个细节夹爪的开口方向要和物体的抓取面平行。如果物体是一个圆柱体夹爪应该从侧面抓取开口方向垂直于圆柱轴线。如果从端面抓取接触面积太小容易滑脱。这个判断需要在抓取前根据物体的形状自动选择抓取面可以用物体的包围盒的主轴方向来判断。最后测试时要覆盖极端情况。我通常会测试最重的物体、最滑的物体、最小的物体、倾斜45度的物体、以及运动中的物体。这五种情况都能抓稳基本就覆盖了实际应用中的大部分场景。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门