2017.10.10
AppleのARKitとGoogleの最新のARプラットフォーム ARCore – 可能性と応用
はじめに
こんにちは。次世代システム研究室の B.M.Kです。
2017年9月25日に開催された社内の研究発表会にて、ARについて発表した内容をご紹介します。
今年の6月にAppleがiOS向けのARフレームワーク「ARKit」を発表し、8月にGoogleがAndroid向けARフレームワーク「ARCore」を発表してスマートフォン向けのAR(Augmented Reality)をめぐる状況が騒がしくなってきました。これによりARKitと ARCoreで何ができるのか?どんなビジネスに繋がるのか?そして我々がどう取り組んでいるのかをこの場を借りて解スマホOS標準のARフレームワークが出揃い、本格的な競争が始まることが予想されます。では、ARCoreとはどういうものなのだろうか? そしてARKitとの違いは?AndroidでもiOSでもARフレームワークがサポートされることは、アプリ開発者には朗報です。現状の説と共有をします。
この度、来店促進サービスに繋ぎ、店舗で子供も親も楽しめ、他の店舗で体験できないことを与える目的で、アプリのプロトタイプ(魔法ホールとどこでもドア)を作りました。
アプリ開発環境
今回のアプリを開発するため、以下の通りで開発環境を構築しました。
- Unity3D 2017.1.0f3 Personal
- Unity ARKit Plugin
- Xcode 9.0 beta 3
- iPhone 7 Plus
- iOS 11.0 beta 3
技術要素
下記の通り、いくつか技術要素を活用しました。
- 平面検出 & 現実空間への当たり判定
- ダブルカメラ
- 360ビデオレンダリング&テクスチャ裏返し処理
- オフスクリーンレンダリング
- レイヤー& カリングマスク
- デプスマスク
平面検出および現実空間への当たり判定
この度、ARKitの平面検出と現実空間への当たり判定機能を活用しました。
まず、トラッキングする平面の自然特徴点抽出及び部分集合分割処理をして平面判定を行った後、ユーザーがタッチしたスクリーン上のポイントのスクリーン座標からARKit座標系での座標に変換し、ARKit座標系の中で平面と当たり判定(レイキャスト活用)を行います。レイキャストとはユーザーがタッチしたスクリーン上のポイントから透明なバーチャル線を引いて、バーチャル空間でのオブジェクト、平面等とヒットしたかどうか検知するものです。
var touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
var screenPosition = Camera.main.ScreenToViewportPoint(touch.position);
ARPoint point = new ARPoint {
x = screenPosition.x,
y = screenPosition.y
};
ARHitTestResultType[] resultTypes = {
ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent,
ARHitTestResultType.ARHitTestResultTypeHorizontalPlane,
ARHitTestResultType.ARHitTestResultTypeFeaturePoint
};
foreach (ARHitTestResultType resultType in resultTypes)
{
if (HitTestWithResultType (point, resultType))
{
return;
}
}
ヒットしたところの三次元座標情報をARKit座標系からアプリの座標系 (Unity座標系等)へ変換し、今回表示するバーチャル魔法のホールとどこでもドアをヒットした所へ移動して表示します。
public Transform m_HitTransform; // どこでもドアの位置、回転、スケールを扱う
List<ARHitTestResult> hitResults = UnityARSessionNativeInterface.GetARSessionNativeInterface ().HitTest (point, resultTypes);
if (hitResults.Count > 0) {
foreach (var hitResult in hitResults) {
m_HitTransform.position = UnityARMatrixOps.GetPosition (hitResult.worldTransform);
m_HitTransform.rotation = UnityARMatrixOps.GetRotation (hitResult.worldTransform);
return true;
}
}

ダブルカメラ
本アプリにて実世界を見るためのメインカメラ以外、ドアの枠内でバーチャル世界を見るためにもうひとつのカメラが必要となります。メインカメラからどこでもドアまでの距離と向きをオフセット値としてリアルタイムに第2カメラの位置と向きを反映します。
この二つのカメラの位置と向きを同期することで部屋の中に窓へ向き移動しながら窓の外世界を見るような感覚を体験できるようになります。
Vector3 cameraOffset = mainCam.transform.position - transform.position; portalCam.transform.position = transform.position + cameraOffset; portalCam.transform.rotation = Quaternion.LookRotation(mainCam.transform.forward, Vector3.up);

360ビデオレンダリング
ユーザーに店舗の生の雰囲気を360度体験してもらうために、店舗の360度動画の撮影、加工の工夫は当然ですが秘訣は360度動画のレンダリングです。360度動画撮影はRICOH THETAなどのような専用撮影機器があれば、苦労せずに撮影することができます。次はUnity上の球形状のゲームオブジェクトの内面にレンダリング及び球形状オブジェクトの中にカメラを移動する処理が必要となります。Unityはデフォルトとして球形状のオブジェクの表面(外面)にレンダリングするので、内面にレンダリングするために表面の法線ベクトルの方向変更若しくはテクスチャの裏返し処理を行わなければなりません。処理は下記のシェーダープログラミングで行います。シェーダープログラミングはオブジェクトを構成するメッシュの頂点単位の処理、ピクセル単位の処理 をカスタマイズするためのプログラムです。
Shader "Custom/InsideVisible" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
Cull front
LOD 100
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
v.texcoord.x = 1 - v.texcoord.x;
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.texcoord);
return col;
}
ENDCG
}
}
}

オフスクリーンレンダリング
カメラはデフォルトとして端末の画面にフルレンダリングします。
しかし、本アプリにてどこでもドアみたいなもの枠内部分だけにバーチャル世界をレンダリングしたい時、オフスクリーンレンダリングを活かすことができます。オフスクリーンレンダリングとはその名の通り、画面に書き込まない描画処理のことを指します。今回、第2カメラの画像フレームは端末スクリーンではなく別のテクスチャにレンダリングして、そのテクスチャをドア内の平面オブジェクトに部分的に貼りました。貼る時ドアの面積相当のテクスチャ分だけを貼らなければならないので下記のシェーダーの処理を行いました。
Shader "Custom/AdjustShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
CGPROGRAM
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
float4 pos_frag : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
float4 clipSpacePosition = UnityObjectToClipPos(v.vertex);
o.pos = clipSpacePosition;
o.pos_frag = clipSpacePosition;
return o;
}
half4 frag(v2f i) : SV_Target {
float2 uv = i.pos_frag.xy / i.pos_frag.w;
uv = (uv + float2(1.0, 1.0)) * 0.5;
return tex2D(_MainTex, uv);
}
ENDCG
}
}
}

オフスクリーンレンダリング実装の流れは以下の通りです。
- 新しいレンダーテクスチャオブジェクトを作成(Aテクスチャ)
- 第2カメラのターゲットテクスチャにAテクスチャを設定
- 上記のシェーダーを作成
- シェーダーを使用するマテリアルオブジェクトの作成
- ドア枠内での平面オブジェクトのマテリアルに上記のマテリアルオブジェクトを設定
レイヤー&カリングマスク
Unityにてカメラのカリングマスクを使用して、特定の1つのレイヤーにあるオブジェクトのみレンダリングできます。カリングマスクプロパティーでレイヤーのチェックをつけたり外したりすることでカリングマスクの設定を変更することができます。本アプリにて、レイヤー 及び各カメラのカリングマスクの活用によりメインカメラにて現実空間、ドア枠、第2カメラから写されたドアの枠内部分のバーチャル世界が見えるようにしますがバーチャル世界の他の部分を隠します。ユーザーがドアを通った時、バーチャル世界の全景を表示し、ユーザーがドアを通って戻る時それら部分をまた隠すとという処理を繰り返し行いました。


デプスマスク
バーチャル空間から現実世界を見るためにデプスマスクを活用しました。
どこでもドアの枠内のサイズと同様を持っているテプスマスクオブジェクトを作成し、テプスマスクのシェーダーをバーチャル世界でのオブジェクトより先にレンダリングします(シェーダーIDをGeometryより小さい値を設定)。そのため、バーチャル世界をレンダリングする時、ドアの枠内部分がレンダリングされないため、その部分だけから現実空間が露出されます。

まとめ
今回、AppleのARKit、GoogleのARCoreそして我々が取り組んでいることを紹介しました。ARKitとARCoreは現時点で同等機能を提供していますがARKitに対応するiPhoneは約3億8千万台で日本国内は特にiOSのシェアが高いので現時点でARKitの方から始めて、ARCoreに水平展開した方が良いかもしれません。
最後に
次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD

