0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Meta Quest 3 のパススルーカメラで QR コードを認識する (Unity + ZXing)

Last updated at Posted at 2025-10-16

Quest 3 のパススルーカメラから QR コードを読み取れるようにしてみました 🎉
最終的にはこんな感じになります 👇

Videotogif (2).gif

環境

  • Unity 6000.0.28f1
  • Meta All-in-One SDK 77.0.0
  • Quest 3 実機
  • Render Pipeline: URP / Built-in 両方で検証
  • ZXing.Net for Unity

1. AndroidManifest に必要な権限

Quest 3 でパススルーカメラを使うには、通常の CAMERA に加えて ヘッドセット専用のカメラ権限 が必要です。

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="horizonos.permission.HEADSET_CAMERA" />

Unity のビルド結果の AndroidManifest.xml に両方が入っているか必ず確認してください。

2. カメラ権限のリクエスト

Unity 側では起動時に以下のように権限をチェックしてリクエストします。

#if UNITY_ANDROID && !UNITY_EDITOR
if (!Permission.HasUserAuthorizedPermission("android.permission.CAMERA") ||
    !Permission.HasUserAuthorizedPermission("horizonos.permission.HEADSET_CAMERA"))
{
    Permission.RequestUserPermissions(
        new[] { "android.permission.CAMERA", "horizonos.permission.HEADSET_CAMERA" },
        new PermissionCallbacks()
    );
    return;
}
#endif

3. WebCamTexture.devices の挙動 (URP と Built-in)

実機で試したところ、Render Pipeline によって WebCamTexture.devices のインデックス が異なっていました。

URPプロジェクトではdevices[0]は映像が取得できず、devices[1],devices[2]で映像が取得できました。
Built-inプロジェクトではdevices[0]で映像が取得できていました。

👉 そのため「devices[0] を使えばいい」と決め打ちすると環境依存でハマります。

4. ZXing を使った QR 読み取り

ZXing の BarcodeReader を使い、取得したカメラ映像 (cam.GetPixels32()) をデコードします。

reader = new BarcodeReader
{
    AutoRotate = true,
    Options = new DecodingOptions
    {
        TryHarder = true,
        TryInverted = true,
        PossibleFormats = new[] { BarcodeFormat.QR_CODE }
    }
};

5. 実装例 (UI付き)

以下は 読み取り結果を TextMeshPro で表示し、RawImage にカメラ映像を出すサンプル です。

using TMPro;
using UnityEngine;
using ZXing;
using ZXing.Common;
using UnityEngine.Android;
using UnityEngine.UI;

public class QrTestWithUI : MonoBehaviour
{
    public TextMeshProUGUI resultText;     // QR結果表示用
    public TextMeshProUGUI cameraNameText; // 現在使用中カメラ名表示
    public RawImage previewImage;          // 映像表示用

    private WebCamTexture cam;
    private BarcodeReader reader;
    private bool permissionRequested = false;
    private int decodeCount = 0;
    private bool hasScanned = false;

    void Start()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (!Permission.HasUserAuthorizedPermission("android.permission.CAMERA") ||
            !Permission.HasUserAuthorizedPermission("horizonos.permission.HEADSET_CAMERA"))
        {
            Permission.RequestUserPermissions(
                new[] { "android.permission.CAMERA", "horizonos.permission.HEADSET_CAMERA" },
                new PermissionCallbacks()
            );
            permissionRequested = true;
            resultText.text = "カメラ権限をリクエスト中…";
            return;
        }
#endif
        StartCamera();
    }

    void Update()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        if (permissionRequested &&
            Permission.HasUserAuthorizedPermission("android.permission.CAMERA") &&
            Permission.HasUserAuthorizedPermission("horizonos.permission.HEADSET_CAMERA"))
        {
            permissionRequested = false;
            StartCamera();
        }
#endif

        if (cam != null && cam.didUpdateThisFrame)
        {
            if (hasScanned) return;

            decodeCount++;
            var pixels = cam.GetPixels32();
            var result = reader.Decode(pixels, cam.width, cam.height);

            if (result != null)
            {
                hasScanned = true;
                resultText.text = $"読み取り成功: {result.Text}";
                resultText.color = Color.green;
            }
            else
            {
                resultText.text = $"スキャン中… (#{decodeCount})";
                resultText.color = Color.yellow;
            }
        }
    }

    void StartCamera()
    {
        var devices = WebCamTexture.devices;
        if (devices.Length == 0)
        {
            resultText.text = "カメラが見つかりません";
            return;
        }

        // Camera1 を検索して使用
        var target = System.Array.Find(devices, d => d.name.Contains("Camera1"));
        if (target.name == null)
        {
            resultText.text = "Camera1 が見つかりません";
            return;
        }

        cameraNameText.text = $"使用カメラ: {target.name}";
        cam = new WebCamTexture(target.name, 1280, 720, 30);
        cam.Play();
        previewImage.texture = cam;

        reader = new BarcodeReader
        {
            AutoRotate = true,
            Options = new DecodingOptions
            {
                TryHarder = true,
                TryInverted = true,
                PossibleFormats = new[] { BarcodeFormat.QR_CODE }
            }
        };

        decodeCount = 0;
        hasScanned = false;
        resultText.text = "QRスキャン中…";
        resultText.color = Color.white;
    }
}

Videotogif (3).gif

6. 実行結果

  • Quest 3 実機でビルドするとパススルーカメラ映像が RawImage に表示されます

  • QR コードをかざすと「読み取り成功: (テキスト)」が表示されます

  • 一度読み取ったら処理を止める仕様にしています

Videotogif (2).gif

まとめ

  • Quest 3 のパススルーカメラには 2種類の権限 が必要

  • WebCamTexture.devices のインデックスは URP / Built-in で異なる

  • ZXing を使えば簡単に QR コード認識を組み込める

これで Quest 3 MR アプリに 現実の QR を使った体験 を追加できます 🎉

応用例

  • 会場で配布した QR を読み取ってシーン切り替え

  • 現実オブジェクトの位置合わせ用マーカー

  • Web 連携で情報を呼び出す

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?