MRが楽しい

MRやVRについて学習したことを書き残す

Unityでカメラの投影方式を透視投影と平行投影で切り替える その3(投影方式をスムーズに変化させる)

本日は Unity の小ネタ枠です。
Unityでカメラの投影方式を透視投影と平行投影で切り替える方法を記事にします。
本記事は投影方式の切り替えをスムーズに行ってみます。

前提条件

前回記事の続きです。
bluebirdofoz.hatenablog.com

カスタム投射行列をスムーズに変化させる

投影方式をスムーズに切り替えるため、カスタム投射行列に Lerp 関数と、Ease-in と Ease-Out の計算式を利用します。
UnityEngine の Matrix4x4 には Lerp 関数がなかったため、System.Numerics の Matrix4x4 で代替しました。
docs.microsoft.com

UnityEngine の Matrix4x4 と System.Numerics の Matrix4x4 の変換例は以下の通りです。

    /// <summary>
    /// UnityEngine.Matrix4x4 から System.Numerics.Matrix4x4 への変換
    /// </summary>
    private System.Numerics.Matrix4x4 Unity4x4ToNumerics4x4(UnityEngine.Matrix4x4 unityMatrix4x4)
    {
        System.Numerics.Matrix4x4 numericsMatrix4X4 =
            new System.Numerics.Matrix4x4(
                unityMatrix4x4[0], unityMatrix4x4[1], unityMatrix4x4[2], unityMatrix4x4[3],
                unityMatrix4x4[4], unityMatrix4x4[5], unityMatrix4x4[6], unityMatrix4x4[7],
                unityMatrix4x4[8], unityMatrix4x4[9], unityMatrix4x4[10], unityMatrix4x4[11],
                unityMatrix4x4[12], unityMatrix4x4[13], unityMatrix4x4[14], unityMatrix4x4[15]
                );
        return numericsMatrix4X4;
    }

    /// <summary>
    /// System.Numerics.Matrix4x4 から UnityEngine.Matrix4x4 への変換
    /// </summary>
    private UnityEngine.Matrix4x4 Numerics4x4ToUnity4x4(System.Numerics.Matrix4x4 numericsMatrix4x4)
    {
        UnityEngine.Matrix4x4 uniMatrix4X4 =
            new UnityEngine.Matrix4x4(
                new Vector4(numericsMatrix4x4.M11, numericsMatrix4x4.M12, numericsMatrix4x4.M13, numericsMatrix4x4.M14),
                new Vector4(numericsMatrix4x4.M21, numericsMatrix4x4.M22, numericsMatrix4x4.M23, numericsMatrix4x4.M24),
                new Vector4(numericsMatrix4x4.M31, numericsMatrix4x4.M32, numericsMatrix4x4.M33, numericsMatrix4x4.M34),
                new Vector4(numericsMatrix4x4.M41, numericsMatrix4x4.M42, numericsMatrix4x4.M43, numericsMatrix4x4.M44));
        return uniMatrix4X4;
    }

今回、非同期処理に UniRx を利用しました。UniRx の利用手順は以下を参照ください。
bluebirdofoz.hatenablog.com

Ease-in と Ease-Out の計算式は徐々に加速して徐々に減速する動きを再現するために Lerp 関数と合わせて利用しました。
以下の記事を参考に実装しています。
appleorbit.hatenablog.com

サンプルスクリプト

以下のサンプルスクリプトを作成しました。
MovePerspectiveToOrthographic 関数を実行すると、カメラの投影方式が透視投影から平行投影にスムーズに変化します。

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

public class CameraProjectionControllerUniRx : MonoBehaviour
{
    /// <summary>
    /// 平行投影の視点にスムーズに移行する
    /// </summary>
    [ContextMenu("MovePerspectiveToOrthographic")]
    public void MovePerspectiveToOrthographic()
    {
        float fieldOfView = 60.0f;
        float perspectiveAspectRatio = 16.0f / 9.0f;

        // 平行投影のProjectionMatrixを計算する
        Matrix4x4 perspectiveMatrix = Matrix4x4.Perspective(
            fieldOfView,
            perspectiveAspectRatio,
            0.1f, 100.0f
            );

        float orthographicSize = 5.0f;
        float orthographicAspectRatio = 16.0f / 9.0f;
        float orthoWidth = orthographicSize * orthographicAspectRatio;
        float orthoHeight = orthographicSize;

        // 平行投影のProjectionMatrixを計算する
        Matrix4x4 orthographicMatrix = Matrix4x4.Ortho(
            orthoWidth * -1, orthoWidth,
            orthoHeight * -1, orthoHeight,
            0.1f, 100.0f
            );

        // Projectionをスムーズに遷移させる
        MovePerspectiveToOrthographicSmooth(perspectiveMatrix, orthographicMatrix);
    }

    /// <summary>
    /// 継続処理の参照
    /// </summary>
    private IDisposable p_Trigger;

    [Header("SmoothMove")]
    [SerializeField]
    private float p_LerpTime = 3.0f;

    private void MovePerspectiveToOrthographicSmooth(UnityEngine.Matrix4x4 unityMatrix4x4From, UnityEngine.Matrix4x4 unityMatrix4x4To)
    {
        // 前回トリガーを終了する
        p_Trigger?.Dispose();

        // 移動処理を開始する
        p_Trigger = Observable
            .IntervalFrame(1, FrameCountType.FixedUpdate)    // 1フレーム毎に呼び出す
            .TimeInterval()                                  // フレーム間の経過時間を取得する
            .Select(timeInterval => timeInterval.Interval)   // TimeSpan型のデータを抽出する
            .Scan((last, current) => last + current)         // 前回までの経過時間を加算する
            .TakeWhile(intervalTimeSpan => (float)intervalTimeSpan.TotalSeconds < p_LerpTime) // Lerp時間を超えるまで実行する
            .SubscribeOnMainThread()                         // メインスレッドで実行する
            .Subscribe(
            intervalTimeSpan =>
            {
                float totalInterval = (float)intervalTimeSpan.TotalSeconds;
                // Ease-in, Ease-Out の計算式で徐々に加速して徐々に減速する補間を行う
                float t = Mathf.Min(totalInterval / p_LerpTime, 1.0f);
                float lerpFactor = (t * t) * (3.0f - (2.0f * t));
                // Leap関数を使って徐々に指定のProjectionMatrixに近づいていく
                UnityEngine.Matrix4x4 lerpMatrix = UnityMatrix4x4Lerp(unityMatrix4x4From, unityMatrix4x4To, lerpFactor);

                // カメラのProjectionMatrixを設定する
                Camera.main.projectionMatrix = lerpMatrix;

                // 参考サイト
                //【Unity5】ゆっくり動き始めて、ゆっくり止まる補間を実装する
                // https://appleorbit.hatenablog.com/entry/2015/10/18/210614
            },
            () =>
            {
                // 最終的に指定のProjectionMatrixを設定する
                Camera.main.projectionMatrix = unityMatrix4x4To;
            }
            )
            .AddTo(this);
    }

    /// <summary>
    /// Lerp処理
    /// </summary>
    private UnityEngine.Matrix4x4 UnityMatrix4x4Lerp(
        UnityEngine.Matrix4x4 unityMatrix4x4From, UnityEngine.Matrix4x4 unityMatrix4x4To, float lerpFactor)
    {
        System.Numerics.Matrix4x4 numericsMatrix4X4From = Unity4x4ToNumerics4x4(unityMatrix4x4From);
        System.Numerics.Matrix4x4 numericsMatrix4X4To = Unity4x4ToNumerics4x4(unityMatrix4x4To);

        System.Numerics.Matrix4x4 numericsMatrix4X4Lerp = System.Numerics.Matrix4x4.Lerp(
            numericsMatrix4X4From, numericsMatrix4X4To, lerpFactor
            );

        return Numerics4x4ToUnity4x4(numericsMatrix4X4Lerp);
    }

    /// <summary>
    /// UnityEngine.Matrix4x4 から System.Numerics.Matrix4x4 への変換
    /// </summary>
    private System.Numerics.Matrix4x4 Unity4x4ToNumerics4x4(UnityEngine.Matrix4x4 unityMatrix4x4)
    {
        System.Numerics.Matrix4x4 numericsMatrix4X4 =
            new System.Numerics.Matrix4x4(
                unityMatrix4x4[0], unityMatrix4x4[1], unityMatrix4x4[2], unityMatrix4x4[3],
                unityMatrix4x4[4], unityMatrix4x4[5], unityMatrix4x4[6], unityMatrix4x4[7],
                unityMatrix4x4[8], unityMatrix4x4[9], unityMatrix4x4[10], unityMatrix4x4[11],
                unityMatrix4x4[12], unityMatrix4x4[13], unityMatrix4x4[14], unityMatrix4x4[15]
                );
        return numericsMatrix4X4;
    }

    /// <summary>
    /// System.Numerics.Matrix4x4 から UnityEngine.Matrix4x4 への変換
    /// </summary>
    private UnityEngine.Matrix4x4 Numerics4x4ToUnity4x4(System.Numerics.Matrix4x4 numericsMatrix4x4)
    {
        UnityEngine.Matrix4x4 uniMatrix4X4 =
            new UnityEngine.Matrix4x4(
                new Vector4(numericsMatrix4x4.M11, numericsMatrix4x4.M12, numericsMatrix4x4.M13, numericsMatrix4x4.M14),
                new Vector4(numericsMatrix4x4.M21, numericsMatrix4x4.M22, numericsMatrix4x4.M23, numericsMatrix4x4.M24),
                new Vector4(numericsMatrix4x4.M31, numericsMatrix4x4.M32, numericsMatrix4x4.M33, numericsMatrix4x4.M34),
                new Vector4(numericsMatrix4x4.M41, numericsMatrix4x4.M42, numericsMatrix4x4.M43, numericsMatrix4x4.M44));
        return uniMatrix4X4;
    }
}

動作確認

サンプルシーンにスクリプトを設定し、コンテキストメニューから MovePerspectiveToOrthographic を実行します。
f:id:bluebirdofoz:20220303233945j:plain

関数の実行と共にカメラの投影方式が滑らかに移動すれば成功です。
f:id:bluebirdofoz:20220303233953j:plain