游戏编程

Unity中屏幕坐标、视图坐标和世界坐标的相互转换

2018-09-07  本文已影响6987人  GolDHeaven

    我们在设计游戏的时候,经常会进行坐标系的变换,Unity为我们提供了多个变换的API,这里主要对它们的使用做一个总结整理!

在Unity中我们通常会用到以下几个坐标系下的点:

    世界坐标系、观察坐标系、ViewPort、屏幕坐标系

这里需要说明的是,我们在获取鼠标位置的时候,Input.mousePosition来获取鼠标的位置,这里获取到的鼠标位置是基于屏幕坐标的。通过该函数返回的是Vector3类型的变量,但z分量始终为0。读者可以自行进行尝试。


这里,我们先来看一下Unity提供的相关常见函数:

//1.屏幕转世界坐标
Vector3 Camera.main.ScreenToWorldPoint(new Vector3(screenPos.x , screenPos.y , zInfo));
//2.世界转屏幕坐标
Vector3 Camera.main.WorldToScreenPoint(new Vector3(worldPos.x , worldPos.y , worldPos.z));
//3.世界转视口坐标
Vector3 Camera.main.WorldToViewportPoint();
//4.视口转世界坐标
Vector3 Camera.main.ViewportToWorldPoint(new Vector3(viewPortPos.x , viewPortPos.y , zInfo));
//5.视口转屏幕坐标
Vector3 Camera.main.ViewportToScreenPoint();
//6.屏幕转视口坐标
Vector3 Camera.main.ScreenToViewportPoint();

作者作为初学者的一员,认为先搞清楚这几个暂时足够,日后若有使用更多的变换,则再进行补充吧。

观察这些个函数,首先一个很明显的共同点,就是这些函数都是Camera的成员函数,输入和输出都为Vector3类型的变量。也即这些函数都是针对当前摄像机的一个变换操作。这很容易理解,因为3D游戏中的坐标从模型空间到最终的屏幕空间经过了model,view,projection,以及之后的NDC变换等,其中除model是用于从模型空间到世界空间的变换外,之后的viewprojection都是基于摄像机的。他们会随着使用相机的变化而变化。至于具体的内容,变化过程,这里不做过多描述,读者可以查看网上的相关文章。

当然,我们在使用这些API的时候,只需要清楚我们的输入和输出的内容及其关系就好了。

接下来,我们来聊一聊这些函数:


首先是屏幕坐标和世界坐标的相互转换:

    WorldToScreenPoint函数接收一个世界空间下的位置信息,然后返回其所在的屏幕空间位置,以及其相对于摄像机的深度信息,该深度信息由世界空间下摄像机和输入位置的z值来决定。
        一个例子是:摄像机的位置为(0.0, 0.0 , -10.0),输入的位置为(0.0,0.0,1.0)。则返回的结果为(screen.width/2 , screen.height/2 , 1-(-10));
        注意摄像机指向-z方向!
    ScreenToWorldPoint则是与之相反,输入屏幕空间位置以及相应的深度信息(注意深度信息应该为目标z值金和相机z值的差值),可以返回其所在的世界坐标位置。

视口坐标和世界坐标与之相似:

    WorldToViewportPoint:输入世界坐标,返回的是对应的点所在的视口位置,当然以及其相对于摄像机的深度信息(距离)
    ViewportToWorldPoint:输入视口坐标(记得对应的深度信息),返回点所在的世界坐标

视口坐标和屏幕坐标非常简单,正如上面的说明,只要知道分辨率就可以轻松转换,这里不再赘述。


一个简单的应用

我们在设计某些游戏的时候(比如摄像机固定不动的类型),会对物体的运动范围进行限制,以防止其跑出边界。比如一盒横屏的飞行射击游戏(雷电),我们可以获得物体的位置信息:transform.position,我们希望对物体的x,y两个轴向的移动进行限制(同时冻结物体的z轴移动)。一个简单的思路如下:

public float leftBorder;
public float rightBorder;
public float topBorder;
public float bottomBorder;

....

Vector3 leftBtm_cornerPos = Camera.main.ViewportToWorldPoint(new Vector3(0f, 0f,
            Mathf.Abs(-Camera.main.transform.position.z))); //这里的z轴在正交视图下意义不大
Vector3 rightTop_cornerPos = Camera.main.ViewportToWorldPoint(new Vector3(1f, 1f,
            Mathf.Abs(-Camera.main.transform.position.z)));
....

leftBorder = leftBtm_cornerPos.x;
rightBorder = rightTop_cornerPos.x;
topBorder = rightTop_cornerPos.y;
bottomBorder = leftBtm_cornerPos.y;

....

if (pos.x <= leftBorder)
{
    pos.x = leftBorder;
}
else if (pos.x >= rightBorde
{
    pos.x = rightBorder;
}

if (pos.y <= bottomBorder)
{
    pos.y = bottomBorder;
}
else if (pos.y >= topBorder)
{
    pos.y = topBorder;
}

上面的代码通过ViewportToWorldPoint获取到了四个边界。之后通过限制位置的xy轴向移动就可以控制避免移动过度。

说明一下,这里假设了雷电类型的游戏,使用的摄像机为正交类型的投影方式,了解正交投影的朋友就知道,视锥体变成了长方体形,因子这里的z轴并无太大的作用。即使我使用ViewportToWorldPoint的时候使用0作为z轴数据,也一样没关系。但是对于透视投影就不太一样了。若这里使用的透视投影,在不同的深度下,其的边界范围肯定也会变化,这个时候就必须输入正确的z轴数据了!。

上一篇 下一篇

猜你喜欢

热点阅读