移动相机以适应3D场景

时间:2010-05-19 14:21:33

标签: 3d zoom perspective

我正在寻找一种算法来适应视口内的边界框(在我的例子中是一个DirectX场景)。我知道用于在正交相机中居中定界球的算法,但是对于边界框和透视相机需要相同的算法。我不能只改变FOV因为这个应用程序将FOV作为用户可编辑变量,所以它必须移动相机。

我有大部分数据:

  • 我有相机的向上传感器
  • 我有边界框的中心点
  • 我有从相机点到盒子中心的观察矢量(方向和距离)
  • 我在垂直于相机的平面上投影了点,并检索了描述最大/最小X和Y坐标在观察平面之内或之外的系数。

我遇到的问题:

如何找到相机位置,使其尽可能完美地填充视口(例如,如果宽高比远离1.0,则只需要填充其中一个屏幕轴)?

我尝试过其他一些事情:

  • 使用边界球和切线查找移动相机的比例因子。这不能很好地工作,因为它没有考虑透视投影,其次,球体是我使用的不良边界体积,因为我有很多扁平和长的几何形状。
  • 迭代调用函数以在摄像机位置获得越来越小的错误。这有点工作,但我有时会遇到奇怪的边缘情况,相机位置过冲太多,误差因子增加。此外,在执行此操作时,我没有根据边界矩形的位置重新定位模型。我无法找到一种可靠,可靠的方法来可靠地完成这项工作。

请帮忙!

7 个答案:

答案 0 :(得分:33)

有许多可能的相机位置+方向,其中边界框适合视锥体。但任何程序都会选择一个特定的摄像机位置和方向。

如果你考虑边界球,那么一个解决方案就是

  • 首先改变方向以查看边界球体中心
  • 然后充分向后移动(负面观察方向),以限制球体以适应锥体内部

使用边界框,您可以考虑先将相机定位在垂直于最大(或最小,任何您喜欢的)立方体面中心的步骤。

我没有使用DirectX的经验,但移动和更改相机的查看方向以使某个点居中应该很容易。 困难的部分是做数学决定移动查看对象的距离。

数学

如果您知道世界坐标中对象的边界大小s(我们对像素或摄像机坐标不感兴趣,因为这些距离取决于您的距离),您可以计算相机的方向如果您知道透视投影的x和y视场角d,则需要将相机距离a到边界形状。

     frustum      ------            
            ------    *****          -  
       -----          *   *          |
   -===     ) FOV a   *bounding box  | BB size s
camera -----          *   *          |
            ------    *****          -
                  ------

  |-------------------|
        distance d

因此,mathtan(a/2) = (s/2) / d => d = (s/2) / tan(a/2) 这将为您提供相机应从最近的边界表面放置的距离。

答案 1 :(得分:9)

我知道上面有一些很好的答案,但我想添加一个非常简单的解决方案,以适应相机截头内的边界球体。它假设您希望保持相机的目标和前向矢量相同,并简单地调整相机到目标的距离。

注意, 这不会给你最合适的但它会给你一个近似的拟合,显示所有几何,只有几行代码,没有屏幕到世界转换

enter image description here

// Compute camera radius to fit bounding sphere
// Implementation in C#
// 

// Given a bounding box around your scene
BoundingBox bounds = new BoundingBox();

// Compute the centre point of the bounding box
// NOTE: The implementation for this is to take the mid-way point between 
// two opposing corners of the bounding box
Vector3 center = bounds.Center;

// Find the corner of the bounding box which is maximum distance from the 
// centre of the bounding box. Vector3.Distance computes the distance between 
// two vectors. Select is just nice syntactic sugar to loop 
// over Corners and find the max distance.
double boundSphereRadius = bounds.Corners.Select(x => Vector3.Distance(x, bounds.Center)).Max();

// Given the camera Field of View in radians
double fov = Math3D.DegToRad(FieldOfView);

// Compute the distance the camera should be to fit the entire bounding sphere
double camDistance = (boundSphereRadius * 2.0) / Math.Tan(fov / 2.0);

// Now, set camera.Target to bounds.Center
// set camera.Radius to camDistance
// Keep current forward vector the same

下面是C#中BoundingBox的实现。重点是Center和Corners属性。 Vector3是3分量(X,Y,Z)向量的非常标准的实现

public struct BoundingBox
{        
    public Vector3 Vec0;
    public Vector3 Vec1;

    public BoundingBox(Vector3 vec0, Vector3 vec1)
    {
        Vec0 = vec0;
        Vec1 = vec1;
    }

    public Vector3 Center
    {
        get { return (Vec0 + Vec1)*0.5; }
    }

    public IList<Vector3> Corners
    {
        get
        {
            Vector3[] corners = new[]
            {
                new Vector3( Vec0.X, Vec0.Y, Vec0.Z ), 
                new Vector3( Vec1.X, Vec0.Y, Vec0.Z ), 
                new Vector3( Vec0.X, Vec1.Y, Vec0.Z ), 
                new Vector3( Vec0.X, Vec0.Y, Vec1.Z ), 
                new Vector3( Vec1.X, Vec1.Y, Vec0.Z ), 
                new Vector3( Vec1.X, Vec0.Y, Vec1.Z ), 
                new Vector3( Vec0.X, Vec1.Y, Vec1.Z ), 
                new Vector3( Vec1.X, Vec1.Y, Vec1.Z ), 
            };

            return corners;
        }
    } 
}

答案 2 :(得分:6)

由于你有一个边界框,你应该有一个描述它的方向的基础。您似乎想要将摄像机放置在与描述盒子最小尺寸的基础向量重合的线上,然后滚动摄像机以使最大尺寸为水平(假设您有OBB而不是AABB)。这假定纵横比大于1.0;如果没有,你会想要使用垂直维度。

我会尝试什么:

  1. 找到最小的方框尺寸。
  2. 找到相关的基础向量。
  3. 将基础矢量缩放距摄像机应该是的盒子中心的距离。这个距离只是boxWidth / (2 * tan(horizontalFov / 2))。请注意,boxWidth是框中最大尺寸的宽度。
  4. 将相机放在boxCenter + scaledBasis处,查看boxCenter
  5. 如有必要,请滚动相机,将相机的向上矢量与相应的方框矢量对齐。
  6. 修改

    所以我认为你所得到的是你将相机放在任意位置看着某个地方,而你在另一个位置有一个AABB。如果不将相机移动到盒子的一侧,您需要:

    • 看看盒子的中心
    • 沿着它的外观矢量平移相机,以便该框占用最大的屏幕空间

    如果是这种情况,你会有更多的工作;这是我的建议:

    1. 旋转相机以查看边界框的中心。
    2. 将框中的所有点投影到屏幕空间中,并在屏幕空间中找到最小/最大边界框(您已经拥有此框)。
    3. 现在Unproject屏幕空间的两个对立角落将盒子绑定到世界空间。对于Z值,请使用AABB的最近世界空间点到相机。
    4. 这会让你看到一个面向相机的世界空间飞机,它位于AABB上最靠近相机的位置。
    5. 现在使用我们现有的侧向方法将相机移动到适当的位置,将此平面视为盒子的一侧。

答案 3 :(得分:1)

我目前没有它,但你想要的书是http://www.amazon.com/Jim-Blinns-Corner-Graphics-Pipeline/dp/1558603875/ref=ntt_at_ep_dpi_1

他有一整章关于这个

答案 4 :(得分:1)

这是从我的引擎直接复制的,它创建了6个平面,代表了frutsum的六个边中的每一个。 我希望它有用。

internal class BoundingFrustum
    {
        private readonly float4x4 matrix = new float4x4(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        private readonly Plane[] planes;

        internal BoundingFrustum(float4x4 value)
        {
            planes = new Plane[6];
            for (int i = 0; i < 6; i++)
                planes[i] = new Plane();
            Setfloat4x4(value);
        }

        private void Setfloat4x4(float4x4 value)
        {
            planes[2].Normal.X = -value.M14 - value.M11;
            planes[2].Normal.Y = -value.M24 - value.M21;
            planes[2].Normal.Z = -value.M34 - value.M31;
            planes[2].D = -value.M44 - value.M41;
            planes[3].Normal.X = -value.M14 + value.M11;
            planes[3].Normal.Y = -value.M24 + value.M21;
            planes[3].Normal.Z = -value.M34 + value.M31;
            planes[3].D = -value.M44 + value.M41;
            planes[4].Normal.X = -value.M14 + value.M12;
            planes[4].Normal.Y = -value.M24 + value.M22;
            planes[4].Normal.Z = -value.M34 + value.M32;
            planes[4].D = -value.M44 + value.M42;
            planes[5].Normal.X = -value.M14 - value.M12;
            planes[5].Normal.Y = -value.M24 - value.M22;
            planes[5].Normal.Z = -value.M34 - value.M32;
            planes[5].D = -value.M44 - value.M42;
            planes[0].Normal.X = -value.M13;
            planes[0].Normal.Y = -value.M23;
            planes[0].Normal.Z = -value.M33;
            planes[0].D = -value.M43;
            planes[1].Normal.X = -value.M14 + value.M13;
            planes[1].Normal.Y = -value.M24 + value.M23;
            planes[1].Normal.Z = -value.M34 + value.M33;
            planes[1].D = -value.M44 + value.M43;
            for (int i = 0; i < 6; i++)
            {
                float num2 = planes[i].Normal.Length();
                planes[i].Normal = planes[i].Normal / num2;
                planes[i].D /= num2;
            }
        }

        internal Plane Bottom
        {
            get { return planes[5]; }
        }
        internal Plane Far
        {
            get { return planes[1]; }
        }
        internal Plane Left
        {
            get { return planes[2]; }
        }
        internal Plane Near
        {
            get { return planes[0]; }
        }
        internal Plane Right
        {
            get { return planes[3]; }
        }
        internal Plane Top
        {
            get { return planes[4]; }
        }
    }

答案 5 :(得分:1)

如果有人对更精确的解决方案感兴趣,我为 3ds Max 相机做了这个。在相机视图上适合任意数量的对象。您可以看到 Maxscript 代码,因为 seudo 代码易于阅读,并且有一些有用的注释。

https://github.com/piXelicidio/pxMaxScript/tree/master/CameraZoomExtents

我为简化所做的是在相机空间上工作。获取对象顶点或边界框顶点并在两个二维平面上进行投影。

第一个就像从顶视图看你的相机(水平视野) 第二个是侧视图(垂直视野)

投影第一个平面上的所有顶点(顶视图) 现在取两条来自相机位置的线,代表相机 FOV,一条代表左侧,另一条代表右侧。我们只需要这条线的方向。

enter image description here

现在我们需要找到一个点(顶点),如果我们在它上面画右线,所有其他点都将落在左侧。 (找到图中的红点)

然后找到另一个点,如果左线越过它,所有其他点都落在这条线的右侧。 (蓝点)

有了这些点,然后我们截取通过这两个点的两条线(我们仍然处于 2D 中)。

由此产生的截取是仅考虑水平 FOV 的相机适合场景的最佳位置。

接下来对垂直 FOV 执行相同操作。

这两个位置将为您提供所需的一切,以决定是否需要从侧面或从顶部和底部进行合身。

让相机更多平移离开场景的一种是获得“完美契合”的一种,另一种会有更多的空房间,然后你需要找到中心......这也计算上面链接中的脚本!

抱歉,现在不能继续解释需要睡觉了 ;) 如果有人感兴趣,请询问,我会尝试扩展答案。

答案 6 :(得分:-1)

检查此链接 https://msdn.microsoft.com/en-us/library/bb197900.aspx

浮动距离= sphere.radius / sin(fov / 2);

float3 eyePoint = sphere.centerPoint - distance * camera.frontVector;