【Unity】相機 Cameras

1 前言

????????主要介紹官方文檔中相機模塊的內容。

????????關于“9動態分辨率”,這部分很多API文檔只是提了一下,具體細節還需要自己深入API才行。

2 攝像機介紹

????????Unity 場景在三維空間中表示游戲對象。由于觀察者的屏幕是二維屏幕,Unity 需要捕捉視圖并將其“平面化”以進行顯示。它使用攝像機來實現這一點。在 Unity 中,我們可以通過將一個Camera組件添加到游戲對象來創建攝像機,也可以右鍵創建相機,如創建Cube一樣。

2.1相機的視野

????????攝像機的視野是由它的Transform和 Camera 組件來定義。Transform來定義相機的位置、旋轉,Z軸是視野方向,Y軸是屏幕頂端,Camera 組件的設置還定義了視圖區域的大小和形狀。當相機移動和旋轉時,顯示的視圖會隨之移動和旋轉。

2.2 透視與正交相機

????????現實世界中的攝像機(實際相當于人眼)在觀察外界事物時,物體距離視點越遠,看起來越小。這種眾所周知的透視效果在藝術和計算機圖形領域廣泛應用,對于創建現實場景至關重要。這就是透視攝像機,當然,Unity是支持的。但有時需要專門在沒有這種透視效果的條件下渲染視圖,例如,需要創建一種與真實世界的對象不完全相同的地圖或信息顯示效果,顯示的對象不隨距離變遠而縮小,實現這種效果的攝像機稱為正交攝像機,Unity 攝像機也有這樣的選項。在透視和正交模式下觀察場景稱為攝像機投影。透視(左)與正交(右)效果:

2.3 視野區域形狀

????????對于從當前位置能“觀察”的最遠距離方面,透視和正交攝像機都存在一定的限制。該限制由垂直于攝像機向前 (Z) 方向的平面定義。此平面稱為遠裁剪面,因為與攝像機距離較遠的對象將被“裁剪”(即,不在渲染范圍內)。攝像機附近還有一個相應的近裁剪面;可觀察的距離范圍位于這兩個平面之間。

????????在非透視模式下,無論距離遠近,對象大小不變。這表示,正交攝像機的視體由兩個裁剪面之間的長方體定義。如圖:

????????使用透視攝像機時,對象會隨其與攝像機的距離增大而縮小。這表示場景中可視部分的寬度和高度隨著距離的增大而增大。因此,透視攝像機的視體不是一個長方體,而是金字塔形狀,其頂點位于攝像機位置而底部位于遠裁剪面。不過,該形狀并不是嚴格的金字塔形,因為頂部被近裁剪面截斷了,這種被截斷的金字塔形狀稱為視錐體。由于視錐體的高度并非常量,視錐體由其寬度與高度之比(稱為寬高比)以及頂部與底部之間在頂點處的夾角(稱為視野即 FOV)定義。請參閱關于了解視錐體的頁面,了解更多詳細說明。視椎體如圖:

2.4 相機視圖的背景

????????在渲染場景之前,我們可以設置相機如何處理物體之間的空白區域的背景。例如,我們可以選擇在背景上渲染場景之前采用純色填充背景,或者繪制天空或遠處的背景,甚至保留前一幀的內容。有關配置此設置的信息,請參閱 Camera Inspector 參考中的 Background 屬性。有關繪制天空的信息,請參見天空。

3 使用多個相機

????????創建一個場景后,默認是包含一個相機的,這在大多數情況下就已經足夠了。但我們也可以在場景中添加任意數量的攝像機,還能以不同的方式組合它們的視圖,如下所述。

3.1 切換相機

????????默認情況下,攝像機會用它所渲染的畫面覆蓋整個屏幕,因此同一時刻只能看到一個攝像機的畫面(即 depth 屬性具有最高值的攝像機)。通過在腳本中禁用一個攝像機并啟用另一個,即可從一個攝像機的鏡頭“切”到另一個,從而得到場景的多個鏡頭畫面。在某些情況下,例如要在俯瞰地圖視角和第一人稱視角之間切換時,就可以采用這種方法。

using UnityEngine;public class ExampleScript : MonoBehaviour {public Camera firstPersonCamera;public Camera overheadCamera;// 調用此函數可禁用 FPS 攝像機,并啟用overhead相機。public void ShowOverheadView() {firstPersonCamera.enabled = false;overheadCamera.enabled = true;}// 調用此函數可啟用 FPS 攝像機,并禁用overhead相機。public void ShowFirstPersonView() {firstPersonCamera.enabled = true;overheadCamera.enabled = false;}
}

3.2 渲染畫中畫

????????通常情況下,我們希望至少有一個攝像機視圖覆蓋整個屏幕(默認設置),但在屏幕的一個小區域內顯示另一個視圖往往也很有用。例如,我們可能會在駕駛游戲中顯示一個后視鏡,或者在第一人視角的屏幕角落里顯示俯瞰小地圖。我們可以使用攝像機的 Viewport Rect 屬性設置攝像機在屏幕上的矩形的大小。

????????視圖矩形的坐標相對于屏幕經過“標準化”,底邊和左邊是 0.0 坐標,而頂邊和右邊是 1.0,坐標值為 0.5 表示位于中間位置。除了視圖大小之外,還應將具有較小視圖的攝像機的 depth 屬性設置為一個高于背景攝像機的值。確切的值無關緊要,總之規則是具有較高 depth 值的攝像機的渲染畫面會覆蓋在較低值攝像機的渲染畫面之上。

4 使用物理攝像機 (Physical Camera)

????????攝像機組件的 Physical Camera 屬性在 Unity 攝像機上模擬真實攝像機格式。這可用于從同樣模擬真實攝像機的 3D 建模應用程序導入攝像機信息。

Unity 提供的設置與大多數 3D 建模應用程序的物理攝像機設置相同。控制攝像機視野的兩個主要屬性是 Focal LengthSensor Size

  • Focal Length:傳感器和攝像機鏡頭之間的距離,即焦距。此屬性決定了垂直視野。Unity 攝像機處于 Physical Camera 模式時,改變 Focal Length 也會相應改變視野。焦距越小,視野越大,反之亦然。

  • Sensor Size:捕捉圖像的傳感器的寬度和高度,表示傳感器大小。這些數值決定了物理攝像機的寬高比。可從對應于真實攝像機格式的幾個預設傳感器大小中進行選擇,或設置自定義大小。傳感器寬高比與渲染的寬高比(在 Game 視圖中設置)不同時,可以控制 Unity 如何將攝像機圖像與渲染的圖像匹配(請參閱下文中關于 Gate Fit 的信息)。

4.1 Lens Shift

????????Lens Shift(鏡頭位移,組件上的屬性) 從傳感器上水平和垂直地偏移攝像機的鏡頭。這樣一來便可以改變焦點中心,并在渲染的幀中重新定位拍攝對象,確保很少或完全沒有失真。

????????這種方法在建筑攝影中很常見。例如,如果要拍攝一座高樓,可以旋轉攝像機。但這會使圖像失真,導致平行線看起來發生會聚。如圖:

如果把鏡頭上移,而不是旋轉攝像機,就可以改變構圖以包含樓頂,但平行線保持直線。如圖:

同樣,可以使用水平鏡頭位移方法來拍攝寬大的對象,避免由于旋轉攝像機而可能產生的失真。如圖:

4.1.1 Lens Shift和視錐體傾斜

????????鏡頭移位的一個副作用是會使攝像機的視椎體傾斜。這意味著攝像機的中心線與其視錐體之間的角度在一側要小于另一側。如圖:

這里再給個動圖演示:

即往那邊移,視椎體就往哪邊傾斜。

????????此功能可用于根據視角來創造視覺效果。例如,在賽車游戲中,可能希望將視角保持在接近地面的較低位置。鏡頭移位是一種不用腳本即可實現視錐體傾斜的方式。

????????有關更多信息,請參閱關于使用斜視錐體的文檔。

4.2 Gate Fit

????????Camera 組件的 Gate Fit 屬性決定了 Game 視圖和物理攝像機傳感器具有不同寬高比時會發生什么情況。

在 Physical Camera 模式中,一個攝像機有兩個“門”。

  • 根據 Aspect 下拉菜單中設置的分辨率,在 Game 視圖中渲染的區域被稱為“分辨率門”。(Game視圖可看到的)

  • 攝像機實際看到的區域(由 Sensor Size 屬性定義)被稱為“膠片門”。(攝像機拍攝到的)

兩個門具有不同寬高比時,Unity 讓分辨率門“適應”膠片門。有幾種適應模式,但是這些模式都會產生以下三種結果之一。

  • 裁剪 (Cropping):適應后,膠片門超過分辨率門時,Game 視圖在滿足寬高比的情況下渲染盡可能多的攝像機圖像面積,并裁掉其余部分。

  • 過掃描 (Overscanning):適應后,膠片門超過分辨率門時,Game 視圖仍然對攝像機視野之外的場景部分進行渲染計算。【感覺是不是應該是膠片門小于分辨率門?文檔上是超過。】

  • 拉伸 (Stretching):Game 視圖渲染完整的攝像機圖像,將其水平或垂直拉伸以適應寬高比。

要在 Scene 視圖中查看這些門,并查看它們如何相互適應,請選擇攝像機并查看其視錐體。分辨率門是攝像機遠裁剪面。膠片門是位于視錐體底部的第二個矩形。

4.2.1 Gate Fit 模式

????????選擇的 Gate Fit 模式決定了 Unity 如何調整分辨率門的大小(因而調整攝像機的視錐體)。膠片門始終保持相同大小。以下部分提供了關于每種 Gate Fit 模式的更多詳細信息。

4.2.1.1 Vertical

????????Gate Fit 設置為 Vertical 時,Unity 讓分辨率門適應膠片門的高度(Y 軸)。對傳感器寬度 (Sensor Size > X) 進行的任何更改都不會影響渲染的圖像(分辨率門)。

????????如果傳感器寬高比大于 Game 視圖寬高比,Unity 會在兩側裁剪渲染的圖像(PS:丟棄了兩側的內容):

如果傳感器寬高比小于 Game 視圖寬高比,Unity 會在兩側對渲染的圖像進行過掃描(PS:兩側綠色是膠片門沒有覆蓋的區域,進行過掃描):

4.2.1.2 Horizontal

????????Gate Fit 設置為 Horizontal 時,Unity 讓分辨率門適應膠片門的寬度(X 軸)。對傳感器高度 (Sensor Size > Y) 進行的任何更改都不會影響渲染的圖像。(基本和Vertical一樣,只是一個適應Y軸,一個適應X軸)

????????如果傳感器寬高比大于 Game 視圖寬高比,Unity 會在頂部和底部對渲染的圖像進行過掃描:

如果傳感器寬高比小于 Game 視圖寬高比,則會在頂部和底部裁剪渲染的圖像。

4.2.1.3 None

????????Gate Fit 設置為 None 時,Unity 讓分辨率門適應膠片門的寬度和高度(X 軸和 Y 軸)。Unity 會拉伸渲染的圖像以適應 Game 視圖寬高比,即會以膠片門的圖像為基準,然后拉伸到分辨率門的尺寸。

如圖所示,兩個都是None模式下的經過適應拉伸的,右下角顯示的小窗口就是分辨率門的尺寸,可以看到左側圖像被拉寬了,右側圖像被拉窄了,都是基于膠片門圖像來拉伸到分辨率門的尺寸的。另外,此時遠裁剪面就是膠片門。

4.2.1.4 Fill 和 Overscan

Gate Fit 設置為 FillOverscan 時,Unity 根據分辨率門和膠片門的寬高比,自動進行垂直或水平適應。

  • Fill 讓分辨率門適應膠片門的較小軸,并裁剪攝像機圖像的其余部分。(這種模式下,膠片門>=分辨率門)

  • Overscan 讓分辨率門適應膠片門的較大軸,并對攝像機圖像邊界以外的區域進行過掃描。(這種模式下,膠片門<=分辨率門)

PS:具體可以自己動手試試看看,這里說小、大軸說得太模糊了,可以理解為分辨率門匹配兩軸,門小時匹配的軸是小軸,門大時匹配的軸是大軸。

5 攝像機和深度紋理

????????相機可以生成深度(depth)、深度+法線(normals)或運動矢量(vector)紋理。這是一個簡化的G-buffer紋理,可以用于后期處理效果或實現自定義光照模型。這些主要用于效果,例如,后期處理效果經常使用深度信息。

????????深度紋理中的像素值在0 ~ 1之間,呈非線性分布。精度通常為32位或16位,具體取決于所使用的配置和平臺。當從深度紋理讀取時,返回0到1范圍內的高精度值。如果我們需要獲得與Camera的距離,或者其他0-1的線性值,請使用helper macros手動計算。

????????大多數現代硬件和圖形 API 都支持深度紋理。下面列出了特殊要求:Direct3D 11+ (Windows), opengl3 + (Mac/Linux), Metal (iOS)和流行的控制臺支持深度紋理。

????????可使用腳本中的Camera.depthTextureMode變量來啟用攝像機的深度紋理模式。還可以使用Shader Replacement功能來自行構建類似紋理。

有三種可能的深度紋理模式:

  • DepthTextureMode.Depth:深度紋理。

  • DepthTextureMode.DepthNormals: 深度和視圖空間法線打包成一個紋理。

  • DepthTextureMode.MotionVectors:當前幀的每個屏幕紋理像素(texel)的每像素屏幕空間運動。打包到RG16 紋理中。

這些是標志(flag),因此可以指定上述紋理的任何組合。

5.1 DepthTextureMode.Depth 紋理

????????此模式將構建一個屏幕大小的深度紋理。

????????渲染深度紋理時使用的著色器通道與用于陰影投射物渲染的著色器通道相同(ShadowCaster 通道類型)。因此,通過擴展,如果著色器不支持陰影投射(即在著色器或任何后備著色器中沒有陰影投射物通道),則使用該著色器的對象將不會顯示在深度紋理中。

  • 讓著色器 fallback 到另一個具有陰影投射通道的著色器,或者

  • 如果我們在使用 surface shaders,那么添加 addshadow 指令也能讓它們生成陰影通道。

請注意,僅“不透明”對象(這些對象的材質和著色器設置為使用小于等于 2500 的 render queue )會渲染到深度紋理中。

5.2 DepthTextureMode.DepthNormals 紋理

????????這將構建屏幕大小的 32 位(8 位/通道)紋理,其中視圖空間法線編碼到 R&G 通道中,而深度編碼到 B&A 通道中。法線使用立體投影(Stereographic projection)進行編碼,深度是打包到兩個 8 位通道中的 16 位值。

????????UnityCG.cginc include文件具有一個 helper 函數 DecodeDepthNormal,可從編碼的像素值中解碼深度和法線。返回的深度在 0 到 1 范圍內。

????????關于如何使用深度和法線紋理的例子,請參考 Replacing shaders at runtime 或在 Post-processing and full-screen effects中替換環境遮擋(Ambient Occlusion)。

5.3 DepthTextureMode.MotionVectors 紋理

????????這將構建屏幕大小的 RG16(16 位浮點/通道)紋理,其中屏幕空間像素運動編碼到 R&G 通道中。像素運動在屏幕 UV 空間中進行編碼。

????????當從這個紋理運動中采樣時,從編碼像素返回的范圍是[-1,1]。這將是上一幀到當前幀的UV偏移量。

5.4 提示和技巧

????????攝像機的Inspector面板會指示攝像機何時渲染深度或深度+法線紋理。

????????從攝像機請求深度紋理的方式(Camera.depthTextureMode)可能意味著在禁用需要深度紋理的效果后,攝像機可能仍會繼續渲染深度紋理。若攝像機上存在多個效果,其中每個效果都需要深度紋理,則無法在禁用單個效果的情況下自動禁用深度紋理渲染。

????????在實現復雜的著色器或圖像效果時,切記平臺之間的渲染差異。尤其是,在圖像效果中使用深度紋理通常需要對 Direct3D和抗鋸齒進行特殊處理。

????????在某些情況下,深度紋理可能直接來自本機 Z 緩沖區。如果在深度紋理中看到了瑕疵,請確保使用該紋理的著色器未寫入 Z 緩沖區(使用 ZWrite Off)。

5.5 著色器變量

????????深度紋理可用于在著色器中作為全局著色器屬性進行采樣。通過聲明名為 _CameraDepthTexture 的采樣器,我們將能夠為攝像機采樣主深度紋理。

????????_CameraDepthTexture 始終引用攝像機的主深度紋理。相比之下,我們可以使用 _LastCameraDepthTexture 來引用任何攝像機渲染的最后一個深度紋理。例如,如果我們使用輔助攝像機渲染腳本中的半分辨率深度紋理并希望將其提供給后期處理著色器,這種做法可能很有用。

????????運動矢量紋理(啟用時)在著色器中可用作全局著色器屬性。通過聲明名為“_CameraMotionVectorsTexture”的采樣器,我們可以為當前執行渲染的攝像機采樣紋理。

5.6 部分內部原理

????????深度紋理可以直接來自實際的深度緩沖(depth buffer),或者在單獨的通道中渲染,這取決于所使用的渲染路徑和硬件。通常當使用延遲著色渲染路徑(Deferred Shading rendering path)時,深度紋理是“免費的”,因為它們是G-buffer渲染的產物。

????????當 DepthNormals 紋理在單獨通道中渲染時,此過程通過 Shader Replacement 完成。因此,務必在我們的著色器中設置正確的“RenderType”標簽。

????????啟用時,MotionVectors 紋理始終來自額外的渲染通道。Unity 會將移動的游戲對象渲染到此緩沖區中,并構建從最后一幀到當前幀的運動。【?官方要不要你自己看看在說什么,“額外”、“此緩沖區”?】

6 相機技巧(Camera Tricks)

????????當設計某些視覺效果或與場景中的物體交互時,理解相機是如何工作的是有用的。本節將解釋相機視角的本質以及它如何能夠增強游戲玩法。

6.1 了解視椎體(View Frustum)

????????視錐體一詞表示看起來像頂部切割后平行于底部的金字塔的實體形狀。這是透視攝像機可以看到和渲染的區域的形狀。以下思維實驗應該有助于解釋為什么會這樣。

????????想象一下,將一根直桿(例如掃帚柄或一支鉛筆)正對著攝像機,然后拍照。如果桿垂直于攝像機鏡頭保持在圖片中心位置,那么只有其一端可在圖片上顯示為圓圈;所有其他部分都會被遮擋。如果將桿向上移動,下側將開始變得可見,但可通過向上傾斜桿再次將其隱藏。如果繼續向上移動桿并進一步將其向上傾斜,則圓形末端最終將到達圖片的頂部邊緣。此時,在世界空間中由此桿跟蹤的界線上方(就是此桿往上)的任何對象在圖片上都不可見。

????????很容易將桿向左、向右或向下(或者水平和垂直方向的任何組合方式)移動或旋轉。“隱藏”桿的角度僅取決于它在兩個軸上距離屏幕中心的距離。

????????這一思維實驗的意義在于,攝像機圖像中的任何一點實際上都對應于世界空間中的一條線,在圖像中只能看到這條線上的一個點。這條線上該位置背后的一切都會被遮擋。

????????圖像的外邊緣由對應于圖像四個角的發散線界定。如果這些線向后追蹤到攝像機,它們最終會聚合到同一個點。在 Unity 中,此點恰好位于攝像機的Transform位置,也稱為透視中心。從屏幕頂部和底部中心聚合到透視中心處的線所形成的角度稱為視野(通常縮寫為 FOV)。

????????如上所述,任何落在圖像邊緣的發散線之外的物體對攝像機而言均不可見,但是針對攝像機渲染的內容還有另外兩個限制。近剪裁面(Near clipping plane)和遠剪裁面(Far clipping plane)平行于攝像機的 XY 平面,兩者沿中心線相隔一定的距離。比近剪裁面更靠近攝像機的任何對象以及比遠裁剪面更遠離攝像機的任何對象都不會被渲染。

圖像四角的發散線以及兩個裁剪面定義了截頭金字塔:視錐體。

6.1.1 遠裁剪平面和浮點數學(遠物體閃爍問題)

????????物體、光線和陰影如果距離較遠,可能會閃爍。出現閃爍是因為距離太大,無法用浮點數學精確計算位置。在每一幀中,物體、光線或陰影的位置都略有不同,因此它們會移進或移出視錐

使用以下方法之一盡量減少閃爍:

  • 減少Camera組件中的遠裁剪平面距離,以避免物體的距離變得太大而無法精確計算。

  • 讓場景中的所有東西都變小,以減少整個場景的距離。

Unity以世界空間位置作為參考點來計算光線和陰影,例如3D場景中的0,0,0。閃爍發生在光和影遠離世界空間位置的時候。為了最小化閃爍,我們可以啟用相機相對剔除(camera-relative culling),以至于Unity使用相機位置作為陰影計算的相對位置。參見Culling settings in Graphics settings。

6.2 距攝像機一定距離的視錐體的大小

????????距攝像機一定距離的視錐體的橫截面將在世界空間中定義一個構成可見區域的矩形。此形狀有時可用于計算此矩形在給定距離處的大小,或者查找矩形為給定大小時所處的距離。例如,如果移動的攝像機需要始終將對象(例如玩家)完全保持在鏡頭中,則不得過于接近攝像機以免該對象的一部分被截斷。

????????可使用以下公式來計算視錐體在給定距離處的高度(均以世界單位表示):

var frustumHeight = 2.0f * distance * Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);

…還可反轉該過程以計算獲得指定視錐體高度所需的距離:

var distance = frustumHeight * 0.5f / Mathf.Tan(camera.fieldOfView * 0.5f * Mathf.Deg2Rad);

當高度和距離已知時,也可以計算 FOV(視野)角度:

var cameraFieldOfView = 2.0f * Mathf.Atan(frustumHeight * 0.5f / distance) * Mathf.Rad2Deg;

視椎體高度、寬度的相互計算:

var frustumWidth = frustumHeight * camera.aspect;
var frustumHeight = frustumWidth / camera.aspect;

6.3 攝像機射線

????????了解視椎體部分說明了攝像機視圖中的任何一點都對應于世界空間中的一條線。有時使用這條線的數學表示形式是有用的,Unity 能夠以 Ray 對象的形式提供該表示形式。Ray 始終對應于視圖中的一個點,因此 Camera 類提供 ScreenPointToRay 和 ViewportPointToRay 兩個函數。兩者之間的區別在于 ScreenPointToRay 期望以像素坐標的形式提供該點,而 ViewportPointToRay 則接受 0..1 范圍內的標準化坐標(其中 0 表示視圖的左下角,1 表示右上角)。這些函數中的每一個函數都返回由一個原點和一個矢量(該矢量顯示從該原點出發的線條方向)組成的 Ray。射線 (Ray) 源自近裁剪面而不是攝像機 (Camera) 的 transform.position 點。

6.3.1 射線投射

????????來自攝像機的射線最常見的用途是將射線投射( raycast )到場景中。射線投射從原點沿著射線方向發送假想的“激光束”,直至命中場景中的碰撞體。隨后會返回有關該對象和 RaycastHit 對象內的投射命中點的信息。這是一種基于對象在屏幕上的圖像來定位對象的非常有用的方法。例如,可使用以下代碼確定鼠標位置處的對象:

using UnityEngine;
using System.Collections;public class ExampleScript : MonoBehaviour {public Camera camera;void Start(){RaycastHit hit;Ray ray = camera.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out hit)) {Transform objectHit = hit.transform;// 對射線投射命中的對象執行一些操作。}}
}

6.3.2 沿著射線移動攝像機

????????獲取對應于屏幕位置的射線再沿著該射線移動攝像機有時很有用。例如,需要允許用戶使用鼠標選擇對象,然后放大該對象,同時將其“固定”到鼠標下的相同屏幕位置(這種操作可能很有用,例如,當攝像機正在查看戰術地圖時)。執行此操作的代碼非常簡單:

using UnityEngine;
using System.Collections;public class ExampleScript : MonoBehaviour {public bool zooming;public float zoomSpeed;public Camera camera;void Update() {if (zooming) {Ray ray = camera.ScreenPointToRay(Input.mousePosition);float zoomDistance = zoomSpeed * Input.GetAxis("Vertical") * Time.deltaTime;//使用射線方向來計算camera.transform.Translate(ray.direction * zoomDistance, Space.World);}}
}

6.4 使用斜視錐體

????????默認情況下,視錐體圍繞攝像機的中心線對稱安放,但這并不是必須的。視錐體可設置為“傾斜的”,即一側與中心線的角度小于對側與中心線的角度。

????????這種做法使得圖像一側的透視看起來更加緊湊,給人的印象是觀察者非常靠近在該邊緣處可見的對象。此功能的一個用法示例是賽車游戲;如果視錐體在其底部邊緣變平,則從觀察者的角度看起來會更貼近道路,凸顯了速度感。如圖:

????????在內置渲染管線中,使用斜視錐體的攝像機只能使用前向渲染路徑。如果攝像機設置為使用延遲著色 (Deferred Shading) 渲染路徑并使視錐體傾斜,Unity 會強制該攝像機使用前向渲染路徑。

6.4.1 設置視錐體傾斜度

????????雖然 Camera 組件沒有專門用于設置視錐體傾斜度的功能,但可以通過啟用攝像機的 Physical Camera 屬性并應用 Lens Shift 設置,或通過添加腳本來更改攝像機的投影矩陣,從而實現這樣的功能。

使用 Lens Shift 設置視錐體傾斜度

????????這個在上面的“4 使用物理相機”部分已經有過介紹了,可以去看。

使用腳本設置視錐體傾斜度

????????以下腳本示例顯示了如何通過更改攝像機的投影矩陣來快速實現斜視錐體。請注意,僅當游戲運行播放模式時才能看到腳本的效果。

using UnityEngine;
using System.Collections;public class ExampleScript : MonoBehaviour {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 值分別設置水平和垂直傾斜量。值為零表示無傾斜。正值使視錐體向右或向上移動(傾斜),從而使左邊或底邊變平。負值使視錐體向左或向下移動(傾斜),從而使視錐體的右邊或頂邊變平。如果將此腳本添加到攝像機并在游戲運行時將游戲切換到 Scene 視圖,則可以直接看到效果。在檢視面板中改變 horizObl 和 vertObl 的值時,攝像機視錐體的線框會發生變化。任一變量中的值為 1 或 –1 表示視錐體的一側與中心線完全齊平。此范圍之外的值也是允許使用的,但通常沒有必要。

7 遮擋剔除

????????“遮擋剔除”過程可防止 Unity 為那些被其他游戲對象完全擋住(遮擋)的游戲對象執行渲染計算。

????????攝像機在每一幀中執行剔除操作,這些操作會檢查場景中的渲染器,并排除(剔除)那些不需要繪制的渲染器。默認情況下,攝像機執行視錐體剔除,這一過程將排除所有不在攝像機視錐體范圍內的渲染器。但是,視錐體剔除不會檢查渲染器是否被其他游戲對象遮擋,因此 Unity 仍會浪費 CPU 和 GPU 時間進行在最終幀中不可見的渲染器的渲染操作。遮擋剔除將阻止 Unity 執行這些徒勞的操作。

何時使用遮擋剔除

要確定遮擋剔除是否有可能改善項目的運行時性能,請考慮以下事項:

  • 防止無意義的渲染操作可以節省 CPU 和 GPU 時間。Unity 的內置遮擋剔除在 CPU 上執行運行時計算,這可能會抵消其節省的 CPU 時間。因此,當項目因過度繪制而具有 GPU 密集型特征時,遮擋剔除最有可能提高性能。

  • Unity 在運行時將遮擋剔除數據加載到內存中。必須確保有足夠的內存來加載此數據。

  • 當場景中一些界限明確的小區域被實體游戲對象彼此隔開時,遮擋剔除的效果最好。一個常見的例子是通過走廊連接的房間。

  • 可以使用遮擋剔除來遮擋動態游戲對象,但動態游戲對象不能遮擋其他游戲對象。如果項目會在運行時生成場景幾何體,則 Unity 的內置遮擋剔除不適用于該項目。

遮擋剔除的工作原理

????????遮擋剔除會在 Unity Editor 中生成有關場景的數據,然后在運行時使用該數據來確定攝像機可以看到的內容。這種生成數據的過程稱為烘焙。

????????在對遮擋剔除數據進行烘焙時,Unity 將場景劃分為多個單元,并生成描述單元內幾何體以及相鄰單元之間可見性的數據。然后,Unity 盡可能合并單元,以減小生成的數據的大小。要配置烘焙過程,可以在 Occlusion Culling 窗口中更改參數,并在場景中使用遮擋區域。

????????在運行時,Unity 會將這些烘焙的數據加載到內存中,并且對于每個啟用了 Occlusion Culling 屬性的攝像機,將會對數據執行查詢以確定該攝像機可以看到的內容。請注意,啟用遮擋剔除后,攝像機將執行視錐體剔除和遮擋剔除。

其他

????????Unity 使用 Umbra 庫來執行遮擋剔除。有關詳細介紹 Umbra 的文章的鏈接,請參閱“遮擋剔除其他資源小節”。

7.1 遮擋剔除使用

7.1.1 設置場景

????????在開始之前,請確定場景中所有要設定為靜態遮擋物 (Static Occluder)(這些游戲對象不會移動,但會阻擋后面的游戲對象)和靜態被遮擋物 (Static Occludee)(這些游戲對象不會移動,但會被靜態遮擋物遮擋)。一個游戲對象可以同時是靜態遮擋物和靜態被遮擋物。

靜態遮擋物

????????適合作為靜態遮擋物的游戲對象包括中型到大型的實體游戲對象(例如墻壁或建筑物)。要被設定為靜態遮擋物,游戲對象必須滿足以下條件:

  • 對象身上有 Terrain 或 Mesh Renderer 組。

  • 不透明。

  • 在程序運行時,不移動。

請注意,如果使用了 LOD 組,則 Unity 會使用靜態遮擋物的基礎細節級別游戲對象 (LOD0) 來確定要遮擋的對象。如果游戲對象的輪廓在 LOD0 和其他 LOD 級別之間變化很大,這個游戲對象可能不適合設定為靜態遮擋物。

靜態被遮擋物

????????任何可能在運行時被遮擋的游戲對象都適合設定為靜態被遮擋物,包括小的或透明的游戲對象。要被設定為靜態被遮擋物,游戲對象必須滿足以下條件:

  • 具有任何類型的 Renderer 組件。

  • 在程序運行時,不移動。

設置場景

????????確定了要設定為靜態遮擋物和靜態被遮擋物的游戲對象之后,便可以設置場景。主要是設置物體為靜態遮擋物、靜態被遮擋物,以及相機開啟遮擋剔除屬性:

設置遮擋物或被遮擋物:

設置相機:

7.1.2 烘焙數據

操作如下:

  1. 在頂部菜單中,選擇 Window > Rendering > Occlusion Culling 以打開Occlusion Culling窗口。

  2. 選擇Bake選項卡。

  3. 在 Inspector 窗口的右下角,按 Bake 按鈕。Unity 會生成遮擋剔除數據,將數據另存為項目中的資源,并將該資源與當前場景關聯。

7.1.3 結果可視化

????????在設置好以上內容后,接下來就是看遮擋剔除的效果。

????????首先確保 Occlusion Culling 窗口和 Scene 視圖均為可見狀態。當 Occlusion Culling 窗口可見時,Unity 在 Scene 視圖中顯示遮擋剔除數據和 Occlusion Culling 彈出窗口。

然后,在場景中選擇一個攝像機,選中Occlusion Culling窗口中的Visualization選項卡。此時就可以看到小球已經消失了:

一個是被遮擋而消失,一個是在相機視椎體外而消失。注意,若遮擋物較小,即使遮擋住視野也可能無法隱藏背后的被遮擋物。后續我們可以在 Occlusion Culling 彈出窗口配置可視化設置。

????????如有需要調整烘焙,在 Occlusion Culling 窗口Bake選項卡中調整烘焙設置,然后重新烘焙即可。

????????我們移動相機,調整到可以看到小球的位置,就可以將消失的小球重新顯示,這里就不演示了。

????????如果使用的是內置渲染管線,則可以使用 Scene 視圖模式 Overdraw 來查看正在發生的過度繪制數量,并可以使用 Game 視圖中的 Stats 面板來查看 Unity 正在渲染的三角形、頂點和批處理數量。

7.2 對動態游戲對象使用遮擋剔除

????????靜態游戲對象和動態游戲對象在 Unity 的遮擋剔除系統中的行為不同:

  • Unity 可以將靜態游戲對象作為靜態遮擋物和/或靜態被遮擋物烘焙到遮擋剔除數據中。

  • Unity 無法將動態游戲對象烘焙到遮擋剔除數據中。動態游戲對象可以在運行時充當被遮擋物,而不能充當遮擋物。

????????要確定動態游戲對象是否充當被遮擋物,可以在任何類型的渲染器組件上設置 Dynamic Occlusion 屬性。啟用 Dynamic Occlusion 后,渲染器在攝像機的視圖中被靜態遮擋物 (Static Occluder) 阻擋時,Unity 會剔除渲染器。禁用 Dynamic Occlusion 后,渲染器在攝像機的視圖中被靜態遮擋物 (Static Occluder) 阻擋時,Unity 不會剔除渲染器。如Mesh Renderer組件:

????????默認情況下會啟用 Dynamic Occlusion。為了獲得特定的效果,例如在墻后的角色周圍繪制輪廓,可能需要禁用 Dynamic Occlusion。

????????如果確定 Unity 絕對不應該將遮擋剔除應用于特定的游戲對象,則可以禁用 Dynamic Occlusion 以減少運行時計算并降低 CPU 使用率。雖然這些計算對每個游戲對象的影響很小,但如果規模足夠大,減少這些計算可能有利于提高性能。(PS:犧牲GPU,利于CPU)

7.3 遮擋剔除和場景加載

????????在運行時,無論打開了多少個場景,Unity 一次僅加載一個遮擋剔除數據資源。因此,必須根據計劃是一次加載一個場景還是一次加載多個場景,從而以不同方式準備遮擋剔除數據。

7.3.1 一次加載一個場景

????????如果使用 LoadSceneMode.Single 一次加載一個場景,則必須分別按以下方式烘焙每個場景的遮擋剔除數據:

  1. 在 Unity Editor 中,打開需要烘焙遮擋剔除數據的單個場景。

  2. 烘焙場景的數據。Unity 會生成數據并將保存為 Assets/[場景名稱]/OcclusionCullingData.asset。Unity 會將對此資源的引用添加到打開的場景中。

  3. 保存場景。

  4. 對每個場景重復步驟 1、2 和 3。

????????在運行時,按以下方式加載場景:

  1. 將場景加載為項目的默認場景,或者使用 LoadSceneMode.Single 來加載場景。如果之前有活動場景,則 Unity 會卸載活動場景以及活動場景的遮擋數據資源(如果有)。Unity 隨后加載我們的場景及其遮擋數據資源。

7.3.2 一次加載多個場景

????????如果使用 LoadSceneMode.Additive 一次加載多個場景,則必須按以下方式一起烘焙這些場景的數據:

  1. 在 Unity Editor 中,打開 Unity 將在運行時加載的場景組的第一個場景。該場景將變為活動場景。

  2. 以累加方式打開組中的其他場景,使所有這些場景同時在 Unity Editor 中打開。

  3. 烘焙所有場景的數據。Unity 會生成所有打開的場景的數據,并將其保存為 Assets/[活動場景名稱]/OcclusionCullingData.asset。Unity 會將對此資源的引用添加到所有打開的場景中。

  4. 保存場景。

????????在運行時,按以下方式加載場景:

  1. 加載場景組的第一個場景作為項目的默認場景,或使用 LoadSceneMode.Single 來加載場景。如果之前有活動場景,則 Unity 會卸載活動場景以及活動場景的遮擋數據資源(如果有)。Unity 隨后加載我們的場景以及共享的遮擋數據資源。

  2. 根據需要,使用 LoadSceneMode.Additive 加載其他場景。如果 Unity 發現以累加方式加載的場景的遮擋數據與活動場景的遮擋數據相同,則遮擋剔除將按預期工作。

????????請注意,共享的遮擋數據資源的文件較大。使用較大的遮擋數據資源不會在運行時對 CPU 產生任何其他影響。

7.4 遮擋區域

????????使用 Occlusion Area 組件可以定義遮擋剔除系統中的視圖體積。視圖體積是在運行時攝像機可能處于的場景區域。在烘焙時,Unity 在視圖體積內生成更高精度的數據。在運行時,當攝像機位于視圖體積內的時候,Unity 進行更高精度的計算。

????????如果尚未在場景中定義任何視圖體積,Unity 將在烘焙時創建一個視圖體積,其中包含標記為 Occluder Static 或 Occludee Static 的所有場景幾何體。在大型或復雜場景中,這可能導致不必要的大量數據、漫長的烘焙時間以及資源密集的運行時計算。為了避免發生這種情況,請將遮擋區域 (Occlusion Areas) 放置在場景中,從而定義攝像機可能處于的區域的視圖體積。

使用遮擋區域 (Occlusion Area) 組件來定義視圖體積

  1. Occlusion Area 組件添加到場景中的空游戲對象。

  2. 在 Inspector 窗口中,配置 Size 屬性,使包圍體積涵蓋所需區域。

  3. 在 Inspector 窗口中,啟用 Is View Volume

  • Size:設置遮擋區域 (Occlusion Area) 的大小。

  • Center:設置遮擋區域 (Occlusion Area) 的中心。默認情況下,此設置為 0,0,0,位于盒體的中心。

  • Is View Volume:如果啟用此屬性,遮擋區域 (Occlusion Area) 將定義視圖體積。如果禁用此屬性,遮擋區域 (Occlusion Area) 不會定義視圖體積。必須啟用此屬性才能使遮擋區域 (Occlusion Area) 生效。

7.4 遮擋入口

????????遮擋入口 (Occlusion Portal) 可以是打開或關閉狀態。遮擋入口關閉時,它將遮擋其他游戲對象。遮擋入口打開時,它不會遮擋其他游戲對象。

????????如果場景中有一個處于打開和關閉狀態的游戲對象(例如門),可以創建一個在遮擋剔除系統中表示該游戲對象的遮擋入口。然后,可以根據該游戲對象的狀態來設置遮擋入口的打開狀態。無需將 Occlusion Portal 組件置于其表示的游戲對象上,即放在空物體上,其就能起到打開與關閉的作用(類似一個無形的墻壁)。

7.4.1 在場景中設置遮擋入口

  1. 選擇場景中的合適游戲對象來充當遮擋入口。適合作為遮擋入口的游戲對象包括中型到大型的實體游戲對象(例如門)。

  2. 確保未將此游戲對象標記為 Occluder Static 或 Occludee Static。

  3. Occlusion Portal 組件添加到游戲對象。

  4. 烘焙場景的遮擋數據。

  5. 確保 Occlusion Culling 窗口(Visualization選項卡)、Inspector 面板和 Scene 視圖均為可見狀態。

  6. 在 Scene 視圖中,將攝像機移至遮擋入口正前方的位置。

  7. 選擇具有 Occlusion Portal 組件的游戲對象。

  8. 在 Inspector 窗口中,開啟和關閉 Occlusion Portal 組件的 Open 屬性。在 Scene 視圖中,觀察遮擋剔除的差異。

????????如圖,這里我把中間的墻壁Cube設置為非靜態,并添加了Occlusion Portal組件,然后烘焙,之后通過控制Occlusion Portal組件的Open屬性來看效果:

可以看到在打開時,對象是被顯示了,在關閉時,對象是被隱藏了。需要注意,這里我是掛載的Cube上,此時Cube是非靜態的,已經不具備遮擋效果了,全程都是Occlusion Portal在發揮作用,其遮擋,其不遮擋,換言之,這里的Cube換成一個空物體也可以實現此效果,但同樣的,就跟前面我們調整Cube大小一樣,需要調整Occlusion Portal的尺寸足夠大才能有遮擋效果。

????????Occlusion Portal組件在圖也能看到,就三個屬性:

  • Open: 如果啟用此屬性,遮擋入口將打開并且不會遮擋渲染器。如果禁用此屬性,遮擋入口將關閉并且會遮擋渲染器。

  • Center:設置遮擋入口的中心。默認值為 0,0,0。

  • Size:定義遮擋入口的大小。

7.4.2 在運行時打開和關閉遮擋入口

????????使用腳本將 Occlusion Portal 組件的Open屬性設置為所需狀態。代碼如:

void OpenDoor() {// 通過組件切換為打開狀態,這樣Unity就會渲染它后面的游戲對象。myOcclusionPortal.open = true;// 后續操作。比如:由于上面已經打開了遮擋入口,后面的對象被渲染了,這時候我們就可以在這里執行“開門動畫”。…
}

7.5 Occlusion Culling窗口

????????打開 Occlusion Culling 窗口的方法是導航到頂部菜單,然后選擇 Window > Rendering > Occlusion Culling。Occlusion Culling 窗口有 3 個選項卡:ObjectBakeVisualization。除此之外,當 Occlusion Culling 窗口和 Scene 視圖均可見時,Scene 視圖中將顯示 Occlusion Culling 彈出窗口(在右下角)。Occlusion Culling 窗口底部是 BakeClear 按鈕,單擊 Bake 按鈕可烘焙遮擋剔除數據,單擊 Clear 按鈕可刪除之前烘焙的數據。

7.5.1 Object選項卡

????????在 Object 選項卡中,可以單擊 All、Renderers 和 Occlusion Areas 按鈕以篩選 Hierarchy 窗口的內容。

????????在 Hierarchy 窗口或 Scene 視圖中選擇一個渲染器,即可在 Occlusion Culling 窗口中查看和更改渲染器的遮擋剔除設置。

????????在 Hierarchy 窗口或 Scene 視圖中選擇一個遮擋區域,然后在 Occlusion Culling 窗口中查看和更改遮擋區域的 Is View Volume 設置。

????????在 Hierarchy 窗口或 Scene 視圖中什么都不選擇時,激活Occlusion Areas按鈕,可以單擊 Create New Occlusion Area 以便在場景中創建新的遮擋區域。

7.5.2 Bake選項卡

????????在 Bake 選項卡中,可以微調遮擋剔除烘焙過程的參數。請通過配置這些設置在烘焙時間、運行時數據大小和可視化結果之間找到平衡。Set Default Parameters 按鈕可將參數重置為默認值。各屬性:

  • Smallest Occluder:可以遮擋其他游戲對象的最小游戲對象的大小(以米為單位)。通常,要使文件最小且烘焙時間最短,應選擇在場景中產生良好結果的最大值。

  • Smallest Hole: 攝像機可以看到的最小間隙的直徑(以米為單位)。通常,要使文件最小且烘焙時間最短,應選擇在場景中產生良好結果的最大值。

  • Backface Threshold:如果需要減小烘焙數據的大小,Unity 可以在烘焙時對場景進行采樣,并排除場景中可見遮擋物幾何體所含背面超過給定百分比的部分。背面百分比很高的區域可能在幾何體的下方或內部,因此不太可能是在運行時攝像機所在的某個位置,大概率相機永遠看不到。默認值 100 表示絕不會從數據中刪除區域。值越小,產生的文件就越小,但可能會導致視覺失真。【PS:背面百分比應該是指相對整體表面來說的。而且這里的背面應該是指相機看不到的面。】

????????更多配置技巧,見“遮擋剔除其他資源小節”中鏈接的文章。

7.5.3 Visualization 選項卡

????????當 Visualization 選項卡可見時,如果在 Scene 視圖或 Hierarchy 窗口選擇一個攝像機,則 Unity 將更新 Scene 視圖,從所選攝像機的視角顯示遮擋剔除的效果。可以使用 Scene 視圖中的 Occlusion Culling 彈出窗口來配置可視化設置。

7.5.4 Occlusion Culling彈出窗口

????????同時讓Scene窗口、Occlusion Culling窗口可見,即可在Scene中出現彈出窗口。

????????在烘焙數據之前,彈出窗口如下:

????????在烘焙數據之后,選中Object、Bake選項卡,依舊如上圖所示。若選中Visualization選項卡,彈出窗口如下:

實際,下拉框代表兩種模式:Edit、Visualize。其與我們上面說的選項卡選擇是同步變化的,可以直接在彈出窗口中修改,比如這里可以再修改為Edit,這時Occlusion Culling窗口就會自動切到Object選項卡,再次修改為Visualize,自動切到Visualization選項卡。

Edit 模式

  • View Volumes:啟用此選項后,Scene 視圖將包含藍線,這些藍線顯示遮擋剔除數據中的單元格。單元格大小受 Smallest Occluder 設置的影響:值越小,產生的單元格越多且越小,從而使精度提高并且文件增大。

Visualize 模式

????????此預覽遮擋剔除的結果。如果選擇了某個攝像機,則預覽與該攝像機相關。否則,預覽與我們在 Visualize 模式下選擇的最后一個攝像機相關

  • Camera Volumes:啟用此選項后,我們會看到黃線,這些黃線指示 Unity 為其生成遮擋剔除數據的場景區域。這取決于場景幾何體以及在場景中使用 Occlusion Areas 選項定義的任何視圖體積 (View Volumes)。當攝像機在黃線之外時,Unity 不會執行遮擋剔除。我們還可以看到灰線,這些灰線指示攝像機當前位置所對應的遮擋剔除數據中的單元格以及當前單元格中的細分。Smallest Hole 設置定義了單元格內細分的最小大小:值越小,每個單元格產生的細分越多且越小,從而使精度提高并且文件增大。

  • Visibility Lines:啟用此選項后,我們會看到綠線,這些綠線指示當前選擇的攝像機可以看到的內容。

  • Portals: 啟用此選項后,我們可以看到一些線代表遮擋數據中單元格之間的連接。當前可見的入口是當前所選攝像機可以看到的入口。

7.6 遮擋剔除其他資源

????????Unity 使用 Umbra 庫來執行遮擋剔除。在以下文章中可以找到有關 Umbra 的更多信息,包括有關烘焙過程、遮擋剔除數據內部的數據結構以及 Umbra 執行的運行時操作的信息:

????????Umbra 在 Gamasutra 上發表的這篇文章詳細介紹了 Umbra 的工作方式:Next Generation Occlusion Culling by Umbra。

????????此 Unity 博客系列包含有關優化 Umbra 的指南。這個系列與 Unity 的舊版本相關,此后用戶界面發生了變化,但核心原理仍然相同:

  • Occlusion culling in Unity: the basics

  • Occlusion culling in Unity: best practices

  • Occlusion culling in Unity: troubleshooting

8 CollingGroup API

????????CullingGroup 提供一種將我們自己的系統融合到 Unity 剔除和 LOD 管線中的方法。這可用于許多目的;例如:

  • 模擬一群人,同時只為現在實際可見的角色提供完整的游戲對象

  • 構建由 Graphics.DrawProcedural 驅動的 GPU 粒子系統,但是跳過對墻背后的粒子系統的渲染

  • 跟蹤不在攝像機視野范圍內的生成點,以便在生成敵人時不讓玩家看到他們“彈入”視圖

  • 將角色從近處的全質量動畫和 AI 計算切換到遠處更低質量、更低成本的行為

  • 在場景中設置 10,000 個標記點,并在玩家進入其中任何標記點的 1m 范圍內時有效發現這一狀態

API 的工作原理是讓我們提供一系列包圍球體。然后計算這些球體相對于特定攝像機的可見性,以及可視為 LOD 級別號的“距離帶”值。

PS:注意,這里的距離等級使我們給定閾值,然后計算距離在閾值之間哪個范圍,給出距離等級。不是LOD組件。需要使用SetBoundingDistances、SetDistanceReferencePoint函數實現。

8.1 如何使用

8.1.1 創建與釋放 Group

????????沒有用于處理 CullingGroup 的組件或可視化工具,只能通過腳本訪問它們。可使用“new”運算符來構造 CullingGroup:

CullingGroup group = new CullingGroup();

要讓CullingGroup執行能見度 和/或 距離計算,請指定它應該使用的相機:

group.targetCamera = Camera.main;

使用球體的位置和半徑來創建并填充 BoundingSphere 結構數組,并將其與實際位于數組中的球體數一起傳遞給 SetBoundingSpheres。球體的數量不需要與數組的長度相同。Unity 建議創建一個足夠大的數組來保存我們一次擁有的最多球體(即使我們最初在數組中實際擁有的球體數量非常少)。如果使用較大的數組,我們可以根據需要添加或刪除球體,而無需在運行時進行調整數組大小這樣計算成本高昂的過程。

BoundingSphere[] spheres = new BoundingSphere[1000];//數組
spheres[0] = new BoundingSphere(Vector3.zero, 1f);//添加球體
group.SetBoundingSpheres(spheres);//設置給Group
group.SetBoundingSphereCount(1);//設置數量

此時,CullingGroup 將開始計算每幀單個球體的可見性。

要清理 CullingGroup 并釋放它使用的所有內存,請通過標準的 .NET IDisposable 機制來處置 CullingGroup:

group.Dispose();
group = null;

PS:務必執行釋放操作。測試中發現,若不執行釋放操作,即使停止運行了,回調函數仍然可以在控制臺輸出內容。

8.1.2 通過 onStateChanged 回調來接收結果

????????為響應球體而更改其可見性或距離狀態的最有效方法是使用 onStateChanged 回調字段。將其設置為一個函數,該函數以 CullingGroupEvent 結構作為參數。對于已改變狀態的每個球體,將在剔除完成后調用此函數。CullingGroupEvent 結構的成員會告訴我們球體的先前狀態和新狀態。(PS:變化后觸發,根據參數判斷是如何變化的。)

group.onStateChanged = StateChangedMethod;private void StateChangedMethod(CullingGroupEvent evt)
{if(evt.hasBecomeVisible)//判斷是否變為可見Debug.LogFormat("Sphere {0} has become visible!", evt.index);if(evt.hasBecomeInvisible)//判斷是否變為不可見Debug.LogFormat("Sphere {0} has become invisible!", evt.index);
}

PS:距離等級的變化也會觸發。

8.1.3 通過 CullingGroup 查詢 API 來接收結果

????????除了 onStateChanged 委托之外,CullingGroup 還提供一個 API,用于檢索包圍球體數組中任何球體的最新可見性和距離結果。要檢查單個球體的狀態,請使用 IsVisible 和 GetDistance 方法:

bool sphereIsVisible = group.IsVisible(0);
int sphereDistanceBand = group.GetDistance(0);

????????要檢查多個球體的狀態,可使用 QueryIndices 方法。此方法將掃描連續范圍的球體以查找與指定可見性或距離狀態相匹配的球體。

// 分配一個數組來保存生成的球體索引 - 數組的大小決定每次調用檢查的最大球體數
int[] resultIndices = new int[1000];
// 還要設置一個 int 來存儲已放入數組的實際結果數
int numResults = 0;// 查找所有可見的球體
numResults = group.QueryIndices(true, resultIndices, 0);
// 查找位于距離帶 1 中的所有球體
numResults = group.QueryIndices(1, resultIndices, 0);
// 查找隱藏在距離帶 2 中的所有球體,跳過前 100 個球體
numResults = group.QueryIndices(false, 2, resultIndices, 100);

請記住,僅在 CullingGroup 使用的攝像機實際執行剔除時,才更新查詢 API 檢索的信息。(PS:距離等級變化,API查詢內容也會更新)

8.2 實踐考慮

????????在考慮如何將 CullingGroup 應用于項目時,請考慮 CullingGroup 設計的以下方面。

8.2.1 利用可見性

????????CullingGroup 為其計算可見性的所有體積都由包圍球體定義;實際上,由位置(球體中心)和半徑值定義。出于性能原因,不支持其他包圍形狀。在實踐中,這意味著我們將定義一個球體來完全包圍希望剔除的對象。如果需要更緊密擬合,請考慮使用多個球體來覆蓋對象的不同部分,并根據所有球體的可見性狀態做出決定。

????????為了評估可見性,CullingGroup 需要知道應該從哪個攝像機可見性開始計算。目前,單個 CullingGroup 僅支持單個攝像機。如果需要評估多個攝像機的可見性,應為每個攝像機使用一個 CullingGroup 并合并結果(PS:我的理解是將多個結果進行綜合考慮)。

????????CullingGroup 將僅基于視錐體剔除和靜態遮擋剔除來計算可見性。它不會將動態對象視為潛在遮擋物。

8.2.2 利用距離

????????CullingGroup 能夠計算某個參考點(例如,攝像機或玩家的位置)與每個球體上最近點之間的距離。此距離值不會直接提供給我們,而是使用我們提供的一組閾值來量化,以便計算離散的“距離帶”整數結果。目的是將這些距離帶解讀為“近距離”、“中距離”、“遠距離”等。

????????一個對象從一個區域移到另一個區域時,CullingGroup 將提供回調,讓我們有機會進行某些操作,例如我們該對象的行為更改為 CPU 使用強度較低的操作。(還是onStateChanged回調)

????????超出最后一個距離的任何球體都將被視為不可見,這使我們可以輕松構建一個剔除實現來完全停用非常遠的對象。如果不想要此行為,只需將最終閾值設置為無限遠的距離。【沒找到API有提供這種隱藏,可能是指讓用戶自己實現這種遠距離剔除效果】

????????每個 CullingGroup 僅支持一個參考點。

????????PS:關于距離部分需要額外的函數設置,如SetBoundingDistances、SetDistanceReferencePoint,不過官方并未提供相關示例代碼。

8.2.3 可視化、距離等級測試

????????由于官方沒有距離等級的示例代碼,這里就做了下整體實驗:

????????首先是創建了兩個球,與相機的分布:

代碼中在兩個球的位置分別創建了球體,半徑為1,0下標球體在原點處,1下標球體在左側那個。

代碼如下:

using UnityEngine;public class test1 : MonoBehaviour
{//組CullingGroup group;void Start(){//創建組group = new CullingGroup();//設置相機group.targetCamera = Camera.main;//創建球體數組,放入兩個球體,設置給組BoundingSphere[] spheres = new BoundingSphere[1000];//數組spheres[0] = new BoundingSphere(Vector3.zero, 1f);//添加球體spheres[1] = new BoundingSphere(new Vector3(-10f, 0f, -5f), 1f);//添加球體group.SetBoundingSpheres(spheres);//設置給Groupgroup.SetBoundingSphereCount(2);//設置數量//設置相機為距離參考點group.SetDistanceReferencePoint(Camera.main.transform);//設置距離閾值,比如這里設置了4個閾值,產生了5個距離等級:0-3,3-6,6-10,10-15,15-group.SetBoundingDistances(new float[] { 3, 6, 10, 15 });//綁定回調函數group.onStateChanged = StateChangedMethod;}private void Update(){//0號球體距離參考點(相機)的距離等級Debug.Log("距離:" + group.GetDistance(0));}private void OnDestroy(){//釋放組group.Dispose();group = null;}/// <summary>/// 回調函數。/// </summary>/// <param name="evt"></param>private void StateChangedMethod(CullingGroupEvent evt){Debug.Log("回調執行!");if (evt.hasBecomeVisible)//判斷是否變為可見Debug.LogFormat("Sphere {0} has become visible!", evt.index);if (evt.hasBecomeInvisible)//判斷是否變為不可見Debug.LogFormat("Sphere {0} has become invisible!", evt.index);}
}

先看“可視化”觸發回調函數的情況:

可以看到,運行時先觸發了一次“距離回調”和“可視化回調”,可視化回調觸發是因為看到了,若相機默認視野內無球體,則運行時是不會觸發的。后續旋轉視野,分別觸發了兩次“可視化回調”,球體0不可視,以及球體0可視。

然后是距離測試:

距離等級有5個,則數值分別是:0,1,2,3,4。直接看相機的Z軸數值變化,根據值所處閾值的間隔,打印不同的距離等級,且變化距離等級時,會觸發回調函數,起始1一次,后續4次變化,總共執行回調5次,不過圖中是6次,因為中途觸發了一次旁邊小球可視化的回調。需要注意,距離是球體到參考點的最近距離,即,這里用與判斷在那個閾值間隔內的距離不是參考點到球體中心的距離,而是到球體表明的距離,要考慮半徑(代碼里設置是1)。比如圖中,初始相機z為-3,即距離為2,所以在0級別;按照閾值,當距離超過3后就會變為1級別,那么考慮到半徑,這里變為1級別時,相機z軸應大于-4(考慮符號是小于-4),圖中也的確是這樣,可觀察圖中z值變化以及等級變化。

????????至此實驗結束,通過這些我們也就能應用可見性與距離了。關于距離的API我就找到這兩個,應該是沒有其他的了。

8.2.4 性能和設計

????????CullingGroup API 不允許我們對場景進行更改后立即請求包圍球體的新可見性狀態。出于性能原因,CullingGroup 僅在執行整個攝像機剔除期間計算新的可見性信息;此時,我們可以通過回調或 CullingGroup 查詢 API 來獲取信息。實際上,這意味著我們應該以異步方式處理 CullingGroup。

????????提供給 CullingGroup 的包圍球體數組將由 CullingGroup 引用,而不是復制。這意味著我們應該保留對傳遞給 SetBoundingSpheres 的數組的引用,并可修改此數組的內容(因為CullingGroup 沒有提供訪問數組的變量,所以我們需要保留原來的引用,方便修改),而無需再次調用 SetBoundingSpheres。如果需要多個 CullingGroup 來計算同一組球體的可見性和距離(例如,對于多個攝像機),那么讓所有 CullingGroup 共享相同的包圍球體數組實例會很高效。

9 動態分辨率

????????動態分辨率是一種攝像機設置,允許動態縮放單個渲染目標,以便減少 GPU 上的工作負載。在應用程序的幀率降低的情況下,可以逐漸縮小分辨率來保持幀率穩定。如果性能數據表明由于應用程序受 GPU 限制而導致幀率即將降低,則 Unity 會觸發此縮放。還可以手動觸發縮放,方法是搶占應用程序中 GPU 資源消耗特別高的部分的優先級,并通過腳本控制縮放。如果縮放逐漸進行,動態分辨率幾乎不可察覺。

9.1 渲染管線兼容性

????????動態分辨率支持取決于項目使用的渲染管線( render pipeline)。

功能內置渲染管線通用渲染管線 (URP)高清渲染管線 (HDRP)
動態分辨率是 (1)是 (1)是 (2)

注意:

  1. 內置渲染管道和通用渲染管道(URP)都支持本文檔中描述的動態分辨率(即本節所描述的)。

  2. 高清渲染管線(High Definition Render Pipeline, HDRP)支持動態分辨率,但我們可以以不同的方式啟用和使用它。有關HDRP中的動態分辨率的信息,請參見Dynamic resoluton in HDRP。

9.2 支持的平臺

????????Unity在iOS, macOS和tvOS(僅限Metal), Android(僅限Vulkan), Windows Standalone(僅限DirectX 12)和UWP(僅限DirectX 12)上支持動態分辨率。

9.3 對渲染目標的影響

????????使用動態分辨率,Unity 不會重新分配渲染目標。從概念上講,Unity 會縮放渲染目標;但實際上,Unity 使用鋸齒,而且縮小的渲染目標僅使用原始渲染目標的一小部分。Unity以完整的分辨率分配渲染目標,然后動態分辨率系統將它們縮小并再次放大(【官方翻譯是“備份”?】),使用原始目標的一部分而不是重新分配新目標。

9.4 縮放渲染目標

????????使用動態分辨率時,渲染目標具有 DynamicallyScalable 標志。可通過設置此標志來指明 Unity 是否應該將此渲染紋理作為動態分辨率過程的一部分進行縮放。攝像機還具有 allowDynamicResolution 標志(組件上也可以設置),可以用它來設置動態分辨率,這樣就不需要覆蓋渲染目標,如果我們只是想應用動態分辨率到一個不太復雜的場景。(PS:說的應該是設置相機標識,就不用設置目標的標識了,但考慮性能問題,這種使用方法一般只在不太復雜的場景使用。)

9.5 MRT 緩沖區

????????在 Camera 組件上啟用 Allow Dynamic Resolution 時,Unity 會縮放該攝像機的所有目標。

9.6 控制縮放

????????可以通過 ScalableBufferManager?控制縮放。借助 ScalableBufferManager 可以控制已標記為由動態分辨率系統進行縮放的所有渲染目標的動態寬度和高度縮放。

????????例如,假設應用程序以理想的幀率運行,但在某些情況下,由于粒子增多、后期效果和屏幕復雜性等多重因素的影響,GPU 性能會下降。Unity FrameTimingManager 可以檢測 CPU 或 GPU 性能何時開始下降。因此,可使用 FrameTimingManager 計算新的所需寬度和高度縮放以將幀率保持在所需范圍內,并將縮放降低到該值以保持性能穩定(立即進行或在設定的幀數內逐漸進行)。當屏幕復雜度降低并且 GPU 性能穩定時,可將寬度和高度縮放提高到我們計算過的 GPU 可以處理的值。

9.7 示例

????????這個示例腳本演示了API的基本用法。將其添加到場景中的相機身上,并在相機設置中勾選 Allow Dynamic Resolution。我們還需要打開 Player 設置(菜單:Edit > Project settings,然后選擇Player類別),并勾選 Enable Frame Timing Stats 復選框。有關Enable FrameTiming Stats屬性背后的功能的更多信息,請參閱 FrameTimingManager。

????????單擊鼠標或用一根手指點擊屏幕,分別將高度和寬度分辨率降低 scaleWidthIncrementscaleHeightIncrement 變量中的大小。用兩根手指點擊會以相同的值提高分辨率。

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;// 跨幀保持的動態分辨率算法變量uint m_frameCount = 0;const uint kNumFrameTimings = 2;double m_gpuFrameTime;double m_cpuFrameTime;// 使用此函數進行初始化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);}// 每幀調用一次 Updatevoid Update(){float oldWidthScale = m_widthScale;float oldHeightScale = m_heightScale;// 一根手指降低分辨率if (Input.GetButtonDown("Fire1")){m_heightScale = Mathf.Max(minResolutionHeightScale, m_heightScale - scaleHeightIncrement);m_widthScale = Mathf.Max(minResolutionWidthScale, m_widthScale - scaleWidthIncrement);}// 兩根手指提高分辨率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);}// 估算下一幀時間并在必要時更新分辨率縮放。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;}
}

PS:測試沒效果?【先放著

9.8 縮放發生時的控制

????????ScalableBufferManager.ResizeBuffers函數在調用時立即縮放渲染紋理。但是,我們可以通過使用DynamicallyScalableExplicit標志來修改此行為。當我們調用RenderTexture.ApplyDynamicScale時,標記為DynamicallyScalableExplicit的渲染紋理會縮放,取代調用ScalableBufferManager.ResizeBuffers時的自動縮放。縮放會導致渲染紋理內容無效,所以我們必須使用DynamicallyScalableExplicitRenderTexture.ApplyDynamicScale來確保渲染紋理數據在縮放因子改變時仍然存在。

????????例如,時間抗鋸齒通過重用前一幀的數據來提高當前幀的視覺質量。如果動態分辨率比例因子在幀之間發生變化,則需要保留前一幀的數據。我們可以通過使用DynamicallyScalableExplicit標記包含這些數據的渲染紋理來實現這一點,允許它們在調用ScalableBufferManager.ResizeBuffers之后仍然有效。我們只需要使用RenderTexture.ApplyDynamicScale調整當前幀的渲染紋理大小,確保前一幀的渲染紋理在采樣時仍然有效。(PS:最后我感覺是提供了兩種思路,一種是把前一幀標記flag,然后調用ResizeBuffers進行縮放,由于flag的存在則進而保留前一幀數據。第二種是標記當前幀flag,然后調用ApplyDynamicScale來縮放當前幀,進而保留了前一幀數據。)

9.9 其他

  • FrameTimingManager API reference.

  • FrameTimingManager User Manual documentation.

  • ScalableBufferManager API reference.

  • RenderTexture API reference.

9.10 FrameTimingManager

????????FrameTimingManager是一個API,它捕獲應用程序中單個幀期間有關性能的詳細定時數據。我們可以使用這些數據來評估框架,以了解應用程序無法滿足性能目標的原因。

在以下情況,使用FrameTimingManager:

  • 我們需要逐幀調試。

  • 我們希望使用動態分辨率特性。

  • 我們需要使用自適應性能(Adaptive Performance)包。

????????Frame timings不會取代來自分析器(Profiler)的數據,在高級剖析應用程序之后(借助分析器),使用FrameTimingManager來調查特定的細節。FrameTimingManager在記錄數據時會降低性能,因此它無法準確測量應用程序的執行情況。

9.10.1 怎樣啟用FrameTimingManager

????????注意:FrameTimingManager對于Development Player 構建總是處于激活狀態。

????????在發布版本和Unity編輯器中啟用FrameTimingManage:

  1. 前往 Edit > Project Settings > Player.

  2. Other Settings 中,導航到 Rendering 標題.

  3. 啟用 Frame Timing Stats 屬性。

如果我們使用OpenGL平臺,我們還需要啟用OpenGL: Profiler GPU Recorders屬性來測量GPU使用情況。屬性位置在前面動態分辨率那里有截圖,兩個屬性都是在一起的。

????????注意:在Unity版本2021.2及更早的版本中,OpenGL Profiler GPU Recorder禁用Frame Timing Stats屬性,因此我們不能同時使用它們。

9.10.2 如何使用FrameTimingManager

????????要訪問FrameTimingManager記錄的數據,使用以下方法之一。

9.10.2.1 View frame time data with a Custom Profiler module

????????查看自定義分析器模塊中的幀時間數據:

  1. 根據Creating a custom profiler module的說明創建自定義分析器模塊。

  2. 在“分析器模塊編輯器”窗口中,選擇自定義模塊。

  3. 在Available Counters面板中,選擇Unity。

  4. 選擇Render以打開包含與內存使用相關的分析器計數器的子菜單,其中包括Frame Timing Stats屬性所啟用的那些計數器。然后可以單擊子菜單中的相關計數器,將它們添加到自定義模塊中。

下表描述了在啟用Frame Timing Stats時,變為可用的每個計數器的目的:

  • CPU Total Frame Time (ms):CPU總幀時間,以毫秒為單位。Unity將其計算為兩幀結束之間的時間,包括任何開銷或幀之間等待的時間。

  • CPU Main Thread Frame Time (ms):幀開始到主線程完成幀內執行的工作之間的時間,以毫秒為單位。

  • CPU Main Thread Present Wait Time (ms):在幀期間等待Present()所花費的CPU時間。

  • CPU Render Thread Frame Time (ms):渲染線程開始工作到Unity調用Present()函數之間的時間,以毫秒為單位。

  • GPU Frame Time (ms):GPU渲染單幀時開始和結束的時間差,以毫秒為單位。

9.10.2.2 Retrieve timestamp data from the FrameTimingManager C# API

????????使用FrameTimingManager API訪問時間戳信息。在每個變量中,FrameTimingManager記錄幀期間特定事件發生的時間。下面顯示了API中可用的值,按照Unity在一個幀中執行它們的順序:

  • frameStartTimestamp:幀開始時的CPU時鐘時間。

  • firstSubmitTimestamp:Unity在此幀期間向GPU提交第一個作業時的CPU時鐘時間。

  • cpuTimePresentCalled:Unity為當前幀調用Present()函數時的CPU時鐘時間。

  • cpuTimeFrameComplete:GPU完成渲染幀并中斷CPU的CPU時鐘時間。

9.10.2.3 Record data with specific profiler counters

????????我們可以使用ProfilerRecorder API而不是FrameTimingManager c# API來讀取FrameTimingManager的值。這樣做的好處是,當我們使用ProfilerRecorder API時,FrameTimingManager僅在將記錄器附加到特定計數器時記錄值。這種行為使我們能夠控制哪些計數器收集數據,從而減少FrameTimingManager對性能的影響。

????????下面的例子展示了如何使用ProfilerRecord API只跟蹤CPU主線程幀時間變量:

using Unity.Profiling;using UnityEngine;public class ExampleScript : MonoBehaviour{string statsText;ProfilerRecorder mainThreadTimeRecorder;void OnEnable(){mainThreadTimeRecorder = ProfilerRecorder.StartNew(ProfilerCategory.Internal, "CPU Main Thread Frame Time");}void OnDisable(){mainThreadTimeRecorder.Dispose();}void Update(){var frameTime = mainThreadTimeRecorder.LastValue;// Your code logic here}
}

9.10.3 FrameTimingManager如何工作

????????FrameTimingManager提供的結果帶有四幀延遲。這是因為計時結果不能在每幀結束時立即可用,所以FrameTimingManager等待獲取該幀的CPU和GPU數據。

????????延遲不能保證精確的計時結果,因為GPU可能沒有任何可用的資源來返回結果,或者可能無法正確返回結果。

????????在某些情況下,FrameTimingManger改變了它產生FrameTimeComplete時間戳的方式:

  • 如果GPU支持GPU時間戳,GPU會提供一個FrameTimeComplete時間戳。

  • 如果GPU不支持GPU時間戳并返回GPU時間,則FrameTimingManager計算gpuFrameTime的值。該值為上報的GPU Time與FirstSubmitTimestamp值之和。

  • 如果GPU不支持GPU時間戳并且不返回GPU時間,FrameTimingManager將PresentTimestamp的值設置為FrameTimeComplete的值。

基于tile的延遲渲染GPU,其可能不準確

????????對于使用基于tile延遲渲染架構的GPU,例如Apple設備中的Metal GPU,報告的GPU Time可能大于報告的幀時間。

????????這可能發生在GPU處于高負載下,或者GPU管道已滿時。在這些情況下,GPU可能會延遲某些渲染階段的執行。因為FrameTimingManager測量幀渲染開始和結束之間的時間,階段之間的任何間隙都會增加報告的GPU時間。

????????在下面的示例中,沒有可用的GPU資源,因為GPU將作業從頂點隊列傳遞到片段隊列。GPU的圖形API因此延遲了下一階段的執行。當這種情況發生時,GPU時間測量包括各階段工作時間和階段間的任何間隙。結果是FrameTimingManager報告的GPU時間測量值比預期的要高。

????????下面圖表顯示了在Metal API中,報告的GPU時間差異是如何發生的:

9.10.4 平臺支持

屬性描述Supported注釋
WindowsDirectX 11
DirectX 12
OpenGL
Vulkan
macOSMetal由于基于tile的延遲渲染GPU的行為,可能會報告比總幀時間更大的GPU時間測量。
LinuxOpenGL部分支持不支持GPU時間測量。
Vulkan
AndroidOpenGL ES
Vulkan
iOSMetal由于基于tile的延遲渲染GPU的行為,可能會報告比總幀時間更大的GPU時間測量。
tvOSMetal由于基于tile的延遲渲染GPU的行為,可能會報告比總幀時間更大的GPU時間測量。
WebGLWebGL部分支持不支持GPU時間測量。

9.10.5 其他資源

  • Dynamic resolution

  • FrameTiming API

  • FrameTimingManager API

  • Profiler overview

10 深度學習超級采樣

????????NVIDIA深度學習超級采樣(DLSS)是一種使用人工智能來提高圖形性能和質量的渲染技術。我們可以用它來:

  • 以高幀率和分辨率運行實時光線追蹤世界。

  • 為柵格化圖形提供性能和質量提升。這對虛擬現實應用程序特別有用,可以幫助它們以更高的幀速率運行。這有助于消除在低幀率下出現的迷失方向、惡心和其他負面影響。

有關DLSS的更多信息,請參閱 NVIDIA documentation。

10.1 需求和兼容性

????????本節包括有關DLSS的硬件要求和渲染管線兼容性的信息。

????????有關硬件和驅動程序要求的信息,請參閱 NVIDIA documentation。

10.1.1 渲染管線兼容性

功能內置渲染管線通用渲染管線 (URP)高清渲染管線 (HDRP)Scriptable Render Pipeline (SRP)
DLSSNo (1)No (1)No (1)

注意:

  1. 底層DLSS框架代碼位于核心Unity中,但HDRP是唯一使用它來實現DLSS的渲染管道。有關更多信息,請參閱 API documentation。

10.1.2 支持的平臺

????????Unity在以下平臺上支持DLSS:

  • DirectX 11在Windows 64位。

  • DirectX 12在Windows 64位。

  • Vulkan在Windows 64位。

????????Unity不支持Metal、Linux、使用x86架構的Windows (Win32)或任何其他平臺的DLSS。要為Windows構建項目,請使用x86_64體系結構(Win64)。

10.2 使用DLSS

????????關于如何在HDRP中使用DLSS的信息,請參閱 Deep learning super sampling in HDRP。

????????如圖,在沒有DLSS的情況下,飛船演示項目以50%的屏幕百分比渲染,其圖像模糊,尤其是周圍有火花的視覺效果。

如圖,飛船演示項目以50%的屏幕百分比渲染,但使用DLSS。這張圖像比之前的圖像不那么模糊,尤其是周圍的火花視覺效果。

質量模式

????????可用的質量模式如下:

  • Auto:選擇當前輸出分辨率的最佳DLSS模式。

  • Balance:平衡性能與圖像質量。

  • Quality:強調圖像質量勝過性能。

  • Performance:強調性能和圖像質量。【為什么是“和”?】

  • Ultra Performance:產生最高的性能和最低的圖像質量。此模式僅適用于8k分辨率(7680 x 4320像素)。

11 多顯示

????????使用多顯示功能可以同時在最多八臺不同的監視器上顯示應用程序的最多八個不同攝像機視圖。此功能可用于 PC 游戲、街機游戲機或公共顯示裝置等設施。

Unity 在以下平臺上支持多顯示功能:

  • 桌面平臺 (Windows, macOS X, and Linux)

  • Android(OpenGL ES 和 Vulkan)

  • iOS

有些功能只適用于某些平臺。有關兼容性的更多信息,請參閱 Display,Screen 和 FullScreenMode API。

11.1 激活多顯示功能

????????Unity 的默認顯示模式僅為一臺顯示器。在運行應用程序時,需要使用 Display.Activate() 顯式激活其他顯示器的顯示,激活的顯示不能停用。

????????激活額外顯示的最佳時間是在應用程序創建新場景時。一個好方法是將腳本組件附加到默認攝像機,確保僅在啟動過程中調用一次 Display.Activate()

示例腳本

using UnityEngine;
using System.Collections;public class ActivateAllDisplays : MonoBehaviour
{void Start (){Debug.Log ("displays connected: " + Display.displays.Length);// Display.displays[0] 是主要的默認顯示,并始終為 ON,因此從索引 1 開始。// 檢查是否有其他可用的顯示,并激活每個顯示。for (int i = 1; i < Display.displays.Length; i++){Display.displays[i].Activate();}}void Update(){}
}

11.2 在項目中進行多顯示預覽

????????如果場景中有兩個相機。要在項目中預覽不同的攝像機視圖,請按照以下步驟操作:

????????為每個相機設置所屬Display,如圖:

然后再Game視圖中,選擇對應的Display,如圖:

11.3 支持的API

????????Unity 支持以下 UnityEngine.Display API 方法:

  • public void Activate(): 根據當前監視器的寬度和高度激活具體顯示。必須在啟動新場景時進行一次此調用。可從新場景中的攝像機或虛擬游戲對象附加的用戶腳本進行此調用。

  • public void Activate(int width, int height, int refreshRate):僅限 Windows。激活自定義寬度和高度的特定顯示。在 Linux 和 macOS X 上,輔助顯示上始終使用當前顯示分辨率(如果可用)。

12 相機組件

????????攝像機是為玩家捕捉和展示世界的設備。通過自定義和操縱攝像機,我們可以讓自己的游戲呈現出真正的獨特性。在場景中可擁有無限數量的攝像機。這些攝像機可設置為以任何順序在屏幕上任何位置或僅在屏幕的某些部分進行渲染。

12.1 相機Inspector面板參考

根據項目使用的渲染管線,Unity 在 Camera Inspector 中顯示不同的屬性。

  • 如果我們的項目使用通用渲染管線 (URP),請參閱 URP 包文檔微型網站。

  • 如果我們的項目使用高清渲染管線 (HDRP),請參閱 HDRP 包文檔微型網站。

  • 如果我們的項目使用內置渲染管線,Unity 會顯示以下屬性:

  • Clear Flags:確定將清除屏幕的哪些部分。使用多個攝像機來繪制不同游戲元素時,這會很方便。

  • Background:在繪制視圖中的所有元素之后但沒有天空盒的情況下,應用于剩余屏幕部分的顏色。

  • Culling Mask:由攝像機渲染的對象層。在檢視面板中將層分配到對象。

  • Projection:切換攝像機模擬透視的功能。

    • Perspective:以完整透視角度渲染對象。

    • Orthographic:均勻渲染對象,沒有透視感。注意:在正交模式下不支持延遲渲染。始終使用前向渲染。

  • Size:攝像機的視口大小。選為Orthographic模式時,將出現此屬性。正交下視椎體變為矩形體,改變此值會改變矩形體視野大小面的尺寸,不影響視野距離。可以選中相機改變此值,在Scene中觀察視野區域變化。

  • FOV Axis:選為Perspective模式時,將出現此屬性。視野軸。

    • Horizontal:攝像機使用水平視野軸。

    • Vertical: 攝像機使用垂直視野軸。

  • Field of view:選為Perspective模式時,將出現此屬性。攝像機的視角,以沿著FOV Axis下拉框中指定的軸的度數為單位來測量。

  • Physical Camera:啟用此屬性后,Unity 將使用模擬真實攝像機屬性的屬性(Focal Length、Sensor Size 和 Lens Shift)計算 Field of View。

    • ISO:傳感器靈敏度(ISO)。

    • Shutter Speed:曝光時間,以秒為單位。

    • Aperture:光圈數,以f-stop為單位。

    • FocusDistance:鏡頭的焦距。如果我們將focusDistanceMode設置為FocusDistanceMode.Camera,則Fieldvolume的深度將覆蓋使用此值。

    • Blade Count:光圈葉片(Diaphragm blades)的數量。

    • Curvature:將孔徑范圍映射到葉片曲率。

    • Barrel Clipping:“貓眼”效應對散焦(光學漸暈)的影響強度。

    • Anamorphism:拉伸傳感器以模擬變形外觀。正值將垂直扭曲相機,負值將水平扭曲相機。

    • Focal Length:設置攝像機傳感器和攝像機鏡頭之間的距離(以毫米為單位)。較小的值產生更寬的 Field of View,反之亦然。更改此值時,Unity 會相應自動更新 Field of View 屬性。

    • Sensor Type:指定希望攝像機模擬的真實攝像機格式。從列表中選擇所需的格式。選擇攝像機格式時,Unity 會自動將 Sensor Size > X 和 Y 屬性設置為正確的值。如果手動更改 Sensor Size 值,Unity 會自動將此屬性設置為 Custom。

    • Sensor Size:設置攝像機傳感器的大小(以毫米為單位)。選擇 Sensor Type 時,Unity 會自動設置 X 和 Y 值。如果需要,可以輸入自定義值。X:傳感器的寬度。Y:傳感器的高度。

    • Lens Shift:從中心水平或垂直移動鏡頭。值是傳感器大小的倍數;例如,在 X 軸上平移 0.5 將使傳感器偏移其水平大小的一半。可使用鏡頭移位來校正攝像機與拍攝對象成一定角度時發生的失真(例如,平行線會聚)。沿任一軸移動鏡頭均可使攝像機視錐體傾斜。X:傳感器水平偏移。Y:傳感器垂直偏移。

    • Gate Fit:具體見“4 使用物理相機”小節。

  • Clipping Planes:開始和停止渲染位置到攝像機的距離。(近遠裁剪面)

    • Near:相對于攝像機的最近繪制點。(近裁剪面)

    • Far: 相對于攝像機的最遠繪制點。(遠裁剪面)

  • Viewport Rect:通過四個值的指示,將在屏幕上繪制此攝像機視圖的位置。值范圍為 0–1。X: 繪制攝像機視圖的起始水平位置。Y:繪制攝像機視圖的起始垂直位置。W:屏幕上攝像機輸出的寬度。H:屏幕上攝像機輸出的高度。

  • Depth:深度。攝像機在繪制順序中的位置。具有更大值的攝像機將繪制在具有更小值的攝像機之上。

  • Rendering Path:定義攝像機將使用的渲染方法。

    • Use Graphics Settings:使用Graphics Settings中指定的渲染路徑 (Rendering Path)。

    • Forward:每種材質采用一個通道渲染所有對象。

    • Deferred:繪制所有對象一次,不帶照明,然后在渲染隊列結束時繪制所有對象的照明。

    • Legacy Vertex Lit:此攝像機渲染的所有對象都將渲染為頂點光照對象。

  • Target Texture:引用將包含攝像機視圖輸出的渲染紋理。設置此引用將禁用此攝像機的渲染到屏幕功能。

  • Occlusion Culling:為此攝像機啟用遮擋剔除 (Occlusion Culling)。遮擋剔除意味著隱藏在其他對象后面的對象不會被渲染,例如,如果對象在墻后面。具體使用更復雜些,具體見“7 遮擋剔除”小節。

  • HDR:為此攝像機啟用高動態范圍渲染。請參閱高動態范圍渲染以了解詳細信息。

  • MSAA:為此攝像機啟用多重采樣抗鋸齒。

  • Allow Dynamic Resolution:為此攝像機啟用動態分辨率渲染。請參閱“9 動態分辨率”小節以了解詳細信息。

  • Target Display:定義要渲染到的外部設備。值為 1 到 8 之間。

12.2 詳細信息

????????為了向玩家顯示游戲,攝像機至關重要。可對攝像機進行自定義、為其編寫腳本或對其進行管控以實現任何可想象的效果。對于拼圖游戲,攝像機可保持靜態以獲得拼圖的完整視圖。對于第一人稱射擊游戲,可讓攝像機跟隨玩家角色,并將其置于角色的視線水平。對于賽車游戲,可能希望讓攝像機跟隨玩家的車輛。

????????可創建多個攝像機,并將每個攝像機分配給不同的深度 (Depth)。按照從低深度到高深度的順序繪制攝像機。換言之,深度為 2 的攝像機將繪制在深度為 1 的攝像機之上。我們可以調整Viewport Rect屬性的值,從而調整屏幕上攝像機視圖的大小和位置。這樣就能創建多種迷你視圖,如導彈攝像機、地圖視圖、后視鏡等。

12.2.1 Render path

????????Unity 支持不同的渲染路徑。我們應該根據自己的游戲內容和目標平臺/硬件而選擇使用哪一個渲染路徑。不同的渲染路徑具有不同的功能和性能特征,主要影響光照和陰影。 在 Player 設置中選擇項目使用的渲染路徑。此外,可針對每個攝像機重寫渲染路徑。

????????有關渲染路徑的更多信息,請查看渲染路徑頁面。

12.2.2 Clear Flags

????????每個攝像機在渲染其視圖時都會存儲顏色和深度信息。使用多個攝像機時,每個攝像機都會在緩沖區中存儲自己的顏色和深度信息,隨著每個攝像機渲染而累積越來越多數據。場景中的任何特定攝像機渲染其視圖時,我們可以設置 Clear Flags 來清除不同的緩沖區信息集合。為此,請選擇以下四個選項之一:

Skybox

????????這是默認設置。屏幕的任何空白部分將顯示當前相機的天空框。如果當前相機沒有設置天空盒,它將默認為在Lighting窗口中選擇的天空盒(Window→Rendering→Lightings),然后它將回落到背景色。另外,可以將 Skybox component 添加到相機上,具體可看鏈接中的內容。

Solid color

????????屏幕的任何空白部分都將顯示當前攝像機的背景顏色(相機組件上的一個屬性)。

Depth only

????????只清除深度信息。(PS:“渲染過的像素如果沒有需要渲染的物體,則會保持原來的顏色值,否則會渲染新的物體顏色值,不論這個點之前是什么(因為深度值被 Clear)”,這是我找到的解釋,可以幫助理解下。)

????????如果要繪制玩家的槍支而不使其陷入環境中,請設置一個深度 (Depth) 為 0 的攝像機來繪制環境,并設置另一個深度為 1 的攝像機來單獨繪制武器。將武器攝像機的 Clear Flags 設置為 Depth Only。這樣槍支就永遠在環境最前方了,槍支之外的空白部分則是0深度相機所繪制的環境。

????????可以簡單理解為適用于多相機的情況,可讓設置一個低深度的相機,以及一個高深度相機,并把高深度相機設置為此模式來渲染個別對象,就可以實現讓低深度相機作為背景,高深度相機渲染對象在最上層。若只有一個相機,那么會出現于下面Don't clear類似的涂抹效果,因為會保持原來的顏色值嘛,多個相機的話也保持,只不過保持的是低深度相機所渲染的背景。

Don’t clear

????????此模式不會清除顏色或深度緩沖區,結果是將每幀繪制在下一幀之上,從而產生涂抹效果。此模式通常不用于游戲,更可能與自定義著色器一起使用。

????????請注意,在某些 GPU(主要是移動端 GPU)上,不清除屏幕可能會導致其內容在下一幀中未定義。在某些系統上,屏幕可能包含前一幀圖像、純黑色屏幕或隨機有色像素。

【PS:所以下一幀這個說法是對的嗎?????】

12.2.3 裁剪面 (Clip Planes)

????????沒啥說的,就一點:裁剪面會定義視椎體的形狀,視椎體之外的內容不會被渲染,即視椎體剔除,無論我們是否在游戲中使用遮擋剔除 (Occlusion Culling),都會發生視椎體剔除。

12.2.4 正交模式 (Orthographic)

????????將攝像機標記為Orthographic,就會從攝像機視圖移除全部透視。此模式最常用于創建等距視圖游戲或 2D 游戲。

????????請注意,霧效在正交攝像機模式下均勻渲染,因此可能無法按預期顯示。這是因為post-perspective空間的 Z 坐標用于霧的“深度”。這對于正交攝像機而言并不是嚴格準確的,但之所以使用該模式,是因為其在渲染過程中有一定的性能優勢。

12.2.5 渲染紋理 (Render Texture)

????????把相機的視圖置于渲染紋理上,然后把紋理應用于另一對象,就可以創建類似監視器的效果,在對象身上展示相機視角的內容。

13 后記

????????多數是直接復制官方文檔說辭,不懂的地方可則可自行查詢資料,必然比文檔講得更好。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/83408.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/83408.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/83408.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SpringBoot(六)--- AOP、ThreadLocal

目錄 前言 一、AOP基礎 1.入門程序 2. AOP核心概念 3. 底層原理 二、AOP進階 1.通知類型 抽取切入點 2. 切入點表達式 2.1 execution 2.2 annoation 2.3 連接點詳解 三、ThreadLocal 前言 AOP&#xff08;面向切面編程&#xff09;&#xff0c;面向切面編程實際就…

【深度學習】 19. 生成模型:Diffusion Models

Diffusion Models Diffusion Models 簡介 Diffusion 模型是一類通過逐步添加噪聲并再逆向還原的方式進行圖像生成的深度生成模型。其基本流程包括&#xff1a; 前向過程&#xff08;Forward Process&#xff09;&#xff1a;將真實圖像逐步加噪&#xff0c;最終變為高斯噪聲…

Y1——鏈式前向星

知識點 模版——鏈表的前插法 head表示頭結點的下標 ver[i]表示結點i 的值 tot存儲當前已經用到了哪個 add用于將x插到頭結點 int head1; intt ver[N],Next[N]; int ttot-1; void add(int x){ver[tot]x;Next[tot]head;headtot; } 常見的鏈式前向星三種實現形式&#xff…

如何排查Redis單個Key命中率驟降?

問題現象 Redis整體命中率98%&#xff0c;但監控發現特定Key&#xff08;如user:1000:profile&#xff09;的命中率從99%驟降至40%&#xff0c;引發服務延遲上升。 排查步驟 1. 確認現象與定位Key // 通過Redis監控工具獲取Key指標 public void monitorKey(String key) {Je…

自定義Shell命令行解釋器

目錄 1、目標 2、顯示命令提示符 2.1 getenv 2.2 getcwd 2.3 putenv 3、獲取用戶輸入的命令 4、解析命令 5、處理內建命令 6、處理外部命令 7、完整代碼 7.1 myshell.cpp 7.2 Makefile 1、目標 實現一個Linux的myshell&#xff0c;有以下基本的功能。 顯示命令提示…

Laplace 噪聲

Laplace 噪聲是一種特定概率分布&#xff08;拉普拉斯分布&#xff09;產生的隨機擾動。它是差分隱私&#xff08;Differential Privacy, DP&#xff09;中最核心、最常用的噪聲機制之一。它的核心作用是在不泄露個體信息的前提下&#xff0c;允許從包含敏感數據的數據庫中提取…

基于空天地一體化網絡的通信系統matlab性能分析

目錄 1.引言 2.算法仿真效果演示 3.數據集格式或算法參數簡介 4.MATLAB核心程序 5.算法涉及理論知識概要 5.1 QPSK調制原理 5.2 空天地一體化網絡信道模型 5.3 空天地一體化網絡信道特性 6.參考文獻 7.完整算法代碼文件獲得 1.引言 空天地一體化網絡是一種將衛星通信…

【Delphi】接收windows文件夾中文件拖拽

本文根據EmailX45的視頻文件&#xff0c;進行了優化改進&#xff0c;原文參見&#xff1a;Delphi: Drag and Drop Files from Explorer into TPanel / TMemo - YouTube 在Windows中&#xff0c;如果將選擇的文件拖動到Delphi程序的控件上&#xff0c;有很多實現方法&#xff0c…

基于熱力學熵增原理的EM-GAN

簡介 簡介:提出基于熱力學熵增原理的EM-GAN,通過生成器熵最大化約束增強輸出多樣性。引入熵敏感激活函數與特征空間熵計算模塊,在MNIST/CelebA等數據集上實現FID分數提升23.6%,有效緩解模式崩潰問題。 論文題目:Entropy-Maximized Generative Adversarial Network (EM-G…

HashMap與ConcurrentHashMap詳解:實現原理、源碼分析與最佳實踐

引言 在Java編程中&#xff0c;集合框架是最常用的工具之一&#xff0c;而HashMap和ConcurrentHashMap則是其中使用頻率最高的兩個Map實現。它們都用于存儲鍵值對數據&#xff0c;但在實現機制、性能特點和適用場景上有著顯著差異。 HashMap作為單線程環境下的首選Map實現&am…

CSS之動畫(奔跑的熊、兩面反轉盒子、3D導航欄、旋轉木馬)

一、 2D轉換 1.1 transform: translate( ) 轉換&#xff08;transform&#xff09; 是CSS3中具有顛覆性的特征之一&#xff0c;可以實現元素的位移、旋轉、縮放等效果 移動&#xff1a;translate 旋轉&#xff1a;rotate 縮放&#xff1a;scale 下圖為2D轉換的坐標系 回憶…

【筆記】在 MSYS2(MINGW64)中安裝 python-maturin 的記錄

#工作記錄 &#x1f4cc; 安裝背景 操作系統&#xff1a;MSYS2 MINGW64當前時間&#xff1a;2025年6月1日Python 版本&#xff1a;3.12&#xff08;通過 pacman 安裝&#xff09;目標工具&#xff1a;maturin —— 用于構建和發布 Rust 編寫的 Python 包 &#x1f6e0;? 安裝…

基于微信小程序的垃圾分類系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業六年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了六年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

工作日記之權限校驗-token的實戰案例

背景說明 我們組負責維護的一個系統&#xff0c;前端界面掛載在其他兩個系統上&#xff0c;因為歷史遺留原因&#xff0c;同時也掛在公網上&#xff0c;沒有登陸功能和用戶體系&#xff0c;只要輸入網址就能訪問&#xff0c;雖然這個系統是給公司內部人員使用&#xff0c;但是…

mysql雙主模式下基于keepalived的虛擬ip實現高可用模式搭建

數據庫安裝和升級和雙主配置的操作可以參考我的另一篇文章&#xff1a; 數據庫安裝和升級和雙主配置 1、在兩臺服務器都下載和安裝keepalived 下載&#xff1a; yumdownloader --resolve keepalived 下載后得到&#xff1a; [rootlocalhost keepalivedRpm]# ll 總用量 1896 …

展會聚焦丨漫途科技亮相2025西北水務博覽會!

2025第三屆西北水務數字化發展論壇暨供排水節水灌溉新技術設備博覽會在蘭州甘肅國際會展中心圓滿落幕。本屆展會以“科技賦能水資源&#xff0c;數智引領新動能”為主題&#xff0c;活動匯集水務集團、科研院所、技術供應商等全產業鏈參與者&#xff0c;旨在通過前沿技術展示與…

單調棧(打卡)

本篇基于b站靈茶山艾府。 下面是靈神上課講解的題目與課后作業&#xff0c;課后作業還有三道實在寫不下去了&#xff0c;下次再寫。 739. 每日溫度 給定一個整數數組 temperatures &#xff0c;表示每天的溫度&#xff0c;返回一個數組 answer &#xff0c;其中 answer[i] 是…

【機器學習基礎】機器學習入門核心算法:層次聚類算法(AGNES算法和 DIANA算法)

機器學習入門核心算法&#xff1a;層次聚類算法&#xff08;AGNES算法和 DIANA算法&#xff09; 一、算法邏輯二、算法原理與數學推導1. 距離度量2. 簇間距離計算&#xff08;連接標準&#xff09;3. 算法偽代碼&#xff08;凝聚式&#xff09; 三、模型評估1. 內部評估指標2. …

已有的前端項目打包到tauri運行(windows)

1.打包前端項目產生靜態html、css、js 我們接下來用vue3 vite編寫一個番茄鐘案例來演示。 我們執行npm run build 命令產生的dist目錄下的靜態文件。 2.創建tarui項目 npm create tauri-applatest一路回車&#xff0c;直到出現。 3.啟動運行 我們將打包產生的dist目錄下的…

Unity3D仿星露谷物語開發55之保存地面屬性到文件

1、目標 將游戲保存到文件&#xff0c;并從文件中加載游戲。 Player在游戲中種植的Crop&#xff0c;我們希望保存到文件中&#xff0c;當游戲重新加載時Crop的GridProperty數據仍然存在。這次主要實現保存地面屬性&#xff08;GridProperties&#xff09;信息。 我們要做的是…