AR 中的3D模型加载优化那些事儿

1,311 阅读3分钟

说明

原来写过《AR 中的 3D 素材那些事儿》对 3D 素材的加载问题做出过简单说明,主要策略:

  • 利用后台线程,或SCNSceneSource类的加载方法,从磁盘加载模型到 CPU 和内存。
  • 通过ARSCNViewprepareObjects方法,将模型数据传到 GPU 上供显示。

这两条基本策略,可以保证大部分情况下的 3D 模型加载卡顿问题得到解决。但是实际开发中的问题很多,并不能完全解决。

所以今天再来说一说这些特殊的问题。

特殊问题

这些特殊问题主要集中在SCNNode 贴图上。

也就是说在开发中,有时候我们并不是拿到一个 3D 模型(如.dae 文件)直接使用就可以了。有时我们需要自己用代码生成贴图,以达到特殊的效果。比如,一个 AR 触摸屏显示器,需要与用户进行交互。

以前写过《SCNNode到底应该怎么贴图?UIImage,UIImageView,CALayer用哪个?》和《再谈 SCNNode 贴图类型选择问题》,说明了面对复杂效果和动画时的贴图选择问题。根据总结,综合显示效果最好的是 CALayer 和 SpriteKit(SKScene),主要问题也就是它们的加载优化问题。

CALayer 问题

CALayer 问题主要集中在加载时,如果使用了后台线程创建 CALayer,那么显示时容易显示不出来或者延迟很久才显示,大约几秒到几分钟。

// 切换后台线程,加载模型到内存
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    SCNScene *scene = [SCNScene sceneNamed:@"mode.dae"];
    SCNNode *modelNode = [scene.rootNode childNodeWithName:@"base" recursively:YES];
// 如果在这里创建 CALayer 对象,并给 modelNode 贴图,后面有可能显示不出来
    dispatch_async(dispatch_get_main_queue(), ^{
// 这个方法必须在主线程调用。它的作用是:当 GPU 相对空闲时,用 CPU 的后台线程将模型和贴图等,从内存传输到 GPU 上,然后在主线程回调。这样减小了 GPU 带宽压力 和 CPU 主线程的压力
        [scnView prepareObjects:@[modelNode] withCompletionHandler:^(BOOL success) {
            if (success) {
                [self addChildNode:modelNode];
            }
        }];
    });
});

当模型数量很多时,这种显示不出来的问题会更加严重。解决方案就是 CALayer 贴图要在主线程创建并且给 modelNode 贴图也要在主线程完成,包括 CALayer 里面的 UIImage 等。

SpriteKit 问题

SpriteKit 问题有两个:

  • 一个是显示性能低下,动画卡顿,内存占用太高,帧数过低甚至崩溃;
  • 另一个是加载过程不支持prepareObjects,强行使用会在运行时崩溃; 解决方案暂未找到,所以只好在开发中尽可能少用 SpriteKit。这真是很让人奇怪的一件事:苹果官方在 WWDC 推荐过 ARKit 与 SpriteKit 联合使用,但在实际中 SpriteKit 表现出的问题这么多。

其他问题

其他问题也有,主要还是在prepareObjects方法上面,它的具体作用我们也说过:这个方法必须在主线程调用。它的作用是:当 GPU 相对空闲时,用 CPU 的后台线程将模型和贴图等,从内存传输到 GPU 上,然后在主线程回调。这样减小了 GPU 带宽压力 和 CPU 主线程的压力。

它接收的参数是一个数组,里面可以放常见 SceneKit 中的对象。当你在 AR 场景中有很多模型时,你需要做的是:将这些模型,放到一个数组中,再统一调用prepareObjects方法。

如果将项目中的 3D 模型分散开来,再多次调用prepareObjects方法,那么也会造成卡顿。