- 廢話不多說,先看效果
- 由於是晚上,筆者選擇的是一個檯燈
其實是會一直圍着你轉圈的,只不過筆者不好意思暴露家裏的場景,所以請讀者朋友們見諒~
- 由於是晚上,筆者選擇的是一個檯燈
1.1-ARKit物體圍繞相機旋轉流程介紹
-
1.點擊屏幕添加物體,已經在第三小節
ARKit從入門到精通(3)-ARKit自定義實現
中介紹 -
2.實現物體的圍繞相機旋轉(這裏主要會用到SceneKit框架中內容)
- 注意:繞相機旋轉的關鍵點在於:在相機的位置創建一個空節點,然後將檯燈添加到這個空節點,最後讓這個空節點自身旋轉,就可以實現檯燈圍繞相機旋轉
- 1.爲什麼要在相機的位置創建一個空節點呢?因爲你不可能讓相機也旋轉
- 2.爲什麼不直接讓檯燈旋轉呢? 這樣的話只能實現檯燈的自轉,而不能實現公轉
- 注意:繞相機旋轉的關鍵點在於:在相機的位置創建一個空節點,然後將檯燈添加到這個空節點,最後讓這個空節點自身旋轉,就可以實現檯燈圍繞相機旋轉
- 核心代碼介紹
#pragma mark- 點擊屏幕添加飛機
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.planeNode removeFromParentNode];
//1.使用場景加載scn文件(scn格式文件是一個基於3D建模的文件,使用3DMax軟件可以創建,這裏系統有一個默認的3D飛機)--------在右側我添加了許多3D模型,只需要替換文件名即可
SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/lamp/lamp.scn"];
//2.獲取檯燈節點(一個場景會有多個節點,此處我們只寫,飛機節點則默認是場景子節點的第一個)
//所有的場景有且只有一個根節點,其他所有節點都是根節點的子節點
SCNNode *shipNode = scene.rootNode.childNodes[0];
self.planeNode = shipNode;
//檯燈比較大,適當縮放一下並且調整位置讓其在屏幕中間
shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
shipNode.position = SCNVector3Make(0, -15,-15);
;
//一個檯燈的3D建模不是一氣呵成的,可能會有很多個子節點拼接,所以裏面的子節點也要一起改,否則上面的修改會無效
for (SCNNode *node in shipNode.childNodes) {
node.scale = SCNVector3Make(0.5, 0.5, 0.5);
node.position = SCNVector3Make(0, -15,-15);
}
self.planeNode.position = SCNVector3Make(0, 0, -20);
//3.繞相機旋轉
//繞相機旋轉的關鍵點在於:在相機的位置創建一個空節點,然後將檯燈添加到這個空節點,最後讓這個空節點自身旋轉,就可以實現檯燈圍繞相機旋轉
//1.爲什麼要在相機的位置創建一個空節點呢?因爲你不可能讓相機也旋轉
//2.爲什麼不直接讓檯燈旋轉呢? 這樣的話只能實現檯燈的自轉,而不能實現公轉
SCNNode *node1 = [[SCNNode alloc] init];
//空節點位置與相機節點位置一致
node1.position = self.arSCNView.scene.rootNode.position;
//將空節點添加到相機的根節點
[self.arSCNView.scene.rootNode addChildNode:node1];
// !!!將檯燈節點作爲空節點的子節點,如果不這樣,那麼你將看到的是檯燈自己在轉,而不是圍着你轉
[node1 addChildNode:self.planeNode];
//旋轉核心動畫
CABasicAnimation *moonRotationAnimation = [CABasicAnimation animationWithKeyPath:@"rotation"];
//旋轉週期
moonRotationAnimation.duration = 30;
//圍繞Y軸旋轉360度 (不明白ARKit座標系的可以看筆者之前的文章)
moonRotationAnimation.toValue = [NSValue valueWithSCNVector4:SCNVector4Make(0, 1, 0, M_PI * 2)];
//無限旋轉 重複次數爲無窮大
moonRotationAnimation.repeatCount = FLT_MAX;
//開始旋轉 !!!:切記這裏是讓空節點旋轉,而不是檯燈節點。 理由同上
[node1 addAnimation:moonRotationAnimation forKey:@"moon rotation around earth"];
}
1.2-完整代碼
#import "ARSCNViewViewController.h"
//3D遊戲框架
#import <SceneKit/SceneKit.h>
//ARKit框架
#import <ARKit/ARKit.h>
@interface ARSCNViewViewController ()<ARSCNViewDelegate,ARSessionDelegate>
//AR視圖:展示3D界面
@property(nonatomic,strong)ARSCNView *arSCNView;
//AR會話,負責管理相機追蹤配置及3D相機座標
@property(nonatomic,strong)ARSession *arSession;
//會話追蹤配置:負責追蹤相機的運動
@property(nonatomic,strong)ARSessionConfiguration *arSessionConfiguration;
//飛機3D模型(本小節加載多個模型)
@property(nonatomic,strong)SCNNode *planeNode;
@end
@implementation ARSCNViewViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)back:(UIButton *)btn
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//1.將AR視圖添加到當前視圖
[self.view addSubview:self.arSCNView];
//2.開啓AR會話(此時相機開始工作)
[self.arSession runWithConfiguration:self.arSessionConfiguration];
//添加返回按鈕
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[btn setTitle:@"返回" forState:UIControlStateNormal];
btn.frame = CGRectMake(self.view.bounds.size.width/2-50, self.view.bounds.size.height-100, 100, 50);
btn.backgroundColor = [UIColor greenColor];
[btn addTarget:self action:@selector(back:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
#pragma mark- 點擊屏幕添加飛機
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.planeNode removeFromParentNode];
//1.使用場景加載scn文件(scn格式文件是一個基於3D建模的文件,使用3DMax軟件可以創建,這裏系統有一個默認的3D飛機)--------在右側我添加了許多3D模型,只需要替換文件名即可
SCNScene *scene = [SCNScene sceneNamed:@"Models.scnassets/lamp/lamp.scn"];
//2.獲取檯燈節點(一個場景會有多個節點,此處我們只寫,飛機節點則默認是場景子節點的第一個)
//所有的場景有且只有一個根節點,其他所有節點都是根節點的子節點
SCNNode *shipNode = scene.rootNode.childNodes[0];
self.planeNode = shipNode;
//檯燈比較大,適當縮放一下並且調整位置讓其在屏幕中間
shipNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
shipNode.position = SCNVector3Make(0, -15,-15);
;
//一個檯燈的3D建模不是一氣呵成的,可能會有很多個子節點拼接,所以裏面的子節點也要一起改,否則上面的修改會無效
for (SCNNode *node in shipNode.childNodes) {
node.scale = SCNVector3Make(0.5, 0.5, 0.5);
node.position = SCNVector3Make(0, -15,-15);
}
self.planeNode.position = SCNVector3Make(0, 0, -20);
//3.繞相機旋轉
//繞相機旋轉的關鍵點在於:在相機的位置創建一個空節點,然後將檯燈添加到這個空節點,最後讓這個空節點自身旋轉,就可以實現檯燈圍繞相機旋轉
//1.爲什麼要在相機的位置創建一個空節點呢?因爲你不可能讓相機也旋轉
//2.爲什麼不直接讓檯燈旋轉呢? 這樣的話只能實現檯燈的自轉,而不能實現公轉
SCNNode *node1 = [[SCNNode alloc] init];
//空節點位置與相機節點位置一致
node1.position = self.arSCNView.scene.rootNode.position;
//將空節點添加到相機的根節點
[self.arSCNView.scene.rootNode addChildNode:node1];
// !!!將檯燈節點作爲空節點的子節點,如果不這樣,那麼你將看到的是檯燈自己在轉,而不是圍着你轉
[node1 addChildNode:self.planeNode];
//旋轉核心動畫
CABasicAnimation *moonRotationAnimation = [CABasicAnimation animationWithKeyPath:@"rotation"];
//旋轉週期
moonRotationAnimation.duration = 30;
//圍繞Y軸旋轉360度 (不明白ARKit座標系的可以看筆者之前的文章)
moonRotationAnimation.toValue = [NSValue valueWithSCNVector4:SCNVector4Make(0, 1, 0, M_PI * 2)];
//無限旋轉 重複次數爲無窮大
moonRotationAnimation.repeatCount = FLT_MAX;
//開始旋轉 !!!:切記這裏是讓空節點旋轉,而不是檯燈節點。 理由同上
[node1 addAnimation:moonRotationAnimation forKey:@"moon rotation around earth"];
}
#pragma mark -搭建ARKit環境
//懶加載會話追蹤配置
- (ARSessionConfiguration *)arSessionConfiguration
{
if (_arSessionConfiguration != nil) {
return _arSessionConfiguration;
}
//1.創建世界追蹤會話配置(使用ARWorldTrackingSessionConfiguration效果更加好),需要A9芯片支持
ARWorldTrackingSessionConfiguration *configuration = [[ARWorldTrackingSessionConfiguration alloc] init];
//2.設置追蹤方向(追蹤平面,後面會用到)
configuration.planeDetection = ARPlaneDetectionHorizontal;
_arSessionConfiguration = configuration;
//3.自適應燈光(相機從暗到強光快速過渡效果會平緩一些)
_arSessionConfiguration.lightEstimationEnabled = YES;
return _arSessionConfiguration;
}
//懶加載拍攝會話
- (ARSession *)arSession
{
if(_arSession != nil)
{
return _arSession;
}
//1.創建會話
_arSession = [[ARSession alloc] init];
_arSession.delegate = self;
//2返回會話
return _arSession;
}
//創建AR視圖
- (ARSCNView *)arSCNView
{
if (_arSCNView != nil) {
return _arSCNView;
}
//1.創建AR視圖
_arSCNView = [[ARSCNView alloc] initWithFrame:self.view.bounds];
//2.設置代理 捕捉到平地會在代理回調中返回
_arSCNView.delegate = self;
//2.設置視圖會話
_arSCNView.session = self.arSession;
//3.自動刷新燈光(3D遊戲用到,此處可忽略)
_arSCNView.automaticallyUpdatesLighting = YES;
return _arSCNView;
}
#pragma mark -- ARSCNViewDelegate
//添加節點時候調用(當開啓平地捕捉模式之後,如果捕捉到平地,ARKit會自動添加一個平地節點)
- (void)renderer:(id <SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
}
//刷新時調用
- (void)renderer:(id <SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
NSLog(@"刷新中");
}
//更新節點時調用
- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
NSLog(@"節點更新");
}
//移除節點時調用
- (void)renderer:(id <SCNSceneRenderer>)renderer didRemoveNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor
{
NSLog(@"節點移除");
}
#pragma mark -ARSessionDelegate
//會話位置更新(監聽相機的移動),此代理方法會調用非常頻繁,只要相機移動就會調用,如果相機移動過快,會有一定的誤差,具體的需要強大的算法去優化,筆者這裏就不深入了
- (void)session:(ARSession *)session didUpdateFrame:(ARFrame *)frame
{
NSLog(@"相機移動");
}
- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor*>*)anchors
{
NSLog(@"添加錨點");
}
- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor*>*)anchors
{
NSLog(@"刷新錨點");
}
- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor*>*)anchors
{
NSLog(@"移除錨點");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
1.3-代碼下載地址
-
ARKit從入門到精通Demo:http://download.csdn.net/detail/u013263917/9868679
-
筆者已經將8、9、10三小節的代碼合併成一個完整的小demo,供讀者交流學習