【技術】Unity:詳細介紹相機的投影矩陣
關於Unity中的Camera,聖典裡面對每一項屬性都做了簡要的介紹,沒看過的小夥伴傳送門在下面
一、裁剪面
先從這個專業的詞彙開始,以下是聖典對裁剪面的介紹:
The Near and Far Clip Plane properties determine where the Camera's view begins and ends. The planes are laid out perpendicular to the Camera's direction and are measured from the its position. The Near plane is the closest location that will be rendered, and the Far plane is the furthest.
近裁剪面及遠裁剪面屬性確定相機視野的開始和結束。平面是佈置在與相機的方向垂直的位置,並從它的位置測量。近裁剪面是最近的渲染位置,遠平面是最遠的渲染位置。
這句話感覺說了和沒說差不多,因為我們沒有看到過裁剪面,所以瞭解裁剪面的第一步,我們需要在Unity當中去直觀的看看它 下圖,當我們近距離觀察Camera的時候,會發現一個用白線畫的金字塔(四稜錐),這很好理解,他表示了Camera的視野範圍,奇怪的是這個金字塔(四稜錐)少了一個角,從而金字塔不僅有了底面,還有一個頂面。相信猜也能猜到了,金字塔的頂部這個面,是近裁剪面(near clip planes),底面,則是遠裁剪面(near clip planes)那麼說了半天,裁剪面有什麼用呢?
我們繼續在Unity的攝像機中改變Clipping Planes的值,看看變化,首先把Clipping Planes的near和far分別調為2和6
如下圖預覽介面,在近裁剪面和遠裁剪面之間沒有包含物體,渲染的影象裡是不會有物體的
增加far的值,現在立方體的很小一塊包含進去了,但是我們看預覽介面,並看不出它是立方體,只能看出平面效果
好的,繼續增加,現在整個立方體都包含進來了,看預覽,終於可以明顯看出是正方體了
這個例子說明了Camera似乎只渲染近裁剪面與遠裁剪面之間的物體,這個原理就好比平面圖的渲染,我們需要對圖片進行裁剪完成後,程式才知道要渲染的範圍,無論這張圖是全部需要還是隻需要一部分,第一步,都是裁剪。現在,3D的渲染也需要裁剪,於是近裁剪面與遠裁剪面就誕生了,只不過裁剪的範圍並不是平面的,而是立體的(被切掉頂端的金字塔),這個立體的形狀,我們稱之為視裁剪體。
二、視裁剪體
視裁剪體,專業的叫法是視錐體(fusum),它由6個面構成,上下,左右,前後,先看聖典裡面的介紹:
The outer edges of the image are defined by the diverging lines that correspond to the corners of the image. If those lines were traced backwards towards the camera, they would all eventually converge at a single point. In Unity, this point is located exactly at the camera's transform position and is known as the centre of perspective. The angle subtended by the lines converging from the top and bottom centres of the screen at the centre of perspective is called the field of view (often abbreviated to FOV).
在影像的邊緣被稱為對應影像角落的偏離線。如果被描繪的那些線向相機的後方轉,他們最終將匯聚在一個點上。在Unity, 這個點恰好位於被稱為檢視中心的變換位置上。在檢視中,螢幕中頂部的中心和底部的中心匯聚的線的夾角,被稱為視野(通常縮寫成FOV)。
As stated above, anything that falls outside the diverging lines at the edges of the image will not be visible to the camera, but there are also two other restrictions on what it will render. The near and far clipping planes are parallel to the camera's XY plane and each set at a certain distance along its centre line. Anything closer to the camera than the near clipping plane and anything farther away than the far clipping plane will not be rendered.
如上所述, 任何超出影像邊緣的偏離先之外的東西都是看不見的。渲染還有另外兩個限制條件。近裁剪面和遠裁剪面是與相機的XY平面平行的,並且每個裁剪面離中心線有一定的距離。任何在近裁剪面的之內和超出遠裁剪面之外的物體都不會被渲染。
我們在上面已經瞭解了遠近裁剪面,即前後,那麼上下左右四個面又是怎麼定義的呢?上面的介紹已經涉及到了,就是FOV的概念。
繼續回到Unity當中,看下FOV的具體效果,修改Field of View的值,30,視錐體收縮,正方體不再內部
FOV修改為60,明顯感覺視錐體擴張,預覽又能看到正方體了
從上面看FOV的值似乎決定了上下左右四個面的夾角,而且其大小是用度來表示的,這裡的60即表示60度
好了,現在一個視錐體的所有引數都已經明確了,已知Camera的座標,只要知道遠近裁剪面的值,FOV的值即可定義一個唯一的視錐體
說了半天,視錐體要怎麼使用?ok,接下來開始正題,投影變換。
三、投影變換
Unity中Camera的投影變換分為兩種:透視投影和正交投影。簡要說明兩者的區別,正交投影的觀察體是長方體,是規則的,它使用一組平行投影將三維物件投影到投影平面上去,相信對Unity瞭解比較深入的同學都知道正交投影的功能,距離Camera的遠近並不會影響物體的縮放,比如說距離10m和1000m的實際大小相同的物體,呈現在畫面裡的大小也是相同的,這顯然是我們不希望的,3D遊戲模擬的是現實生活,而在現實生活當中,離我們遠的物體,看起來當然比較小,而即使是一部手機,放在眼睛前方的時候,看起來,卻會碩大無比。於是正交投影在3D遊戲當中的使用就非常有限了。
接下來是透視投影,這是3D遊戲中廣為使用的一種技術,它使用一組由投影中心產生的放射投影線,將三維物件投影到投影平面上去。透視投影的觀察體就是以上一直在說的視錐體。它具有通過物體遠近來縮放的能力,現在,需要把視錐體包含的物體投影成畫面,這個過程,需要做的變換,就是投影變換
那麼為什麼要變換呢?
視錐體實際上不是一個規則體,這對於我們做裁剪很不利,從3D物體到畫面的過程,通常需要經歷以下三步:
1. 用透視變換矩陣把頂點從視錐體中變換到裁剪空間的規則觀察體(CVV)中
2. 使用CVV進行裁剪
3. 螢幕對映:將經過前述過程得到的座標對映到螢幕座標系上。
這個過程,可以用一張圖來表示(圖摘自它處)
從視錐體變換到立方體的過程中,近裁剪面被放大變成了立方體的前表面,遠裁剪面被縮小變成了立方體的後表面,這就解釋了為什麼透視投影可以將物體的遠近很直觀表達出來的原因,很簡單,因為它放大了近處的物體,縮小了遠處的物體。
那麼怎麼做這個變換呢,我們可以理解為視錐體中某一個點(x,y,z,1)與某一個矩陣相乘得到的新點(x1,y1,z1,1)即為對應CVV中的點,這樣把視錐體中所有的點與該矩陣相乘,獲得的就是一個CVV。而這個矩陣,就是透視投影矩陣。
直接亮出這個矩陣的值,想看詳細推導的同學,給個連結:
這裡有很多引數的意義用下圖來表示
對於一個視錐體,我們取它的截面一般有如下兩種方法,不過一般都取yz面作為截面來計算引數,這裡我們要取FOV,near,far,botton,top,right,left的值,其中botton,top,right,left是投影平面的上下左右邊界值,投影平面,就是近裁剪面。
四、修改投影矩陣建立一個非標準投影
我們繼續迴歸到Unity當中,Unity關於Camera投影矩陣的文件相當相當的少,唯一可用的就是Camera.projectionMatrix的API裡面零星的介紹,連結:但至少我們是可以輸出投影矩陣看一下的
print(Camera.main.projectionMatrix); //這句話輸出主攝像機的投影矩陣
上圖看到了投影矩陣的值,FOV=60,near=0.3,far=1000的情況下,進行計算,發現除了第一個值有問題其他都正確。
第一個值為什麼是1.08878?
經過研究,我發現Unity有一個特性,無論怎麼修改視窗的比例,m【1,1】的值總是不變,固定為1.73205但是,只要改變FOV,它就會改變,所以Unity一定是把FOV定義為投影平面的上邊緣與下邊緣的夾角,即top=near*tan(FOV/2),而right就不能通過right=near*tan(FOV/2)來計算了,而是要用right=top*aspect這條公式,我們調節螢幕尺寸的時候,實際上改變了m【0,0】的結果而不會改變其他值。
我們寫一個指令碼去改變投影矩陣的值,看看效果
using UnityEngine;
using System.Collections;
public class example : MonoBehaviour {
public Matrix4x4 originalProjection;
void Update() {
//改變原始矩陣的某些值
Matrix4x4 p = originalProjection;
Camera.main.projectionMatrix = p;
}
public void Awake() {
originalProjection = Camera.main.projectionMatrix;
print(Camera.main.projectionMatrix);
}
}
這裡取E01=0.5,發現遠近裁剪面變成平行四變形,相應的畫面也斜了
其他的我就不演示了,改變其他的值會得到相應的效果
原因很簡單,還是要貼出之前推匯出來的公式
M矩陣的m01我們把他從0改到了0.5,影響的是x座標變換的結果,本來x座標是與y無關的,現在隨著y的增加,x也會增加
如下圖,相當於本來正方形中的每一個畫素與y都無關,現在每一個畫素在y不為0的時候都會向右平移0.5y的距離,這樣,就導致看起來像平行四邊形了
其他的就不一一推導了,反正VR在做投影的時候會涉及到這一塊,這樣以後涉及到投影矩陣的時候大家就不會那麼迷茫了
最後貼一串程式碼,是在聖典上發現的,實現畫面像水一像波動的特效,也是通過修改投影矩陣的方式實現的
複製黏貼後,加在主攝像機上就可以實現了,這麼強大的特效居然幾行程式碼就可以搞定,實在覺得不可思議。
using UnityEngine;
using System.Collections;
//讓相機以流行的方式晃動
public class example : MonoBehaviour {
public Matrix4x4 originalProjection;
void Update() {
//改變原始矩陣的某些值
Matrix4x4 p = originalProjection;
p.m01 += Mathf.Sin(Time.time * 1.2F) * 0.1F;
p.m10 += Mathf.Sin(Time.time * 1.5F) * 0.1F;
Camera.main.projectionMatrix = p;
}
public void Awake() {
originalProjection = Camera.main.projectionMatrix;
}
}