Help us understand the problem. What is going on with this article?

AR/MRで異なる座標系を合わせる

ARやMRは座標系の扱いが難しい

今回は前提としてVuforiaを使用し、3つの座標系を扱う必要があって苦労したので忘れないようにメモ

ARで表示したいオブジェクトをImageTargetの子要素にするケースがほとんどだが、親子関係は操作できない制約があったので
「親子関係を作らずに、ImageTargetの移動や回転に合わせてオブジェクト群の表示を更新する」必要がある

Example.png

ImageTarget

Vuforiaに登録した画像マーカー

ARオブジェクト用シーン

Vuforiaを動かすメインのシーンとは別に用意したオブジェクト配置用のシーン
その中でImageTargetの位置を設定

異なる3つの座標系

  • カメラに映る座標系(ImageTarget含む)
  • ARオブジェクト用シーンの座標系 (※Prefab化して配置)
  • ARオブジェクト用シーンに含まれるImageTargetを中心とした座標系

カメラ映像上にARオブジェクト用シーンを配置する

実際にカメラに映るImageTargetの位置にARオブジェクト用シーンを配置する
以下の手順で配置を行う

  1. ImageTargetの位置にARオブジェクト用シーンを移動
  2. ARオブジェクト用シーンに含まれるImageTargetをImageTargetの位置に移動
  3. ARオブジェクト用シーンに含まれるImageTargetをImageTargetと一致するように回転
  4. 以降ImageTargetの回転や移動に合わせて、ARオブジェクト用シーンも更新する

Success.gif

完成したコード

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ARObjectsSetter : MonoBehaviour
{
    [SerializeField]
    private Transform _actualImageTarget;
    [SerializeField]
    private Transform _arObjectsScene;

    [SerializeField]
    private Vector3 _virtualImageTargetPosition;
    [SerializeField]
    private Vector3 _virtualImageTargetRotation;

    private GameObject dummyImageTarget;
    private Vector3 relativePosition;
    private Vector3 parentLocalAxis;
    private float rotationAngle;
    private bool _enableUpdate = false;

    [ContextMenu("Initialize")]
    private void Initialize()
    {
        // ARオブジェクト用シーンをImageTargetに合わせる
        _arObjectsScene.position = _actualImageTarget.position;
        _arObjectsScene.rotation = _actualImageTarget.rotation;

        // ARオブジェクト用シーンのローカル座標からワールド座標でのVirtualImageTargetのPositionを求める
        var offset = _arObjectsScene.TransformPoint(-_virtualImageTargetPosition);
        _arObjectsScene.position = offset;

        // 仮想の親オブジェクトを生成し、ARObjectsSceneを子要素のように扱う
        dummyImageTarget = new GameObject("DummyImageTarget");

        // DummyImageTargetの位置と回転をVirtualImageTargetに合わせる
        var rot = _actualImageTarget.rotation * Quaternion.Euler(_virtualImageTargetRotation);
        dummyImageTarget.transform.SetPositionAndRotation(_actualImageTarget.position, rot);

        // ワールド座標からDummyImageTargetのローカル座標でのPositionを求める
        relativePosition = dummyImageTarget.transform.InverseTransformPoint(_arObjectsScene.position);

        // DummyImageTarget(親)のRotationと回転行列Sの積からARObjectsScene(子)のRotationが求まる
        // ARObjectsScene.rotaion = DummyImageTarget.tranform.rotation * S
        // 両辺にDummyImageTarget.transfrom.rotaionの逆行列をかける
        // ※ 回転の合成はQuaternionの積
        var rotationMatrix = _arObjectsScene.rotation * Quaternion.Inverse(dummyImageTarget.transform.rotation);

        // rotationMatrixがワールド座標での値なので、回転量(rotaitonAngle)と回転軸(globalAxis)を抽出する
        rotationMatrix.ToAngleAxis(out rotationAngle, out Vector3 globalAxis);
        // globalAxisをワールド座標からDummyImageTargetのローカル座標に変換する
        parentLocalAxis = dummyImageTarget.transform.InverseTransformVector(globalAxis);

        // ここまでで親子階層を操作せずに、DummyImageTargetの回転と移動がARObjectsSceneにも影響されるようになったので、
        // DummyImageTargetとImageTargetの回転を一致させる
        dummyImageTarget.transform.rotation = Quaternion.LookRotation(_actualImageTarget.forward);

        _enableUpdate = true;
    }

    private void LateUpdate()
    {
        if (!_enableUpdate) return;

        dummyImageTarget.transform.SetPositionAndRotation(_actualImageTarget.transform.position, _actualImageTarget.transform.rotation);

        // あらかじめ計算しておいたDummyImageTargetのローカル座標でのARObjectsSceneのPositionをワールド座標に変換
        var position = dummyImageTarget.transform.TransformPoint(relativePosition);
        // あらかじめ計算しておいたDummyImageTargetのローカル座標での回転軸をワールド座標に変換
        var globalAxis = dummyImageTarget.transform.TransformVector(parentLocalAxis);
        // あらかじめ計算しておいた回転をDummyImageTargetのRotationに合成する
        var rotation = Quaternion.AngleAxis(rotationAngle, globalAxis) * dummyImageTarget.transform.rotation;

        _arObjectsScene.SetPositionAndRotation(position, rotation);
    }
}

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした