2017年6月14日 星期三

Unity 5.6中的混合光照詳解

作者:Kemal Akay 原文
潤稿:Kelvin Lo

在Unity 5中,光照得到了很大的改進。現在要打造超逼真的遊戲已成為可能。Adam動畫短片只是證明這種可能性的一個例子。出於對效能的考量,許多Unity開發的遊戲仍然依賴烘焙光照(Baked lighting)。但有時候又必須使用即時和烘焙光照。這在以前會是個問題,在5.6中Unity已解決了這個開發者呼聲很高的需求。

光照功能對新手來說可能比較難懂,因為編輯器預設不會顯示光照設定的介面。而且我們的調查顯示,幾乎所有新手對於光照貼圖都沒有概念。因此,我們改了UI使它更顯眼,不過,有些概念性的知識還是必須要瞭解的。首先簡單介紹即時光照與烘焙光照(光照貼圖)之間的區別。之後再對混合光照進行講解。如果你已熟悉這些概念,可以直接跳過。



何謂預計算即時光照(Precomputed Realtime GI)和烘焙光照(Baked GI)

首先,Unity中新的光照模式沒有為預計算即時GI帶來任何新的內容,而是為開發者提供了可以在場景中同時使用即時與烘焙光照的新方法。即時光照很容易理解,對燈光的任何調整會即時更新場景中的所有光照,唯一的問題是會耗效能。每次調整燈光都會對效能產生很大的影響。因此,Unity同時引入光照貼圖的概念。可以烘焙光照,並將所有光照資料以光照貼圖的形式保存在專案中。這樣做代價很低,因為本質上它只是一個貼圖。

烘焙光照與即時光照的一個主要區別是:烘焙光照都是靜態的,你不能移動這些靜態物件,否則就會出現破綻。將遊戲物件設為靜態物件很簡單,只需要在檢視視窗中將它們標記為static(或只是lightmap static)。這樣Unity會為該物件烘焙光照貼圖。如果你希望物件保持動態,就不要打勾,它們預設就是動態的。



預計算GI – 日夜迴圈


有關這方面概念的詳細介紹已超出本文範圍,而且Unity官網上已有一個由David Llwelyn和光照團隊製作的
系列教學介紹了Unity中使用即時GI光照的優化技巧。

除此之外,我們最近還更新了AssetStore中的Courtyard範例,讓它和Unity 5.5相容。有興趣可以下載試試看,瞭解如何在專案中使用即時光照。


直接光照(Direct light)與間接光照(Indirect light)

許多新手容易忽略的另一個重要概念是,直接光照與間接光照之間的區別。與即時光照類似,直接光的概念也很容易理解。就是從一個光源發射的光射線直接影響一個物體,可以從畫面直接觀察到的。

相對難理解的是間接照明這個概念。基本上,間接照明透過反彈光來實現。除了直接光照,我們還能有反彈的光照。可見光子在一個場景中基於數學公式做簡單的反彈。間接照明的原理如下圖:




透過Light元件的“Bounce Intensity”參數,可以設定一個燈光的間接光貢獻度。換句話說,如果你希望關閉間接光照,可以把這個參數設為0。

所以,預計算即時GI很有趣,能在Unity中發揮的很完美。但是它的代價很高,針對比較低端的設備(例如手機),或是有特別需求的渲染(VR),就需要有更優化的場景設定。然後Unity就會選擇對所有靜態物件進行光照烘焙(例如環境),並對所有動態物件(例如角色)使用即時GI。所以就會產生接下來的問題。


混合光照(Mixed lighting)

從字面上來看混合光照已經說明一切,就是能混合使用即時和烘焙光照。我們所提到的狀況大多數是遊戲會用到的典型狀況。例如在《Last of Us》中,環境場景是固定的,因此它對應的所有東西都儲存在光照貼圖中,而所有移動物件則使用的是即時光照。

可以先烘焙所有環境,然後對角色設定即時光照。但這樣會引發一個嚴重問題:所有的已烘焙物件也會接收動態光源,造成這些物件雙重曝光。當然也可以對這些烘焙物件進行即時光源遮擋,但這樣很容易造成亮度不均。下圖就是這個問題的一個例子。相同材質的相同模型,但其中之一是動態的,另一個是靜態的:



或許這樣你還能勉強接受,但在有些情況下可能就完全走鐘。
此時就該換混合模式登場了。基本上混合模式會幫你處理好一切,平衡範圍內的動/靜態物件亮度(保留視覺逼真度與一致性)。在Unity 5.5中,你可以在檢視面板中將Light Type設為Mixed來啟用混合光照。

那麼現在可以安心的對動態和靜態物件使用混合光照了吧? 
等等……為什麼在這種模式下聚光燈(Spot light)或點光源(Point light),無法投射動態物件的陰影?

原來期望聚光燈能同時影響動態與靜態的物體。到底是我哪裡改錯了還是這是一個Bug?很多開發者會問為何混合聚光燈不會投射任何陰影?

其實原因是混合光照只能
"只有"一個定向光(Directional light)時才能正常工作。

蝦米!你過來我保證不打死你!


Subtractive模式

這就是在Unity中導入新光照模式的主要原因,基本上就是為了解決這個問題。5.6在光照介面中你有四種光照模式可以選擇。它們是Baked Indirect, Distance Shadowmask, Shadowmask以及Subtractive。只有三項新的光照模式加入。其中Subtractive算是舊的混合模式,與其他模式相比它的效能消耗要小,這在開發像是手機專案時仍有點用處。

讓我們用Unity的技術術語來重新描述下這個模式的工作原理:選擇‘Mixed’烘焙模式,標記為靜態的物件會把來自混合燈光的資訊保存為光照貼圖。但與標記為‘Baked’的燈光不同,混合燈光還會為場景中的動態物件提供即時光照。前面提到只有單個定向光時它才能正常工作對吧?沒錯,因為混合點光源和聚光燈不會在物件上投射任何陰影。

此外,Subtractive模式裡還有一個特別的功能。它與其他模式不同,直接光照也是烘焙的,因此在Subtractive光照模式中,沒法有鏡面反射。


探針遮蔽(Probe Occlusion)

在Subtractive模式中,另一個動態物件需要使用的重要元件是光照探針組(Light probe groups)。沒有光照探針,動態物件就無法對用在靜態物件的光照進行採樣,更重要的是它們無法使用遮擋資料。因此為了能接收陰影,動態物件需要將來自光照探針的光照進行混合。在下面的圖像中,你能看到一個動態物件是如何利用光照探針做遮蔽的。


為了實現更精確的遮擋,可以為場景中重要的物件(例如主要角色)指定光照探針代理(LPPV)。但是要注意這會帶來額外的效能消耗。

色彩空間與Subtractive模式中的渲染路徑

關於Subtractive模式還有一點需要提一下。這個模式是為Forward 渲染模式和Gamma顏色空間設計的。不適用於Deferred 渲染模式和Linear顏色空間。因為Subtractive模式與其他模式相比,效能消耗相對要小,主要用於像手機這樣的低階設備。有關渲染路徑(Rendering paths)和色彩空間(Color space)的資訊可以在官方手冊中查看。


新的光照模式Distance Shadowmask, Shadowmask 和Baked Indirect

Unity對Shadowmask的支援是新混合光照模式中的一個重要新功能。此外,還有密度與方向性貼圖,Unity現在能為所有的光照類型生成Shadowmask了。與Subtractive模式不同,Shadowmask可以將所有光照類型的即時與烘焙陰影無縫混合。這表示我們可以獲得遠距離陰影。出於效能考量,可以在近處使用即時陰影而遠處使用烘焙陰影,來保持更低的Draw Call。與Subtractive模式相反,Shadowmask功能還可以產生高品質的即時鏡面高光。除了這些功能外,我們還對UI做了改進與重建,加了新的除錯模式來協助你更好的在場景中設定光照。

這個影片顯示了用Shadowmask 和Distance Shadowmask的效果。預計算即時GI在這個場景設定中已完全關閉。


我們已經說過Subtractive模式就是Unity中舊的Mixed模式。除此之外,Lighting Mode介面中還有兩個新的選項: Baked Indirect 和Shadowmask。要重點注意的是,Baked Indirect和Shadowmask不是品質設定。所有模式都有自己獨特的功能特性,它們能在不同的場合中發揮不同的作用。

Shadowmask的主要功能本質上非常容易理解,它可以解決Subtractive無法完成的事情。Shadowmask不僅能處理方向光,還可以使用點光源和聚光燈混合即時與烘焙陰影而不會增加雙重陰影/光照,意思就是可以正確合成重疊的陰影。


在繼續之前,我們需要先定義陰影距離的概念,它與光照模式息息相關,但許多新手不知道這個概念。要正確有效的製作場景,就必須要掌握這個觀念。否則很容易就會碰到不必要的draw call和FPS下降。


陰影距離(Shadow Distance)和陰影投射物(Shadow Casters)的概念

在Unity中,陰影的行為由陰影距離和光照模式決定。查看陰影距離的一種快速方式就是使用Shadow Cascades模式。在場景視圖中可以選擇Shadow Cascades模式來顯示陰影距離。這個參數可以在Quality設定中修改。你也可以用程式呼叫API來更改陰影距離,只不過使用場合相當有限。不過它在某些極端情況下會有作用(例如當需要在某些光照模式中為了降低draw call而減少陰影距離)。


因此,物件在陰影距離之內還是之外,會影響陰影的行為,並對效能產生直接影響。

實際上,如果打開Quality設定(Edit > Project Settings > Quality),會發現有很多參數可用來調整陰影的設定。很可惜這些設定被藏在這裡,大部分開發者都不知道它們的存在,除非在需要時搜索到這些參數。

尤其是可以在Distance Shadowmask和Shadowmask之間切換的Shadowmask功能選項。預設的是Distance Shadowmask選項,接下來會討論這兩個模式之間的區別。讓我們先從Shadowmask開始。

Shadowmask

在Shadowmask模式中,靜態物件透過Shadowmask從其他靜態物件接收陰影,不必考慮陰影距離。來自動態物件的陰影只能透過陰影距離內的陰影貼圖獲得。動態物件透過陰影距離內的陰影貼圖接受來自其他動態物件的陰影。來自靜態物件的陰影僅能通過光照探針獲得。

現在,當你同時在動態物件和光照貼圖靜態物件上使用Shadowmask模式時,首先你可能會發現問題就是,動態物件的陰影(Shadow maps)和來自光照貼圖靜態物件的烘焙陰影不一致。Unity 5.6在執行時只支持簡單版本的PCF( Percentage Closer Filtering )陰影,這種陰影很簡陋,走近時還可能會出現陰影瑕疵。因此,動態物件是不可能獲得軟陰影(Soft Shadow)的。與之相比,靜態物件則相對較容易獲得軟陰影。實際上,通常需要增加解析度以渲染更多靜態物件的陰影,因為你需要更多的貼圖圖元來定義一張光照貼圖上的陰影。只有這樣,你才能渲染出與來自動態物件的陰影貼圖相近的陰影。


你可以看到,使用高解析度光照貼圖,來自靜態物件的烘焙陰影與來自動態物件的陰影貼圖已十分接近。


使用低解析度的光照貼圖,來自靜態物件的烘焙陰影與來自動態物件的陰影貼圖明顯不同。

關於Shadowmask模式的一個友善提示:動態物件只會在陰影距離內投射陰影貼圖。而靜態物件,無論陰影距離是多少,只會投射烘焙陰影。Shadowmask 和Distance Shadowmask兩者均可提供即時鏡面高光。

那麼問題來了,什麼是Distance Shadowmask?它與Shadowmask有何不同?



Distance Shadowmask

普通的Shadowmask模式要比Distance Shadowmask模式產生更少的Draw Call。為什麼呢?

這是因為Distance Shadowmask的行為是由陰影距離決定的。在陰影距離內,動、靜態物件都會渲染進陰影貼圖,靜態物件可以在動態物件上投射銳陰影。超出陰影距離,靜態物件會通過預計算Shadowmask接收來自其他靜態物件的高品質陰影,而動態物件則通過光照探針與LPPV,接收來自靜態物件的低解析度陰影。

這樣應該能解釋清楚Distance Shadowmask模式下動、靜態物件的陰影投射方式以及為何Shadowmask模式要比Distance Shadowmask模式資源消耗更少了。基本上,陰影貼圖的效能消耗要高於烘焙陰影(因為它們在每幀都進行渲染,而烘焙陰影是保存在一個貼圖/光照貼圖中的)。

在Shadowmask模式下,動態物件只會在陰影距離內投射陰影貼圖。Distance Shadowmask的效能消耗更大,因為靜態物件也可以在陰影距離內投射即時陰影貼圖,這樣會使Draw Call增加。這也就是為什麼Distance Shadowmask模式更適合高階PC或遊戲主機的專案。而Shadowmask則作為一種廉價的解決方案,在中低階設備使用。

Shadowmask中的遮蔽

有關Shadowmask的功能,我還想就不同物件類型遮蔽的工作原理進行詳細說明。本文我們談到了為何光照探針的放置位置對於動態物件接收來自靜態物件的陰影如此重要。本質上,動態物件需要光照探針從遮蔽物採樣遮蔽資料。因此,Subtractive模式中的探針遮蔽概念同樣可以直接適用於Shadowmask和Distance Shadowmask。

有關物件是如何根據類型與光照模式接收對應陰影的快速參考可以查看光照模式參考卡(Lighting Modes Reference Card),也可以列印出來以備不時之需。



作為Quality設定的Shadowmask

在Unity 2017.1中,Shadowmask和Distance Shadowmask選項被移到了Quality設定中,這麼做的原因有兩點。

首先,我們希望開發者能對陰影行為能有更多創意的控制,所以可以在執行時對模式進行切換。例如,你可以在同一場景中的室內環境使用Shadowmask(比如在機庫中實現軟陰影),在室外環境時切換到Distance Shadowmask。事實上,我有另外一篇文章來說明如何用API來實現。

其次,可以針對不同的硬體設定不同的要求。例如在遊戲中的設定頁面,可以顯示一個玩家可以調整的設定選項,如果你要發佈遊戲到Steam,可以在低階硬體上使用普通的Shadowmask,在高階PC上使用Distance Shadowmask。

之所以能這樣隨心所欲進行設定,是因為技術上在Shadowmask 和Distance Shadowmask之間的切換非常簡單,唯一的缺點就是Draw Call的數量,但不管怎麼說,最後都是取決於開發者。


技術限制和調試工具

最後,讓我們聊聊Shadowmask功能(Shadowmask 和Distance Shadowmask)的技術限制,以及能幫你在設定過程中進行問題診斷的除錯工具。

你已知道如何和為何使用Shadowmask,讓我們來聊聊它的限制。由於技術約束,在一個場景中,Unity僅支援4個重疊的混合燈光。如果在相同位置有超過4個的混合燈光,其中之一就會降級為烘焙。通常如果你看到其中有某個燈光突然爆亮,大概就是因為這個。

還好我們有個視覺化除錯工具可以幫你解決問題。如果將場景的visualization模式從“Shaded”切換到“Light Overlap”,你可以看到問題區域顯示為亮紅色。

值得一提的是,與點光源或聚光燈不同的是,方向光沒有定義的邊界,它是全方向的。因此,你在除錯重疊的混合燈光時,總要將它考慮在內。在螢幕上,你可能只看到4個重疊燈光,但實際上,方向光可能已在某些位置與之有了交疊。如果在場景中使用了多個方向光來進行照明(例如補光),那情況就有點麻煩了,所以小心使用它。



Baked Indirect

目前為止我們已經介紹了Subtractive模式、Distance Shadowmask和Shadowmask功能。還剩最後一個模式,Baked Indirect。

與Shadowmask複雜的選項相比,Baked Indirect模式相當容易理解。Baked Indirect沒有使用任何Shadowmask。所以在這個模式中沒有遠距離陰影。在陰影距離之內,所有靜態和動態的物件都投射 即時陰影貼圖。但超出陰影距離後,就沒有陰影。

在Baked Indirect中,除了間接照明之外所有的東西都是即時的。這代表即時光照、即時陰影以及即時鏡面高光,但是反射的光照資訊儲存在光照貼圖中是靜態的。在這個模式中,你能做的事情有限,但是你可以用它來做些很有創意的事情。下面你會看到搖擺的Baked Indirect模式混合聚光燈,是如何偽造即時全域照明的。


你可以看到燈光在變化,亮度忽明忽暗。但如果你仔細觀察街燈的間接照明,你會發現它並沒有變化。所以從技術上來說,你可以移動你的燈光,但不能太多,否則會出現陰影瑕疵。在下面的影片中,你會看到在場景中移動物件時,間接照明是如何導致這種類型陰影瑕疵的:



關於Unity 5.6中的混合光照就介紹到這裡,我們會不斷的為大家繼續翻譯分享從Unity官方部落格的技術文章!

2017年6月7日 星期三

iOS和macOs上的XR

作者:Scott Flynn, 原文

Unity的三大宗旨之一就是讓開發普及化,我們也關心著VR/AR行業相關的最新消息,期望全球開發者們將創意變為現實。

在6月6日的WWDC上,Apple首次公開展示了iOS對AR以及macOS對VR的支援。開發者將可以用整合ARKit的Unity直接為iOS設備開發AR應用,並且可以為macOS平台建立360全景影片以及VR內容。我們也非常興奮能與Apple合作來共同擴展VR/AR生態,確保XR(VR、AR、MR統稱為XR)行業能讓全球社群隨手可得。

現在Unity也推出了可用於在macOS平台上製作VR內容的體驗版,並在BitBucket上放了開源的Unity ARKit套件。

Unity滿足大家對於VR及AR內容開發的期望,能夠支援在Mac版的Unity編輯器中直接測試XR應用並快速反覆運算。使用這個VR體驗版已經可以製作內容並發佈至App Store。我們也找了一些開發者一起來測試這個版本,以下是他們的反饋:

“除了將專案升級為最新的Unity體驗版之外,幾乎毫不費力就可以將PC端的VR遊戲完美移植到macOS平台”
-- Zack Brown, Zulubo Productions


“整體來說,使用Unity將《Space Pirate Trainer》移植到macOS平台的過程非常流暢。我們讓它在macOS系統上跑了好幾個小時。之前我對Metal支持還有些猶疑,但Unity與Apple讓整個流程相當簡潔。過去幾個月裡Unity對Metal的支持進行了大量優化,並且支援很多自訂著色器,儘管在建立這些著色器時並非從Metal的角度出發。Unity、Valve及Apple都做得非常出色!”

– Dirk Van Welden, I-Illusions


macOS平臺Unity編輯器VR模式運行Space Pirate Trainer


在這個Unity體驗版中,我們與Apple及Valve共同合作對Metal 2進行了優化,以符合目前的VR渲染通道、Multi-Pass以及Single-Pass和Variants。在最初的版本中,我們利用WWDC上公佈的Metal 2新功能結合Instancing獲得了顯著的效能提升,而需要的Draw Call數量更是直接減半。


使用Unity為macOS開發VR應用

我們希望所有想在macOS平台上開發VR內容的開發者都來試用這個體驗版並給我一些反饋,協助我們改善產品品質。和所有Unity體驗版一樣,在進行升級之前請務必備份您的專案!


硬體及軟體需求:

  • 從論壇下載Unity XR體驗版,這個版本會包含開啟VR模式的選項,並加了OpenVR平臺。
  • 您將需要支援macOS的SteamVR套件。可以到Valve官網瞭解更多詳情
  • 為macOS開發VR內容需要系統版本為macOS High Sierra。該版本包含大量驅動與Metal優化,能最大化發揮硬體的潛能。
  • 與所有VR內容一樣,硬體品質會決定最終內容的品質。對於3D的VR內容開發建議使用Radeon Pro 500系列顯卡。



使用ARKit

我們在BitBucket提供了Unity ARKit套件,可以從論壇找下載。這個Unity套件將讓開發者們輕鬆使用ARKit的功能,例如世界追蹤(World Tracking)、即時影片渲染(Live Video Rendering)、平面預測與更新(Plane Estimation and Updates)、碰撞檢測API(Hit-testing API)、環境光預測(Ambient Light Estimation)以及原點雲資料(Raw Point Cloud Data)。


Unity中可以直接透過C#腳本API存取所有ARKit公開的功能介面。同時還提供了Unity腳本以便於為現有Unity遊戲專案整合這些新功能。請查閱BitBucket中Unity-ARKit-Plugin專案說明文件瞭解更多資訊。

硬體及軟體需求如下:

  • 使用Unity ARKit套件需要Unity 5.6.1p1及以上版本。同時也支援上述的體驗版。
  • iOS 11及以上版本
  • XCode 9 beta及以上版本,並且需要包含ARKit框架的iOS SDK
  • ARKit框架能夠支援的iOS設備

2017年5月25日 星期四

如何在Unity中實現Raymarching圖形效果


潤稿:Gallant Chu - 密卡登遊戲

本文由David Arppe分享一些在遊戲中使用Raymarching技術的建議,也會介紹用在遊戲中的Raymarching程式碼,看看Raymarching這種舊的渲染技術如何用新的平行處理和計算技術進行優化!

Raymarching技術實際上已經“歷史悠久”,在很早之前就被用於一些“古老”而經典的遊戲中。例如下面兩款經典的“老”遊戲:

1.《Tennis for Two》


《Tennis for Two》被廣泛認為是最早的遊戲之一,它是一款使用示波器進行遊玩的遊戲!非常酷並且很有創意!由William Higinbotham在1958年推出。

2. 《Donkey Kong》


《Donkey Kong》是一款誕生於遊戲黃金年代的家機遊戲。遊戲主角是Jumperman(也就是現在的瑪利歐)。它被認為是首批帶有故事情節的遊戲之一,玩家在螢幕上可以像“看電影”一樣,驚恐地看著公主一次又一次被綁架。由Nintendo Research and Development 1在1981年推出。

這些老遊戲都非常有創意,他們突破了當時電腦硬體和軟體的限制使用了現在仍未過時的技術。

什麼是Raymarching技術


Raymarching是一種電腦圖形渲染方式,但它的潛力仍未被完全發掘。Raymarching一般用於渲染體積貼圖、高度圖以及解析曲面。如今,大多數遊戲用OpenGL或Direct3D(DirectX)來使用顯卡的硬體加速器繪製多邊形,電腦可以以每秒60幀的速度渲染幾百萬個三角面。雖然Raymarching沒有那些圖形API那麼出名,但它可以僅用兩個三角面實現無與倫比的細節。

RayMarching是一種數學渲染方式。它是由距離場(點到一個圖元的距離)、固定步長(通常用於體積渲染)和根定位(一個數學方法)完成的。

這個Demo可以按播放喔!


建立上圖這樣的場景需要借助建模工具(Maya, Blender, 3DsMax),繪圖工具(Photoshop, Gimp, MSPaint)。而該場景使用數學方法建立,透過Raymarching技術來渲染,不再受渲染三角面數的限制。不過,Raymarching技術並不是萬能的,它速度較慢,我認為它應該與多邊形渲染一起使用。

上圖並非是"影片",而是真的透過程式渲染出來的畫面,你可以點左上方的Snail來看到完整的程式碼。

如何在遊戲中加入Raymarching

結合Raymarching與多邊形兩種渲染方式並不難。不過首先要理解它們之間的區別:
  • Raymarching並非百分之百精確。而使用距離場可以趨近於希望渲染的表面,但幾乎無法得到真正想要的距離。
  • 渲染多邊形(透視模式下)使用了投影矩陣。這是深度,不是距離。
通常兩者結合使用時,最簡單的方式就是從多邊形開始,用Raymarching作為結束。使用距離緩衝區進行深度測試是很難的,並且會局限於實體物體。Raymarching階段需要在所有渲染結束後進行(就好比實體物體無法在透明物體之前渲染)。您可透過原文中的具體程式碼,瞭解如何準備好深度緩衝區,並將它轉化為距離緩衝區!

這是一個在Unity裡寫的相機深度緩衝


float GetDistanceFromDepth(float2 uv, out float3 rayDir)
{
    // Bring UV coordinates to correct space, for matrix math below
    float2 p = uv * 2.0f - 1.0f; // from -1 to 1

    // Figure out the factor, to convert depth into distance.
    // This is the distance, from the cameras origin to the corresponding UV
    // coordinate on the near plane. 
    float3 rd = mul(_invProjectionMat, float4(p, -1.0, 1.0)).xyz;

    // Let's create some variables here. _ProjectionParams y and z are Near and Far plane distances.
    float a = _ProjectionParams.z / (_ProjectionParams.z - _ProjectionParams.y);
    float b = _ProjectionParams.z * _ProjectionParams.y / (_ProjectionParams.y - _ProjectionParams.z);
    float z_buffer_value =  tex2D(_CameraDepthTexture, uv).r;

    // Z buffer valeus are distributed as follows:
    // z_buffer_value =  a + b / z 
    // So, below is the inverse, to calculate the linearEyeDepth. 
    float d = b / (z_buffer_value-a);

    // This function also returns the ray direction, used later (very important)
    rayDir = normalize(rd);

    return d;
}
當我使用投影矩陣的相反數來確定UV坐標(變換為[-1,-1]→[1,1])位於近平面(x,y,-1)。 在這一點上,我沒有使用視圖矩陣,所以假設相機是原點([0,0,0])。 該坐標的長度將隨著各種UV坐標而不同。 使用UV坐標[0.5,0.5],它應該與近平面距離相同。
得到這些數字後,我設定了規範化的rayDir變數。 這個很重要。因為 Raymarching得透過Raycasting運作。


Raymarching的工作原理

在準備工作就緒後,獲得深度緩衝區裡的距離,就可以處理相交。透過逆投影矩陣計算出正確的射線,來匹配遊戲中攝影機的視角。然後定位攝影機即可。


fixed4 frag(v2f i) : SV_Target
{
    float3 rayDirection;
    // Our code from above!
    float dist = GetDistanceFromDepth(i.uv.xy, rayDirection);

    // The cameras position (worldspace)
    float3 rayOrigin = _cameraPos;
    // Accounting for the cameras rotation
    rayDirection = mul(_cameraMat, float4(rayDirection, 0.0)).xyz;

    //...
    // more to come!

}


紫色的部分是特別標出的,我將距離存入浮點數,透過一個float3作為一個out變數,所以它會輸出一個正確的FOV,但也丟失了相機的旋轉資訊。我們可以使用一個標準的統一變數(_cameraPos)獲得這個位置。 將rayDirection與視圖矩陣相乘,用0.0作為w參數的原因是因為我們不希望存此變數中的攝像機位置,因為我們只會旋轉它。
在Unity中的效果如下圖,兩個黃色球體與一個長方體相交。其中一個(右邊的)球體使用多邊形渲染。它按照預期與立方體相交。左邊的球體則按設置從遊戲攝影機中計算的正確FOV,位置和旋轉資訊。

另外請注意,與右側多面球體相比,用Raymarching渲染球體相交的立方體表面邊緣非常平滑。


渲染其他內容

使用Raymarching渲染需要較深的數學底子,或者用一些作弊的方法,就算是網路也沒太多資料可以找,這篇文章可以參考一下。http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
Inigo Quilez - 一個真正的 raymarching 傳奇)

下面用球體以外的形狀來實現一些特殊物體!

float sdTorus( float3 p, float2 t )
{
  float2 q = float2(length(p.xz)-t.x,p.y);
  return length(q)-t.y;
}

這是一個環面的距離公式。此距離函數回傳從點到距離圖元最近的點的距離,將用於渲染甜甜圈。在下圖中可以看到黑色圖形、紅圈、藍點及紅線。左下角的藍點是攝影機,右上角的藍點是正在觀察的點。除了知道與最近平面(底部中心粗短的黑線)的距離以外沒有任何資訊。因此,使用這個距離來向前移動。不斷重複這個過程,直到到達最終想要的平面!最後就可以得到目標平面的距離。



要實現甜甜圈,還需要實現以下功能:

  1. 獲取射線源(攝影機位置)
  2. 獲取射線方向(攝影機FOV,長寬比還有旋轉角度)
  3. 在函數中添加一個距離函數(環形)
  4. 將光線投射到圖形上
  5. 在該光線上獲取到圖形表面的距離

下面,首先要計算一個點。使用標準的point-along-a-vector方程式,沿著所投射的射線移動一定的距離,然後計算到圖元的距離。將剛剛計算的數值加上沿射線移動的距離,然後重複該過程。透過FOR迴圈進行控制。


// Let's store the distance we're going to be calculating here
float d = 0.0f;
// We will step along the ray, 64 times. This value can be changed.
for (int i = 0; i < 64; i++)
{
    // Here's where we calculate a position along our ray. with the very
    // first iteration, it will be the same as just rayOrigin.

    float3 pos = rayOrigin + rayDirection * d;

    // This is the distance from our point, to the nearestPoint on the torus
    float torusDistance = sdTorus(pos, float2(0.5, 0.25));

    d += torusDistance;
}

//...
// more to come!



結果出來了,現在還只渲染出純黃色。到此已成功建立了一個環形,您也可以嘗試一些其它的距離函數,並觀察它們的工作工作原理。後面還會使用一些更高級的東西。


獲取G-Buffer資訊

還需要更多資訊來使用光照模型。現在,只有一條射線的距離。要在圖形上進行更多操作。
需要知道:

  • 3D座標
  • 表面法線

還好這些屬性都非常容易獲得!


// This is pretty self explanatory. We have the distance. We just need to move that
// far down the ray to get the WorldSpace position

float3 pos = rayOrigin + rayDirection * d;

// What were doing here is, offsetting the position on the X axis, Y axis, and Z axis,
// and normalizing it to get an estimate of the surface normals
// Declaring eps as a float3, allows us to do some swizzle magic

float3 eps = float3( 0.0005, 0.0, 0.0 );


// This is ugly, but you can wrap it in a function. All distance functions create
// a distance field, which is usually in a function called 'map'

#define TORUS(p) sdTorus(p, float2(0.5, 0.25)).x
float3 nor = float3(
    TORUS(pos+eps.xyy) - TORUS(pos-eps.xyy) ,
    TORUS(pos+eps.yxy) - TORUS(pos-eps.yxy) ,
    TORUS(pos+eps.yyx) - TORUS(pos-eps.yyx) );
#undef TORUS

nor = normalize(nor);

// The reason this works, is because we're normalizing the result. If the surface is
// up, the difference between the +Y and -Y sample will be larger than the differences
// between +X/-X and +Y/-Y. It all adds up to a really good estimate. For being very
// math-based, you're starting to see that raymarching is mostly estimates?
//...
// more to come

結果得到一個有法線和世界座標的圓環,讓我們繼續做下去。



為了照亮它,我用了標準的Phong光照模型來幫圓環打光。


// Let's create some variables to work with
float3 l = normalize(sundir);   
float3 e = normalize(rayOrigin); // with raymarching, eyePos is the rayOrigin
float3 r = normalize(-reflect(l,nor));
 
// The ambient term
float3 ambient = 0.3;    

// The diffuse term
float3 diffuse = max(dot(nor,l), 0.0);
diffuse = clamp(diffuse, 0.0, 1.0);     
   
// I have some hardcoded values here, 
float3 specular = 0.04 * pow(max(dot(r,e),0.0),0.2);
specular = clamp(specular, 0.0, 1.0); 
// Now, for the finished torus
float4 torusCol = float4(ambient + diffuse + specular, 1.0);

//...
// more to come!


看起來效果不錯,還缺少材質,可惜的是,也沒有辦法取得UV資訊,這部分我後面會解釋。





中場休息

在繼續說明之前,我想分享Shadertoy的另一個很酷的展示。 這也是一個可以互動的場景。 它具有折射,原始圖像和一些其他很酷的東西能和raymarching搭配使用。 陰影的半影效果基本上沒有任何消耗。 形狀的組合很容易實現(加,減,差)。 變形空間(扭曲,變形,彎曲)和實例也很簡單。


這個Demo可以按播放喔!

投影映射


接下來幫甜甜圈上撒些東西!可以在這裡這裡下載所用的貼圖。目標是盡可能地讓它看起來更像甜甜圈。


fixed4 frag(v2f i) : SV_Target
{
    // ..
    // All of the code we wrote is up here
    // .. 


    // Sample the dough, from 2 planes. We're using the z and x normals
    // to assure that we don't get any additive colors we don't want

    doughnutColor = tex2D(_Dough, pos.xy - float2(0.5, 0.5)).rgb * abs(nor.z);
    doughnutColor += tex2D(_Dough, pos.zy - float2(0.5, 0.5)).rgb * abs(nor.x);

    // Using a top-down plane, sample from the sprinkles
    // This should be a hard cutoff. So I will use an if statement
    // Also, I am going to use some noise to get the 'drizzle' effect.

    float noiseOffset = tex2D(_Noise, pos.xz * 0.2).x * 0.5f;
    if (nor.y + noiseOffset > 0.7)
    {
        doughnutColor = tex2D(_Sprinkles, pos.xz).rgb;
    } else {
        doughnutColor += float3(1.0, 0.75, 0.5); // a color should work here
    }

    torusCol.rgb *= doughnutColor;

    // AND FINALLY, remember how we calculated distance from the depth buffer?
    // You can use your favorite depth-test mode right here. 


    return (dist < d ? tex2D(_MainTex, uv) : torusCol);
}


最終結果就會像這樣


參考資料:


Tennis for Two - https://commons.wikimedia.org/wiki/File:Tennis_For_Two_on_a_DuMont_Lab_Oscilloscope_Type_304-A.jpg
Donkey Kong - https://en.wikipedia.org/wiki/Donkey_Kong_(video_game)#/media/File:Donkey_Kong_Gameplay.png
Distance Based Raymarching - 
http://hugi.scene.org/online/hugi37/sphere_tracing.jpg


2017年5月8日 星期一

Unity教學 - 低模場景打光與後製技巧

原文

@註:原文裡面有很多圖,由於沒有授權就不方便直接轉過來,因此除了影片外,我們只用中文描述整個過程。你可以另外開一頁原文來對照那些圖。

如何為低模場景打光



之前的Unity預計算即時GI系列受到了不錯的迴響。今天我們轉一篇開發者寫的Unity 5.5低模場景打光和後製技巧。



課前介紹


你可以瞭解到如下內容:


  • Unity中光照的基礎概念
  • 為場景設定預計算即時全域光照(Precomputed Realtime GI) 
  • 為動態(非靜態)物件增加光照探針(Light Probes)以使用預計算即時全域光照
  • 更改光照設定/顏色
  • 從Unity標準資源包中導入Images Effects
  • 設定攝像機的後處理圖像效果:
  • 環境光遮蔽(Ambient Occlusion)
  • 全域霧(Global Fog)
  • 景深(Depth of Field)
  • 色調映射(Tonemapping)
  • 抗鋸齒(Antialiasing)
  • 泛光(Bloom)
  • 色彩矯正定址貼圖(Color Correction Lookup Texture)
  • 暈影和色差(Vignette and Chromatic Aberration)
  • 調整場景顏色襯托不同的氛圍


教學使用的軟體:Unity 5.5和Photoshop CC 2017


1.確保將目標平台設為PC,Mac & Linux Standalone。
功能表File -> Build Settings,選擇“PC,Mac & Linux Standalone“,然後點Switch Platform按鈕切換平台。

2.啟用預計算即時GI(全域光照)
點Unity功能表Window -> Lighting打開Lighting介面,在Lighting介面選擇Scene頁籤。
打開Precomputed Realtime GI頁籤,設定即時解析度(Realtime Resolution)為0.5(解析度越小,全域光照預計算越快)
這裡建議在開發時可以將Realtime Resolution設得更低,在最後才設回0.5或更大。並將CPU Usage設為Medium。

關閉Baked GI

在介面底部取消打勾Auto,停止自動運算。

3.清除GI緩存

這樣做是因為場景可能未按預期進行烘焙。點功能表Edit -> Preferences,在GI Cache下點Clean Cache按鈕清除暫存。

4.使用預計算即時GI

在Hierarchy視圖選地表資源,並將它們設為靜態物件,以便使用預計算即時GI。對於非靜態資源可以使用光照探頭(Light Probes),後面會說明。

現在打開Lighting介面,啟用Auto Build。並等它算完(右下角會顯示藍色進度條)。代表地形正在預計算即時全域光照(Precomputed Realtime GI)或反射光照(Bounced Lighting)。

啟用Auto Build後,當場景發生改變時,例如移動地形,場景會自動重算即時全域光照。

5.確保將顏色空間(Color Space)設為線性(Linear)

點功能表Edit > Project Settings > Player,將Other Settings下的Color Space設為Linear。這裡設為Linear效果會比Gamma更佳。

6.更改光照設定

選取場景中的定向光(Directional Light),將它拖到Lighting介面Scene頁籤裡的Sun欄位,作為一個太陽光。
當使用一個程式化的天空盒時(Procedural Skybox),你可以指定一個方向光來表示“太陽”的方向(或是照亮場景的大型遠距離光源)。如果沒設定該值,則場景中最亮的方向光將被指定為太陽光。

另外,將Ambient Source改為 Color,並設定顏色為您喜歡的顏色(本例使用#896262)。它用於改變場景中的環境光顏色。

在Hierarchy視窗中選擇剛剛的太陽,將它依照X和Y軸旋轉來改變場景時間和氛圍。
調整光線和陰影直到效果滿意。

7.增加光照探頭

非靜態(移動的)物件使用光照探頭(Light Probes)來獲取反射光(預計算即時GI)資料。場景中所有的樹、石頭、蘑菇上都使用了光照探頭,即使它們靜止不動。最好為小物件使用光照探頭以提高效能,包括記憶體耗用和全域光照構建時間。

在Hierarchy視窗依次點Create > Light > Light Probe Group。

選Light Probe Group並將它移到場景資源(樹、石頭、等等)所在的地方。並將光照探頭放在地面,以確保所有物體都將受到預計算即時全域光照的影響。

選Light Probe Group,啟用Edit Light Probes模式。

在檢視面板點擊按鈕進入編輯模式,或滑鼠左鍵點光照探頭上黃色的球來自動啟用Edit Light Probes模式。同時按住SHIFT鍵可以複選多個部分。

這裡選一面的所有光照探頭,將它們移動到資源(樹、石頭等等)的邊界位置。按這種方式移動了所有面,讓主場景的非靜態資源如樹、石頭等等,都在光照探頭組內部。因此,它們可以通過預計算即時全域光照獲得地面的反射光。

在正交視圖(Orthographic View)下選擇整面的光照探頭,複製(按住CTRL+SHIFT)並移動幾次,以覆蓋主場景的所有非靜態資源。

複製並移動光照探頭後,整個Light Probe Group就完成

刪除或移動多個光照探頭還有其它方式,但上面這種方式可用於儘快創建示例場景。在層級視圖中取消選擇Light Probe Group,光照探頭會變灰色

接下來測試光照探頭如何運作,例如有多少光從地面反彈到石頭上。通常禁用光照探頭組Gizmo會有助於更好地觀察場景。在場景視圖中的Gizmos下拉式功能表中取消勾選LightProbeGroup。

現在可以透過檢視面板上關閉或啟用Light Probe Group組件來測試光照探頭組的效果。可以清楚看到石頭從地面上反射出綠光。這是預計算即時全域光照對靜態地形和使用光照探頭組的非靜態物體所帶來的影響。

如何為低多邊形場景設置後處理


1.首先導入後製包資源
選擇Assets > Import Package > Effects。

或是可以從Unity官網下載標準資源包,導入所需的特效資源。將Image Effects導入到項目中。

在鏡頭上加上後處理圖像特效

加入你想要的特效,如果想瞭解每個效果是甚麼功能,也可以觀看另一個教學

1)加上Screen Space Ambient Occlusion,在Hierarchy視窗選Main Camera,在檢視面板中點Add Component,加上Screen Space Ambient Occlusion組件。

可以調整設定以讓場景效果更好。可以參考原文的設定。 

2)加上Global Fog元件,點Add Component按鈕並搜索Global Fog。

另外,Lighting介面Scene頁籤下的Fog,以設定Global Fog Color和其他霧效屬性。

3)加上Depth of Field組件,點Add Component並且搜索Depth of Field(Lens Blur,Scatter…)

調整設定讓場景效果是你要的。比如Depth of Field設定焦點在篝火上。

4)加上Tonemapping組件

5)加上Antialiasing並保持預設值。

6)加上Bloom,我通常將Threshold設為0.6。

7)加上Color Correction (3D Lookup…),並保持預設值。

8)加上Vignette and Chromatic Aberration。調整到想要的效果

3.設定 Color Correction Lookup Texture

拍一張螢幕抓圖,打開Photoshop貼上剛剛的圖。然後調整圖像的Adjustments到想要的場景氛圍。可以嘗試一些不同的感覺,作者只稍微改變一點亮度/對比度和色彩平衡

調整好後,需要匯入Color Lookup Texture到Photoshop中的截圖上。可以在Unity專案中找到Color Lookup Texture。在Assets > Standard Assets > Image Effects > Textures目錄下有名為Neautral3D16的貼圖。

如果找不到該圖,請確保之前有按照我的流程從Standard Assets資源包匯入了Image Effects。

導入Color Lookup Texture到Photoshop後,確保貼圖層級在Image Adjustment Effects的下方。

然後選擇Color Lookup Texture並用Crop Tool進行裁剪,僅保留該貼圖。

選擇File -> Save As…將檔存為.PNG格式(任和名稱)用於Unity專案,也可以放在新資料夾。

現在打開Unity,在層級視圖選中Main Camera,在檢視面板中打開Color Correction Lookup (Script)。將剛剛存的Color Lookup Texture拖拽至” None (Texture 2D)”的方形區域上,並且點擊“Conver and Apply”。

你就會看到調整後的效果。

@註:Unity有釋出新版本的後製工具,會比Unity內建的後製工具效能好很多,也支援VR。建議學習過這個流程之後,也可以換成新的後製工具。要注意的新的工具設定流程是完全不同的。

2017年4月24日 星期一

Asset Bundles與Resources的記憶體大對決

作者:Ryan Caltabiano 原文
潤稿:Kelvin Lo

*連結全部導向英文頁面

近期有不少Unity開發者詢問有關Asset Bundle和舊的資源系統Resources的相關問題:為何Asset Bundle載入Asset時消耗的記憶體要比Resources多。


首先說明事實並非如此。或者說從長遠來看,如果用好Asset Bundle中一些Resources系統所沒有的新特性,Asset Bundle的記憶體消耗會小的多。如果您不熟悉Asset Bundle,可以參考Unity手冊Asset BundleResources指南

根據我們收到的一些Bug回報,基本上說的都是同一件事情:當從一個Asset Bundle載入某個Asset時,記憶體使用量會衝高好幾MB,但在使用Resources時卻沒發生這樣的情況。我們嘗試重現這些問題時,看到的結果也非常相似:啟動時記憶體正常,載入Asset後記憶體激增,並且不會回到原來的水平。

Asset Bundle的記憶體使用情況



Resources的記憶體使用情況




下面我們就透過記憶體系統就關聯關係、資料保存方式、記憶體使用量的含意和記憶體使用效率幾個方面來分析一下這個問題的原理。注意:本文使用的Unity版本為Unity 5.5.0f3。

Unity的原生記憶體系統會使用1MB到32MB(平均1MB到4MB)之間幾個不同固定大小的塊記憶體分配器。具體大小根據分配的工作類型而定,例如主執行緒還是背景執行緒或根據當下執行的平台而定。

保留總量(Reserved Total)是作業系統分配的所有區塊的總量,已用總量(Used Total)是其中正由Unity使用的記憶體量。每個區域標籤、FMOD、Porfiler等等,表示系統列出的相應分配器或大概的外部記憶體。這些區域標籤資訊在Memory Profiler手冊頁面中可以查詢。有幾點手冊上沒說明的,像是Used and Reserved totals並不包含FMOD數值(Unity 5.5.0f3),
這問題我們已經有個修復更新。

總系統記憶體使用量(Total System Memory Usage)是由平台系統提供的虛擬記憶體大小,不支援此功能的平台會顯示為0。最後,已用總量並不包括物件的標頭(Header)或位元組對齊(byte alignment),但會保留總計。因此,要對比Asset Bundle和Resources間的記憶體使用量,我們會主要針對已用總量和保留總量中的Unity區域標籤。

另外,瞭解Asset Bundle以及Resources資料在磁碟上的保存方式對於理解分析器的原理也十分必要。

Resources和Asset Bundle在底層資料結構上非常相似,它們都有一個用來存放每個物件序列化資料的檔,一些用來有效率非同步載入的額外資源檔(貼圖、音效等),以及一個包含序列化物件Asset檔路徑
映射表。Asset Bundle將這些檔都打包在一個壓縮包中,映射表存在Asset Bundle物件的序列化資料中。Resources將映射表保存在一個名為ResourceManager的全域單例(global singleton)中,其他檔則散落在磁碟各處。此外,與Resources系統不同的是,Asset可以分散在不同的Asset Bundle中,因此可以透過載入資料子集來最大限度地減少記憶體使用量。關於Asset Bundle的內部結構資料可以參閱Unity手冊

Asset Bundles



Resources




瞭解Unity的記憶體與檔案系統之後,我們再詳細解釋下這些記憶體使用量數值的意義。

第一個凸顯的問題是Unity用於Asset Bundle的保留區域多了10MB,而用於Resources系統的則沒有增長。這是為什麼呢?這主要是因為前面提到的區塊記憶體分配器。就這個特定的記憶體使用率測試而言,我們使用的是非同步載入API 協程(Coroutines)的AsyncBundleLoader.cs行為。值得注意的是,這種組合實際上會使用不同的區塊記憶體分配器,這些區塊記憶體分配器在此時其實尚未被使用。所以,10MB的占用源於兩個區塊記憶體分配器初始化區塊的行為,其中一個區塊記憶體分配器需要為新物件分配更多記憶體,所以它分配了一個4MB的區塊。兩個新分配器中,一個為Asset Bundle非同步載入分配了一個2MB的區塊,另一個則為類型樹(type trees)分配了一個4MB的區塊。這些區塊的大小是專為同時載入多個Asset與Asset Bundle而優化的。例如,您可以同時從4 - 5個Asset Bundle中載入物件,而無須為Asset Bundle非同步載入或需要新塊的類型樹創建新的分配器。當然,這具體還是要根據Asset Bundle的大小,採取的壓縮方式,以及這些包中所使用的唯一腳本類型的數量而定。

在這些分配的區塊中,用於類型樹的4MB區塊,僅在從Asset Bundle載入物件時使用。操作完成後這個塊理論來說會釋放。但是,由於範例中構建協程的方式,導致AssetBundleRequest物件一直處於作用中,沒有被垃圾回收器(GC)清除。而用於Asset Bundle非同步載入的2MB區塊,它是讀取Asset Bundle檔案時的緩衝區,在沒有包的內部引用後會被釋放。最後的4MB區塊的使用者是負責我們所有物件存儲的主分配器,因此不會被釋放。

通常在一個專案中,物件的建立/刪除非常頻繁,我們會使用記憶體池來重用記憶體而非將它釋放回分配器。觀察最終卸載後保留區域的Unity數值時,您會發現使用Asset Bundle(64.1MB)與使用Resources系統(63.3MB)的差異很小,和區塊記憶體分配器獲得新塊的順序有關。

我們一直都在討論保留記憶體,那Asset Bundle與Resources之間的保留記憶體實際使用效率差別又有多大呢?

這個問題非常簡單,因為已使用中的Unity區域已經告訴了我們答案。使用Asset Bundle時,佔用了保留記憶體中的21.7MB,而使用Resources時稍多,大概在22.2MB。此外,在卸載時,這個記憶體數值分別下降為20.7MB和21.2MB。所以,很顯然Asset Bundles是記憶體利用效率方面的贏家。

你可能已經注意到,Asset Bundle的使用量在卸載後要比它啟動時更大(4.4MB)。這是因為前面提到的記憶體池的關係,因此如果您重新載入Asset Bundle與Asset,它將會回到21.7MB。而對於Resources來說,啟動與卸載時的記憶體差異來自四捨五入誤差。對於Asset Bundle的塊記憶體分配可以減少,但是要犧牲效能與向下相容性。正如上面提到的,為了滿足載入物件所需的記憶體量,所以必須要分配4MB大小的區塊。剩下的6MB中,2MB用於了非同步載入API。

因此,為了防止區塊記憶體分配,只要犧牲FPS使用同步API即可。最後的4MB是源於類型樹的分配。這個系統會在Asset Bundle裡儲存Resources系統中沒有的額外資料,這些資料使Asset Bundle可以相容更多版本的Unity,並使諸如FormerlySerializedAs這樣的序列化特性正常工作。這使您可以在升級到更新的Unity版本後依然能使用相同的Asset Bundle,或只需修改少量程式,而不是重新打包,導致開發者因為更新必須重新下載整個Asset Bundle。向BuildPipeline.BuildAssetBundlesAPI傳入BuildAssetBundleOptions.DisableWriteTypeTree選項,可以禁止寫入這個額外資料。

無類型樹Asset Bundle同步載入



您可以用Asset Bundles 1, Resources 0試試。如果您想產生自己的資料,這個範例專案已經上傳到了 Github 上。目前它設定為亂數取樣每種建立100個:Textures、Monobehavior、Prefabs,所以在自己機器上每次執行都會產生同樣的輸出(但會和別人不一樣)。請確保你的Asset Bundle專案沒有意外包含了一個有內容的Resources資料夾,否則您的記憶體將會比預期高出兩倍。試試將每類資源的數量提高至300或甚至500個測試。

2017年4月17日 星期一

Draw Call未被批次處理?告訴你在Unity 5.6中如何找出原因

作者:Valentin Simonov 原文
潤稿:Kelvin Lo


相信各位都知道Unity內建動態和靜態批次處理,能有效地降低Draw Call的數量。

當你查看Stats介面時,如果看到一個標為"Saved by batching"的值,它就是用來顯示批次處理的次數。可惜它很難用來反推為何批次沒有被處理。儘管Unity手冊裡有說明可能的發生原因,但要理解這些資訊需要開發者已有相關的基礎知識。

好裡加在,Unity5.6 在Frame Debugger中新增了一項功能,能解釋這些批次資訊。

Frame Debugger是Unity 5.x推出的功能,你可以點功能表的Window > Frame Debugger 來打開Frame Debugger。它能顯示遊戲中所有的批次處理資訊,以及這些批次處理的所有細節資訊,包括著色器、貼圖及批次處理所用的大量資訊等。

在詳細介紹Unity何時發起新的批次處理之前,我們先來瞭解批次處理的概念及作用。

Unity 5.6中的Frame Debugger,這裡說明為何Unity要發動批次處理



批次處理 - Batch


Unity為了在螢幕上繪製物件,它需要向圖形API發起一次“繪製”命令,就是一次“Draw Call”。但在發起命令之前,Unity還需要為繪製的物件設定所需的GPU狀態:網格、著色器、貼圖、混合設定以及一些其他的著色器屬性。而狀態改變命令再加上一個或多個繪製命令就稱為一次批次處理(a Batch)



批次處理過程 - Batching


導致批次處理緩慢的原因就是改變GPU狀態的指令,而繪製指令實際上僅佔用很少的資源。所以Unity總是試圖利用同一個GPU狀態同時渲染多個物件。這一過程被稱為批次處理(Batching)。


Unity提供三種類型的批次處理:靜態批次處理(Static batching),動態批次處理(dynamic batching)以及GPU Instancing。

  • 靜態批次處理會在構建時將多個靜態網格物件合併為一個或多個大的網格物件,然後在運行時一次批次處理渲染一個大網格中的多個物件。
  • 動態批次處理在每幀中獲取多個小型網格物件,在CPU中對其進行頂點變換,將相似的頂點組合到一起,然後一次繪製它們。
  • GPU Instancing(Unity 5.3導入)可以利用少量Draw Call繪製多個具有不同的位置、旋轉以及其他著色器屬性的相同物件。


導致批次處理失敗的原因


有時在編輯器中可以清楚地看到,一些本應被批次處理的物件出於某些原因沒有被批次處理。首先,請檢查Player Settings中是否啟用批次處理功能。這個步驟看似多餘,但我們遇到太多的無法處理的原因都是因為忘記開啟。

我們專門為此提供了展示專案來演示Unity在什麼情況下必須發起新的批次處理請求。首先下載專案並複製到Unity專案中。請注意,你需要安裝Unity 5.6才能看到Frame Debugger中關於批次處理狀態的說明。


以下是展示專案(Unity 5.6)中導致無法進行批次處理的原因。每個原因對應一次單獨的批次處理:

  • Additional Vertex Streams — 物件使用MeshRenderer.additionalVertexStreams設定了額外的頂點資訊流。
  • Deferred Objects on Different Lighting Layers — 該物件位於另一不同的光照層中。
  • Deferred Objects Split by Shadow Distance — 兩個物體中有一個在陰影距離範圍內而另一個不是。
  • Different Combined Meshes — 該物件屬於另一個已合併的靜態網格。
  • Different Custom Properties — 該物件設定了不同的MaterialProperyBlock。
  • Different Lights — 該物件受不同的前向光照(Forward Light)影響。
  • Different Materials — 該物件使用不同的材質。
  • Different Reflection Probes — 該物件受不同的反射探頭(Reflection Probe)影響。
  • Different Shadow Caster Hash — 該物件使用其他的陰影投射著色器,或是設定了不同的著色器參數/關鍵字,而這些參數/關鍵字會影響陰影投射Pass的輸出。
  • Different Shadow Receiving Settings — 該物件設定了不同的“Receive Shadows”參數,或是一些物件在陰影距離內,而另一些在距離之外。
  • Different Static Batching Flags — 該物件使用不同的靜態批次處理設定。
  • Dynamic Batching Disabled to Avoid Z-Fighting — Player Settings中關閉了動態批次處理,或在當前環境中為避免深度衝突而被臨時關閉。
  • Instancing Different Geometries — 使用GPU Instancing渲染不同的網格或子網格。
  • Lightmapped Objects — 物件使用了不同的光照貼圖,或在相同的光照貼圖中有不同的光照貼圖UV轉換關係。
  • Lightprobe Affected Objects — 物件受其他光照探頭(Light Probe)影響。
  • Mixed Sided Mode Shadow Casters — 物件的“Cast Shadows”設定不同。
  • Multipass — 物件使用了帶多個Pass的著色器。
  • Multiple Forward Lights — 該物件受多個前向光渲染影響。
  • Non-instanceable Property Set — 為instanced著色器設定來non-instanced屬性。
  • Odd Negative Scaling — 該物件的縮放為很奇怪的負值,例如(1,-1,1)。
  • Shader Disables Batching — 著色器使用“DisableBatching”標籤顯式關閉了批次處理。
  • Too Many Indices in Dynamic Batch — 動態批次處理索引過多(超過32k)。
  • Too Many Indices in Static Batch — 靜態批次處理中的組合網格索引過多。對於OpenGL ES來說是48k,OSX是32k,其他平台是64k。
  • Too Many Vertex Attributes for Dynamic Batching — 欲進行動態批次處理的子網格擁有超過900個頂點屬性。
  • Too Many Vertices for Dynamic Batching — 欲進行動態批次處理的子網格頂點數量超過300個。

結論


現在可以開始使用Frame Debugger新功能來檢查你的專案,看看是否能找到可以優化的線索。隨著引擎不斷更新,將來也會加入更多批次處理的資訊。

這裡是展示專案的Github,如果你有興趣也可以關注一下。

2017年3月17日 星期五

Unity推廣活動 - 加速開發包


活動日期:2017/3/13 起至 2017/5/28 止。

內容:在活動期間訂 Unity Plus 或 Unity Pro至少一年,每一套即可免費獲得一套價值 190 美元的“加速開發包”,裡面包括Asset Store裡非常熱門的三個套件:


Playmaker




Amplify Shader Editor




Ultimate FPS




注意事項:

 以原價或正常經銷/代理價格購買授權才享有此活動優惠。如不符合此資格請洽原廠以個案處理。

 一套對應一個兌換券碼,兩套即可有兩個兌換券碼,依此類推。

 客戶拿到
兌換券碼之後需於 30 天之內自行到資源商店下載 套件使用,過期失效。

 活動期間下訂單時,請註明是否需要
兌換券碼,如註明需要,會在出貨時一併附上。


FAQ

Q: 如果我購買後,資源發行商更新了版本並要求我為更新付費怎麼辦?
A: 如果資源發行商更新了資源,並且您想要獲得最新版本,則請到 Asset Store上付費購買。

Q: 可以將加速開發包給朋友或同事使用嗎?
A: 在未使用兌換卷之前,可將其贈予朋友或同事。一旦使用成功,就不能再次使用,也不能轉贈給其他人。

Q: 可以退掉加速開發包裡的某幾個不喜歡的套件嗎?
A: 很抱歉,無法辦理退貨。如果對某個套件有使用疑問,請直接聯繫該發行商。

Q: 我已擁有其中幾個套件,是否可以換成其他套件?
A: 很抱歉,本次活動無法替換或自由組合。

Q: 我在兌換加速開發包或下載套件的時候遇到了問題
A: 如果您在兌換加速開發包或下載套件的時候遇到問題,請發送郵件(英文)至:mikeg@unity3d.com 尋求協助。

Q: 我會受到 Unity Plus 或 Unity Pro 使用者條款的規範嗎?
A: 是的,您會受限於標準使用者條款的規範,並且在合約期截止前不能取消訂閱。

Q: 這個資源商店 Asset Store 的兌換券代碼是否會過期?
A: 會的,您需於 30 天內使用並下載套件。

Q: 我的訂單包括多少份加速開發包?每個加速包裡有幾個套件?
A: 一套 Unity Pro 或 Unity Plus 對應一個加速開發包,每個加速包裡含下列三個套件:


一個 Playmaker
一個 Amplify Shader Editor
一個 Ultimate FPS

關於我自己

我的相片
Unity台灣官方部落格 請上Facebook搜尋Unity Taiwan取得Unity中文的最新資訊