2015年6月28日 星期日

Unity 5光源運作淺談

翻譯:Kelvin Lo

近代的遊戲大量的採用”全域光照”技術
全域光照(Global illumination,簡稱GI),是一個用來模擬光的互動和反彈等複雜行為的演算法,要精確的模擬全域光照非常有挑戰性,付出的代價也高,正因為如此,現代遊戲會先一定程度預先處理這些計算,而非遊戲執行時即時運算

同一場景裡:沒有照明(左),只有直接光源(中),和有間接光源的全域光照(右)的表現,可以注意到顏色如何在不同的表面進行光的”反彈”,產生更真實的結果

在本文中,我們會描述全域光照如何在Unity裡運作,帶領你透過不同的照明技術解釋如何在專案裡設定照明,並思考如何透過各種工具幫場景打光

選擇一個照明技術
廣義的來說,Unity的全域光照是”即時”或是”預先計算好”的,在某些情況下兩種方法可以結合使用,照出更逼真的場景

本節我們會針對兩種技術的差異優勢和使用時機做個簡單的描述

即時照明(REALTIME LIGHTING)
預設情況下,Unity的燈源(直接光源, 投射燈, 點光源)都是即時的,代表這些燈源會把光線照射到場景並以每一幀的頻率更新,由於光源是可以在場景內移動的物件,場景燈光的更新是即時的,你可以在遊戲視窗和場景視窗看到改變


即時照明的影響:注意到因為沒有反射光源的關係陰影是全黑的,只有投射光錐體範圍內的物件表面才有光源影響

即時照明是場景裡照亮物體最基本的方法,用來照亮角色和會動的物件,可惜的這種照明的光線不會反射,因此我們才導入了全域光照系統,啟用了預先計算的技術,都是為了表現一個更逼真的場景

烘焙全域光照(BAKED GI LIGHTING)
當烘焙一張光照貼圖(Lightmap)時,場景內的靜態物件會基於光的影響算出一張貼圖成果,並疊在場景物件之上建立照明效果

左:一個簡單的光照貼圖場景成果,右:由Unity算出的一張光照貼圖(陰影和光源資訊都被納入計算)

這些”光照貼圖”可以包含場景內投射到物體表面的直接光源,以及在不同物體間反射的”間接光源”,這樣的光照貼圖可以透過物體材質上的著色器(Shader)描述像是顏色的表面資訊(Albedo)和凹凸(Normals)資訊

烘焙光照所產生出來的貼圖,是無法在遊戲運作的時候變更運算的,因此被定義為靜態(Static),雖然仍可在這層貼圖上繼續疊加光源計算,但兩者已無法交互運算

通常我們採用這光照法來讓低階的手機能順利執行,解決光在遊戲中運行的效能問題

預計算全域光照(PRECOMPUTED REALTIME GI LIGHTING)
雖然傳統的靜態光照貼圖無法在遊戲執行時改變場景光照條件,但預先計算的即時全域光照系統能幫我們即時運算複雜的場景光源互動

透過這種方法,就能建立昏暗的環境帶有豐富的全域光照反射,並即時反映光源的改變,好比做個日晷,陰影的位置和顏色會隨著光源移動改變,這在原本的烘焙光照系統是無法達成的 

一個用GI呈現的日晷案例
為了在合理的幀率實現這些效果,我們需要在即時運算之前先將一堆壟長的數字資料做”預計算”,預計算負責計算遊戲過程中光的複雜行為,它可以在時間空檔時進行計算,我們稱作一個”離線”運算

如何運作?

最常見的需求是我們希望間接光源能夠列入場景光照貼圖的計算,幸好,原理上這些間接光源都是從直接光源慢慢轉變過來的顏色,只有少部分特定情況有比較大幅度的顏色改變,這樣的Unity的全域光照預計算,利用間接光源漫反射(diffuse)特性對運算有利

通常好的陰影是透過即時光源所計算出來的,而非烘焙到光照貼圖,假定我們並不需要太複雜的細節取樣,可以大大降低全域光照所產生的資料大小

透過預計算來簡化整個流程,有效的降低了原本要在遊戲中即時計算的全域光照運算數量,如果你要常在遊戲中改變光源顏色,旋轉光源或是調整光的強度,甚至對場景表面做變更,這點就很重要

Unity從表面上採樣底層貼圖,並從廣義定義顏色的值到一個大型的群組,或是”叢集”,這會產生一個低解析的模擬靜態幾何,以方便我們用來計算光照


左:場景預覽設定為”Albedo”時,可以清楚看到由Unity預計算所產生的叢集,右:如同遊戲的場景一樣,即時光照計算完結果後套用到場景

基本上,當在計算全域光照時,我們會針對靜態場景周圍做”光跡追蹤”運算,這是非常耗效能的,因此無法苛求要即時運算,相反的,Unity把光跡追蹤用在計算這些表面的叢集關係 - 在預計算”光傳輸”的階段,然後把世界串成一個網路結構,我們在關鍵性的遊戲過程就不再需要耗費效能的光跡追蹤法

我們有效的創造出一個簡化的演算法可以在遊戲過程中變化輸入結構,這代表我們可以改變光源或是表面顏色,並很快的看到場景內全域光照的影響,算出的結果產出光照貼圖透過GPU著色,並和其他照明或是表面混合,最後輸出到螢幕上

效益和成本(BENEFITS AND COSTS)
雖然能同時使用烘焙GI和預計算GI,但要注意是同時模擬兩個系統,效能負擔也會兩次運算的總和,不只是因為要儲存兩套光照貼圖在顯卡記憶體(VRAM),同時著色器也得付出兩次的處理成本

最終要選擇哪個方法還是要取決於你專案的性質和預期的硬體考量,例如在手機平台上,效能和記憶體限制較高,烘焙的GI法就會比較適合,如果是在有顯卡的電腦上或是遊戲機上執行,那可能使用預計算即時全域光照,或兩個同時使用就比較可行

決定採用哪一種方法可以針對你的目標平台評估,記得如果專案要同時符合幾個不同的硬體需求,往往都是以效能最高的平台為考量

預先計算的過程(THE PRECOMPUTE PROCESS)
在Unity裡,預計算是在背景執行,不管是自動流程或是手動啟用,計算期間你都可以繼續編輯你的遊戲物件而不受影響,預計算的時候會在右下角出現一個進度條,不同的演算法會有不同的運算階段,進度條上方也會顯示階段名稱與進度

進度條顯示Unity預計算的進度


從上面的例子可以看出,11個工作已經進展到第5個,”叢集”還有108件工作要執行完才會到第6個工作階段,數值狀態列表如下:



啟用一個預計算(STARTING A PRECOMPUTE)


只有靜態物件會被納入GI預計算,要讓預計算啟動首先必須最少要有一個靜態物件,不管是單獨設定物件或是從層級選單用 Shift + 選擇多個物件後一次修改

從屬性面板,將物件的Static勾選起來,這會將該物體所有跟靜態物件相關的旗標打開,包含導航旗標或是批次處理旗標,這或許不是你想要的,針對預計算只要把”Lighting Static”這個旗標打勾即可

更細部的控制,只要點選屬性介面Static右邊的下拉式選單即可,此外,從Window裡的Lighting介面也能指定設定靜態物件

如果你的場景設為自動(Lighting->Scene->Auto),Unity的預計算就會自動啟動,否則就需要用下列的流程手動執行

自動/手動 預計算(AUTO/MANUAL PRECOMPUTE)
假如Lighting介面底下Auto這個選項是被勾選的(Lighting->Scene->Auto),那麼預計算就會自動在背景不停的改變場景產出的靜態幾何

但如果這個勾選沒勾,你將需要點擊在Auto旁邊的”Build”按鈕手動啟動預計算,這會用同樣的方式進行預計算,讓你比較好控制計算的開始時間

手動啟動預計算會對場景所有的照明與各方面進行重新評估並重新計算,如果你希望有選擇性的計算,可以從”Build”旁邊的下拉選單來選擇

使用烘焙GI或是預計算GI(ENABLING BAKED GI OR PRECOMPUTED REALTIME GI)
預設情況下,兩種計算法在Unity裡都是啟用狀態(Lighting->Scene),因為如此,你可以針對單獨的光源設定要採用哪種計算法(Inspector->Light->Baking)

在一個場景同時採用兩種方法可能會對效能造成負擔,最好的做法在同一個時間只用一種方法,要關閉任何一種方法可以從GI的介面(Lighting->Scene),把不要用的方法取消打勾即可,只有有打勾的算法會計算,任何相關光的設定都會被覆蓋

預燈光設定(PRE-LIGHT SETTINGS)
Unity裡每盞燈光預設的烘焙模式都是”Realtime”,這代表這些燈光仍然會照亮你的場景,Unity的預計算GI系統會處理間接光

但如果預設的烘焙模式是”Baked”,那麼這些燈光將會透過Unity的烘焙GI系統處理直接光源和間接光源,產生出來的光照貼圖一旦貼到場景上在執行期間是不能改變的


一個設定烘焙模式為”Realtime”的點光源

選擇烘焙模式為”Mixed”的話,場景內的靜態物件會被烘焙GI拿去做計算,然而,不像”Baked”模式,混合模式的燈光仍會繼續運算即時光源到非靜態物件上,這對於你想把靜態環境烘成光照貼圖,但同時又希望同樣一盞燈能為會動的角色計算陰影很有幫助

GI快取(GI CACHE)
無論是烘焙還是預計算系統,Unity會”緩存”場景的光照資料到”GI快取”,並會在計算時嘗試重複運用這些數據來節省時間,你對場景的改變會影響這個數據重複利用的多寡

如果你要清除這個快取可以從(Preference->GI Cache->Clear Cache)來清除,清除後代表所有資料都必須重新運算,因次會花費一些時間,在某些情況下你也許需要降低檔案空間大小(例如要把專案轉到另外一台電腦)是有幫助的

場景設定(SCENE SETUP)
選擇著色路徑(CHOOSING A RENDERING PATH)

Unity支援許多渲染技術或”路徑”,在啟動一個專案時,必須要訂出出一個路線,Unity預設是”Forward Rendering”

在”Forward Rendering”裡,每個物件渲染是根據每個影響物件的光,透過”Pass”來渲染,所以有可能一個物件被重複渲染了好幾次,取決於有幾盞燈在作用範圍裡

這種方法的優點是快速,也代表硬體需求低,此外,這種正向渲染提供了廣泛的自訂”著色模型”,可以快速處理透明度,也支援像是多重採樣柔邊(MSAA)的硬體功能,等等有些在其他路徑上是無法實現的功能,對於圖形品質有很大的影響

然而他的缺點是要為每盞燈光付出相對應的成本,也就是說,物件被越多盞燈光影響,花費的運算成本就越高,有些類型的遊戲必需要大量的光源,就會令人望之卻步,反觀如果你能管理好你的燈光數量,那這個路徑會是一個非常快速的解決方案

“Deferred”路徑,是延遲了光的遮蔽與混合資訊直到第一次接收到的表面的位置 法線 以及材質資料渲染到一個”幾何緩衝器”(G-buffer)作為一個螢幕空間的貼圖,最後合成這些結果,這種方法優點是照明的渲染成本是和像素數量成正比,而非燈光數量,因此你不用再管控場景燈光數量,某些遊戲類型將會是一個關鍵優勢

“Deferred”路徑呈現可預見的效能特點,但通常需要較強大的硬體,對於手機平台支援度也較低

關於”Deferred”,”Forward”或是其他路徑的更多資訊,可以參閱這裏(英文連結)


選擇一個色彩空間(CHOOSING A COLOR SPACE)
除了要選好渲染路徑之外,一開始選擇一個”色彩空間(Color Space)”也是很重要的事情,色彩空間決定採用哪種演算法來計算照明或材質載入時的顏色混合,這會對遊戲的畫面真實感有很大的影響,但大多數情況下,太超過的色彩空間設定可能會被目標平台的硬體強制限制

推薦比較接近真實的色彩空間是Linear,你可以從Player Setting裡面找到”Color Space”來設定(Edit->Project Setting->Player)

設定Linear的優點是會讓場景內的提供給著色器的顏色也會因為光強度增加變亮,如果換成”Gamma”色彩空間,亮度馬上會轉為以白色做為參考,這將不利於圖像的品質


採用Linear和Gamma顏色空間的圖像對照表,可以注意到切換成Gamma時顏色快速的換成以白色為光照強度為基準

Linear另一個好處是著色器能在沒有Gamma補償的情況下對貼圖進行取樣,這有助於確保顏色品質在經過渲染管道還能保持一致性,能提高色彩和計算的精度,最後螢幕的輸出結果更為真實

可惜的是Linear顏色空間有些手機平台甚至有些遊戲機不支援,應該說PC或是一些新手機硬體和次世代遊戲機才會支援Linear顏色空間,在這種情況下,就得用Gamma方法替代了

確認你的目標平台是哪一種之後才選擇適合的顏色空間是很重要的,想要了解更多關於顏色空間的資訊,可以參閱這裏(英文)


高動態範圍(HDR)
如同顏色空間,相機的”Dynamic range”動態範圍是需要指定的,從本質上來說,這是用來定義相機截取的最亮與最暗的顏色範圍,要啟用HDR可以從相機元件裡把HDR項目打勾即可,要注意的是,某些手機硬體是不支援HDR的,在渲染路徑為Forward Rendering並啟用MSAA柔邊時也不支援HDR

HDR和Linear顏色空間一起搭配是最好的,在處理非常明亮的顏色的時候還能保有準確性

在預設的情況下,Unity是使用低動態範圍(LDR),顏色會以每個頻道8位元儲存三原色(紅 綠 藍),8位元確切的意思是用8個0或1的數字組合成的值,一共會有256種組合,三個頻道256 x 256 x 256可以組合出1600萬種色採來表達從黑色到白色不同層度的灰階

在現實環境中,顏色遠遠超出1600萬個色階,顏色和亮度的排列甚至遠遠超越了我們人眼所能辨識的,同樣的,Unity能夠處理超出這些範圍的顏色並輸出高於LDR設備支援的顏色,用來提供給像是電腦畫面的高品質結果,儘管現今輸出裝置仍有很多限制,這些多出來的數值仍然可有很多的應用

啟用HDR之後,會儲存更大精度的顏色資料(使用浮點運算表示),能處理更多更亮的顏色範圍

HDR能讓我們在同一畫面下維持兩個巨大差異的亮度,例如場景的陰影區域和戶外的亮光,我們也可以在環境場景建立一個”光暈”或發光特效,透過這些特效或可視的光影效果能增加畫面的真實感,但也要小心處理這些效果以防止他們曝光過度

色調映射(TONEMAPPING)
以攝影來比喻的話,如果我們使用不同的曝光度來拍攝我們的場景,我們可以先觀察到哪些顏色會因為曝光過度而遺失,淺色調在很亮的區域可能被白色給覆蓋過去,暗色調可能會被黑色給覆蓋,這像是電腦圖學的”色調映射”,當顏色在顯示設備(比如電腦螢幕)表現範圍之外時,演算法會將顏色修正為裝置合理的顏色並重現在螢幕上

當相機啟用HDR時,建議相機加入Tonemapping這隻程式(Assets->Import Package->Effects),這隻程式可以幫你轉換控制超出範圍的顏色成為合理的顏色

更多關於色調映射的資料可以查看這裡(英文)


環境光(Ambient Lighting)
場景中一個照亮整體環境非常重要的就是”環境光”,可以說是影響場景光源最全面的一個要素

環境光很多情況都很有用,也取決於你所選的風格,比如卡通風格的陰影不清楚或燈光是手繪風格,環境光也很適合用在當你不想單獨調整場景內的燈光但又想要增加整體場景亮度的時候

在沒有使用Unity 5全域光照的功能時,環境光不會算出準確的物理遮擋,但如果開啟了任何一種GI的情況下,從Skybox照下來的環境光就能被算出遮擋,結果會更加真實


在同一個場景之下,左邊是沒有任何光源的場景,右邊則開啟了環境光,可以注意到天空盒的亮度並沒有因為調整環境光的強度而改變 

透過將物件設定為靜態物件來啟用全域光照的場景,可以注意到光線在不同的表面有遮擋的效果

使用環境光的好處是耗用效能很低,因此在手機平台上只要燈光數量控制得宜也能得到很好的表現

你可以從Lighting視窗(Window->Lighting)找到Environment Lighting設定區域(Lighting->Scene),改變Ambient Source來改變環境光來源

Ambient Source的預設值是”Skybox”,上圖的天空盒是Unity 5系統產生的一個預設天空盒,帶有一個藍色調的環境光,以及一些用該半球體上純色與漸變色的設定

要注意的是 改變Lighting裡面的Ambient Source並不會讓Skybox的顏色改變,但會影響到場景內環境光照射

反射源(REFLECTION SOURCE)
預設的情況下,場景中的物件會使用Unity內建的標準著色器(Standard Shader)來渲染,標準著色器是一個基於物理的著色器(PBS),它會透過模仿真實世界的物理特性像是反射或能量傳遞等來模擬真實世界裡光在材質上的表現

當使用標準著色器時,每一個材質都會具有一定程度的鏡面反射(specularity)和金屬反射(metalness)屬性,在沒有強大的硬體來處理即時光跡追蹤反射的情況下,我們得仰賴預先計算渲染反射,我們使用了一個由六張描述天空的圖片所組成的方體貼圖(Cubemap)或從Unity 5用來從定點蒐集環境資訊的反射探頭(Reflection Probe)產生所需的貼圖,然後在和其他光和地表資訊混合運算來模擬如同我們真實世界看到的反射效果



在預設的情況下,調高Specular和Metalness會更清楚的反射天空盒,反射的來源可以從設定調整

場景內的物件在預設的情況下會反射天空盒的內容,但你可以從Lighting介面裡找到Reflection Source屬性來改變來源,指定一個新的方體貼圖,或指定一個反射探頭來定義

反射探頭(REFLECTION PROBES)
天空盒的資訊不可能包含所有的場景物件,在許多情況下,物件從天空蒐集反射資訊時可能會被遮蔽,像是室內物件或是在類似橋或是隧道等建築物裡的物件,為了要準確反射這些物件,我們必須用反射探頭針對這些物件取樣,這種探頭從他們的位置對周圍取樣並把結果寫到方體貼圖,可以讓周圍經過的物體得到環境的反射影像

你可以透過GameObject->Light->Reflection Probe來新增一個反射探頭

反射探頭的位置會決定方體貼圖取樣的內容,以及反射所看起來的樣子,一般來說,基於效能考量反射探頭越少越好,請記住,反射探頭並非用來讓物理得到精確結果,而是讓遊戲世界有更好的反射,大多數情況下幾個安排妥當的反射探頭就很足夠了


左圖:場景只有預設的反射設定 右圖:場景加入了反射探頭後的結果

在反射探頭的屬性面板我們可以設定Type為即時(Realtime),烘焙(Baked)或自訂(Custom),需要明白的是,即時反射的設定對效能極為不利,每多一個即時反射探頭就會多出額外六次的渲染運算,因此擺設即時的反射探頭應該要有明確的需求,例如反射會閃動的霓虹燈,否則一般來說建議設成烘焙的就夠了,效能也會好很多

需要注意的是,只有標記為”Reflection Probe Static”的物件才會被反射探頭取樣,從屬性介面上靜態物件(Static)的下拉選單打勾即可,相反的,即時的反射探頭會對所有可見的物體取樣,除非你在遮罩(Mask)選單指定剔除它


照亮一個場景(LIGHTING A SCENE)
我們已經介紹了一些在Unity裡針對場景照明開始工作之前所需要考慮的條件,希望你對目標平台該用哪些設定已經有了一個方向(手機平台採用烘焙GI和Gamma顏色空間,PC或遊戲機採用即時GI和Linear顏色空間)

接下來讓我們來看看有哪些協助制定光源的工具


定向光源(DIRECTIONAL LIGHTS)
“定向光”非常適合用來模擬陽光,它的特性就像是個太陽,定向光能從無限遠的距離投射光源到場景

從定向光發出來的光線是互相平行的,也不會像其他種光源會分岔,結果就是不管物件離定向光源多遠,投射出來的陰影看起來都一樣,這其實對戶外場景的照明很有利



定向光沒有真正的光源座標,放置在場景任何地點都不會影響光的效果,只有旋轉會影響定向光的照射結果

其他有光源座標的燈光類型,例如投射燈(Spotlights),角色陰影會因為接近或遠離光源而改變,這也許在照亮室內環境時會是個問題,一般來說,避免角色太接近隱形的光源,我們會建立一個亮點來假裝光源

使用定向光不用考慮距離,不管多遠它都會影響場景所有的表面(除非被剔除),當使用延遲(Deferred)渲染路徑時會造成一些效能損耗,要注意的是,使用這個渲染路徑時,光的效能代價和他影響的像素數目是成正比的,但雖然需要消耗效能,起碼結果較為統一,因此比較容易調整平衡

在預設情況下,新的場景都會附帶一盞定向光,在Unity 5裡還會與天空盒系統關聯(Lighting->Scene->Skybox),你也可以刪除預設的定向光並創建一個新的光源,然後從Sun這個屬性重新指定(Lighting->Scene->Sun)

旋轉預設的定向光會導致天空盒也跟著更新,如果光的角度和地面平行就可以做出日落的效果,把光源轉到天空導致變黑就能做出夜晚的效果,從上往下照就會模擬日間的效果

如果天空盒有指定為環境光源(Ambient Source),那麼天空盒的顏色就會影響環境裡面的物件


點光源(POINT LIGHTS)
點光源可以想像是在3D空間裡一個對著所有方向發射光線的點,很適合用來製作像是燈泡, 武器發光或是從物體發射出來的爆炸效果

點光源的亮度從中心最強一直到範圍屬性(Range)設定的距離遞減到0為止,光的強度從光源到距離成反比,這是所謂的”平方反比定律”,類似光在現實世界的行為


點光源從它的位置對四面八方射出光線,球形的小圖示代表光的”範圍”,光線到達此範圍是會”衰減”到0,但如果有間接光源或反射光則會繼續投射

點光源開啟陰影運算是很耗效能的,因此必須謹慎使用,點光源的陰影為了要給六個不同的世界方向會運算六次,在比較差的硬體開啟此功能會造成較大的效能負擔

當在場景中加入點光源時要注意,目前它們不支援陰影的間接反射,這代表由點光源產生的光線,只要在距離內有可能會穿過物件反射到另外一面,這可能會導致牆壁或地板”漏光”,因此放置點光源要格外注意,然而如果是採用Backed GI的話,就不會有這類的問題產生


聚光燈(SPOTLIGHTS)
聚光燈投射一個錐體在他的Z軸前方,這個錐體的寬度由投射角度(Spot Angle)屬性控制著,光線會從源頭到設定的範圍慢慢衰減到0,同時越靠近錐體邊緣也會衰減,把投射角度的值加大會讓錐體寬度加大,同時也讓邊緣淡化的力度變大,這現象學名叫做”半影”


聚光燈有許多用途,他們可以用來模擬路燈, 壁燈, 或許多創意用法,例如模擬手電筒,因為投射區域能精確的控制,因此很適合用來模擬打在角色身上的光或是模擬舞台燈光效果等等


光線會因為離源頭越遠而遞減,可以注意到光也會因為越靠近錐體邊緣而變弱,我們稱之為半影區,這會因為錐體角度變大而更明顯

和點光源一樣,使用預計算GI時,聚光燈不支援間接光陰影,這表示燈光會穿過幾何影響到另外一面,因此放置投射燈要特別注意


區域光(AREA LIGHTS)
區域光可以當作是攝影用的柔光燈,在Unity裡面他們被定義為單面往Z軸發射光線的矩形,目前只能和烘焙GI一起使用,區域光會均勻的照亮作用區域,雖然區域光沒有範圍屬性可以調整,但是光的強度也是會隨著距離光源越遠而遞減


區域光照亮表面並在區間產生漫反射與柔和的陰影

區域光用在建立柔和的照明效果非常有用,光線在任何方向穿過光的表面時會產生不同方向的折射 - 造成在物件上產生漫反射,常見的用途是拿來當作天花板壁燈或是背光燈

為了實現這功能,我們必須從每個光照貼圖像素上發射一定數量的光線,背對著區域光以確定光有能見度,這代表區域光的計算是消耗很大的,而且會延長烘焙的時間,但如果運用得宜可以增加場景光的深度,那麼消耗就很值得,值得注意的是區域光只能用在烘焙,因此不影響遊戲效能


發光材質(EMISSIVE MATERIALS)
雖然區域光不支援即時GI,Unity提供另外一個柔和的燈光效果叫做發光材質(Emissive Materials),和區域光一樣,發光材質可以讓物體表面發光,他們可以反射場景內像是顏色或是光強度等等能在遊戲內改變的光源

自發光(Emission)是一個在標準著色器(Standard Shader)內的屬性,允許靜態物件成為一個發光體,預設情況下是0,代表指定了這個材質並不會有任何的自發光反應,HDR顏色選擇器能指定發光顏色,強度能在0-1的範圍調整,來建立類似區域光的效果

發光材質並沒有範圍屬性,但從材質發出的光會以二的次方速度遞減,自發光材質只會作用在有標記為”Static”或”Lightmap Static”標籤的物件,同樣的,如果發光材質附加在非靜態物件或像是角色的動態物件則不會有任何作用

然而,材質設定只要emission數值大於0,即使他們不接收場景光源在畫面上也會有發光的效果,這種效果也可以透過將emission屬性底下的”Global Illumination”改為”None”,像這樣的自發光材質很適合來模擬霓虹燈等類似的光源


使用Unity標準著色器並附加自發光材質的一個範例,注意從Unity Logo的自發光也會計算陰影,在這個案例讓球體有了陰影

發光材質只會影響場景內的靜態物件,如果你想要影響像是角色的動態物件,就必須採用光探頭系統(Light Probes),在遊戲週期改變發光材質的值會更新光探頭取樣,並直接在結果上看到變化


光探頭(LIGHT PROBES)
靜態物件只被Unity全域光照GI系統計算,為了使動態物件能夠和靜態場景接收到的光影資訊互動,我們需要紀錄這些光的資訊並做成可以在執行期間快速存取的格式

我們在場景放置許多取樣點來截取各個方向蒐集來的資訊,顏色資訊會被編成能在遊戲中快速被取出的一組數值(或係數),這些取樣點我們稱為”光探頭”


使用了光探頭的場景,注意圖中光探頭放置位置在光線容易變化的地方,例如陰影或是顏色轉換的地方
光探頭允許移動物件接受由全域光照GI所計算出來複雜的反射光源,物件在渲染網格的時候會判斷附近光探頭的位置並且把光的資訊一併融合計算,這是透過找尋由光探頭所產生的一個四面體,然後決定哪個四面體的落入物件的軸向,這樣就能讓場景內的動態物件正確地接受光資訊,如果沒有放置光探頭,動態物件就無法接受全域光照的資訊,造成動態物件比場景還要暗

預設的情況下,場景是沒有任何光探頭的,你必須從GameObjects->Light->Light Probe Group自行建立光探頭群組

假如全域光照裡的Auto是打勾的(Lighting->Scene->Auto),當光源或是靜態物件更新時,光探頭資訊也會即時更新,沒打勾的話必須點Build運算才會更新
接下來...
本篇文章大致描述了Unity的光源運作基礎原理,但是還有許多細節沒有涵蓋到,後面還有兩篇文章會陸續推出,請密切注意

2015年6月9日 星期二

Unity 5.1更新大綱

Unity 5.1已發佈了,我們在5.0發佈之後並沒有鬆懈太久便立即投入5.1的更新,想要了解5.1全部的更新請訪問官方Release Notes(英文), 本文會描述一下有哪些重大改變


新功能

Unity 5.1放入了新的網路功能,你可以從官方文件或是UnityEngine.Networking命名空間來查詢更多的細節,透過這個API開發者能從較低階的傳輸層傳輸資料,不管是低階的NAT支援或是高階的遊戲配對都沒問題


未來你可以透過https://multiplayer.unity3d.com

設定你的伺服器,目前的RakNet伺服器仍然服役中,但未來會下架


新的網路管理控制台能管理多人連線遊戲設定

Unity 5.1整合了Oculus Rift 到編輯模式,只要把Player setting裡面的 Virtual Reality Supported打勾,執行的時候Game View就會自動切換為左右3D格式,可以從官方文件或查詢 UnityEngine.VR 命名空間來了解更多細節



新的 HDR 顏色選擇工具




Graphics: 有支援DXT硬解的平台現在能使用Crunch模式壓縮材質,讓檔案更小但圖片傷害也大



Graphics: 實驗性讓Windows支援 OpenGL 4.5 和 ES 3.1. 用 -force-glcore, -force-gles(20|30|31|31aep) 參數來啟用. 目前GL4不支援Mac和Linux平台.

Graphics: OpenGL ES 3.1 支援 Android

Unity Analytics: Unity 5.1整合了新的Unity分析系統(目前為測試版本), 細部語法可以查詢 UnityEngine.Analytics 命名空間.

把Unity Cloud申請的ID填入Player Setting裡的”Cloud Project ID”就可以完成基本設定.


訪問 http://analytics.unity3d.com 來申請所需要的資料.
Unity 4.x和 5.0也能用這個服務
分析報表範例



現在你能從開始畫面登入你的Unity帳號,體驗完整的線上功能,你也可以選擇離線工作,只要點選”Work Offline”即可.


也可以從這個視窗管理你的Unity Pro序號.


Editor: 複製物件時,命名增加改變了規則: MyName, MyName (1), MyName (2)


GI: 自動烘焙在2D專案預設改為”Disabled”關閉.
GI: "continuous baking" 標籤改為較短的"Auto".
Input: 現在新的專案 'Fire3' 綁定會從原本的 Left Cmd 改為 Left Shift
iOS: 現在預設情況下 iOS 只支援 Metal 和 OpenGL ES 2.0. 如果你需要支援 OpenGL ES 3.0, 你可以從Player Settings裡面改.
Networking: 舊的NetworkView元件已被移除.
Physics: Rigidbody2D元件新增了移動約束參數

Standard Shader裡面的Emission顏色現在可以用動畫呈現顏色變換

這些只是少部分的描述,其他大量的修正與功能強化,請參閱Unity 5.1 Release Notes(英文)

2015年6月3日 星期三

Matrix - Unity官方推出遊戲分析報告服務

遊戲功能開發完畢,如何進行系統的優化?發現效能問題,我該如何在一堆程式碼中快速找出問題所在?我的產品還有多少提升的空間?那麼多手機到底怎麼調整才會符合大多機種?如果上述任何一條戳中您的痛,那麼這篇介紹你一帖良藥,那就是Unity官方最新的服務 Matrix!

Inline image 4
  
憑借這幾年技術支持經驗積累,Unity推出一套成熟的游戲效能實機測試系統。簡單來說,客戶上傳APK專案,最快24小時內會獲得一份幾十頁的完整的效能分析報告。在報告中,我們會針對各種測試進行詳盡的分析,並給出優化建議。
Inline image 5

Matrix效能測試主要包括:
執行效能測試:執行時的總體效能數據,包括 CPU 效能、GPU 效能、記憶體占用、Draw Call 統計。資源使用測試:測試遊戲在執行時各種資源的使用情況,包括貼圖、網格、動畫、音效等。
程式碼效率測試:測試游戲執行時每幀各代碼函數的具體時間占用,將每幀存在大量耗時的函數顯示給用戶。

以下為官方專案《Angrybots》的測試數據(樣稿可在Matrix網站首頁下載)




從結果可以看到測試內容非常豐富。一份幾十頁報告的數據雖然看起來紛繁復雜,但我們已經為您快速過濾了報告中的“有危機”的部分,並以表格的形式列出。這好比健檢之後,醫生為你做出診斷並對症下藥。更貼心的是,我們在分析中會列出問題的優先級。因為時間就是金錢,如何把握好黃金時間,解決核心問題也是開發管理中重要的一節。

 


我們仍不斷完善Matrix效能分析報告,提供更具體的優化建議。希望透過這個服務可以真正幫到開發者!

AssetBundle檢測方案
除了檢測APK之外,Matrix的AssetBundle資源檢測方案,也即將對外開放測試,它能針對你遊戲的Assetbundle文件進行詳細的檢測,包括資源的使用情況、是否存在記憶殘留、Assetbundle之間的依賴關系等等,讓大家可以全方位地掌控自己的Assetbundle文件。


或許您也許會對下列問題有疑問:

Q:有提供iOS平台的分析服務嗎?
A:未來會支持iOS平台。

Q:目前是免費,未來會收費嗎?
A:特定的服務,我們會提供額外收費服務。

Q:非公司單位可以申請測試嗎?
A:可以。

Q:是否有安全性疑慮? 
A:用戶交給我們的是遊戲發佈的包,並非原始碼。我們也將妥善保密這個包,不會對外透漏一切資料。

Q:我能一天申請測試多次嗎?
A:用戶能成功申請並接受服務的作品數量每個帳號每月不得超過6次,每個專案每月只能申請兩次。

Q: 分析報告不滿意該如何處理?
A:可以通過網站右下方的信息反饋功能將問題給我們,我們會盡快處理。
目前反饋信息有兩種方式,一種是填寫詳細的問題表單,另一種則是通過QQ來進行反饋。

最後最重要的資訊,就是上
matrix.unity3d.com
來申請你的專案報告吧!

2015年6月2日 星期二

深入IL2CPP核心 – 生成碼的除錯技巧

作者:JOSH PETERSON 原文


這是專題系列文章-「深入IL2CPP核心」的第三篇文章。本篇我們將探討一些如何更容易地為IL2CPP所生成的C++程式碼進行除錯的技巧。我們將了解如何設定中斷點、如何查看字串與使用者自定型別的內容,以及如何判斷例外事件的發生處。

要繼續之前,請先假想此時的我們正對由.NET IL程式碼所產生得來的C++程式碼進行除錯。這樣的除錯過程是不太可能會讓人開心得起來。然而,藉由一些小技巧的幫助,讓我們得以對發佈在實機上的Unity程式碼是如何地執行,能有著更透徹的理解(我們會在文章的最後淺談如何對managed程式碼進行除錯)。

同時,請留意在你的專案中所得的生成碼將有可能與此文件所呈現的生成碼存在些許差異。那是因為在每個Unity新版本中,我們都將持續地尋用更好、更快並具更小的生成碼產生方法。

設定

在這篇文章中,我是在OSX系統上使用Unity 5.0.1p3來進行說明的。雖然我將使用的專案是已在另一篇討論生成碼的文章中示範過的同一個專案,但是這次除了同樣選擇了IL2CPP來作為編譯後端外,發佈的目標平台則改為iOS。與上篇文章中所示範的一樣,我將選擇“Development Player”選項來進行發佈,因此il2cpp.exe這個程式會參照IL程式碼中的型別與方法來生成C++程式碼中的型別與方法。

在Unity完成Xcode專案的生成後,用Xcode開啟該專案(我使用的版本是6.3.1,但是其他略新的版本應該也是能使用的),選擇發佈至我的設備(我的設備是一台iPad Mini 3,但是發佈到其他的iOS設備應該也是可以的),最後以Xcode來建立專案。

設定中斷點

執行專案之前,我會先在HelloWorld類別裡的Start方法的一開始設定一個中斷點。正如先前文章中所示,在所生成的C++程式碼中該方法將被命名為HelloWorld_Start_m3。我们可以在Xcode裡使用快速鍵Cmd+Shift+O,輸入想要尋找的方法名稱,找到後,在該處設定一個中斷點。




我們也可以使用Xcode選單:依序選擇Debug > Breakpoints > Create Symbolic Breakpoint,然後填入方法名稱設定中斷點。




現在當我執行Xcode專案時,可以立即看到專案會在該方法開始的時候中斷暫停。

也就是說,如果我們已經知道了某個方法的名稱,就可在生成碼中直接找出該方法並在其上設定中斷點。我們也可以在Xcode裡為生成碼的某一行設定中斷點。事實上,由於所生成的檔案都用以組建Xcode專案。因此可以在專案導覽區塊(Project Navigator)中的Classes/Native次目錄下找到這些檔案。



觀察字串變數

在Xcode中有兩種方法可以用來觀察一個IL2CPP字串變數的表示值。我們可以直接觀察這字串的記憶體,或是藉由呼叫libil2cpp中的字串工具程式轉換該字串成為Xcode可以顯示的std::string型別。一起來看一下變數名_stringLiterall的變數值(先透露一下:其值為“Hello, IL2CPP!”)。

在使用插件Ctags (或是在Xcode中使用Cmd+Ctrl+J)而得的生成碼所建置的程式碼中,我們可以跳到_stringLiteral1定義的所在處並且得知其型別為Il2CppString_14:



實際上,IL2CPP中的所有字串的表示都類似於此。我們可以在標頭檔object-internals.h中找到Il2CppString 的定義來進行觀察。這些字串首先含有IL2CPP中某一managed(受管)型別的標準標頭檔,這是個Il2CppObject (透過Il2CppDataSegmentString 型別定義進行存取),再來是一個4 bytes大小的整數用以記錄字串長度,最後是一個由2 bytes文字所組成的陣列。在編譯時期所定義的字串,如stringLiteral1 ,最後的chars 陣列其長度將是固定的,而在執行時期產生的字串的其陣列大小是分配而取得。這些字串裡的文字都是使用UTF-16來進行文字編碼。

如果在Xcode中把變數_stringLiteral1加入到監看視窗後,我們可以選擇「View Memory of “_stringLiteral1”」這個選項,以便在記憶體中對這個字串格式進行觀察。




接著在記憶體監視器中,我們可以觀察到如下圖所示:



這個字串的header成員,其大小是16 bytes,在向後跳過該header後,我們可以看到大小為4 bytes的值0x000E (14)。而在長度之後的下一個byte是字串的第一個字元, 0x0048 (‘H’)。因為每個文字大小為2 bytes,而在此字串中的所有文字都只需要一個byte就足夠了,因此Xcode在右側顯示字元時,會在每個文字之間以點相隔。如此字串內容依然明白可讀。這樣的觀察字串方法確實可行,可是對於複雜的字串來說就顯得有點困難。

在Xcode中我們也能透過除錯器lldb的命令列下達來觀察字串的內容。標頭檔utils/StringUtils.h提供給我們一些在函式庫libil2cpp裡能使用的的字串工具的界面。此時,讓我們在lldb的命令列中呼叫方法Utf16ToUtf8。其界面如下所示:



1
static std::string Utf16ToUtf8 (const uint16_t* utf16String);

我們可以把C++結構中的字元成員做為參數傳進這個方法,如此將回傳一個UTF-8編碼的std::string。而若在lldb命令提示列中下達指令p,則能印出此字串的內容。



(lldb) p il2cpp::utils::StringUtils::Utf16ToUtf8(_stringLiteral1.chars)
(std::__1::string) $1 = "Hello, IL2CPP!"

觀察使用者定義型別

我們也能觀察使用者定義型別的內容值。在此專案所使用簡單的腳本程式碼中,我們建立了一個名為Important 的 C#型別,內含一個名為InstanceIdentifier的屬性。在脚本中,如果我在建立第二個Important型別實體的程式碼後即設定一個中斷點,則可以看到生成碼與預期相符地將InstanceIdentifier的值設定為1。



所以觀察生成碼的使用者定義型別內容就跟平時在Xcode中觀察C++程式碼內容時所用的方法相同。


生成碼例外事件的中斷

我常發現自己在試著追查臭蟲的成因時會去對生成碼進行除錯。很多時候這些臭蟲看起來像是發生了managed
程式碼的例外狀況。如上一篇文章所討論的,IL2CPP使用C++的例外狀況是實作自managed 程式碼的例外狀況,所以我們可以在Xcode裏用幾種方法讓程式在發生managed例外狀況時進行中斷。

在managed
(受管)例外狀況被丟出時進行中斷最簡單的方法是在函式il2cpp_codegen_raise_exception上設定一個中斷點,il2cpp.exe利用此中斷點在managed例外狀況明確發生處進行中斷。



如果我接著執行了這個專案,Xcode將在函式Start中發生了InvalidOperationException例外狀況時中斷。這是個得以很方便觀察字串內容的地方。如果我點入深究exargument的成員,則可發現有個名叫___message_2的成員,他是用來表示例外狀況訊息的字串。



小小炫技一下,我們可以印出這字串的值並且查看發生了什麼問題:

(lldb) p il2cpp::utils::StringUtils::Utf16ToUtf8(&ex->___message_2->___start_char_1)
(std::__1::string) $88 = "Don't panic"

需要注意的是這裡出現的字串配置跟之前的都一樣,但是所生成的屬性名卻有些許不同。原本叫chars 的屬性,在這裡被命名為___start_char_1,而且型別也從uint16_t[]變成uint16_t。不過其值依然是一個陣列的第一個字元,所以我們可以把這字元的位址傳入轉換函式,隨後我們發現所得的例外狀況訊息內容將更易於閱讀。

但是並非所有的managed
(受管)例外狀況都會被生成碼所丟出。有時執行時期程式碼libil2cpp將會丟出managed例外狀況,但非藉由呼叫函式il2cpp_codegen_raise_exception來達成。此時我們該如何獲取這些例外狀況呢?

如果我們使用Xcode的選單,Debug > Breakpoints > Create Exception Breakpoint,然後編輯中斷點,設定Exception選項為C++,named選項為Il2CppExceptionWrapper 使這型別的例外狀況發生時中斷。因為這C++型別總是包含了所有的managed例外狀況,如此將得以獲得所有的managed例外狀況。



讓我們藉由在腳本中Start函式的一開始新增以下兩行程式碼來驗證一下這方法可行:



1
2
Important boom = null;
Debug.Log(boom.InstanceIdentifier);

如此第二行將會使得例外狀況NullReferenceException 被丟出。如果我們在Xcode中執行了已設定例外狀況中斷點的程式碼,我們將會看到當此例外狀況被丟出時,Xcode也將確實中斷。然而,此中斷點是設在libli2cpp的程式碼裏,所以我們所能看到的也只有組譯程式碼。如果我們往call stack裏看一下,可以知道我們需要上移幾個frames使之到NullCheck 這個方法,此方法是被il2cpp.exe所插入至生成碼中的。



從那裡,我們可以回移一個以上的frame,然後觀察型別Important 的實體其值是否確實為NULL。



結論

在討論過對生成碼除錯的一些小技巧後,我希望大家對如何追查那些可能在IL2CPP生成C++程式碼時衍生出的問題,能有更進一步的了解。我鼓勵大家研究IL2CPP使用的其他型別配置,藉此學習更多有關如何對生成碼除錯的知識。

可是針對IL2CPP managed程式碼的除錯器在哪裏呢?難道我們不能對正透過IL2CPP編譯後端執行在設備上的那些managed程式碼進行除錯嗎?實際上,這是可能。我們現在已有一個內部、已進入Alpha 階段,適用於L2CPP的managed程式碼除錯器。雖然還沒到達release階段,但是這已在我們的規劃中,敬請關注。

本專題系列文章的下一篇將探討,在managed程式碼中使用IL2CPP編譯後端,實作出各種方法調用型別的不同方法。我們將一起看看每個方法調用型別的執行成本。

著作人