1. HOME
  2. ブログ
  3. IT技術
  4. 【iOS + SwiftUI】DragGestureで動かすVR動画
【iOS + SwiftUI】DragGestureで動かすVR動画

【iOS + SwiftUI】DragGestureで動かすVR動画

ドラッグで視界を操作できるVR動画を実装!

iOSでVR動画といえば、ジャイロセンサーを使った実装のイメージが強いかもしれませんが、今回は以下のようにドラッグで視界を操作できるVR動画を実装していきます!

今回は横画面のみの対応とするので、プロジェクトで以下のようにDevice Orientationを設定しておきましょう。

VR動画を表示させてみる

まずはドラッグで動かすとかを考えず、単純にVR動画を画面上に表示させてみます。

VR動画はSceneViewで表示させることができます。

scene: 引数にはSCNSceneを要求されますが、これは3Dコンテンツを表示させるためのクラスです。

今回はSCNSceneを継承したVrSceneというクラスを作成し、そこでVR動画を表示させる処理を書いていきましょう。

SCNScene上にカメラを配置する

最初にVR動画の視点となるカメラを追加しましょう。

このカメラを通してVR動画上での視点を決めていく感じになります。

SCNCameraを初期化し、メンバ変数の cameraNode.camera に入れていますね。

メンバ変数にしているのは、後ほどドラッグで視点を移動する際にSCNNodeの操作が必要になるからです。

SCNSceneへのSCNNodeの追加は、 self.rootNode.addChildNode(cameraNode) で行っていきます。

ノードを使用して、シーン(空間)の構造を定義していくイメージです。

またSCNNodeの初期化では、どこに表示するかの座標を定義していませんが、何も指定しない場合は3D空間上の中央に配置されるようになっています。

次は動画の設定を行なっていきましょう。

動画プレイヤーを初期化する

3D空間に配置する以前に、動画を再生するということで動画プレイヤーを用意する必要があります。

最初の urlPath では動画がどこにあるのかを記述しています。

今回はローカルファイルとして動画を配置してあるので、 Bundle.main.path でPath情報を取得し、 URL(fileURLWithPath:) でAVAssetに渡していますね。

動画ファイルをプロジェクトに追加した際には、Build PhasesのCopy Bundle Resourceへの追加も忘れずやっておきましょう。

もしWeb上の動画を使う場合は、 URL(string: "動画のURL") に変えればいけるはずです。

AVAssetができたら、 AVPlayerItem(asset:)  -> AVQueuePlayer(playerItem:) の順に初期化していきましょう。

queuePlayer.isMuted = true はその名の通り、無音にする設定です。

動画によっては音がうるさい場合もあるので、お好みで設定してください。

最後にAVPlayerLooperをメンバ変数に入れていますが、これはその名の通りループ再生するプレイヤーになっています。

これで動画プレイヤーはできたので、次は3D空間上に配置していきましょう!

SCNScene上に動画を配置する

表示する動画には、以下のような全天球動画と呼ばれるものを使用します。

普通に表示させると歪んでしまうため、今回の動画配置では球状のオブジェクトに動画を貼り付け、その球の内側からカメラを回していくことになります。

まずは動画のシーンを作成していきましょう。

動画をシーンに入れるにはSKSceneとSKVideNodeを使用します。

SKSceneの初期化では size: の指定を行っていますが、1920x1080ということで解像度の設定ですね。

SKVideoNode(avPlayer: queuePlayer) で先ほど作ったAVQueuePlayerを入れてあげましょう。

そうしたら videNode.positionvideNode.size でSKSceneと同じ配置・サイズに変更します。

あとは動画を再生するために videoNode.play() を実行し、SKSceneにSKVideoNodeをセットしましょう。

次は動画を貼り付ける球状のオブジェクトを作成します。

球状のオブジェクトはSCNSphereで描画することができます。

今回はこのオブジェクトの中にカメラを入れるので、内側も描画するために firstMaterial?.isDoubleSided = true を設定しています。

firstMaterial?.diffuse.contents には先ほど作成した動画のSKSceneを代入しておきましょう。

その後は SCNNode(geometry: sphere) でノードにして、 self.rootNode.addChildNode() でシーンに追加します!

ビルドしてシミュレーターで表示してみる

ここまでで動画を表示するところまではできているので、ビルドして表示させてみましょう。

以下のようになると思います。

このままだと2点ほど問題があり、上下が逆になっているのと、VR動画の表示されている部分が動画の右端のほうになってしまっています。

この2点を修正していきましょう。

まずは上下が逆になっている問題からです。

これはvideoNodeを初期化しているところで、 yScale = -1.0 を設定すれば解決します。

yScale はノードの高さの倍率設定で、デフォルト値が1.0なのですが、これをマイナスにすることで上下を逆転させることができます。

次は動画の右端が表示されている問題の修正です。

これはカメラの orientation を設定することで解決します。

上記のコードでは省略されていますが、 SCNVector4.init(_ x: Int, _ y: Int, _ z: Int, _ w: Int) というイニシャライザを使用しています。

横回転なのにyを設定している?と思われる方もいるかもしれませんが、これはy軸を回転させているからです。

以下の画像でy軸がクルクル回っているというイメージがわかりやすいかもしれません。

y軸がクルクル回っているというイメージ

これらの値も倍率での計算となっており、1で180度回転してくれます。

これで以下のように修正されました!

ドラッグでカメラを操作する

VR動画の表示・再生はできたので、いよいよ本題のドラッグでカメラを動かす実装をしていきましょう!

まずはDragGestureをViewのほうに実装します。

ここは普通にGestureを追加する時と同じですね。

引き続き VrScene.drag(value:) のほうを実装していきましょう。

ドラッグの移動量を計算する

まずはドラッグでどれだけカメラを移動させるかを計算していきましょう。

ここでは currentDragValue というメンバ変数を作成し、前回の DragGesture.Value を保存しています。

startLocation パラメータはドラッグの開始地点を取得してくれるのですが、ドラッグ中にこれは更新されません。

ドラッグ中は継続的に移動量からカメラを回転させる必要がありますが、 startLocation をそのまま計算に使うと前回のドラッグの開始位置になってしまい、移動量がどんどん累積していってしまいます。

なので、初回の計算以外は currentDragVlaue?.location から現在のドラッグの開始位置を取得し、「現在の位置 - 現在のドラッグ開始位置」という計算で移動量をxとyそれぞれで取得しています。

これでドラッグの移動量が計算できたので、次はカメラを回転させていきましょう!

カメラを回転させる

まずはカメラを回転させるために、ドラッグの移動量を半円の角度である180度に変換していきます。

180度というのは、画面いっぱいのドラッグで最大でどれだけスクロールさせるのか?という値です。

動画の見えている範囲がちょうど180度に近いので、これに設定しています。

つまりドラッグの感度ってことなので、お好みで設定してください。

cameraDragPoint から見ていきましょう。

先ほども言ったように180度をangleとして取得し、(ドラッグの移動量 / 画面サイズ) * 角度 でxyそれぞれの移動量を計算しています。

画面サイズで見てどれだけ移動したかを、180度にそのまま適用しているだけですね。

次は上記で取得した値と現在のカメラの角度を使って、 rotateCamera でカメラを回転させましょう!

cameraNode.orientation が要求する型はSCNQuaternionですが、回転の計算をするためにGLKQuaternionに変換します。

受け取った現在のカメラの姿勢であるSCNQuaternionの値を、 GLKQuaternionMake(x:, y:, z:, w:) で変換してあげましょう。

GLKQuaternionの姿勢を変えたい場合は GLKQuaternionMultiply() によって、GLKQuaternion同士を掛けることで可能です。

まずは横回転の値をGLKQuaternionで取得しましょう。

回転の値の取得は GLKQuaternionMakeWithAngleAndAxis(_ radians: Float, _ x: Float, _ y: Float, _ z: Float) で行います。

radians: には GLKMathDegreesToRadians(Float(point.x)) で、DragGestureから取得したx方向への移動量を指定しましょう。

x:, y:, z: には radians: で指定した値を基準として、どの方向にどの倍率で動くかを指定します。

少し前にも言った通り、横回転はy軸の回転ですね。

y軸がクルクル回っているというイメージ

なので y: に1を入れて、x方向への移動量でそのまま回転させるようにしましょう。

縦回転も同じ要領で radians: にy方向への移動量を入れます。

縦回転はx軸の回転でできるので、 x: に1を指定してあげましょう!

最後にこれらの値を GLKQuaternionMultiply でそれぞれ合わせて、SCNQuaternionに戻して値を返却します。

シミュレーターで動かしてみる

以上で実装は完了したので、実際に動かしてみましょう。

横方向にも縦方向にもぐりぐり動かせるようになりましたね!

完成品はGithubにあげたので、参考にしてみてください。

https://github.com/ryuto-imai/VrPlayer

また、今回表示した動画は以下のサイトのものを利用しました。

https://360rtc.com/

関連記事

採用情報

\ あの有名サービスに参画!? /

バックエンドエンジニア

\ クリエイティブの最前線 /

フロントエンドエンジニア

\ 世界を変える…! /

Androidエンジニア

\ みんなが使うアプリを創る /

iOSエンジニア