ARKit 进阶:物理世界
写在前面
ARKit的渲染能力是由其他框架实现的,除了苹果的SceneKit, Unity3D、UE, 或者其他自定义的OpenGL、Metal渲染引擎都可以与ARKit相结合。本文所介绍的技术都是基于SceneKit。
Demo
关于物理模拟
虽然物理引擎都具有真实的物理变量,如质量、重力、摩擦力等,但当我们说道物理模拟,不是要真的去用真实世界的数值去模拟物理行为,事实上那样反而会失真。我们要做的是维护好各种变量的相对关系,制造一种真实的物理感官即可。
SCNPhysicsWorld
游戏中的物理引擎用来模拟3D世界中的物理特效,使物体具备的真实的动态行为。SceneKit使用SCNPhysicsWorld
来管理这种物理模拟,让物体的碰撞、连接、掉落等具有真实感。
ARSCNScene
具有继承自SCNScene
的默认SCNPhysicsWorld
。任何添加到ARSCNScene
的物理对象,都会注册到SCNPhysicsWorld
中,维护其中的物理关系是重点。
利用SCNPhysicsWorld
,我们主要做以下工作:
- 管理全局的物理变量。
- 利用其代理方法观察物理行为。
- 使用contact/ray/convex test方法,检测物理之间的物理关系。
SCNPhysicsBody
想要一个SCNNode
参与到物理模拟中,只需要给node.physicsBody
赋值一个合适的值。
所有拥有physics body的node,会在render loop的physics simulation阶段,计算该node的物理行为,在接下来的渲染阶段对node做相应的变换。
一个合适的SCNPhysicsBody
需要合理设置其type
与physicsShape
。
type:
- dynamic: 可以被碰撞、力影响。适合场景中物理引擎可以完全接管的类型,如掉落的石块。
- static: 不受碰撞、力影响,且不能移动。适合场景中地面、墙体等。
- kinematic: 不受碰撞、力影响,但移动的时候会影响其他body。适合场景中的角色,毕竟我们不想角色的移动不想被太多力影响。
physicsShape: 当physics body参与到物理模拟时,一个更贴合的形状能得到一个更令人满意的结果。但是对于一个比较复杂的几何体,简单的convex会显得过大,concave又会太复杂影响性能。这种情况可以使用若干个简单的形状拼装一个相似的形状,或者由设计给出一个合理的形状,总之形状的选择要平衡性能与真实感。
body category
一个场景中会有许多node,需要给他们设置category,让我们只关注感兴趣的碰撞、接触。尤其要注意的是它们各自的默认值,不然很容易出现bug。 categoryBitMask: 指定body的类型, dynamic/kinematic body默认为1,static body默认为2。 collisionBitMask: 指定能与该body产生碰撞的physics body类型。默认是-1,即每位都置1。 contactTestBitMask: 指定哪种类型的physics body与该body发生接触(几何体交叉)后,通知给physics world。 这个属性在OSX10.11和iOS9以上默认值是0,以下与collisionBitMask相同。
记住重设physics body时,要恢复这些值
注意SCNNode
也有一个categoryBitMask
,用法与这个类似。但在scene test时,这两个容易搞混。这里吐槽以下苹果的命名。
SCNPhysicsShape
当物理引擎检测碰撞时,使用的是SCNPhysicsShape
来计算结果,除了性能,我碰到两个关于physicsShape
的问题:
- 如果
node.geometry
是不可见的,那个虽然它有physics shape,在调试时也会显示,但不会参与物理模拟。 - SceneKit的物理引擎是不支持缩放变换的。如果一个node做缩放变换后,physics body将仍是原来的尺寸。这种情况看我的回答,重点是当attach body之前如果没有指定形状,那么SceneKit才会使用scale信息,使用
SCNPhysicsShapeScaleKey
也有一样的效果。
//still has identity scale
SCNPhysicsBody *body = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeKinematic shape:[SCNPhysicsShape shapeWithGeometry:ramp.geometry options:nil]];
//this did worked
SCNPhysicsBody *body = [SCNPhysicsBody bodyWithType:SCNPhysicsBodyTypeKinematic shape:nil];
SceneKit automatically creates a physics shape for the body when you attach it to a node, based on that node’s geometry property
物理模拟与其他动画的冲突
从SceneKit的render loop可以看到,物理模拟实际上也是一种动画,只不过动画的参数由物理引擎控制。SceneKit也遵循iOS的传统,具有隐式、显式动画,同时有SCNAction
接口。由于物理引擎
是将所有的计算结果应用到动画层上,即node.presentationNode
,这会让新加入的动画显得不正常。因为其他动画的初始值是从node.transform
中读取的。对于这种问题,需要读出node.presentationNode.transform
的值用于动画的初始值。
process of collision
对于简单的碰撞,只要设置好physics body和category bit mask,collision bit mask等参数,其他的就由物理引擎接管了。 碰撞的处理过程由3个部分组成。
collision detection
物理引擎会在渲染时检测物体之间的physics body是否发生重叠,这一过程我们可以通过中的方法观察。
collision determination
与操作两个物体的之间的categoryBitMask和collisionBitMask,若返回非0,则发生碰撞。
collision respond
物理引擎会在渲染之前,计算物理碰撞的结果并应用到物体上。
contact test
当有两个物体相接触,若categoryBitMask
和contactTestBitMask
相与不为零,那么会调用的方法。很显然这个结果的集合是小于碰撞结果的。通过这个方法,我们能够控制两个物体之间的碰撞,这在物理引擎接管的碰撞动画不理想时,是非常有用的。
当接触发生时,代理方法会传来SCNPhysicsContact
对象,它包含了接触的对象、部位、法线与重叠距离。通过它可以修正错误的动画。例如我将一个石块从高处坠落,如果速度特别大,那么它会直接穿过底部的平面。因为在render loop的渲染时,两者相接触的那一帧在物理模拟时,石块已经大部分穿过了平面,这样在下一帧石块会直接穿过去,而不是回弹。可以看我的回答。
scene test
SceneKit与ARKit中共有以下几种scene test,用以观察世界中的物体关系,作用类似UIKit的 hitTest: 方法。
AR scene test
//ARSCNView
- (NSArray<ARHitTestResult *> *)hitTest:(CGPoint)point
types:(ARHitTestResultType)types;
根据ARSCNView中的点,构造一条3D世界的射线,搜索ARAnchor
或真实物体(特征点或已检测出的平面)。
scene test
//SCNSceneRenderer
- (NSArray<SCNHitTestResult *> *)hitTest:(CGPoint)point
options:(NSDictionary<SCNHitTestOption, id>
//SCNNode
- (NSArray<SCNHitTestResult *> *)hitTestWithSegmentFromPoint:(SCNVector3)pointA
toPoint:(SCNVector3)pointB
options:(NSDictionary<NSString *,id> *)options;
第一个方法:根据SCNSceneRenderer(SCNView等)中的点,构造一条3D世界的射线,搜索与射线相交的几何体,node.geometry
为nil则忽视。
第二个方法:在目标node的局部空间中,搜索与pointA-pointB线段相交的子node。
physics body test
//SCNPhysicsBody
- (NSArray<SCNHitTestResult *> *)rayTestWithSegmentFromPoint:(SCNVector3)origin
toPoint:(SCNVector3)dest
options:(NSDictionary<SCNPhysicsTestOption, id> *)options;
- (NSArray<SCNPhysicsContact *> *)convexSweepTestWithShape:(SCNPhysicsShape *)shape
fromTransform:(SCNMatrix4)from
toTransform:(SCNMatrix4)to
options:(NSDictionary<SCNPhysicsTestOption, id> *)options;
第一个方法:在物理世界中,返回在两点之间的physics body所属的node。 第二个方法:在物理世界中,按form-to变换滑动指定的形状,返回相交的physics body所属的node。
contact test
//contact test
- (NSArray<SCNPhysicsContact *> *)contactTestBetweenBody:(SCNPhysicsBody *)bodyA
andBody:(SCNPhysicsBody *)bodyB
options:(NSDictionary<SCNPhysicsTestOption, id> *)options;
- (NSArray<SCNPhysicsContact *> *)contactTestWithBody:(SCNPhysicsBody *)body
options:(NSDictionary<SCNPhysicsTestOption, id> *)options;
第一个方法:检测物理世界中,两个body是否发生接触,返回所有的接触点。 第二个方法:返回所有在物理世界中与指定body发生contact的node。
最后
物理引擎能够帮助我们模拟真实世界的效果,虽然高级的特效一般都是自己在渲染循环中实现的,但它大大减轻了我们计算成本。拥有良好的物理特效,能够让用户有真实的感受,希望本篇文章能够帮助大家。
- 来源于WCF的设计模式:可扩展对象模式[上篇]
- 我的数据访问函数库的源代码(三)——返回结构数组
- 我的数据访问函数库的源代码(四)—— 存储过程部分,包括存储过程的参数的封装
- [WCF 4.0新特性] 路由服务[实例篇]
- [WCF 4.0新特性] 默认终结点
- 三层架构之我见 —— 不同于您见过的三层架构。
- 来源于WCF的设计模式:可扩展对象模式[下篇]
- [WCF 4.0新特性] 标准终结点与无(.SVC)文件服务激活
- 我的数据访问类(第二版)—— for .net2.0 (二)
- 我的数据访问类(第二版)—— for .net2.0 (一)
- [WCF 4.0新特性] 路由服务[原理篇]
- 通过“访问多种数据库”的代码来学习多态!(.net2.0版)
- [WCF-Discovery] 客户端如何能够“探测”到可用的服务?
- WCF的安全审核——记录谁在敲打你的门
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 清除CentOS 6或CentOS 7上的磁盘空间的方法
- leetcode栈之二叉树的前序遍历
- 解决Linux下Mysql5.7忘记密码问题
- CentOS8.0 安装配置ftp服务器的实现方法
- Linux实现自动登录的实例讲解
- Linux中date命令转换日期提示date: illegal time format问题解决
- leetcode队列之最近的请求次数
- 安防视频云服务EasyCVR集成海康SDK时语音对出现杂音问题,如何解决?
- arm linux利用alsa驱动并使用usb音频设备
- linux 磁盘转移空间的方法
- 详解git中配置的.gitignore不生效的解决办法
- Apache Thrift环境配置
- CentOS 7更新时出现:Multilib version problems问题的解决方法
- Linux模拟网络丢包与延迟的方法
- centos6.5通过yum安装nginx