Unity 圖形 - 攝像機

1. Camera

攝像機定義瞭如何將場景顯示到屏幕上。

攝像機位置定義了視口的位置,forward(Z)軸定義了視口方向,upward(Y)定義了上方。Camera component同時還定義了視口的尺寸和形狀。

1.1 透視和正交

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CVj5Amrx-1581516550394)(https://docs.unity3d.com/uploads/Main/CameraPerspectiveAndOrtho.jpg)]

Unity的攝像機支持正交視圖,實現跟人眼一樣近大遠小的效果。有時我們希望不要近大遠小的效果,比如渲染戰略地圖的攝像機,這叫做正交視圖。透視和正交視圖模式可以在攝像機組件上選擇。透視和正交其實是攝像機投影的兩種模式(矩陣)。

1.2 視口區域形狀

攝像機需要定義可視區域,由垂直於攝像機Z軸的平面構成,該平面有2個,分別是最近和最遠可視距離,小於最近距離,或大於最遠距離的對象將不被顯然。由這兩個距離確定的平面叫做近裁剪平面(near plan)和遠裁剪平面(far plan)。

沒有透視時,物體的大小跟到攝像機的距離沒有關係,所以遠/近裁剪平面也是2個一樣大小的矩形。

有透視時,物體距離越遠就越小,意味着距離越遠,可以看到的場景範圍就越大。可視區域就變成了從攝像機位置的點開始,到遠裁剪面的錐體。當然因爲可視區域並不是從攝像機位置開始的,而是還要靠前一些(near plan),所以是一個平頂的錐體。這個平頂錐體,學術上我們叫Frustum(視錐體)。視錐體由寬高比(aspect ratio),和視口範圍(field of view FOV上下裁剪面的角度)來定義。根據FOV和Znear確定高度,根據高度和寬高比確定寬度。

1.3 背景

再室內場景,攝像機完全在場景的結構內部,攝像機顯示沒什麼問題。相對的當在室外時,除了我們看到的景物,大部分區域(包括天空)都是單色的(Camera.background設置的顏色),着肯定不是我們要的效果。解決這個問題我們用SkyBox(天空盒)。天空盒就是一個立方體的盒子,永遠跟隨攝像機移動(或者說攝像機永遠在盒子的中心點),在盒子向裏的面,貼上天空的圖案,這樣,無論怎樣移動,旋轉攝像機,都能看到天空。天空盒距離攝像機假定是無窮遠,所以它在所有對象的後面,也是在最後渲染。當然天空盒的貼圖,不僅包含天空,還由地平線以下的部分,可以由遠山等,用來表達部分場景內容。天空盒也可以用來實現在太空中,會水中這樣的環境,相應的天球盒的圖(是一張立方體貼圖CubeMap)。

通過Window>Rendering>Lighting Settings打開光照設置窗口,來設置skybox屬性。

2. 使用多個攝像機

除了使用一個攝像機來渲染場景,我們可以用多個攝像機對場景進行渲染,比如在多個機位之間切換,或渲染小地圖。

2.1 多個攝像機切換

切換時,只需要將當前要渲染的攝像機激活,並關閉其它攝像機:Camera.enabled

using UnityEngine;

public class ExampleScript : MonoBehaviour {
    public Camera firstPersonCamera;
    public Camera overheadCamera;

    // Call this function to disable __FPS__ camera,
    // and enable overhead camera.
    public void ShowOverheadView() {
        firstPersonCamera.enabled = false;
        overheadCamera.enabled = true;
    }
    
    // Call this function to enable FPS camera,
    // and disable overhead camera.
    public void ShowFirstPersonView() {
        firstPersonCamera.enabled = true;
        overheadCamera.enabled = false;
    }
}

2.2 全屏渲染+部分屏幕區域渲染

該用法典型的是一個玩家視口攝像機,用來渲染玩家看到的東西,同時屏幕特定區域渲染小地圖。

首先我們需要2個攝像機,通過修改參數Depth來確定渲染順序,越小的先渲染,同時將第二個渲染的攝像機的ClearFlags 設置爲 Don’t Clear。

因爲我們希望第二個攝像機在屏幕的部分區域顯示,所以我們設置要設置Viewport Rect參數。比如我們要在左下角顯示,則x,y=0,0,w,h=0.1,0.1,表示在左下角10%屏幕區域顯示。

Viewport:攝像機的屏幕區域Viewport rect 被歸一化到0-1之間。左下角是0,0點,右上角是1,1點。

Depth:depth決定了攝像機的渲染順序,越小的則先渲染。

ClearFlags:攝像機渲染時,可以將整個屏幕清理成一個設置的單色,或天空盒,也可以設置爲Don’t Clear表示保留之前攝像機渲染的圖像。

3. 使用物理攝像機 Using Physical camera

攝像機組件的Physical Camera選項,讓我們可以用來模擬真實世界中的攝像機。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ev29yQkk-1581516550396)(https://docs.unity3d.com/uploads/Main/InspectorCameraPhysCam.png)]

有兩個重要的屬性分別是 Focal Length 和 Sensor Size:

  • Focal Length 焦距。感光器和鏡頭之間的距離。着決定了垂直的FOV。焦距越小,FOV越大。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-th7qAEAF-1581516550396)(https://docs.unity3d.com/uploads/Main/PhysCamAttributes.png)]

  • Sensor Size 感光元件尺寸。這決定了攝像機的寬高比。可以在Sensor Type下拉列表中選擇根據顯示世界攝像機類型對應而預定義的尺寸,也可以自己設置。如果感光器寬高比與渲染視口的寬高比不一致,則需要通過Gate Fit 下拉列表選擇匹配方式。

Lens Shifts

Lens Shifts 控制將頭在水平或垂直上進行相對於感光器的偏移。這樣可以改變焦點,重新定位渲染目標在底片上的位置,可能會帶來一點失真(可能也是我們想要的效果)。

該技術在攝影技術中很常見。例如,如果想要捕捉一個很高的建築,則需要旋轉攝像機。但那會導致失真,讓平行線不再平行。如下圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oKCWdtsX-1581516550397)(https://docs.unity3d.com/uploads/Main/LensShift_VRot.png)]

如果該爲提高焦點,也可以捕捉完整的建築,但是平行線不會變形,如下圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YtKq9CnA-1581516550397)(https://docs.unity3d.com/uploads/Main/LensShift_VShift.png)]

同樣的可以用水平偏移來在水平上捕捉目標而不失真。下面兩張圖,第一張通過旋轉攝像機捕捉,有失真,第二張通過偏移焦點,沒有失真

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-boKn310a-1581516550398)(https://docs.unity3d.com/uploads/Main/LensShift_HRot.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-J5P9Sx2Q-1581516550398)(https://docs.unity3d.com/uploads/Main/LensShift_HShift.png)]

Gate Fit

在物理攝像機中,有2個"gates":

  • 遊戲視口(屏幕),由寬高比的分辨率決定,也叫“resolution gate” 屏幕視口
  • 攝像機視口,有Sensor Size決定,也叫“file gate” 膠片視口

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-p0Vn0LB5-1581516550399)(https://docs.unity3d.com/uploads/Main/GateFitGates.png)]

當這兩個視口的比例不一致時,Unity會將屏幕視口匹配到膠片視口,並提供了多種適配模式,但是隻有3種結果:

  • Cropping : 裁剪,當膠片尺寸在經過適配後,依然超過屏幕尺寸,遊戲視圖僅渲染攝像機視口內的部分,多餘的將被裁剪掉。
  • Over scanning:當膠片尺寸在經過適配後,依然超過屏幕尺寸,遊戲視圖仍然會渲染在攝像機視口之外的部分。
  • Stretching:拉伸,遊戲視口根據攝像機視口進行拉伸,並渲染整個攝像機視口內的場景。

選中攝像機,查看其視錐體。屏幕視口就是其遠裁剪面,攝像機視口則是第二個矩形:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XtbPS57v-1581516550399)(https://docs.unity3d.com/uploads/Main/GateFitUI.png)]

上圖,矩形A是屏幕視口,B是攝像機視口。

Gate Fit Modes

Gate Fit Modes 決定了Unity如何改變屏幕視口的尺寸,同時也改變了視錐體。攝像機視口不會改變。

譯者:這部分,理解不深刻,總之,都是按照遊戲視口的尺寸渲染。這部分主要就是如何根據攝像機寬高比調整視口寬高比,底片多出來的裁剪掉,遊戲視口大就都顯示。

Vertical

當 Gate Fit Modes 設置爲 Vertical 時,Unity根據Y軸(高度)來進行適配。所以改變感光器的寬度(Sensor Size.X)不會對渲染結果產生影響。

如果感光器的寬高比大於遊戲視口的寬高比,Unity會將兩邊的裁剪掉:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nHAe48go-1581516550400)(https://docs.unity3d.com/uploads/Main/GateFitV_600x900_16mm.png)]

Gate Fit set to Vertical: Resolution gate aspect ratio is 0.66:1 (600 x 900 px). Film gate aspect ratio is 1.37:1 (16mm). The red areas indicate where Unity crops the image in the Game view.

如果感光器寬高比小於遊戲視口寬高比,Unity會將感光器外的也渲染出來:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-f54uG5pq-1581516550400)(https://docs.unity3d.com/uploads/Main/GateFitV_16-9_16mm.png)]

Gate Fit set to Vertical: Resolution gate aspect ratio is 16:9. Film gate aspect ratio is 1.37:1 (16mm). The green areas indicate where Unity overscans the image in the Game view.

所以渲染的內容,依然是以遊戲視口爲基準,但是根據攝像機視口調整遊戲視口尺寸。

Horizontal

當 Gate Fit Modes 設置爲 Horizontal 時,Unity根據X軸(寬度)來進行適配。所以改變感光器的高度(Sensor Size.Y)不會對渲染結果產生影響。

如果感光器寬高比大於遊戲視口的,Unity依然會渲染垂直上超出感光器範圍的部分:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4ILu9jTU-1581516550401)(https://docs.unity3d.com/uploads/Main/GateFitH_600x900_16mm.png)]

Gate Fit is set to Horizontal: Resolution gate aspect ratio is 0.66:1 (600 x 900 px). Film gate aspect ratio is 1.37:1 (16mm). The green areas indicate where Unity overscans the image in the Game view.

如果感光器寬高比小於遊戲視口的,Unity依然會在垂直上超出視口範圍的部分裁剪掉:(Unity文檔裏的圖貼錯了,這裏也不再引用了,免得造成誤解。

例子:Gate Fit set to Horizontal: Resolution gate aspect ratio is 16:9. Film gate aspect ratio is 1.37:1 (16mm). The red areas indicate where Unity crops the image in the Game view.

None

當設置爲None,Unity在X,Y軸上分別進行匹配,將渲染的圖片適配(拉伸)在遊戲視口顯示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ij9RCy6H-1581516550401)(https://docs.unity3d.com/uploads/Main/GateFitF_16mm.png)]

攝像機使用的 film gate 寬高比=1.37:1 (16mm), 並水平拉伸圖像適配遊戲視口寬高比= 16:9 (左圖),如果遊戲視口寬高比= 0.66:1 則進行垂直拉伸(右圖)

Fill and Overscan

當Fit Modes設置爲Fill 或 Overscan,Unity會根據遊戲視口和攝像機視口的寬高比,自動執行垂直或水平的匹配。

  • Fill 根據攝像機視口的較短的軸改變遊戲視口。多餘部分裁剪掉。
  • Overscan 根據攝像機視口較長的軸改變遊戲視口,會渲染更多內容

4. 使用技巧 Camera Tricks

4.1 理解視錐體 Understanding the View Frustum

視錐體像一個被截掉頂部的錐體,只有該錐體範圍內的對象才能被看到和渲染。

假設我們有一根直的木棒,一段朝向攝像機。如果木棒在攝像機的中間,垂直於攝像機鏡頭,則我們只能看到一個圓點,其它部分被自身擋住了。如果向上移動一點,則底下的部分能夠看到,這時如果向上旋轉,依然能隱藏掉其他部分,只剩一個圓點。這時在木棒的兩個端點同時繼續向上移動,則最終該圓點會到達屏幕頂端,在該點,所有在該木棒之上的對象都不可見。(上裁剪面)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-P0ojoInc-1581516550401)(https://docs.unity3d.com/uploads/Main/Rods.png)]

可以在水平或垂直,或同時水平垂直,上下左右移動並旋轉木棒,隱藏木棒使之成爲一個點的旋轉角度,僅取決於在兩個軸上,到屏幕中心點的距離。

該思維實驗表達的意思是我們在攝像機影像中任意點定義爲空間中的一條直線,而渲染爲一個點。如果對這些直線,向攝像機位置延長,最終會交匯到一個點,這個點也是攝像機的位置。在該位置,屏幕頂部中心和底部中心兩條直線的夾角,被稱爲事業(FOV)。

沿着屏幕外沿的對應的所有直線,在空間中圍成一個錐體,在該錐體內的對象是可見的。同時還有兩個限制,即兩個平行於攝像機XY的近裁剪面和遠裁剪面,在這兩個平面之外的對象是不可見的。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fZlDWmOB-1581516550402)(https://docs.unity3d.com/uploads/Main/ViewFrustum.png)]

4.2 給定距離上的視錐體截面尺寸

給定距離上的視錐體截面是一個屏幕可見的矩形區域。有時需要根據距離求矩形區域,或根據矩形區域來計算距離。例如,我們希望無論怎麼拉遠或拉近,對象都在屏幕內。

計算指定距離上視錐體的高度公式:

float frustumHeight = 2.0f * distance * Mathf.Tan(camera.filedOfView*0.5f*Mathf.Deg2Rad);

根據給定視錐體高度求距離:

float distance = frustumHeight * 0.5f / Mathf.Tan(camera.fieldView * 0.5f * Mathf.Deg2Rad);

當知道高度和距離時,可以求出FOV:

camera.fieldOfView = 2.0f * Mathf.Atan(frustumHeight * 0.5f / distance) * Mathf.Rad2Deg;

知道寬或高時,可以求出另一個:

float frustumWidth = frustumHeight / camera.aspect;
float frustumHeight = frustumWidth / camera.aspect;

4.3 攝像機射線

Unity中,用 Ray 對象來定義射線。攝像機提供了2個得到設想的接口:ScreenPointToRayViewportPointToRay。它們的不同是ScreenPointToRay需要的是屏幕像素座標爲參數,ViewporPointToRay是以歸一化(0-1)座標(0,0爲左下角,1,1爲右上角)。需要注意的是,射線的源點在攝像機的近裁剪面上,而不是攝像機位置。

射線檢測

攝像機射線最常用來向場景投射射線,檢測與哪個碰撞體發生碰撞,如果發生碰撞,返回結RaycastHit對象,包含了碰撞對象,以及碰撞點。這種基於屏幕的射線檢測非常有用,例如檢測鼠標所在的位置的對象(鼠標拾取對象)的代碼:

RaycastHit hit;
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out hit))
{
	Transform objectHit = hit.transform;
}

沿着射線移動攝像機

例如,可能允許用戶用鼠標選擇一個對象,然後攝像機向該對象移動,代碼如下:

Ray ray = camera.ScreenPointToRay(Input.mousePosition);

float zoomDistance = zoomSpeed * Input.GetAxis("Vertical") * Time.deltaTime;

camera.transform.Translate(ray.direction * zoomDistance, Space.World);

4.4 使用傾斜的攝像機

默認視錐體是以攝像機中心線對稱的,但那不是必須的。可以創建一個斜的視錐體,也就是說某一側的裁剪面與中線的角度,比另一側小(大)。可以獲得一種在角度小的那一側的對象距離更近的錯覺。一個應用這個效果的例子是賽車遊戲,壓縮視錐體底部,讓玩家看起來距離地面很近,有更強烈的速度感。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cHVTGxlg-1581516550402)(https://docs.unity3d.com/uploads/Main/ObliqueFrustum.png)]

實現該效果,需要使用物理攝像機,調整Lens Shifts的值來獲取想要的效果,同時不會造成失真。

偏移鏡頭會改變對側的視錐體角度。例如向上偏移,下面的視錐面與中線的角度會變小。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5z072qcP-1581516550403)(https://docs.unity3d.com/uploads/Main/ObliqueFrustum_LensShift.png)]

在腳本中進行控制

可以通過腳本修改投影矩陣來達到傾斜攝像機的目的:

void SetObliqueness(float horizObl, float vertObl)
{
	Matrix4x4 mat = Camera.main.projectionMatrix;
	mat[0,2] = horizObl;
	mat[1,2] = vertObl;
	Camera.main.projectionMatrix = mat;
}

horizObl和vertObl爲正值,標識向上或向右偏移。偏移最大值爲1或-1,表示對側完全跟中心線重合了。

5. Occlusion Culling

遮擋剔除用於在渲染時,剔除對被其它對象遮擋,攝像機無法觀察到的對象的渲染。由於很多時候,距離攝像機遠的對象先被渲染,近的後渲染時就會在遠的上渲染,這導致一個像素被繪製多次,而且之前的很多次,實際上都是不可見的,這就叫overdraw。

下圖展示了沒有frustum裁剪時,渲染了所有的對象:

第二幅圖展示了視錐體裁剪後,被渲染的對象少了很多:

第三幅圖,使用遮擋剔除後,只有被攝像機看到的對象渲染,少了很多

遮擋剔除的處理過程,是用一個虛擬的攝像機遍歷場景,建立潛在可見對象集合的層級關係。運行時攝像機利用這些數據標識哪些對象可見,哪些不可見。利用這些數據,Unity可以確保僅將可見的對象送入渲染管線,降低DrawCall,提升效率。

遮擋數據由一些單元格組成,每個單元格是整個場景的包圍體的細分,這些單元格構建成一顆二叉樹。遮擋剔除用了2棵樹,一棵是View Cells(靜態對象),和一棵Target Cells(移動對象)。View Cells映射到定義可見靜態對象的索引列表,從而爲靜態對象提供更精確的篩選結果。

創建對象時,需要在對象的尺寸和Cell的尺寸間取得一個好的平衡點。不能定義相對於對象太小的Cell,也不應該創建覆蓋太多Cells的對象。有時,將一些大的對象拆分成小對象,可以提升裁剪效果。同樣的,可以將一些小的在同一個Cell裏的對象合併來降低DrawCall。

可以在Scene視圖中選擇"OverDraw"渲染模式來查看OverDraw,在Game視圖的Stats信息面板中查看總的渲染三角形數量,以及渲染批次。下面2張圖,對比了是否開啓遮擋剔除的效果及數據

該圖未開啓遮擋剔除,注意Scene視圖中的OverDraw,牆後面的大量的房間的渲染,造成了較高的OverDraw,這些房間在遊戲視圖中是看不到的。

應用遮擋剔除後,遠處的房間不再渲染,OverDraw也低了很多。繪製的三角形數量和渲染批次也顯著降低了。

5.1 建立遮擋剔除

首先,將關卡打散到合適的尺寸,這樣有利於高效地被那些大的對象遮擋住,以剔除,如牆,大的建築。例如如果一個房間裏有很多傢俱,這些傢俱應該各自進行剔除,而不是合併到一起,雖然合併到一起降低了drawcall。

爲了讓對象參與遮擋剔除,需要將對象的tag設置爲Occluder Static或者Occludee Static。可以選擇多個對象一次進行設置。

Occludee Static:那些小的,會被其它對象擋住的對象,設置爲 Occludee Static。

Occluder Static:大的物體,會遮擋其它對象的對象,設置爲 Occluder Static 。

當使用了LOD group時,只有LOD0的對象會被當做遮擋對象。

5.2 遮擋剔除窗口

從 Window>Rendering>Occlusion Culling 打開遮擋剔除窗口。

在該窗口,可以處理遮擋Mesh和遮擋區域。

在Object面板,如果在場景裏選擇了Mesh,可以修改它們的遮擋類型:遮擋或者被遮擋,或者2者都設置

如果時選擇了遮擋區域(Occlusion Area),則可以設置遮擋區域屬性

注意:如果沒有創建裁剪區域,那麼裁剪將會被應用到整個場景。

注意:一旦攝像機不在裁剪區域內,遮擋剔除將會失效,所以設置遮擋區域完全覆蓋攝像機的可達區域非常重要。但是過大的裁剪區域也需要更長的數據烘焙時間。

5.3 遮擋剔除烘焙 - bake

set default parameters 按鈕,重置參數爲Unity默認參數,以適應大多數情況。當然可以調整參數,適應我們的場景。

參數 含義
Smallest Occluder 執行遮擋剔除時,會遮擋其他對象的對象的最小尺寸。小於改尺寸將不會遮擋其它對象。選擇一個合適的值來平衡裁剪精度和裁剪數據尺寸。
Smallest Hole 該值表示攝像機視線能穿過的,2個幾何體之間的最小距離。該值表示能通過該孔的對象的直徑。
Backface Threshold Unity通過背面測試來減少不必要的細節,來優化遮擋數據。默認值100非常簡單地,不從數據集合中移除背面。將參數設置爲5,可以根據可見背面的位置,大大降低數據量。例如,當攝像機在地形下面,或者實體對象內部,這些攝像機不會到達的區域。距離背面大於該參數的位置將不會生成遮擋數據。

Occlusion Culling - Visualization

窗口最下方,時Clear和Bake按鈕。點擊Bake按鈕,開始生成遮擋數據。生成後,可以切到 Visualization面板,預覽測試遮擋剔除。如果覺得結果不滿意,點擊Clear按鈕,清除數據,然後調整參數,再重新烘焙。

場景中的所有對象都會影響包圍體的大小,所以要儘量讓它們都在場景的可見範圍內。

烘焙完成後,可以看到藍色的立方體,每個立方體是一個裁剪區域。

5.4 Occlusion Area 裁剪區域

爲了對移動對象進行遮擋,需要創建 Occlusion Area ,並修改它的尺寸來適應移動對象能到達的區域。可以爲一個空的對象添加 Occlusion Area 組件來創建。

創建完後,選擇 Is View Volume 來遮擋移動對象。(移動對象與攝像機不在同一個遮擋區域內,不可見?)

創建完遮擋區域後,需要查看是如何將盒子拆分到Cells中的。要看遮擋區域是如何計算的,在 Occlusion Culling Preview Panel 中,選擇 Edit,並選中View Volumes複選框。

測試效果

設置好遮擋後,可以在 Occlusion Culling Preview Panel 的 Visualize mode 中,選中 Occlusion Culling,在Scene窗口中移動攝像機來進行測試。

當移動攝像機時(無論是否在play模式),會看到一些對象被禁掉了。在這裏,可以查找遮擋數據的錯誤。當你移動攝像機時,看到一個對象突然跳出來,說明有錯誤發生。當發生這種情況,可以改變分辨率,或者調整對象位置來解決。可以將攝像機移動到出問題的對象處,進一步檢查問題。

數據生成後,視圖中會有一些帶顏色的立方體。藍色的表示Target Volume(被遮擋對象)的Cell劃分,白色的是View Volume(攝像機位置包圍框)的Cell劃分。如果參數設置正確,可以看到一些對象不被渲染,可能不在視錐體內,也可能是被遮擋剔除掉了。

如果你發現場景中沒有任何對象被遮擋,那麼打碎你的對象,讓這些碎片完全包含在一個Cell內。

Occlusion Portals

爲了創建可以在運行時打開和關閉的圖元,使用 Occlusion Portals。

屬性:

  • Open 阻擋的門是否是打開的
  • Center 遮擋區域的中心點。默認0,0,0在盒子的中心點
  • Size 遮擋區域的尺寸

6. Dynamic resolution

攝像機的動態分辨率參數允許在運行時動態改變分辨率,以改變render target,來降低GPU負載。如果遊戲的幀率降低了,可以逐步降低分辨率來保持幀率。如果性能數據表明幀率的降低是由GPU瓶頸導致的,則會觸發降低分辨率(自動的?這可高級了)。也可以手動的找到GPU負載比較高的部分,降低分辨率(比如某個後效)。逐步縮放分辨率,幾乎無法發覺。

支持的平臺

  • XBox One
  • PS4
  • Nintendo Switch
  • iOS
  • macOS and tvOS(metal only)
  • Android(Vulkan only)
  • Windows Standalone and UWP(DirectX 12 only)

對Render Target的影響 Impact on render targets

使用動態分辨率時,Unity不會重新分配render target。理論上Unity應該會縮放render target,但實際上,Unity是執行了一種扭曲,縮小render target僅使用原始render target的部分區域。Unity分配全尺寸的Render Target,然後再動態分辨率時,縮小或放大真正使用的區域。

縮放 Render Targets

開啓動態分辨率後,render targets 會設置DynamicallyScalable 標識。可以設置該標識,告訴再動態分辨率時,是否對該render target進行處理。攝像機也有allowDynamicResolution 標識,當場景渲染比較簡單,(僅攝像機用到RT),則可以用來設置攝像機的相關的RT的動態分辨率功能。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8tN5SIth-1581516550409)(https://docs.unity3d.com/uploads/Main/DynamicResolution.png)]

MRT Buffers

當攝像機啓用了Allow Dynamic Resolution,所有該攝像機相關的Render Targets都會被縮放。

控制縮放

可以通過ScalableBufferManager控制縮放,該類可以控制標識爲DynamicallyScalable標識的RT進行寬高的動態縮放。

例如你的遊戲運行再正常的幀率,但是在某些情況下,GPU效率降低了,比如例子增多,後效過於複雜。Unity的FrameTimingManager可以幫助發現CPU或GPU的效率降低。可以通過FrameTimingManager來計算出保持幀率需要的寬和高,然後應用該縮放來讓幀率穩定(立即或逐步地)。當屏幕複雜度降低並且GPU效率穩定時,可以提升分辨率到GPU可以處理的程度。

例子

下面的例子演示了API的使用。這裏需要將場景中攝像機的Allow Dynamic Resolution選項打開,還要把PlayerSettings>Player>Other Settings>Enable Frame Timing Stats 選項打開。

鼠標點擊,或單手指點擊屏幕,以 scaleWidthIncrement and scaleHeightIncrement來降低分辨率。2個手指觸屏以同樣方式增加分辨率。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.__UI__;

public class DynamicResolutionTest : MonoBehaviour
{
    public Text screenText;

    FrameTiming[] frameTimings = new FrameTiming[3];

    public float maxResolutionWidthScale = 1.0f;
    public float maxResolutionHeightScale = 1.0f;
    public float minResolutionWidthScale = 0.5f;
    public float minResolutionHeightScale = 0.5f;
    public float scaleWidthIncrement = 0.1f;
    public float scaleHeightIncrement = 0.1f;

    float m_widthScale = 1.0f;
    float m_heightScale = 1.0f;

    // Variables for dynamic resolution algorithm that persist across frames
    uint m_frameCount = 0;

    const uint kNumFrameTimings = 2;

    double m_gpuFrameTime;
    double m_cpuFrameTime;

    // Use this for initialization
    void Start()
    {
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\n",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight);
    }

    // Update is called once per frame
    void Update()
    {
        float oldWidthScale = m_widthScale;
        float oldHeightScale = m_heightScale;

        // One finger lowers the resolution
        if (Input.GetButtonDown("Fire1"))
        {
            m_heightScale = Mathf.Max(minResolutionHeightScale, m_heightScale - scaleHeightIncrement);
            m_widthScale = Mathf.Max(minResolutionWidthScale, m_widthScale - scaleWidthIncrement);
        }

        // Two fingers raises the resolution
        if (Input.GetButtonDown("Fire2"))
        {
            m_heightScale = Mathf.Min(maxResolutionHeightScale, m_heightScale + scaleHeightIncrement);
            m_widthScale = Mathf.Min(maxResolutionWidthScale, m_widthScale + scaleWidthIncrement);
        }

        if (m_widthScale != oldWidthScale || m_heightScale != oldHeightScale)
        {
            ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale);
        }
        DetermineResolution();
        int rezWidth = (int)Mathf.Ceil(ScalableBufferManager.widthScaleFactor * Screen.currentResolution.width);
        int rezHeight = (int)Mathf.Ceil(ScalableBufferManager.heightScaleFactor * Screen.currentResolution.height);
        screenText.text = string.Format("Scale: {0:F3}x{1:F3}\nResolution: {2}x{3}\nScaleFactor: {4:F3}x{5:F3}\nGPU: {6:F3} CPU: {7:F3}",
            m_widthScale,
            m_heightScale,
            rezWidth,
            rezHeight,
            ScalableBufferManager.widthScaleFactor,
            ScalableBufferManager.heightScaleFactor,
            m_gpuFrameTime,
            m_cpuFrameTime);
    }

    // Estimate the next frame time and update the resolution scale if necessary.
    private void DetermineResolution()
    {
        ++m_frameCount;
        if (m_frameCount <= kNumFrameTimings)
        {
            return;
        }
        FrameTimingManager.CaptureFrameTimings();
        FrameTimingManager.GetLatestTimings(kNumFrameTimings, frameTimings);
        if (frameTimings.Length < kNumFrameTimings)
        {
            Debug.LogFormat("Skipping frame {0}, didn't get enough frame timings.",
                m_frameCount);

            return;
        }

        m_gpuFrameTime = (double)frameTimings[0].gpuFrameTime;
        m_cpuFrameTime = (double)frameTimings[0].cpuFrameTime;
    }
}

7. Cameras Reference

Camera

Camera負責將場景內容呈現給玩家。通過配置攝像機參數,可以讓遊戲畫面獨具特色。可以創建很多個攝像機,以特定的順序進行渲染,渲染到屏幕任何地方。

不同的渲染管線,攝像機在Inspector窗口顯示的屬性也不一樣:

屬性:

  • Clear Flags 指示攝像機渲染完後,如何填充沒被繪製過的區域
    • Skybox 用天空盒清理
    • Solid Color 以下面指定的 Background 顏色清理
    • Depth only 僅清理深度緩衝區
    • Don’t clear 不清理
  • Background 當攝像機渲染完成後,該顏色被填充到未被渲染的區域。
  • Culling Mask 指示該攝像機要渲染哪些層的對象。
  • Projection 投影方式
    • Perspective 透視投影
    • Orthographic 正交投影。延遲着色不支持,僅前向渲染支持
  • Size 當選擇正交投影時,定義正交攝像機視口尺寸
  • FOV Axis 透視投影時,指定FOV是定義在哪個軸上
    • Horizontal 在水平軸上
    • Vertical 在垂直軸上
  • Field of view 透視投影時,設定攝像機在FOV Axis軸上的可視角度。
  • Physical Camera 選擇該項,啓用物理攝像機,並顯示物理攝像機參數
    • Focal Length 焦距(毫米),感光元件與鏡頭之間的距離。該值越小,FOV越大。改變該值,Unity會自動更新FOV。
    • Sensor Type 指定預定義的真實世界中的攝像機類型,從下拉列表中選擇。不同的類型,會自動設置預置的對應的Sensor Size參數。如果手動修改了Sensor Size,該值也會自動切換到Custom。
    • Sensor Size 設置感光元件的尺寸(毫米)。X:寬度;Y:高度。
    • Lens Shifts 設置鏡片的偏移。該值需要乘以感光器尺寸。例如設置爲x=0.5,則偏移尺寸的一半。可以用該參數修正當攝像機和對象之間有角度時造成的變形(例如保持平行線)。還可以通過該參數讓視錐體傾斜。
      • X,Y 在水平,垂直方向上偏移。
    • Gate Fit 如何修改遊戲視口寬高比來匹配感光器寬高比
    • Clipping Planes
      • near 近裁剪平面與攝像機位置的距離
      • far 遠裁剪平面與攝像機位置的距離
    • Viewport Rect 指示該攝像機渲染到屏幕的哪個區域,用視口座標表示(0,0左下角,1,1右上角)
      • X,Y 區域的左下角
      • W,H 區域的寬,高
    • Depth 攝像機繪製順序。攝像機根據depth從小到大依次渲染
    • Rendering Path 配置使用什麼方式渲染該攝像機
      • Use Graphics Settings 使用在Player Settings中的設置
      • Vertex Lit 頂點光照
      • Forward 前向渲染,每個對象每個材質渲染一次
      • Deferred Lighting 延遲着色。每個對象先不帶光照渲染一次,最後再對對象進行光照渲染。正交攝像機該設置無效,會自動使用前向渲染。
    • Target Texture 可以指定渲染到貼圖(Render Texture),而不再渲染到屏幕。
    • Occlusion Culling 開啓遮擋剔除。參考遮擋剔除 Occlusion Culling
    • Allow HDR 開啓高動態範圍光照,參考 High Dynamic Range Rendering
    • Allow MSAA 開啓多重採樣抗鋸齒
    • Allow Dynamic Resolution 開啓動態分辨率,參考Dynamic Resolution
    • Target Disply 渲染到哪個顯示器,1-8

細節

攝像機可以配置,腳本控制,或通過父子關係,實現各種效果。比如一個智力遊戲,可能要保持攝像機固定來顯示整個謎題場景。對第一人稱射擊類遊戲,將攝像機掛到控制的角色身上,再角色眼睛位置。對競速遊戲,需要讓攝像機正確的跟隨車輛運動。

可以創建多個攝像機,並設置不同的Depth,這些攝像機從低Depth到高Depth依次渲染,也就是說,Depth2的攝像機,將覆蓋到Depth爲1的攝像機上面。可以調整 Viewport Rect來指定攝像機渲染再屏幕的特定區域,這可以用來實現小視口,例如導彈視口,小地圖,後視鏡,等。

Render Path

Unity支持不同的渲染路徑。應該根據遊戲類型和目標平臺/硬件來選擇合適的渲染路徑。不同的渲染路徑擁有不同的特性和效率特徵,主要是光照和陰影。在Project Setting中設置渲染路徑,之後可以在攝像機上覆蓋選擇新的。

參考rendering paths

Clear Flags

攝像機渲染的數據,包括顏色和深度,會保存下來。屏幕上沒有被繪製過的地方是空的,默認會顯示天空盒。當使用多個攝像機時,每個都會累加存儲自己顏色和深度到緩衝區。任意攝像機渲染時,可以指定一個Clear Flags來清除緩衝區的不同區域。可以選擇如下選項:

  • Skybox 默認選項,空白的部分將會顯示該攝像機上掛的Skybox組件定義的天空盒,如果沒有,則使用Window>Rendering>Lighting Settings中指定的天空盒。如果最終沒有天空盒可以使用,則使用Background Color。創建天空盒,you can use this guide.
  • Solid color 屏幕空白區域將用該攝像機的Background Color 填充。
  • Depth only 僅清除深度數據。如果想繪製玩家的槍,但是不會插到環境中被遮擋,用Depth=0的攝像機渲染環境,Depth=1的攝像機單獨渲染武器,並設置武器攝像機的Clear Flags = depth only。這會保留環境圖形在屏幕上,但是丟棄了已有圖形的3D深度。當繪製武器時,不透明的部分將完全覆蓋在最上面,而無論武器與牆有多近,甚至事實上已經查到牆裏了。

  • Don’t clear 該模式不進行清理,不同攝像機渲染的對象被塗抹到一起,通常不在遊戲中使用,可能只在某些特殊shader效果使用。

在多數GPU上(尤其是移動端GPU),不清理屏幕可能會導致下一幀圖像不確定。在一些系統上,屏幕可能包含上一幀的圖像,純黑色,或隨機顏色。

Clip Planes

Near和Far Clip Plane決定了攝像機視口的開始和結束範圍。平面垂直於攝像機方向向量,從攝像機位置開始計算。Near Plane 和 Far Plane 之間的被渲染。

遠近裁剪面還定義了深度緩衝區的精度如何對應到場景。通常爲了獲得較好的深度精度,儘量遠近裁剪面不要太遠。

由遠近裁剪面,以及由FOV定義的平面,共同組合成攝像機的視錐體。完全在視錐體外的對象不會被渲染,這叫視錐體裁剪,該裁剪與遮擋剔除無關。

爲了提升執行效率,可能想要提前裁剪掉那些小物體。例如小石頭,碎片,一定距離後太小可能就看不到了。可以將這些小對象放到特定的層,然後在腳本中,通過 Camera.layerCullDistances 設置該層的裁剪距離。

Culling Mask

Culling Mask用來選擇該攝像機渲染哪些層下的對象。

歸一化的視口矩形 Normalized Viewport Rectangles

Normalized Viewport Rectangles 用來指定當前攝像機渲染到屏幕的哪個區域。可以在屏幕右下角創建地圖視口,或者在左上角顯示導彈跟蹤視口。可以設計實現某些特定的效果。

可以很容易的創建兩個玩家分屏的遊戲。創建好2個攝像機後,修改它們的H=0.5,玩家1的的Y=0,玩家2的Y=0.5:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yIdbwa2C-1581516550411)(https://docs.unity3d.com/uploads/Main/Camera-Viewport.jpg)]

正交視圖

正交視圖去掉了透視效果,尤其是對創建等距的2D遊戲。

需要注意的是,霧效是用統一的方式渲染的,所以在正交視圖下的渲染可能會不正確。因爲霧效使用透視後的Z座標作爲深度進行計算。但是處於效率(該霧效的效率相對較高)考慮,還是會使用。

透視攝像機:

正交攝像機:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CRa3w3bE-1581516550412)(https://docs.unity3d.com/uploads/Main/Camera-Ortho-FPS.jpg)]

Render Texture

指定該參數,攝像機將會最終渲染到一張特殊的紋理(Render Texture)上,可以被作爲一張普通貼圖在渲染時使用。例如創建競技場的 大屏幕 ,或創建反射。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZB1vGojs-1581516550412)(https://docs.unity3d.com/uploads/Main/Camera-RenderTexture.jpg)]

Target Display

攝像機最多有8個顯示目標選項來設置,控制渲染到8個顯示器中的一個。這僅在PC,Mac,Linux下支持。當前選擇的顯示器在Game視窗顯示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章