Webチームの蔵下です。弊社で開発しているintdashには、Media Servicesという動画や音声などのメディアデータを扱うサービスがあります。さまざまなカメラに対応できることもあり、RICOH THETAのような360°カメラで撮影した動画を扱うこともあります。
「全天球画像 | RICOH THETA」より引用
▲360°動画。360°の映像が1つのパノラマ動画内に収められています。歪みを補正して再生するためには、専用の動画Playerが必要になります。
この360°動画をブラウザで再生する360°動画Playerを、JavaScriptの3Dフレームワークであるthree.jsで開発しました。
本記事では、360°動画Playerを開発するときの実用的なTIPSを紹介します。
360°動画Playerの仕組み
360°動画をHTMLのVideoタグで再生しても、上の画像のように歪んだ状態で再生されてしまいます。この歪みを解消するためには3D(three.js)で実装します。詳しいロジックについての解説は記事「お手軽360°パノラマ制作入門!」がわかりやすいのでご覧ください!
- 3D空間に球体を配置する
- 球体の内側に動画をテクスチャとして貼り付ける
- 目となるカメラを球体の内側に配置する
配置したカメラの回転角度を垂直方向(Pitch)・水平方向(Yaw)に回転させることで、360°動画内の見たい方向の映像が描画できます。
実用TIPS集
360°動画をthree.jsで表示する方法の解説は、すでに他のブログでわかりやすく解説されているため、本記事では360°動画を表示した先の実用TIPSを紹介します。
TIPS1: three.jsのRenderingとVideo描画でLoop処理を分離する
使用するカメラやユースケースによって動画のFPSは異なります。すべてのFPSへ対応するために最大FPSで描画処理のタイミングを固定してしまうと、低FPSの動画でムダな描画処理が実行されてしまいます。
動画FPSによって描画処理を間引けばムダな描画はカットできるのですが、そのままthree.jsのRenderingを間引いてしまうと、操作時にカクつきが出て操作性が落ちてしまい本末転倒です。
そこで、three.jsのRenderingとVideo描画でLoop処理を分離し、Video描画用のTextureもVideoTextureからCanvasTextureへ置き換えました。これにより、Video描画を動画FPSの間隔で実行できるようになります。
// three.jsのLoop処理 const tick = () => { renderer.render(scene, camera) } // three.jsのRendering処理開始: requestAnimationFrame renderer.setAnimationLoop(tick) // VideoのFPS const VIDEO_FPS = 10 // Videoの描画用Loop処理 const loopVideo = () => { // VideoをCanvasへ描画する const context = captureCanvas.getContext('2d') context?.drawImage(video, 0, 0) // Rendering時にMaterialを更新する material.map.needsUpdate = true // Videoの描画はsetTimeoutで実行 window.setTimeout(loopVideo, 1000 / VIDEO_FPS) } // 動画の描画処理開始 loopVideo()
※ 複数のrequestAnimationFrameでLoop処理すると、片方の実行タイミングにもう片方が引っ張られてしまったため、setTimeoutで分離しました。
TIPS2: Playerの外からPitch, Yaw, Zoomを操作できるようにする
360°動画Playerのソースコードはネット上に数多く公開されていますが、Playerの外部からボタンなどで操作する方法を解説している記事は多くありませんでした。
360°動画Playerに最低限必要な機能はOrbitControlsを使用すると手軽に実装できるのですが、不要な機能も多く、外部から操作しづらいとうこともあり、今回はOrbitControlsのロジックを流用して必要な機能を自前で実装しました。詳しい解説は割愛しますが、次のソースコードは実装箇所の一部です。
// Set Pitch const setPitch = (angle: number) => { // angle: -90 ~ 90 targetPitchAngle = ((angle + 90) * Math.PI) / 180 } // Set Yaw const setYaw = (angle: number) => { // angle: 0 ~ 360 targetYawAngle = ((angle - 180) * Math.PI) / 180 } // Set Zoom const setZoom = (zoom: number) { // zoom: 100 ~ 200 targetZoom = (this.maxDistance - this.minDistance) * (1 - (zoom - 100) / 100) } // 略... // Update Yaw if (targetYawAngle !== undefined) { spherical.theta = targetYawAngle + sphericalDelta.theta targetYawAngle = undefined } else { spherical.theta += sphericalDelta.theta } spherical.theta = Math.max( minYawAngle, Math.min(maxYawAngle, spherical.theta), ) // Update Pitch if (targetPitchAngle !== undefined) { spherical.phi = targetPitchAngle + sphericalDelta.phi targetPitchAngle = undefined } else { spherical.phi += sphericalDelta.phi } spherical.phi = Math.max( minPitchAngle, Math.min(maxPitchAngle, spherical.phi), ) // Update Zoom if (targetZoom !== undefined) { spherical.radius = targetZoom + sphericalDelta.radius targetZoom = undefined } else { spherical.radius += sphericalDelta.radius } spherical.radius = Math.max( minDistance, Math.min(maxDistance, spherical.radius), )
TIPS3: 動作テストはStorybookで実装する
360°動画Playerを実装するにあたり、動作テストもさまざまなユースケースが考えられました。ゼロから動作テスト用の画面を組むのは実装コストがかかり現実的ではなかったため、Storybookを使用して実装しました。Storybookを起動するだけで環境が構築できるので、誰でも動作テストができるところもメリットです。
▲ローカルにある360°動画を実装したPlayerに読み込んだ動作テスト。360°動画(上部Video)とPlayer(下部Canvas)の表示を比較できる。
▲外部に設置したボタンから360°動画Playerを操作する動作テスト。想定通りの方向を描画できるか確認します。
おわりに
アプトポッドでは、intdashのユーザーへより充実した機能を届けるべく、さまざまなアプローチで日々試行錯誤しています。本記事で紹介した360°動画Playerをintdashと組み合わせることで、360°カメラで撮影した映像を遠隔から低遅延で確認できるようになり、より人の目に近い体験を提供できると考えています。
今回、実用的な360°動画Playerを開発するにあたり、ネット上の情報だけでは「もう少し深い機能がほしい!」「かゆいところに手が届かない!」という場面が多々ありました(もちろん情報を発信してくださっている方々には頭が上がりません!)。そこで本記事では、開発する中で用いた実用TIPSを、これから360°動画Playerを実装する皆さんの力になれればという思いで紹介しました。
記事のボリュームの兼ね合いで割愛した部分も多いですが、リクエストいただければ別記事で解説できればと思いますので、お気軽にお問い合わせください!