🕶️

画角の端になるにつれ透過する環境を作る(XREAL Air2Ultra)

に公開

はじめに

この記事は サイバーエージェント26卒内定者 Advent Calendarの1日目として投稿しています。
1日目を投稿できるなんて光栄ですね。今世は運がいい

この記事でやること

XREALってどうしてもVRに比べてCGを表示できる視野が限られているんですよね。
だから目の前に大きめのオブジェクトを出そうとしても表示可能な視野角を出るとプツッと表示されなくなっちゃってちょっと冷める、的な現象があります。
それを防ぐために、視野角外に近づく領域ほど透過していく、そんな実装を簡単に紹介します。

使用技術

・Unity 6000.0.58f2
・XREAL SDK 3.0.0

実装方法

今回はCubeに適用していこうと思います。

0. Unityをインストールして起動し、XREAL SDK 3.0.0を導入する

こちらは上記の本を参考にしてください。本記事では省略します。

1. 適当に空のシーンを作る

Unity内で適当にシーンを作り、MainCameraなどの自動生成されたオブジェクトは全て削除します。

2. XREALで動くようにオブジェクトを配置する

Project内で「XR Origin」と検索して、出てきたXR Origin(XR Rig)をシーン内にドラッグ&ドロップします。

XR Origin>Camera Offset>Main Cameraの設定を以下の画像のように変えます。
(ARグラス内で背景を表示しないようにするため)

ヒエラルキー上で右クリックをして、XR>AR Session を選択して、シーン内に追加します。

最終こんな状態になっていればOK

3. Cubeを配置する

AR Sessionと同様に3DObject>Cubeを選択して配置します。

4. 視野角外近くで徐々に透過するためのC#スクリプト、Shaderを作成する

以下2つのShader、C#スクリプトを追加します

Shader "Custom/FOVEdgeFade"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color   ("Color", Color) = (1,1,1,1)

        // 画面の端からどれくらい内側までフェードさせるか
        _InnerEdge ("Inner Edge", Range(0,0.5)) = 0.25
        _OuterEdge ("Outer Edge", Range(0,0.5)) = 0.05
    }

    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        Cull Back

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;

            float _InnerEdge;
            float _OuterEdge;

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

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

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

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;

                // NDC (-1〜1) → ビューポート(0〜1)
                float2 ndc = i.clipPos.xy / i.clipPos.w;
                float2 vp  = ndc * 0.5 + 0.5;

                // 画面外は即透明(お好みで)
                if (vp.x < 0.0 || vp.x > 1.0 || vp.y < 0.0 || vp.y > 1.0)
                {
                    col.a = 0;
                    return col;
                }

                // 各端までの距離(0 = 端、0.5 = 画面ど真ん中)
                float distLeft   = vp.x;
                float distRight  = 1.0 - vp.x;
                float distBottom = vp.y;
                float distTop    = 1.0 - vp.y;

                // 「一番近い端」への距離
                float edgeDist = min(min(distLeft, distRight), min(distTop, distBottom));

                // edgeDist:
                //   0            → 画面の端
                //   0.5 近辺    → ど真ん中

                float epsilon = 1e-5;
                // _OuterEdge 以下 → 透明、_InnerEdge 以上 → 不透明
                float t = saturate((edgeDist - _OuterEdge) / max(_InnerEdge - _OuterEdge, epsilon));
                float fade = t;  // 中央付近 = 1, 端 = 0

                col.a *= fade;
                return col;
            }
            ENDCG
        }
    }
}

using UnityEngine;

[RequireComponent(typeof(Renderer))]
public class FOVEdgeFade : MonoBehaviour
{
    [Header("参照カメラ(XREALのメインカメラ)")]
    public Camera targetCamera;

    [Header("フェード範囲(ビューポート半径)")]
    [Range(0f, 1f)]
    public float innerRadius = 0.2f;  // ここまではフル表示
    [Range(0f, 1f)]
    public float outerRadius = 0.5f;  // ここを超えたら完全に透明

    private Renderer _renderer;
    private Material _material;
    private int _fadeId;

    private void Awake()
    {
        _renderer = GetComponent<Renderer>();

        // 他オブジェクトとマテリアルを共有したくない場合は material を使う
        _material = _renderer.material;

        _fadeId = Shader.PropertyToID("_Fade");

        if (targetCamera == null)
        {
            targetCamera = Camera.main;
        }
    }

    private void LateUpdate()
    {
        if (targetCamera == null || _material == null) return;

        Vector3 viewportPos = targetCamera.WorldToViewportPoint(transform.position);

        if (viewportPos.z < 0f)
        {
            _material.SetFloat(_fadeId, 0f);
            return;
        }

        // 画面中心(0.5, 0.5)からの距離
        float dx = viewportPos.x - 0.5f;
        float dy = viewportPos.y - 0.5f;
        float dist = Mathf.Sqrt(dx * dx + dy * dy); // 半径方向の距離

        // 画面外に出てたら透明
        if (viewportPos.x < 0f || viewportPos.x > 1f ||
            viewportPos.y < 0f || viewportPos.y > 1f)
        {
            _material.SetFloat(_fadeId, 0f);
            return;
        }

        // innerRadius以内 → 1, outerRadius以上 → 0 になるよう補間
        float fade = Mathf.InverseLerp(outerRadius, innerRadius, dist);
        fade = Mathf.Clamp01(fade);

        _material.SetFloat(_fadeId, fade);
    }
}

5. Cubeに適用する

適当にマテリアルを作成し、Custom>FOVEdgeFade

を選択します。

そのマテリアルをSceneビューのCubeにドラッグアンドドロップします。

そのあとに、先ほど作成したFOVEdgeFade.csもCubeにアタッチします。

UnityEditor上で再生すると、上記のようになります

6. ビルドして確認する

本の2章にビルドと確認方法があるので参考にしてください!

最後に

視野角外でプツッと消える現象は軽減されたのではないでしょうか
これを本格的に載せるとなるとさまざまなShaderを用意する必要がありそうですね、、
ただARグラスの冷める瞬間をソフトウェア側でに解決しに行く姿勢は大事かなと思います!

Discussion