Unity3D中摄像机的FOV和Camera.ScreenToWorldPoint方法的关系

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

在Unity3D编程中经常会碰到以下的问题:

比如一开始的FOV为60度,后来设置为22度,可以通过怎样的公式,使得使用22度FOV对应生成的3D位置点,通过这个公式,可以转换到其位置点和使用60度生成的一样呢

当从一个FOV为60度的视角切换到22度的视角时,使用Camera.ScreenToWorldPoint函数生成的3D位置点会发生变化,因为FOV直接影响视锥体的宽度和高度。要解决这个问题,需要通过调整视锥体的缩放因子,来抵消FOV变化带来的位置偏移。

问题分析

当FOV变化时,屏幕坐标对应的世界坐标会随之缩放。假设有一个屏幕坐标screenPos,在FOV为60度时通过Camera.ScreenToWorldPoint生成一个3D位置点P60。当FOV变为22度时,假设生成的3D位置点为P22。我们需要找到一个转换公式,使得P22转换回P60

FOV与世界坐标的关系

视锥体的高度(在深度为z处)与FOV通过以下公式相关:

$$ Heightz=2×z×tan⁡(FOV2)\text{Height}_z = 2 \times z \times \tan\left(\frac{FOV}{2}\right)Heightz=2×z×tan(2FOV) $$

其中:

  • Height_z :在深度为z处的视锥体高度。
  • FOV:垂直视野角度。
  • z:深度(从摄像机到目标点的距离)。

视锥体的宽度和高度成比例地变化,具体公式为:

$$ Widthz=Heightz×Aspect Ratio\text{Width}_z = \text{Height}_z \times \text{Aspect Ratio}Widthz=Heightz×Aspect Ratio $$

对于两个不同的FOV(如60度和22度),可以计算出它们对应的视锥体高度比值。为了让FOV为22度时生成的世界坐标与FOV为60度时生成的一致,需要应用这个比值进行缩放。

缩放因子的推导

在FOV为60度时的视锥体高度:

$$ \mathrm{Heigth}_{60}=2\times \mathrm{z}\times \tan \left( \frac{60^o}{2} \right) $$

在FOV为22度时的视锥体高度:

$$ \mathrm{Heigth}_{22}=2\times \mathrm{z}\times \tan \left( \frac{22^o}{2} \right) $$

缩放因子: 需要将FOV为22度时的世界坐标缩放到FOV为60度的视锥体高度,因此缩放因子是这两个视锥体高度的比值:

$$ ScaleFactor=\frac{Height_{60}}{Height_{22}}=\frac{\tan \left( 30^{o} \right)}{\tan \left( 11^{o} \right)} $$

可以计算出这个缩放因子:

$$ \mathrm{ScaleFactor}=\frac{0.5774}{0.1944}\approx 2.97 $$

公式应用

在FOV为22度时生成的3D世界坐标P22可以通过如下公式转换为FOV为60度时的3D世界坐标P60

$$ \mathrm{P_{60}}=\mathrm{P_{22}}\times \mathrm{ScaleFactor} $$

其中ScaleFactor为大约2.97。

实际代码实现

下面的代码展示了如何将FOV为22度生成的3D位置点,转换为FOV为60度的等效位置点:

using UnityEngine;

public class FOVConversion : MonoBehaviour
{
    public Camera mainCamera;
    public float depth = 10f; // 距离摄像机的深度
    private float scaleFactor;

    void Start()
    {
        // 计算缩放因子
        float fov60 = 60f;
        float fov22 = 22f;
        scaleFactor = Mathf.Tan(fov60 * Mathf.Deg2Rad / 2) / 
		              Mathf.Tan(fov22 * Mathf.Deg2Rad / 2);
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePos = Input.mousePosition;

            // 使用22度FOV生成世界坐标
            Vector3 worldPos22 = ScreenToWorldWithFOV(mousePos, 22f, depth);
            
            // 将22度FOV生成的坐标转换为60度FOV等效坐标
            Vector3 worldPos60 = worldPos22 * scaleFactor;

            Debug.Log("World Position at FOV 22: " + worldPos22);
            Debug.Log("Equivalent World Position at FOV 60: " + worldPos60);
        }
    }

    Vector3 ScreenToWorldWithFOV(Vector3 screenPos, float fov, float z)
    {
        // 设置摄像机的FOV
        mainCamera.fieldOfView = fov;

        // 使用ScreenToWorldPoint进行转换
        Vector3 worldPos = mainCamera.ScreenToWorldPoint(
		                   new Vector3(screenPos.x, screenPos.y, z));
        return worldPos;
    }
}

总结

  • FOV的影响:较大的FOV会使视锥体的宽度和高度增加,导致相同的屏幕坐标映射到较远的世界坐标。较小的FOV则会使视锥体变窄,映射到较近的世界坐标。
  • 缩放因子:通过计算两个FOV视锥体的高度比值,可以得到一个缩放因子,将小FOV生成的世界坐标转换为大FOV生成的等效世界坐标。
kumakoko avatar
kumakoko
pure coder
comments powered by Disqus