先日ハッカソンでこんなプロトタイプを作ってみました
空間を切り取ってARで部屋に飾るプロトタイプ作ってみた!
— ふしっきー (@fusikky) October 27, 2019
おしゃれな装飾で自分の部屋を簡単に飾り付けできる
#withARハッカソン #空間 pic.twitter.com/LIbYOjIips
カメラに写っている空間を平面として切り取って、AR配置するというものです。
ハッカソン当日にいろいろ調べながらやったのでそれの備忘録ですが、全部書くと結構な量になってしまうので、
「カメラに写った画像をテクスチャとして切り出してARで平面として配置する」ところまで記述します。
この記事の内容で以下のようなアプリが作成できます。
ARで空間を切り取るアプリのプロトタイプでラッセン絵のポストカードをARで置きまくってみたら結構楽しかったw pic.twitter.com/Y91QjHl50L
— ふしっきー (@fusikky) November 5, 2019
開発環境
開発は以下を使いました
- Unity (2019.2.0f1)
- ARfoundation (3.0.0 preview.4)
- ARKit XR Plugin (3.0.0 preview.4)
AR Foundationの導入方法はこちら
https://qiita.com/fushikky/items/e43a1974d0f833121804
やっていること
- AR CameraオブジェクトからARCameraBackgroundを取得しRenderTextureに一時保存
- 平面オブジェクトのマテリアルに保存したテクスチャを設定しAR設置
上記で終わりなのですが、寂しいのでコードも書いておきます。
ソースコード
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
[RequireComponent(typeof(ARRaycastManager))]
public class ARSaveTexture : MonoBehaviour
{
[SerializeField]
ARCameraBackground arCamBg;
[SerializeField]
GameObject planePrefab;
ARRaycastManager raycastManager;
List<ARRaycastHit> hitResults = new List<ARRaycastHit>();
RenderTexture capturedTex;
bool saved = false;
void Start()
{
raycastManager = GetComponent<ARRaycastManager>();
// スクリーンサイズで初期化
capturedTex = new RenderTexture(Screen.width, Screen.height, 0);
}
public void SaveTex()
{
if (arCamBg.material != null)
{
// RenderTextureに deep copy
Graphics.Blit(null, capturedTex, arCamBg.material);
}
}
void Update()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
// 1度目のタップでテクスチャ保存
if (!saved)
{
SaveTex();
saved = true;
}
// 2度目以降のタップは平面設置
else
{
if (raycastManager.Raycast(touch.position, hitResults))
{
var plane = Instantiate(planePrefab, hitResults[0].pose.position, hitResults[0].pose.rotation);
var mat = plane.GetComponent<Renderer>().material;
mat.mainTexture = capturedTex;
// Billboard
if (Camera.main != null)
{
Vector3 target = new Vector3(
Camera.main.transform.position.x,
plane.transform.position.y,
Camera.main.transform.position.z
);
plane.transform.LookAt(target);
// 裏表逆向きになってしまうので反転
var angles = plane.transform.localEulerAngles;
angles.y += 180;
plane.transform.localEulerAngles = angles;
}
}
}
}
}
}
}
少し解説します。
savedフラグ
簡易的な処理ではありますが、bool saved
のフラグを用いて以下を実現しています。
- 1度目のタップでテクスチャ保存
- 2度目以降のタップで平面を設置
ARCameraBackGround
冒頭のarCamBg変数にはARCameraオブジェクトのARCameraBackGroundコンポーネントがアタッチされています。
これはカメラで撮影している内容をCameraの背景(Background)に描画するものです。こちらのコンポーネントからマテリアルとしてカメラ画像を取得できます。
[SerializeField]
ARCameraBackground arCamBg;
設置する平面
planePrefab変数には Create>3D Object>QuadからQuad Prefabを生成し、Scaleに以下のパラメータを設定しています。
これは使用する端末の解像度の縦横比に合わせています。そうしないとテクスチャを貼り付けた際に歪んだ見た目になってしまいます。
(私はiPhone11 proユーザーなので2,436x1,125の比率にしています。)
カメラ画像をテクスチャとしてコピー
上記ソースコードのSaveTex
関数の以下の処理でarCamBg.materialのマテリアルをcapturedTexにコピーしています。
Graphics.Blit(null, capturedTex, arCamBg.material);
Graphics.Blit関数を使えば、第3引数に与えたマテリアルを第2引数のレンダーテクスチャへコピーすることができます。
https://docs.unity3d.com/ja/2017.4/ScriptReference/Graphics.Blit.html
平面設置
2度目以降のタップで平面を設置しています。
まず、RaycastManagerでタップ位置とAR空間の衝突判定を行い、衝突位置に平面をInstantiateしています。
設置した平面からマテリアルを取得し、mainTextureに先ほど保存したテクスチャを設定しています。
if (raycastManager.Raycast(touch.position, hitResults))
{
var plane = Instantiate(planePrefab, hitResults[0].pose.position, hitResults[0].pose.rotation);
var mat = plane.GetComponent<Renderer>().material;
mat.mainTexture = capturedTex;
...
}
このままだと平面がこちらを向いてくれないので、以下のコードでBillboard的な処理をして平面をこちらに向かせています。
内容としては、カメラ位置の高さ(y座標)を平面と同じにした点を向くようにLookAt関数を使用しています。
また、そのままだと裏表逆向きになってしまったので、localEulerAnglesのy軸を180度回転させることでこちらを向くようにしています。
Vector3 target = new Vector3(
Camera.main.transform.position.x,
plane.transform.position.y,
Camera.main.transform.position.z
);
plane.transform.LookAt(target);
// 裏表逆向きになってしまうので反転
var angles = plane.transform.localEulerAngles;
angles.y += 180;
plane.transform.localEulerAngles = angles;
ここで、Camera.mainがnullになってしまう場合は、CameraにMainCamera
タグが設定されていないからです。
以下のように設定しましょう。
Tips
平面のマテリアルがDefault-MaterialのままだとStanderdShaderになり暗く見えてしまうので、
Unlit系のShaderを使用したマテリアルを作っておくのがオススメです。
(ちゃんとARやろうと思ったら環境光も影響させた方がよいですが)
参考記事