4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ARFoundationでヘッドトラッキング

Last updated at Posted at 2020-08-30

ARFoundationを用いてユーザーの視点をトラッキングすることで、以下のような錯視空間を表現することができます。
視点と画面上の空間を連動させることで、ユーザーからはスマートフォン画面内部に箱型の空間が存在するように感じられます。

ezgif-7-d45de45d8a2d.gif

フェイストラッキングを使用しているため、顔とデバイスカメラの位置関係によりシーンの見え方が変化します。
以下に実装サンプルのリンクも貼っていますので、サンプルをビルドして、デバイスの向きを変えたり自分の顔を移動させてみてください。

検証したUnityやパッケージのバージョン、端末は以下になります。

  • Unity: 2020.1.1f1
  • AR Foundation: 3.1.3
  • ARKit XR Plugin: 3.1.3
  • ARKit Face Tracking: 3.1.3
  • 端末: iPhoneXs
  • iOSシステムバージョン: 13.6.1

また、デバイスの向きはポートレートモードのみに対応しています。

実装について

実装に関しては、「TheParallaxView」をARFoundationに移植させていただきました。
オリジナルはARKitPluginで実装されています。

「TheParallaxView」の記事から、カメラのプロジェクションや各種パラメータなど参照しています。
実装の詳しい解説もされてますのでご興味ある方はご一読されると良いかと思います。

ソースコードはかなりシンプルになっています。
フェイストラッキングはFindWithTagで強引に検索しているので、もっと良いやり方があるかと思います...。

OffAxisProjectionPortrait.cs
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;

namespace Parallax
{
    [ExecuteInEditMode]
    public class OffAxisProjectionPortrait : MonoBehaviour
	{
        [SerializeField] private Camera deviceCamera, eyeCamera, arCamera;
        [SerializeField] private float left, right, bottom, top, near, far;
		[SerializeField] private Vector2 moveAmount = new Vector2(2f, 2f);

        private GameObject arFaceObj;
        private ARFace arFace;

        private void LateUpdate()
        {
            if (deviceCamera == null ||
				eyeCamera == null ||
                arCamera == null)
            {
                Debug.LogWarning("deviceCamera, eyeCamera, arCameraがセットされていません");
                return;
            }				

            Quaternion q = deviceCamera.transform.rotation * Quaternion.Euler(Vector3.up * 180f);
            eyeCamera.transform.rotation = q;

            if (arFaceObj == null || arFace == null) {
                try
                {
                    arFaceObj = GameObject.FindWithTag("ARFace");
                    arFace = arFaceObj.GetComponent<ARFace>();
                }
                catch (System.Exception e)
                {
                    Debug.LogWarning(e);
                }
            }
            else
            {
                Vector2 eyePos = arCamera.WorldToViewportPoint(arFace.leftEye.position);
                eyePos.x -= 0.5f;
                eyePos.y -= 0.5f;
                eyePos.x = Mathf.Clamp(eyePos.x, -moveAmount.x, moveAmount.x);
                eyePos.y = Mathf.Clamp(eyePos.y, -moveAmount.y, moveAmount.y);
                eyeCamera.transform.localPosition = new Vector3(-eyePos.x, eyePos.y, 0f);
            }

            Vector3 deviceCamPos = eyeCamera.transform.worldToLocalMatrix.MultiplyPoint(deviceCamera.transform.position);
			Vector3 fwd = eyeCamera.transform.worldToLocalMatrix.MultiplyVector (deviceCamera.transform.forward); 
			var devicePlane = new Plane(fwd, deviceCamPos);
			Vector3 close = devicePlane.ClosestPointOnPlane(Vector3.zero);
			near = close.magnitude;

			// iPhoneのサイズを設定する
			left = deviceCamPos.x - 0.040f;
			right = deviceCamPos.x + 0.022f;
			top = deviceCamPos.y + 0.000f;
			bottom = deviceCamPos.y - 0.135f;
			far = 10f;

			float scale_factor = 0.01f / near;
			near *= scale_factor;
			left *= scale_factor;
			right *= scale_factor;
			top *= scale_factor;
			bottom *= scale_factor;

			Matrix4x4 m = PerspectiveOffCenter(left, right, bottom, top, near, far);
			eyeCamera.projectionMatrix = m;
		}

		private static Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far)
		{
			float x = 2.0f * near / (right - left);
			float y = 2.0f * near / (top - bottom);
			float a = (right + left) / (right - left);
			float b = (top + bottom) / (top - bottom);
			float c = -(far + near) / (far - near);
			float d = -(2.0f * far * near) / (far - near);
			float e = -1.0f;
			var m = new Matrix4x4();
			m[0, 0] = x; m[0, 1] = 0; m[0, 2] = a; m[0, 3] = 0;
			m[1, 0] = 0; m[1, 1] = y; m[1, 2] = b; m[1, 3] = 0;
			m[2, 0] = 0; m[2, 1] = 0; m[2, 2] = c; m[2, 3] = d;
			m[3, 0] = 0; m[3, 1] = 0; m[3, 2] = e; m[3, 3] = 0;
			return m;
		}
	}
}

シーン内のボックスはシェーダーでグリッドを描画しています。
以下ソースコードです。

Box.shader

Shader "Custom/Box"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Col ("Col", Range(0,20)) = 1
        _Row ("Row", Range(0,20)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            float grid(float2 st, float size)
            {
                size = 0.5 + size * 0.5;
                st = step(st, size) * step(1.0 - st, size);
                return st.x * st.y;
            }

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _Col;
            float _Row;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 st = frac(i.uv * float2(_Col, _Row));
                return 1 - grid(st, 0.98);
            }
            ENDCG
        }
    }
}

最後に

シーンを追加していくと楽しいです。

https://vimeo.com/451829511/209d7d4229

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?