Unity中屏幕坐標、視圖坐標和世界坐標的相互轉換


我們在設計游戲的時候,經常會進行坐標系的變換,Unity為我們提供了多個變換的API,這里主要對它們的使用做一個總結整理!

在Unity中我們通常會用到以下幾個坐標系下的點:

  • 世界坐標系:World Space
    • 簡單來講,我們通過 transform.position | transform.rotattion 獲取得到的位置和旋轉信息都是基於世界坐標系的,可以說,我們的很大一部分操作都是基於世界坐標系。
  • 觀察坐標系:Eye Space
    • 我們在Unity的Game視圖中觀察的畫面始終是由攝像機提供的,基於攝像機的一個坐標系也就是"Eye Space"(簡單來講就是把攝像機看作原點位置)。
  • ViewPort:View Port
    • 視口是針對游戲顯示的畫面進行描述的,View Port用於描述整個游戲畫面的坐標,左下角為(0,0),右上角為(1,1),我們在設計分屏游戲的時候可以通過設置攝像機所占據的視口空間來控制。
  • 屏幕坐標系:Screen Space

 

    • 屏幕坐標開始和像素扯上關系了,也就是說屏幕坐標和分辨率有關,屏幕的左下角為(0,0),但右上角為(screen.width,screen.height)比如游戲的分辨率為500*600,則screen.width=500;screen.height=600
 
這里需要說明的是,我們在獲取鼠標位置的時候,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軸數據了!。
 
 
 轉載於  https://www.jianshu.com/p/b5b6ac9ab145  ,本文僅供個人學習記錄,無其他用途
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM