3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Quest3から両目の映像を取得してリアルタイムで立体映像を楽しんでみる

Last updated at Posted at 2025-12-03

この記事はIwakenLab. Advent Calendar 2025の3日目の記事です!

昨日はToLpazさんの 「自己紹介:『異世界クリエイター』として生きる」 でした!

今回は、Quest3の両目カメラを使って奥行のある立体的な映像を作る方法についてお話します。

今回作るもの

こちらが今回の記事で作るものです。

PCAStereoscopic3D.gif

Quest3のカメラ映像を板ポリに映しているのですが、そこにひと手間加えることで板ポリに映る映像をより立体的にします。

動画だと分かりづらいですが、実機でみると奥行を感じることが出来、結構立体的に見えます。

以下のリポジトリのAssets\Kenty\Scenes\PCA-Stereoscopic3Dのシーンをビルドして、是非、実機で確認してみてください!

概要説明

今回行うのは「ステレオスコピック」と呼ばれるものです。

人間は 左目と右目で少しだけ違う位置から世界を見ています。その「わずかなズレ(視差)」を脳が合成することで、人間は奥行きがある立体の世界を認識出来ているわけです。

ステレオスコピックもそれと同じで、視差のある映像を左目と右目それぞれに見せることで、ただの画像でも奥行きを感じられるようにしています。

スマホVRなどが分かりやすい例で、スマホVRではSBS(Side By Side)と呼ばれる視差のある映像を仕切りで区切り、左目と右目に見せています。

今回はMetaの新機能を使ってSBSの映像を作り、それを左右それぞれの目に映すことで立体的な映像を作ることを目指します。

動作環境

プロジェクトのセットアップ

Unityを開いて上記のパッケージをインストールし、Meta XR ToolsProject Setup Toolを使ってエラーを解消します。

image.png

Unity-PassthroughCameraApiSamplesのリポジトリからZipファイルを取得します。

image.png

Zipを展開しUnity-PassthroughCameraApiSamples-main\Unity-PassthroughCameraApiSamples-main\AssetsにあるPassthroughCameraApiSamplesのフォルダをUnityにドラッグ&ドロップします。

image.png

Assets\PassthroughCameraApiSamplesにあるMultiObjectDetectionは使わないのでフォルダごと削除します。

PassthroughCameraApiSamplesのシーンがちゃんと機能するか確認するため、CameraViewerのシーンを開き、Project Setup Toolのエラーを修正します。

image.png

エラーを修正したらビルドして、カメラアクセスがちゃんと出来ていることを確認しましょう。

PCAStereoscopic3D_1.jpg


上記まででQuest3のカメラ映像を取得することは確認できたので、ここから本題に入っていきます。

方針としては、カメラ映像を横にならべてSBSの映像を作り、それを板ポリに映します。板ポリに映す際、Shaderで映像を分割して左目・右目にそれぞれ違う映像を見せることで立体的に見せるという感じです。

SBS(Side By Side)の映像を作る

新しくシーンを作り、以下のBuildingBlockを追加します。

  • Camera Rig
  • Passthrough
  • Hand Tracking
  • InteractionRig
  • Grab Intaraction

image.png

映像を映した板ポリを手で動かしたいので、Grab IntaractionのCubeをQuadに変えて、サイズや位置を調整します。

image.png

次に、空のオブジェクトを2つ作ってそれぞれにPassthroughCameraAccessのコンポーネントを追加します。この時に、PassthroughCameraAccessのCameraPositionはそれぞれLeftとRightにして下さい。

image.png

image.png

CreateSBSTexture.csを作り、空オブジェクトにコンポーネントとして追加します。

CreateSBSTexture.cs
using Meta.XR;
using System.Collections;
using UnityEngine;

public class CreateSBSTexture : MonoBehaviour
{
    [SerializeField] private PassthroughCameraAccess _leftCameraAccess;
    [SerializeField] private PassthroughCameraAccess _rightCameraAccess;
    [SerializeField] private Material _stereoTargetMaterial;

    private RenderTexture _stereoTexture;
    private static readonly int MainTexId = Shader.PropertyToID("_MainTex");

    private void OnEnable()
    {
        StartCoroutine(EnsureStreams());
    }

    private IEnumerator EnsureStreams()
    {
        while (!OVRPermissionsRequester.IsPermissionGranted(OVRPermissionsRequester.Permission.PassthroughCameraAccess))
        {
            yield return null;
        }

        StartCoroutine(StreamEye(_leftCameraAccess, _rightCameraAccess));
    }

    private IEnumerator StreamEye(PassthroughCameraAccess accessL, PassthroughCameraAccess accessR)
    {
        if (!accessL || !accessR || !_stereoTargetMaterial)
        {
            yield break;
        }

        while (isActiveAndEnabled)
        {
            if (!accessL.IsPlaying || !accessR.IsPlaying)
            {
                yield return null;
                continue;
            }

            var leftTexture = accessL.GetTexture();
            var rightTexture = accessR.GetTexture();
            if (!leftTexture || !rightTexture)
            {
                yield return null;
                continue;
            }

            EnsureStereoTarget(leftTexture.width, leftTexture.height, rightTexture.width, rightTexture.height);

            Graphics.SetRenderTarget(_stereoTexture);
            GL.PushMatrix();
            GL.LoadPixelMatrix(0, _stereoTexture.width, 0, _stereoTexture.height);
            GL.Clear(false, true, Color.clear);
            Graphics.DrawTexture(new Rect(0, 0, leftTexture.width, leftTexture.height), leftTexture);
            Graphics.DrawTexture(new Rect(leftTexture.width, 0, rightTexture.width, rightTexture.height), rightTexture);
            GL.PopMatrix();
            Graphics.SetRenderTarget(null);

            _stereoTargetMaterial.SetTexture(MainTexId, _stereoTexture);
            yield return null;
        }
    }

    private void EnsureStereoTarget(int leftW, int leftH, int rightW, int rightH)
    {
        int targetWidth = leftW + rightW;
        int targetHeight = Mathf.Max(leftH, rightH);

        if (_stereoTexture && (_stereoTexture.width != targetWidth || _stereoTexture.height != targetHeight))
        {
            _stereoTexture.Release();
            Destroy(_stereoTexture);
            _stereoTexture = null;
        }

        if (!_stereoTexture)
        {
            _stereoTexture = new RenderTexture(targetWidth, targetHeight, 0, RenderTextureFormat.ARGB32)
            {
                wrapMode = TextureWrapMode.Clamp,
                filterMode = FilterMode.Bilinear
            };
            _stereoTexture.Create();
        }
    }

    private void OnDisable()
    {
        if (_stereoTexture)
        {
            _stereoTexture.Release();
            Destroy(_stereoTexture);
            _stereoTexture = null;
        }
    }
}

このコードでは、PassthroughCameraAccessでカメラ映像をテクスチャとして取得し、それを横方向に結合したレンダーテクスチャを作成しています。その後、作成したテクスチャをマテリアルの_MainTexプロパティにセットしています。

CreateSBSTextureのコンポーネント追加後、LeftCameraAccessRightCameraAccessに先ほどの作ったPassthroughCameraAccessコンポーネントを持ったオブジェクトをアタッチします。

次にUnlit/Textureのマテリアルを作成し、そのマテリアルをQuadとCreateSBSTextureのStereoTargetMaterialにアタッチします。

image.png

PlayしてシーンのQuadが以下のように見えていれば、SBSの映像は完成です。

PCATexture.gif

映像を分割して左目と右目に分けて見せる

SBSSplit.shaderのマテリアルを作成します。

SBSSplit.shader
Shader "Custom/SBSSplit"
{
    Properties
    {
        _MainTex("SBSTexture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;

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

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO
            };

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

            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
                float2 uv = i.uv;
                uv.y = 1 - uv.y;

                uv.x = uv.x * 0.5 + unity_StereoEyeIndex * 0.5;
                fixed4 col = tex2D(_MainTex, uv);
                return col;
            }
            ENDCG
        }
    }
}

このShaderでは、appdataやv2f構造体にUNITY_VERTEX_INPUT_INSTANCE_IDUNITY_VERTEX_OUTPUT_STEREOなどのマクロを追加することで、左目と右目に別々の映像を見せることをしています。

特に重要なのがunity_StereoEyeIndexで、これは左目のレンダリングの時は0を、右目のレンダリングの時には1を返してくれます。

左目にはSBS映像の左側、つまりuv.xが0~0.5の所が必要で、右目にはSBS映像の右側、つまりuv.xが0.5~1の所が必要になります。
そのためuv.x = uv.x * 0.5 + unity_StereoEyeIndex * 0.5と書くことで、左右の目で違うものを見せることが出来ます。

マテリアル作成後、QuadとCreateSBSTextureのStereoTargetMaterialにアタッチします。

image.png

image.png

シーンを保存してBuildすると完成です!

最後に

如何でしたか?これを機に、Quest3のカメラアクセスについて興味を持っていただけると幸いです!

明日はOtoriffさんの記事になります。乞うご期待!

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?