這篇作為上一篇的補充介紹,主要講Unity里面的投影矩陣的問題:
上篇的鏈接寫給VR手游開發小白的教程:(三)UnityVR插件CardboardSDKForUnity解析(二)
關於Unity中的Camera,聖典里面對每一項屬性都做了簡要的介紹,沒看過的小伙伴傳送門在下面
http://www.ceeger.com/Components/class-Camera.html
一、裁剪面
先從這個專業的詞匯開始,以下是聖典對裁剪面的介紹:
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.
近裁剪面及遠裁剪面屬性確定相機視野的開始和結束。平面是布置在與相機的方向垂直的位置,並從它的位置測量。近裁剪面是最近的渲染位置,遠平面是最遠的渲染位置。
下圖,當我們近距離觀察Camera的時候,會發現一個用白線畫的金字塔(四棱錐),這很好理解,他表示了Camera的視野范圍,奇怪的是這個金字塔(四棱錐)少了一個角,從而金字塔不僅有了底面,還有一個頂面。
相信猜也能猜到了,金字塔的頂部這個面,是近裁剪面(near clip planes),底面,則是遠裁剪面(near clip planes)
那么說了半天,裁剪面有什么用呢?
我們繼續在Unity的攝像機中改變Clipping Planes的值,看看變化,首先把Clipping Planes的near和far分別調為2和6
如下圖預覽界面,在近裁剪面和遠裁剪面之間沒有包含物體,渲染的圖像里是不會有物體的
增加far的值,現在立方體的很小一塊包含進去了,但是我們看預覽界面,並看不出它是立方體,只能看出平面效果
好的,繼續增加,現在整個立方體都包含進來了,看預覽,終於可以明顯看出是正方體了
這個例子說明了Camera似乎只渲染近裁剪面與遠裁剪面之間的物體,這個原理就好比平面圖的渲染,我們需要對圖片進行裁剪完成后,程序才知道要渲染的范圍,無論這張圖是全部需要還是只需要一部分,第一步,都是裁剪。現在,3D的渲染也需要裁剪,於是近裁剪面與遠裁剪面就誕生了,只不過裁剪的范圍並不是平面的,而是立體的(被切掉頂端的金字塔),這個立體的形狀,我們稱之為視裁剪體。
二、視裁剪體
視裁剪體,專業的叫法是視錐體(fusum),它由6個面構成,上下,左右,前后,先看聖典里面的介紹:
http://www.ceeger.com/Manual/UnderstandingFrustum.html
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。而這個矩陣,就是透視投影矩陣。
直接亮出這個矩陣的值,想看詳細推導的同學,給個鏈接:
http://blog.csdn.net/popy007/article/details/1797121
這里有很多參數的意義用下圖來表示
對於一個視錐體,我們取它的截面一般有如下兩種方法,不過一般都取yz面作為截面來計算參數,這里我們要取FOV,near,far,botton,top,right,left的值,其中botton,top,right,left是投影平面的上下左右邊界值,投影平面,就是近裁剪面。
四、修改投影矩陣建立一個非標准投影
我們繼續回歸到Unity當中,Unity關於Camera投影矩陣的文檔相當相當的少,唯一可用的就是Camera.projectionMatrix的API里面零星的介紹,鏈接:
http://www.ceeger.com/Script/Camera/Camera.projectionMatrix.html
但至少我們是可以輸出投影矩陣看一下的
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;
}
}
后面會繼續回歸VR的主題,繼續去詳細解釋腳本里面的東西。