<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 剖析虛幻渲染體系(14)- 延展篇:現代渲染引擎演變史Part 3(開花期)

     

     

    14.4 開花期(2010~2015)

    本章將闡述2010年度前期的引擎架構、渲染和相關模塊的技術。

    14.4.1 圖形API

    14.4.1.1 DirectX

    在2010時代的前期,DirectX發布了11的兩個小版本(11.1、11.2)和DirectX 12。

    DirectX 11.1于2012年8月發布,新增的特性有:

    DirectX 11.2于2013年10月發布,新增的特性有:

    DirectX 12.0于2015年7月發布,新增的特性有Resource Binding、Tiled Resources (Texture2D)、Typed UAV Loads (additional formats)等。

    DirectX 11的曲面細分階段有Hull Sahder(殼著色器)、Tessellator(細分器)、Domain Shader(域著色器)。它們的作用如下:

    • Hull Sahder。有兩個階段:控制點階段(每個控制點運行一次)和打包階段(每個輸入圖元運行一次,返回細分因子)。
    • Tessellator。生成新的頂點,更高的細分因子意味著生成更多的三角形。
    • Domain Shader。每個頂點運行一次,用于每個頂點的表面計算,作為參數坐標傳入的頂點數據。

    DirectX 11曲面細分管線。

    DirectX 11曲面細分的應用案例1。

    DirectX 11曲面細分還可以應用于曲面參數化、貼花細分等:

    Domain Shader開銷很大,特別是小三角形效率更低,硬件希望三角形至少為8像素,優化方式是減少細分頂點的數量,以降低Domain Shader執行,Hull Shader可以調整細分因子。

    利用DirectX 11曲面細分,還可以實現距離自適應細分(Distance Adaptive Tessellation)、位移自適應(Displacement Adaptive)、貼花細分的位移自適應以及基于Hull Shader的背面裁剪等技術。

    此外,DirectX 11曲面細分的優化技術有:視錐體剔除、組合細分的繪圖調用、方向自適應細分(使用dot(V, N) 查找輪廓塊)
    、減少Hull Shader的輸入和輸出數據、嘗試將Domain Shader的工作轉移到像素著色器(減少Domain Shader輸出的數據)、使用Stream Out避免重復細分對象。

    Direct3D 11 Performance Tips & Tricks談及了DirectX 11的SM 5.0、資源和資源視圖、多線程等方面的技術。其中SM 5.0的特點:

    • 使用Gather/GatherCmp()進行快速多通道紋理提取。

      • 使用較少數量的RT,同時仍然有效地獲取,將深度存儲到SSAO的FP16 alpha,使用Gather*() 獲取alpha/深度的區域。
      • 僅在三個操作中獲取4個RGB值,常用于圖像后處理。

    • 使用“保守深度”為快速深度精靈保持提前深度拒絕有效。

      • 從PS輸出SV_DepthGreater/LessEqual而不是SV_Depth。即使使用經過著色器修改的Z,也能保持提前深度剔除有效。
      • 硬件/驅動程序將強制執行合法行為。如果寫入無效的深度值,它將被截取為光柵化值。

    • 使用EvaluateAttribute*()進行快速著色器AA,無需超級采樣。

      • 在子像素位置調用EvaluateAttribute*(),用于程序化材質的更簡單的著色器AA。
      • 輸入SV_COVERAGE以計算每個覆蓋子樣本的顏色并寫入*均顏色,圖像質量比純MSAA稍好。
      • MSAA alpha測試的輸出SV_Coverage。此功能自DX 10.1以來一直存在,EvaluateAttribute*()使實現更簡單,但要檢查覆蓋率的alpha是否已經滿足需求,因為它應該更快。
    • UAV和原子:明智地使用PS散射、UAV和Interlocked*()操作。

    • 減少流輸出通道。可尋址的流輸出,單個通道輸出最多4個流,所有流都可以有多個元素。

    • 使用幾何著色器實例化編寫更簡單的代碼。使用SV_SInstanceID代替循環索引。

    • 使用[earlydepthstencil]對PS強制進行早期深度模板測試。

      • 如果寫入UAV或AppendBuffers,則可以顯著提高速度。
      • 將[earlydepthstencil]放在像素著色器函數聲明上方以啟用它。

    ? 啟用[earlydepthstencil]后,像素著色器將剔除UV之外的所有像素。

    • 使用眾多新的內在函數來實現更快的著色器。

      • 快速的位操作:countbits()、reversebits()(FFT中需要)等。
      • 轉換指令:f16to32() 、f32to16(),更快的打包/拆包。
      • 快速粗糙導數 (ddx/y_coarse)。
      • ...
    • 明智地使用子程序(subroutine)的動態著色器鏈接。

      • 子程序不是免費的,沒有跨函數邊界優化。
      • 僅對大型的子程序使用動態鏈接,避免使用大量小的子程序。

    資源和資源視圖的特點:

    • 減少內存大小和帶寬以獲得更高的性能。
      • BC6和BC7提供新功能,非常高的質量和HDR支持,所有靜態紋理都應該是可壓縮的。

    ?

    • 使用只讀深度緩沖區以避免復制深度緩沖區。
      • Direct3D 11允許對仍綁定用于深度測試的深度緩沖區進行采樣。
        • 如果深度是GBuffer的一部分,則對延遲光照很有用。
        • 適用于軟粒子。
      • AMD:使用深度緩沖區作為SRV可能會觸發解壓縮步驟,盡可能晚地進行。

    DX11的其它特性:

    • 免費線程資源創建。

      • 使用快速的異步資源創建,通常應該更快、更高并行。
      • 不要在使用資源的幀中銷毀資源。銷毀資源很可能會導致同步事件。
      • 避免創建、渲染、銷毀的串行序列。
    • 顯示列表(從延遲上下文創建的命令列表)。

      • 確保應用程序是多線程的。
      • 僅當命令構造是一個足夠大的瓶頸時才使用顯示列表。
      • 考慮顯示列表來表達GPU命令構造中的并行性,避免細粒度的命令列表。
      • 驅動程序已經是多線程的。
    • 延遲上下文。

      • 在延遲上下文中,Map()和UpdateSubResource()將使用額外的內存。請記住,所有初始Map都需要使用DISCARD語義。
      • 請注意,在單核系統上,延遲上下文將比僅使用立即上下文慢。對于雙核,最好只使用立即上下文。
      • 除非有顯著的并行性,否則不要使用延遲上下文。
    • 其它。

      • 使用DrawIndirect進一步降低CPU開銷。使用來自GPU寫入緩沖區的參數啟動實例化繪制調用/調度,可以使用GPU進行有限的場景遍歷和剔除。
      • 使用Append/Consume緩沖區用于快速的流輸出(stream out)。比GS更快,因為沒有輸入順序約束,具有“無限”數據擴展的流輸出。

    14.4.1.2 OpenGL

    在2010到2015年期間,OpenGL發布的版本和特性如下表:

    版本 時間 特性
    OpenGL 3.3 2010年3月 Mesa支持軟件驅動程序SWR、軟件管線和帶有NV50的舊Nvidia卡。
    OpenGL 4.0 2010年3月 對標Direct3D11。硬件支持:GeForce 400系列及更新版本、Radeon HD 5000系列及更新版本、英特爾Ivy Bridge處理器和高清顯卡。
    OpenGL 4.1 2010年7月 硬件支持:GeForce 400系列和更新版本、Radeon HD 5000系列和更新版本、英特爾Ivy Bridge處理器和高清顯卡。GPU實現此規范的最小“最大紋理大小”為 16k×16k。
    OpenGL 4.2 2011年8月 帶原子計數器的著色器和加載-存儲-原子讀取-修改-寫入操作到紋理;繪制從GPU頂點處理(包括曲面細分)捕獲的多個數據實例,以使復雜對象能夠有效地重新定位和復制;支持修改壓縮紋理的任意子集,而無需將整個紋理重新下載到GPU以顯著提高性能;部分硬件支持。
    OpenGL 4.3 2012年8月 計算著色器、著色器存儲緩沖區對象、圖片格式參數查詢、ETC2/EAC紋理壓縮作為標準功能、完全兼容OpenGL ES 3.0 API、接收調試消息、紋理視圖以不同方式解釋紋理、提高安全性和穩健性。
    OpenGL 4.4 2013年7月 強制緩沖區對象使用控制、對緩沖區對象的異步查詢、更多界面變量布局控件在shader中的表達、同時高效綁定多個對象。
    OpenGL 4.5 2014年8月 直接狀態訪問 (DSA) 、刷新控制、穩健性、OpenGL ES 3.1 API和著色器兼容性。

    同期,OpenGL ES發布的版本和特性如下表:

    版本 時間 特性
    OpenGL ES 3.0 2012年8月 渲染管道增強:遮擋查詢、變換反饋、實例化、MRT、ETC2/EAC作為標準;新版的GLSL ES著色語言,完全支持整數和32位浮點運算;增強紋理功能,包括保證支持浮點紋理、3D 紋理、深度紋理、頂點紋理、NPOT紋理、R/RG紋理、不可變紋理、2D陣列紋理、swizzles、LOD和mip級別鉗制、加強的紋理和渲染緩沖區格式。
    OpenGL ES 3.1 2014年3月 計算著色器、獨立的頂點和片段著色器、間接繪圖命令。
    OpenGL ES 3.2 2015年8月 幾何和曲面細分著色器、浮點渲染目標、ASTC、增強的混合、高級紋理目標:紋理緩沖區、多重采樣2D數組和立方體貼圖數組、調試和健壯性功能。

    14.4.1.3 其它圖形API

    • Mantle

    Mantle由AMD最初于2013年開始與DICE合作開發,被設計為Direct3D和OpenGL的替代方案,主要用于個人計算機。它是針對3D視頻游戲的低開銷渲染API。

    不久之后,AMD將Mantle API捐贈給了Khronos組織,后者將其開發為Vulkan API。簡而言之,Vulkan的前身就是Mantle。2015年,Mantle的公共開發暫停,并在2019年完全停止,因為DirectX 12和源自Mantle的Vulkan越來越受歡迎。

    • Vulkan

    2015年初,LunarG(由Valve資助)展示了一款Linux驅動程序,在HD 4000系列集成顯卡上實現了Vulkan兼容性。

    2015年8月,Google宣布未來版本的Android將支持Vulkan。2015年12月,Khronos集團宣布Vulkan規范的1.0版本已接*完成,將在符合標準的驅動程序可用時發布。

    • Metal

    Metal是由Apple創建的低級、低開銷的硬件加速3D圖形和計算著色器API,在iOS 8中首次亮相,結合了類似于OpenGL和OpenCL的功能,旨在通過為iOS、iPadOS、macOS和tvOS上的應用程序提供對GPU硬件的低級訪問來提高性能,可以與Vulkan和DirectX 12等其它*臺上的低級API進行比較。

    Metal自2014年6月起在搭載Apple A7或更高版本的iOS設備上可用,自2015年6月起在運行OS X El Capitan的Mac(2012 型號或更高版本)上可用。

    14.4.2 硬件架構

    PowerVR Graphics - Latest Developments and Future Plans闡述了2015年Imagination的PowerVR Rogue硬件架構和特性。PowerVR Rogue支持基于分塊的延遲渲染器,建立在前五代技術的基礎上,在2012年消費電子展上正式宣布,USC(Universal Shading Cluster,通用著色簇)是新的標量SIMD著色器內核,通用計算機是核心的最大特色,同時也非常適合傳統的圖形渲染。

    該架構還支持延遲光柵化(Deferred rasterisation),不直接讓GPU做任何像素著色,硬件支持完全延遲的光柵化和像素著色,光柵化是像素精確的。該技術被稱作隱藏表面刪除(Hidden Surface Removal,HSR)。

    TBDR可以在渲染的所有階段節省帶寬,僅獲取tile所需的幾何圖形,僅處理tile中的可見像素。高效處理,最大限度地利用可用的計算資源,盡可能利用硬件帶寬。最大化核心效率,少激活USC以節省消耗。最小化帶寬,減少紋理以省電,幾何體提取和裝箱通常超過每幀帶寬的10%,為渲染的其它部分節省帶寬。

    Rogue USC是架構的搭建積木,全稱統一著色簇,Rogue架構的基本構建塊,成對布局,共享TPU(紋理處理單元),1、0.5和0.25的USC設計是特殊的,設計中的不同*衡,傾向于用在非游戲應用程序。

    16寬的硬件,32寬的分支粒度,每個時鐘運行半個任務/warp,標量SIMD,優化的ALU管線,混合使用F32、F16、整數、浮點特殊值、邏輯運算。可在IP核心中配置,F16路徑有時是可選的,F16路徑的性能在第一代之后顯著提高。著色器中的性能:F32路徑為雙FMAD,F16路徑下每個周期可以執行不同的操作,具體取決于著色器,不過,ISA可以使用反匯編編譯器進行查詢。

    向量架構難以良好地編程,標量ALU好處多,雖然不是免費的午餐,但可以讓性能更加可預測。


    PowerVR Series6XT Rogue硬件架構。


    PowerVR Series7XT Rogue硬件架構。

    Series6XT到Series7XT:改變了架構的擴展方式,改進了USC,流線型ISA,新特性是硬件曲面細分、DX11兼容USC(主要是精度)、FP64。

    PowerVR Graphics Wizard硬件架構,新增了光線追蹤相關的單元和處理。

    Wizard的3個獨特功能:固定功能射線盒和射線三角形測試器,一致性驅動的任務形成與調度,流式場景層次生成器。相干引擎(Coherency Engine)可以讓我們同時處理下圖所示的所有光線:

    PowerVR還提供了PVRTrace、PVRTune、PVRShaderEditor等開發和分析工具。

    對于Rogue圖形驅動程序,DDK(驅動程序開發工具包)發布流程:向PowerVR IP許可證持有人發布的參考驅動程序源代碼,大約每6個月進行一次小修改,頂級客戶盡早參與,DDK正式發布后不久產品中的驅動程序。

    14.4.3 引擎演變

    14.4.3.1 綜合演變

    2010前后的通用游戲引擎都具備場景管理器,包含所有對象、材質、燈光以及所有場景設置(例如分辨率、視角、抗鋸齒級別等)的列表。為了避免加載已經加載的對象和紋理,可能還提供了紋理和對象緩存。其中材質可以從文件(自定義著色器)加載或動態生成(下圖),材質屬性可以從顏色和表面紋理等基本材質設置到具有多個紋理的視差映射等更高級的功能。

    材質生成器是主要組成部分,可以根據用戶定義的材質表面屬性動態生成著色器,這些屬性可以是用于精確定義表面屬性的顏色或紋理。除了可以通過相加、相乘或混合來組合的多層顏色紋理外,還可以定義高級表面,包括凹凸、視差、環境和立方體貼圖(靜態或動態)。基于所有這些屬性,材質生成器生成著色器,能夠執行生成所需材質表面的命令。

    著色器鏈接和編譯后,默認頂點屬性就可以綁定到材質,設置材質配置(通過uniform結構),著色器即可使用,其它公共變量(如變換矩陣)在統一塊(uniform block)中定義并由所有著色器共享。

    同時,HDR渲染管線也逐漸在游戲引擎中普及開來,以適應HDR照明的發展趨勢,由于當時的顯示設備大多依然是LDR,需要色調映射將HDR映射回LDR。(下圖)

    下圖是基于物理照明模型和感知色調映射的虛擬環境HDR渲染管道的一種實現流程:

    The Game Engine Space for Virtual Cultural Training: Requirements, Devices, Engines, Porting Strategies and a Future Outlook談到了2010年的游戲*臺功能的趨勢及它們與文化建模的相關性和模擬,還有行業的未來發展,闡述了當時流行的游戲引擎的特點,并針對不同的設備和*臺給予多維度的技術選型方案。

    文中提到幻引擎 3 (UE3) 是最常用的商業引擎之一,部分原因是它的存在時間比大多數引擎都長,還因為Epic Games定期添加新功能和改進。在視覺保真度方面,虛幻引擎3與其他頂級3A游戲引擎不相上下,非常擅長在為文化培訓而設計的3D虛擬環境中創建所需的真實感。

    CryENGINE 3的優勢是高保真圖形、角色和開發工具方面的首要游戲引擎,紋理、材料、照明和動畫的質量非常出色,強大的動畫系統允許使用面部和全身動作捕捉,以及游戲內動畫混合、同步、分層和重定向;用于環境創建、動畫、材料編輯和腳本編寫等的開發工具對用戶友好且功能強大;具有高質量的人工智能和尋路系統,支持可視化腳本系統進行修改;對室外和室內環境都有很好的支持。

    Gamebryo Lightspeed的優點是提供快速的應用程序開發框架,還支持處理聲音、圖形、物理和多人游戲的各種技術。Gamebryo的視覺保真度可與業內最好的引擎相媲美,曾被用于制作具有廣闊景觀和復雜面部細節的游戲,例如Fallout 3和Oblivion。

    Unity3D的優點是一流的跨*臺開發支持,強大的社區和最低的入門成本,簡化了大多數工具,所有3D資源都可以以原始格式導入,從而消除了導入為引擎特定格式的任務,保留資產的原始格式允許非破壞性工作流程,從而顯著提高管線效率。

    以上引擎在跨*臺與滿足需求的排名如下圖:

    該文改給出了一種加速游戲開發工作流的管線:

    Future graphics in games闡述了CryTek在2010年使用的渲染技術,并預測未來的圖形趨勢。文中對比了延遲光照、延遲著色及前向渲染在帶寬和材質種類之間的對比:

    文中還提到,渲染架構的突破并非易事,已由硬件供應商多次證明,尤其是最*多次嘗試使用軟件渲染器,圍繞著龐大的基礎設施的拖尾,需要多年開發經驗的成果。圖形架構將更加多樣化,回到舊的好技術,如體素、微多邊形等。

    未來將給某些游戲打上烙印的替代品:基于點的渲染、光線追蹤、像往常一樣光柵化、微多邊形,數據表示有稀疏體素八叉樹(數據結構)、稀疏面元八叉樹。

    不得不說,當年(2010年)的這個預測真是準啊,截至當前(2022年),已經流行或逐漸流行的技術包含了基于點的渲染、光線追蹤、微多邊形及稀疏體素八叉樹(數據結構)、稀疏面元八叉樹。

    稀疏體素八叉樹(數據結構)的優點:數據結構是未來替代渲染的證明,非常適合獨特的幾何、紋理幾何,紋理預算變得不那么相關,藝術自由成為現實,自然地適合自動LOD方案。缺點是基礎設施和硬件都沒有,稍微占用內存,非常適合光線追蹤,但仍然太慢。

    CryTek已經在生產中使用稀疏體素八叉樹:在關卡導出期間用于烘焙幾何體和紋理,存儲在三角形分區的稀疏八叉樹中,非常易于管理和流式傳輸幾何和紋理,無需GPU計算(盡管有虛擬紋理),自動校正LOD構建,自適應幾何和紋理細節(取決于游戲玩法)。每個級別的磁盤空間都很大!使用積極的紋理壓縮,明智地烘焙,而不是整個世界。

    感知驅動的圖形有:基于PCF的軟陰影、隨機OIT、基于圖像的反射、SSAO、大多數后處理、LPV、很多隨機算法等,實時圖形中的大多數都是假設,因為人類的感知有限。實時圖形是感知驅動的,因為人的眼睛有一些特點:

    • 約350M(3.5億)像素的空間分辨率,在這方面很難欺騙它。
    • 約24Hz的時間分辨率,非常低,給了技術操作的空間,當大于40Hz時,人眼就不會注意到閃爍。
    • 我們不會為另一臺機器創建圖像,我們的目標客戶是人類。

    欠采樣/超采樣的技術有:

    • 空間
      • 欠采樣
        • 推斷著色(Inferred shading)
      • 景深
        • 解耦采樣
    • 時間
      • 時間抗鋸齒
      • 運動模糊
    • 混合
      • 時空(spatio-temporal,后來的不少文獻稱為spatial-temporal)抗鋸齒

    存在混合渲染。沒有靈丹妙藥的渲染管線,即使是REYES也沒有以原始形式用于電影。通常結合所有合適和有幫助的內容:光線追蹤的反射和陰影(可能是三角形/點集/體素結構/等),體素以獲得更好的場景表示(部分),屏幕空間接觸效果(例如反射)等等。

    *期的趨勢是立體(stereoscopic)渲染,技術存在很長時間,因技術而流行,在游戲中也是如此,沒有新概念,但類似于攝影藝術,一條黃金法則:不要讓觀眾感到疲倦。Crysis 2已經具備一流的3D立體支持,使用深度直方圖確定軸間距離。

    CryENGINE 3中支持的立體渲染模式:強力立體渲染,帶重投影的中央眼框,一只眼睛的實驗性隨機渲染。立體輸出模式:浮雕(分色)、隔行掃描、水*聯合圖像、垂直聯合圖像、兩臺顯示器。

    為了找出當時的硬件架構的問題,使用小型綜合測試(模擬 GPU 行為)模擬高度并行調度,擁有512個內核(也可以解釋為共享緩存的插槽),32k個相同的小任務要執行,每個項目在一個核心上需要1個時鐘(所以合成),在256到2048個線程的范圍內,在總時間中考慮調度開銷(任務投送、上下文切換、開銷權重并不重要)。輸出的幾個重要參數的曲線如下圖:

    上圖可以知道,總時間曲線可分為飽和階段和并發并行階段,拐點在調度開銷從0爬升處。在飽和階段總時間和執行時間不斷下降,但到了并發并行階段,調度開銷隨著線程數量增加而增加,總時間也隨調度開銷成類似的曲線增加。

    另一個測試使用真正的GPU!渲染屏幕空間效果 (SSAO),帶寬密集型像素著色器,每個項目在一個核心上需要1個時鐘(所以合成),在5到40個線程的范圍內,緩存污染在飽和狀態之后立即導致峰值,時間漸*地達到更多線程的飽和性能:

    調度開銷是個問題,并行可擴展性,對于同質任務,飽和時達到最大值,異構工作負載如何?最小值的存在取決于調度對性能的影響,我們需要減少它,需要可配置的硬件調度程序,使用它可以實現類似GRAMPS的架構,光線追蹤變得更快,SoL出現帶寬瓶頸。

    實際上,需要不同的原子,主要使用它來進行收集/分散操作,而必須可以處理浮點數!在大多數情況下,不需要結果:改進無回讀的原子(即發即忘的概念),操作應該在內存控制器/智能內存端進行。需要一個數量級的圖形原子性能。

    未來的技術挑戰:切換到可擴展的代碼庫,考慮并行和異步作業,多線程調度,更大的代碼庫、多個*臺和API。未來的生產挑戰:資產成本每年增加約50%,內容除了質量提高,還變得越來越“可互動”,考慮改進工具、管道和瓶頸以產生反作用,自動化源后端 ->資源編譯器,工具越好,產出就越便宜和/或越好。

    在效率上,可以降低數據精度,不需要像靜止圖片那樣的高分辨率和清晰度,圖形硬件應該挑戰緩存不一致的工作負載。

    總之,實時渲染管線改造指日可待,需要硬件改進,當前生產實時渲染技術的演變,準備新的表示和渲染管道,更好的并行開發基礎設施,工具和創作管道需要現代化,考慮服務器端渲染:可能會徹底改變方向。感知驅動的實時圖形是技術驅動力,避免圖形技術中的恐怖谷。

    DirectCompute Optimizations and Best Practices分享了DirectCompute的概念、特性、機制、使用及在NVIDIA GPU內的優化技巧。

    DirectCompute(直接計算)適用于Windows Vista和Windows 7的微軟標準GPU計算*臺,在DX10和DX11硬件受支持,CUDA架構的另一個實現,和OpenCL、CUDA C是同級API。

    DirectCompute允許通過計算著色器在CUDA GPU上進行通用計算,與Direct3D資源相互操作,包括所有紋理特征(立方體貼圖、mip 貼圖),類似于HLSL,在Windows上跨所有GPU供應商的統一API,保證跨不同硬件有相同的結果。

    DirectCompute程序將并行工作分解為線程組,并調度多個線程組來解決一個問題。如下圖,調度(Dispatch)是線程組的3D網格,數十萬個線程;線程組(Thread Group)是線程的3D網格,數十或數百個線程;線程(Thread)是著色器的一次調用。

    并行執行模型如下圖,同一組中的線程并發運行,不同組中的線程可以同時運行。

    內存合并(Memory Coalescing)是half-warp的協調讀取(16個線程),是全局內存的連續區域:64字節 - 每個線程讀取一個字:int、float、...,128 字節 - 每個線程讀取一個雙字:int2、float2、...,256 字節 – 每個線程讀取一個四字:int4, float4, ...

    內存合并的附加限制有區域的起始地址必須是區域大小的倍數,half-warp中的第k個線程必須訪問塊中的第k個元素,例外情況是并非所有線程都必須參與,比如預測訪問、half-warp內的分歧。

    讀取浮點數的合并訪問的示例如下圖,上排是所有線程都參與,下排則不是:

    讀取浮點數的合并訪問的示例如下圖,上排是按線程排列訪問,下排則不是,因為是未對齊的起始地址(不是64的倍數):

    對于合并(Compute 1.2+的GPU),在10系列架構中大大改進了合并功能,硬件將half-warp內的地址組合成一個或多個對齊的段(32、64 或128字節),一個段內地址的所有線程都由一個內存事務處理,無論段內的順序或對齊方式如何。下圖顯示了對于未對齊的起始地址(不是 64 的倍數),遞歸減少事務尺寸以最小化尺寸:

    共享內存庫尋址在無沖突和有沖突的圖例如下:

    2路沖突和9路沖突的圖例如下:

    什么是占用率(Occupancy)?GPU 通常同時運行1000到10000個線程,更高的占用率 = 更有效地利用硬件,在硬件中通過在任何給定時刻并發運行的warp(32個線程)執行的并行代碼,線程指令按順序執行,通過執行其它warp,可以在硬件中隱藏指令和內存的延遲。盡可能最大化占用率,占用率為1.0為最佳方案。

    \[\text{占用率} = \cfrac{\text{駐留warp數}}{\text{最大可能的駐留warp數}} \]

    一個或多個線程組駐留在單個著色器單元上,占用率受資源使用限制:線程組大小聲明、線程組共享內存使用、線程組使用的寄存器數。示例:一個硬件著色器單元最多8個線程組、48KB總共享內存、最多1536個線程,以256個線程的線程組大小啟動著色器并使用32KB的共享內存,導致每個硬件著色器單元只能運行1個線程組。此時受到共享內存的限制。

    調度/線程組大小啟發式:

    • 讓線程組數 > 多處理器(multiprocessor)數。所有的多處理器至少有一個線程組來執行。
    • 線程組數/多處理器數 > 2。多個線程組可以在多處理器中同時運行,不在屏障處等待的線程組使硬件保持忙碌,視資源可用性而定 – 寄存器、共享內存。
    • 線程組 >100以擴展到未來的設備。以流水線方式執行的線程組,每次調度1000個組將跨多代擴展。
    • 線程/線程組數是warp尺寸的倍數。warp中的所有線程都在工作。

    DirectCompute優化的用例之一是并行規約(Parallel Reduction),常見且重要的數據并行原語(例如,求一個數組的總和),易于在計算著色器中實現(更難做到正確)。文中介紹了7個不同的版本,展示了幾個重要的優化策略。

    每個線程塊中使用的基于樹的方法,需要能夠使用多個線程塊,處理非常大的數組,讓GPU上的所有多處理器保持忙碌,每個線程塊減少數組的一部分,但是如何在線程塊之間傳遞部分結果呢?

    著色器分解,通過將計算分解為多個調度來避免全局同步。

    交叉尋址,會引入新問題——共享內存庫沖突。

    順序尋址。

    不同并行規約的算法性能對比。

    文中還提到了多GPU并行,多個GPU可在單個系統中用于任務或數據并行GPU處理,主機顯式管理每個GPU的I/O和工作負載,選擇最佳分割以最小化GPU間通信(必須通過主機內存發生)。

    多GPU和CPU的通訊模型,可以實現任務并行和數據并行。

    內存合并(矩陣乘法),每次迭代,線程訪問A中的相同元素,僅適用于CS 1.2+。

    DirectCompute Performance on DX11 Hardware闡述了DirectCompute的特點、收益、優化及性能監測等方面的內容。

    文中說到使用DirectCompute的原因有允許對GPU進行任意編程(通用編程、后處理操作等),更好地針對具有嚴重TEX或ALU瓶頸的PS,使用CS線程來劃分工作并*衡著色器。雖然并不總是能戰勝PS,但即便*衡良好的PS也不太可能被CS擊敗。

    GPU是面向吞吐量的處理器,延遲由工作覆蓋,需要提供足夠的工作以提高效率,尋找細粒度的并行性,簡單的映射效果最好(屏幕上的像素、模擬中的粒子)。如果有助于避免往返主機的數據交換,則在GPU上運行小型計算仍然是有利的,包含延遲的收益,例如為后續的內核啟動或繪圖調用準備參數,與DispatchIndirect()結合,無需CPU干預即可完成更多工作。

    NVIDIA的GPU是標量,不需要顯式矢量化,在大多數情況下不會有影響(但也有例外),將線程映射到標量數據元素。AMD的GPU是向量,向量化對性能至關重要,避免依賴標量指令,使用IHV工具檢查ALU使用情況。

    相比CS4.0,CS5.0的優勢很多,如線程、線程組共享內存、原子靈活性等,利用CS5.0的功能通常運行更快。聲明合適數量的線程組對性能至關重要:

    numthreads(NUM_THREADS_X, NUM_THREADS_Y, 1)
    void MyCSShader(...)
    {
        (...)
    }
    

    總線程組大小應高于硬件的wavefront大小(大小因GPU而異,ATI 的最大為64,NV的為32),避免尺寸低于wavefront尺寸,避免numthreads(1,1,1) 這樣的線程組,較大的值通常適用于各種GPU,使用低端GPU更好地擴展。

    使用線程組時,嘗試在組中的所有線程之間*均分配工作,動態流控制將為線程創建不同的工作流,意味著工作較少的線程將處于空閑狀態,而其它線程仍處于忙碌狀態:

    [numthreads(groupthreads, 1, 1)]
    void CSMain(uint3 Gid : SV_GroupID, uint3 Gtid: SV_GroupThreadID)
    {
        (...)
        
        if (Gtid.x == 0)
        {
            // 這里的代碼只針對一個線程執行!
        }
    }
    

    可以混合Compute和光柵化,減少Compute和Draw調用之間的轉換(transition)次數,而轉換通常會有很高的開銷!舉個具體的例子,假設有以下順序的調用質量:

    Compute A
    Compute B
    Compute C
    Draw X
    Draw Y
    Draw Z
    

    可以改成交叉地調用Compute和Draw:

    Compute A
    Draw X
    Compute B
    Draw Y
    Compute C
    Draw Z
    

    對于無序訪問視圖(Unordered Access View,UAV),它并非嚴格意義上的DirectCompute資源,也可以與PS一起使用。無序訪問支持分散的讀寫,分散訪問 = 緩存丟棄,優先分組讀/寫(強烈建議),例如從/向float4(而不是float)讀取/寫入,但NVIDIA標量架構不會從中受益。對UAV的連續寫入,如果不需要,請勿創建帶有UAV標志的緩沖區或紋理,渲染操作后可能需要同步,僅在需要時使用D3D11_BIND_UNORDERED_ACCESS。避免將UAV用作便簽本,使用TGSM(線程組共享內存)更佳。

    帶計數的UAV緩沖是Shader Model 5.0支持的特性,不支持紋理,在CreateUnorderedAccessView()中使用D3D11_BUFFER_UAV_FLAG_COUNTER標志。訪問方式有uint IncrementCounter()uint DecrementCounter()
    ,比使用UINT32大小的R/W UAV實現手動計數器更快,因為避免了對UAV進行原子操作。但在NVIDIA硬件上,更傾向于使用Append buffer。

    追加/使用緩沖區(Append/Consume buffer)用于將數據并行內核的輸出序列化為數組,也可用于圖形,例如延遲片元處理。需要小心使用,可能代價高昂,在API中引入序列化點,大的記錄尺寸可以隱藏append操作的成本。

    原子操作是不能被其它線程中斷直到完成的操作,通常與UAV一起使用,原子操作由于同步需求會影響性能。僅在需要時使用它們,許多問題可以重鑄為更有效的并行規約或掃描,帶有反饋(feedback)的原子操作成本更高,例如:

    Buffer->InterlockedAdd(uAddress, 1, Previous);
    

    線程組共享內存(Thread Group Shared Memory,TGSM)是組內線程間共享的快速內存,不會跨線程組共享!例如:

    groupshared float2 MyArray[16][32];
    

    在Dispatch()調用之間不持久(亦即TGSM只能用于單次Dispatch),用于減少計算量,通過將相鄰計算存儲在TGSM中來使用它們(如后處理紋理指令)。

    影響TGSM性能的因素主要有:

    • 訪問模式。

    I/O的bank(存儲庫?)數量有限,ATI和NVIDIA硬件上的32個bank,bank沖突會降低效率。32bank示例,每個地址為32位,bank按地址線性排列:

    相距32個DWORD的TGSM地址使用相同的bank,從多個線程訪問這些地址將產生bank沖突,將TGSM二維數組聲明為MyArray[Y][X],并先增加X,再增加Y(如果X是32的倍數,則必不可少!),填充數組/結構以避免bank沖突會有所幫助,例如MyArray[16][33] 代替MyArray[16][32]

    • 盡可能減少訪問。例如將數據打包成uint而不是float4(但請注意增加的ALU)。

    • 基本上每個TGSM地址都嘗試讀/寫一次。復制到臨時數組可以幫助避免重復訪問。

    • 展開循環訪問共享內存,幫助編譯器隱藏延遲。

    屏障(Barrier)為組內的所有線程添加同步點,如GroupMemoryBarrier()GroupMemoryBarrierWithGroupSync(),屏障過多會影響性能,如果工作未在線程之間*均分配,則尤其如此,當心使用許多屏障的算法。

    最大化硬件占用。線程組不能跨多個著色器單元拆分,無論是進還是出,與像素工作不同,像素工作可以任意分割。影響占用的因素有線程組大小聲明、聲明的TGSM大小、使用的GPR數量,這些數字會影響可以實現的并行度,例如,硬件著色器單元的參數是最多8個線程組、32KB總共享內存、最多1024個線程,線程組大小為128個線程,需要24KB共享內存,每個著色器單元(128 個線程)只能運行1個線程組(錯誤)。

    寄存器壓力也會影響占用,但我們對此幾乎沒有控制權,依賴驅動做正確的事。需要調整和實驗才能找到理想的*衡,但這種*衡因硬件而異!存儲不同的預設以獲得跨各種GPU的最佳性能。

    DiRT2 DirectX 11 Technology介紹了游戲Colin McRae Dirt 2使用的DirectX 11的技術,包含移植到DX11、曲面細分、基于直接計算的HDAO及線程資源加載。文中提到了基于曲面細分的網格細節化、布料和水體模擬。

    基于DX11曲面細分的布料。左邊是原始的網格,右邊是PN曲面細分+位移的網格。

    HDAO(High Definition Ambient Occlusion)的工作原理和HBAO(Horizon Based Ambient Occlusion)基本一致,通過考慮環境光和環境而不僅僅是像素來解決SSAO像素深度測量帶來的顆粒和噪聲問題。主要缺點是需要更多的CPU和GPU處理能力。HBAO+提供了一種性能任務較少的光影采樣算法,將AO的細節級別提高了一倍,運行速度提高了三倍。

    HBAO的與以前的SSAO變體不同,使用了基于物理的算法,該算法與深度緩沖區采樣*似積分,使HBAO能夠生成更高質量的SSAO,增加每個像素的樣本數量以及AO的定義、質量和可見性。出于性能原因,HBAO通常以半分辨率渲染,將AO像素數量減少四分之三,但是,以降低的分辨率渲染HBAO會導致在所有情況下都難以隱藏的閃爍。

    如果使用PS方式的后處理,可能存在大量過采樣,樣本覆蓋的區域是內核大小,然后對中心像素周圍的一大堆紋素進行采樣。文中嘗試使用Computer Shader來加速。

    首先是重疊地分塊,使用LDS大幅降低紋理采樣成本,將屏幕劃分為tile以供線程組處理。內核大小決定重疊程度,下圖顯示了涉及LDS寫的紋素采樣區域、涉及LDS讀/寫的ALU PP計算區域和內核大小:

    // 實現代碼
    // CS result texture
    RWTexture2D<float> g_ResultTexture : register( u0 );
    // LDS
    groupshared float g_LDS[TEXELS_Y][TEXELS_X];
    
    [numthreads( THREADS_X, THREADS_Y, 1 )]
    void CS_PPEffect( uint3 Gid : SV_GroupID, uint3 GTid : SV_GroupThreadID )
    {
        // Sample texel area based on group thread ID – store in LDS
        g_LDS[GTid.y][GTid.x] = fSample;
        // Enforce barrier to ensure all threads have written their
        // samples to the LDS
        GroupMemoryBarrierWithGroupSync();
        // Perform PP ALU on LDS data and write data out
        g_ResultTexture[u2ScreenPos.xy] = ComputePPEffect();
    } 
    

    基于CS的HDAO相比PS的性能,深度提升1.3倍,深度+法線提升3.6倍(測試環境Windows 7 64-bit, AMD Phenom II 3.0 GHz, 2 GB RAM, ATi HD5870, Catalyst 10.2):

    另外,還可以使用DX11的GatherCmp() 來快速采樣PCF陰影,實現更簡單、統一。

    DiRT2使用后臺加載線程,放置在隊列中的資源,在DX9模式下,資源在主線程上創建。在DX11模式下,資源在加載線程上創建,更簡單、更快速的實現,加載時間明顯加快,約快50%。

    R-Trees -- Adapting out-of-core techniques to modern memory architectures介紹了R-Tree的特點、原理,展示了如何使它們適應內存使用,在緩存行為以及SIMD處理等方面獲得重大優勢。

    R-Tree本質上就是一個AABB樹,但有一些特定的屬性和提前準備的大量工作。其節點是由大的固定大小的子AABB和指針組成的塊,AABB用于存儲在其父節點中的節點,給定訪問模式是有意義的,有些松散(通常高達50%),減少拓撲更改的頻率。

    以2-3的R-Tree的構建為例(2-3是指子節點數量控制在2-3個,實際會使用更大的節點數量,如16-32的R-Tree):

    R-Tree的優點是:

    • 緩存友好的數據布局。

    • 沒有剛體細分的模式。

    • 更高的分支系數。

      • 更短的深度,更少的讀取,每個節點內更多的工作。
    • 預讀取(寬度優先遍歷)

      • 堆棧(深度優先):當前節點可以更改下一個是哪個節點。
      • 隊列:知道下一個是哪個節點,所以預取之。
    • 每個節點有很多子節點。

      • 展開測試以隱藏VMX延遲。
      • 隱藏預讀取的延遲。
    • 可用于動態物體。

      • 即使物體移動,拓撲結構依然有效。需要傳播任意的AABB更改給父節點。
      • 可能最終表現不佳,但是仍然是正確的。
      • 延遲重新插入,直到物體移動了較大的距離。調整AABB比重新插入要快得多。

    總之,R-Tree是快速的基于塊的AABB樹,具有層次樹的所有常規優點,對緩存和SIMD非常友好,不需要桶加載(bulk-loading),但需要大量的前期開發工作。

    Streaming Massive Environments from 0 to 200 MPH介紹了賽車游戲Forza Motorsport 3中用于制作和渲染大型賽道環境的流程,從藝術到游戲的流程,以及渲染大量的高細節模型的一些關鍵技術。

    Forza Motorsport 3的流式目標是以60fps渲染,包含賽道、8輛汽車和用戶界面等模塊,支持后期處理、反射、陰影、顆粒、滑道、人群,支持分屏、回放等。游戲擁有海量環境:100多條軌道,有些長達13英里,超過47000個模型和超過60000個紋理。典型的海量模型可視化層次:

    現代內存模型如上圖,速度依次提升但容量依次下降:磁盤/局部存儲、壓縮緩存、解壓堆、GPU/CPU緩存、GPU/CPU。下面對這些存儲類型加以說明。

    在硬盤上,以zip包的形式存儲,以zip格式存儲一些額外的數據,但遵循基本格式,因此標準瀏覽工具仍然有效(資源管理器、WinZip 等),在存檔中以LZX格式存儲,每個軌道(track)有90-300MB。

    從磁盤到壓縮的緩存時,利用高速緩存塊大小的快速IO,Block是zip中的一組文件,文件的總大小,直到達到塊大小,通過單次讀取檢索該文件組。壓縮緩存減少了查找,峰值達到15MB/s,*均10MB/秒,但查找需要100ms。

    壓縮緩存以LZX格式在內存中存儲,按需流入和流出LRU的緩存塊,約56MB,逐軌道調整塊大小,但通常為1MB。

    從壓縮緩存到解壓堆時,使用快速的*臺特定解壓,*均20MB/秒,解壓堆實現時針對分配和釋放操作的速度進行了優化,并且使用地址排序優先的良好分段特性。

    解壓堆準備好供GPU或CPU使用,每個分配連續且對齊,約194MB。

    多級的紋理存儲,每個紋理的三個視圖:Top Mip是Mip 0,全分辨率紋理,Mip–Chain是Mip 1,下采樣到1x1,小紋理則從32x32到1x1。此處針對*臺的支持不需要重新定位紋理,因為Top Mip是流式傳輸的。

    多級的幾何存儲,將不同的LOD視為不同的對象,以允許流式傳輸在更高的LOD沒有貢獻時轉儲它們,模型使用每個實例的變換和著色器數據進行實例化。

    從內存到GPU/CPU緩存時,針對緩存友好渲染的CPU特定優化,高頻操作具有扁*、高速緩存行大小的結構,CPU的L1/L2高速緩存,大量使用命令緩沖區以避免接觸不必要的渲染數據。

    GPU/CPU緩存根據著色器需求調整格式大小,GPU的頂點/紋理提取緩存(如頂點格式、流計數、紋理格式、大小、mip使用情況),使用*臺特定的渲染控件來減少mip訪問等。

    對于預先計算的可見性,標準解決方案是給定場景在給定位置實際可見的內容,許多實現使用保守遮擋。而本文使用的變量包括遮擋(深度緩沖區拒絕)、LOD 選擇、貢獻拒絕(如果小于n像素,則不繪制模型)。

    剔除的方式有:遮擋剔除——在視圖中被其它物體阻擋的物體(下圖紅色方形)和貢獻被剔除——對視圖的貢獻不足的物體(下圖黃色圓圈)。

    可以在運行時做到,LOD和貢獻很容易,可以實現遮擋。最重要的是必須在運行時進行優化,或者根本不這樣做,但意味著流式傳輸和渲染過多。可見性信息通常是大量數據,意味著需要接觸大量數據,對緩存性能不利。Forza Motorsport 3的解決方案是不要將CPU/GPU花費在可以離線處理的工作負載上。

    Forza Motorsport 3的軌道處理流程分為5個采樣主要步驟:采樣、拆分、構建、優化和運行。所有步驟都是全自動的,源自場景中的藝術檢查,管線生成優化的游戲就緒軌道。

    在優化步驟,為包裹創建緩存有效的序號,縮短查找距離并提高緩存命中率,使用“首次看到”的指標,走過區域并跟蹤哪個區域首先使用模型或紋理,將所有模型組合在一起并按第一個區域排序,與紋理相同。

    在運行步驟,創建區域增量,確定攝像機在可見空間中的位置,將攝像機位置映射到要加載的區域,當前加載的區域和要加載的區域的差異。根據區域增量創建資源增量,基本上是引用計數,整合工作以確保免費首次訂購(這是為了幫助解決碎片化問題)。在尾部區域流出(免費)數據,在前導區域流入(分配、IO 和解壓縮)數據。

    運行時注意事項,重點領域包含工作順序、堆效率、解壓效率、磁盤效率,對于許多問題,任何解決方案都比什么都不做要好,確保層次結構的所有級別都得到解決。

    管線的錯誤主要有:

    • 跳變。
      • 僅限于兩個層級。
        • 延遲加載(通過限制每個區域保持在系統吞吐量內所需的數量進行調整)。
        • 可見性錯誤(通過進一步聚類對象或使抽樣結果有偏差進行調整)。
      • 雖然這些調整有沖突。
      • 提供手動操作。
        • 幾何偏差(影響采樣結果)。
        • 紋理偏差(在優化期間影響紋理工作集中的位置)。
    • 再多的自動化也無法與不切實際的期望相抗衡。
      • 例如,所有模型都在單個區域中可見,意味著不會有任何用于紋理的空間。

    A Dynamic Component Architecture for High Performance Gameplay詳細介紹了系列游戲Resistance中使用的動態組件架構,以表示實體和系統行為的各個方面。該組件系統解決了傳統游戲對象模型在高性能游戲中的幾個弱點,特別是在多線程或多處理器環境中。動態組件從高效的內存池中按需分配和釋放,系統提供了一個方便的框架,用于在不同的處理器(如SPU)上并行運行更新。該系統可以分層在傳統游戲對象模型之上,因此代碼庫可以逐漸遷移到這種新架構。本文將討論系統的動機、目標和實現細節。

    以往的游戲對象組織層次比較單一、高深度和不*衡,內存上在編譯期綁定數據,性能上緩存一致性差,架構上通過繼承來獲得能力,另外是使用習慣。

    解決方案是通過在運行時組合組件來構建游戲對象,小塊(Small chunk),表示數據變換。可以并行實現,無需重構現有代碼,與組件共存。但是,動態組件不解決反射、序列化、數據構建、實例版本控制等問題。

    動態組件系統的特點有:

    • 組件

    組件是最初的特點,基礎組件類有8字節的管理數據,從池中分配,每種具體類型一個池,“名冊”索引實例,“分區”分隔已分配/空閑的實例。

    • 高性能

    微量的恒定的時間操作,包含分配/免費、解析句柄、獲取類型、類型實現(派生自),無實例復制。按類型更新(按池),緩存友好,促進異步更新(例如在SPU上),名冊是分配實例的連續列表,分區名冊是DMA列表。解析句柄具有微量的恒定的時間操作:索引到池中、比較生成、返回組件。

    • 動態

    游戲對象的運行時組合,無包袱動態地改變行為,已分配的組件 == 正在使用,池大小 == 最大并發分配。

    高頻地alloc()free()alloc()包含可用性測試、從索引和生成構造句柄、增加名冊分區、Component::Init()free()包含Component::Deinit()、交換名冊索引與分區相鄰索引、減少分區、增量生成。動態組件的釋放接口如下:

    // free the component from host's component chain                                                   
    void DynamicComponent::Free( Type type, HostHandle host_handle, Chain& chain, ComponentHandle& component_handle );
    

    結合下圖,有一個給定組件類型的實例池,還有有一個名冊,它是實例池中的索引數組。注意,池中的所有實例都屬于同一類型,因此它們的大小相同。因此,名冊索引處的值本身就是池中的索引。可以看到有一個分區值,它將代表已分配實例和空閑實例的名冊索引分開。

    現在將釋放由第3個roster元素表示的組件,它當前在池中的索引為3:

    要做的是交換第3個和第4個名冊元素:

    現在正在釋放第4個roster元素,它代表池中的第3個實例。由于將要釋放的元素的roster條目交換到分區相鄰的roster的條目中,因此釋放該實例所需要做的就是將分區的索引上移1個單位(減1),就是如此簡單:

    • 系統

    不是全有或全無!例如對話、腳本事件、投籃:無游戲對象。下面是動態組件系統的相關接口定義:

    namespace DynamicComponent                                                                           
    {                                                                                                    
        // Hosts' API                                                                                      
        Component*        Allocate                  ( Type type, HostHandle host_handle, Chain* chain, void* prius = NULL );                  
        Component*        ResolveHandle             ( Type type, ComponentHandle component_handle );       
        Component*        Get                       ( Type type, HostHandle host_handle, Chain chain );    
        Component*        GetComponentThatImplements( Type type, HostHandle host_handle, Chain chain );    
        Component**       GetComponents             ( Type type, HostHandle host_handle, Chain chain, u32& count );                           
        Component**       GetComponentsThatImplement( Type type, HostHandle host_handle, Chain chain, u32& count );                           
        void              Free                      ( Type type, HostHandle host_handle, Chain& chain, ComponentHandle& component_handle );                 
        void              FreeChain                 ( HostHandle host_handle, Chain& chain );              
    
    #define COMPONENT_CAST(component, type) \                                                            
      ((type##Component*)ValidCast(component, DynamicComponent::type))                                   
      inline Component* ValidCast                 ( Component* component, Type type );                   
    
        // Systems' API                                                                                    
        Type*             GetTypesThatImplement     ( Type type, u32& count );                             
        bool              TypeImplements            ( Type type, Type interface );                         
        u32               GetNumAllocated           ( Type type );                                         
        Component**       GetComponents             ( Type type, u32& count );                             
        Component*        GetComponentsIndexed      ( Type type, u16*& indices, u32& count );              
        void              UpdateComponents          ( UpdateStage::Enum stage );                           
        void              Free                      ( Type type, ComponentHandle& component_handle );      
        UpdateStage::Enum GetCurrentUpdateStage     ( );                                                   
        u8                GetTypeUpdateStages       ( Type type );                                         
    }
    

    實現的過程涉及腳本事件、分配和初始化、異步更新等細節。

    無獨有偶,Entity Component Systems也談及了Unity引擎ECS和作業系統的特點。

    實體組件系統 (ECS) 是一種主要用于游戲和模擬的數據組織方式。實體(或游戲對象)是游戲中可以看到或與之交互的任何對象,例如玩家、敵人、障礙物、通電。組件是分配給實體的屬性,例如附加到玩家實體,可以擁有健康、碰撞、變換和運動組件。系統是向組件添加功能的地方,即使用運動和變換組件,可以制作基本的運動系統。下圖分別展示了實體、組件、系統管理器:

    ECS模型包含純粹(Pure)與混合(Hybrid)方式,兩者對比如下:

    Pure Hybrid
    實體是新的游戲對象 包含Pure ECS的所有功能
    沒有更多的mono行為 包括將游戲對象轉換為實體并將mono行為轉換為組件的特殊輔助類
    數據存儲在組件中,邏輯存儲在系統中
    利用提供性能優勢的新C#作業系統

    Burst是Unity開發的一種新的數學感知編譯器,可以生成高度優化的機器代碼,充分利用正在編譯的*臺,完全自動化。需要做的就是將 Burst編譯器包添加到項目中,然后確保C#作業標有Burst Compile屬性。隨后Unity將獲取作業系統代碼并將其編譯為高度優化的機器代碼,意味著用C++甚至C等語言編寫邏輯可以直接在處理器上執行。Burst的優勢是非常容易實現,不需要了解復雜的低級代碼。與ECS相結合,大大提高了性能。

    Unity作業系統讓開發人員可以輕松安全地編寫多線程代碼,它通過創建作業而不是線程來做到這一點。作業表示系統可以作為一系列線程處理的工作單元,安排作業時,系統會將其放入一個特殊的隊列中, 工作線程會將作業從隊列中拉出并執行。工作線程是由作業系統管理的單個線程,它們在后臺完成作業,因此不會中斷主線程。

    使用Unity作業系統的原因包括:它確保多線程代碼是確定性的的。例如,作業系統將通過為每個邏輯CPU內核創建一個工作線程來盡量避免上下文切換,使得開發人員可以(在合理范圍內)創建任意數量的作業,而不必擔心它會如何影響CPU的性能。作業系統還有一個內置機制,用于以作業依賴的形式防止競爭條件。例如,如果作業A需要為作業B準備一些數據,則可以將其分配為作業B的依賴項,這樣,作業A將始終首先運行,作業B將始終擁有正確的數據。

    Unity作業系統允許應用層開發人員以安全、簡單且完全由Unity管理的方式使用多線程。當它與Unity ECS和Burst Compiler結合使用時,會立即獲得性能極佳的代碼并進行優化。

    Reflection for Tools Development探討程序員在開發內容創作工具時遇到的常見模式。無論是用于動畫、音頻、圖形還是游戲玩法,開發提供出色工作流程的工具并非易事,工具必須穩定,同時適應生產過程中的變化,必須與用C++編寫的游戲集成,必須是特定于游戲的,但也必須是可重復使用的。Jeremy Walker分享了育碧溫哥華使用的一個內部開發的框架,可以快速開發具有出色工作流程的工具。

    在引擎開發過程中,經常遇到硬編碼和數據驅動的系統的權限、選擇和設計:


    各個模塊或子系統中有著錯綜復雜的聯系、交互或依賴:

    這些子模塊部分可以直接選擇硬編碼:

    然后分別將每個子系統的編輯、構建、序列化、渲染部分單獨合并成一個模塊組,形成整體引擎方法:

    問題是如何*衡低開發成本和出色的工作流、適應變化和穩定工具、可重用的系統和游戲和特定類型的需求配對之間的關系呢?

    解決方案是對于所有類型的內容:最大限度地降低開發出色工作流程的成本,在滿足特定游戲需求的同時設計可重復使用的系統,開發能夠適應生產過程中不斷變化的穩定工具。軟件包發布流程圖如下:

    單體軟件包發布的問題:

    可以采樣解耦包:

    鎖步發布的問題:

    可以采用反射系統來降低開銷和復雜度。下面是硬編碼、單體引擎、使用反射的解耦系統對比圖,其中使用反射的解耦系統可以達到低開銷、簡單、高度可重用、出色的工作流等目標:

    現在再轉向闡述計算機語言的反射(Reflection)機制。下圖是C++的編譯流程和反射機制,其中反射在函數聲明時收集信息,然后將收集到的信息綁定到函數定義:

    對于混合語言的反射,流程相似但略有不同:

    游戲腳本語言的反射:

    游戲序列化的反射:

    通用語言的反射規范:

    C++反射的流行方法:宏(Macros)、代碼解析器(Code Parser)、類型定義語言(Type Definition Language)。它們的示例代碼如下:

    // ----- 宏 -----
    
    class SimpleVehicle : public Entity
    {
    public:
        DECLARE_TYPE();
         float    m_MaxSpeedKPH;
         void     Reset(bool useDefaults);
        float     GetMaxSpeedMPH() const;
        void    SetMaxSpeedMPH(float maxSpeedMPH);
    };
    
    //In a separate .CPP file:
    DEFINE_TYPE(SimpleVehicle)
        BASE_CLASS(Entity)
        FIELD(“MaxSpeedKPH”, m_MaxSpeedKPH)
        METHOD(Reset)
        PROPERTY(GetMaxSpeedMPH, SetMaxSpeedMPH)
    DEFINE_TYPE_END()
        
        
    // ----- 代碼解析器 -----
        
    /// [Class]
    class SimpleVehicle : public Entity
    {
    public:
        /// [Field(“MaxSpeedKPH”)]
        float    m_MaxSpeedKPH;
        /// [Method]
        void Reset(bool useDefaults);
        /// [Property]
        float     GetMaxSpeedMPH() const;
        /// [Property]
        void    SetMaxSpeedMPH(float maxSpeedMPH);
    };
    
    // ----- 類型定義語言 -----
    
    class SimpleVehicle : Entity
    {    
        float    MaxSpeedKPH;
        void     Reset(bool useDefaults);
        float     MaxSpeedMPH { get; set; }
    };
    

    以上三種C++反射方法的優缺點見下表:

    C++反射方法 優點 缺點
    沒有外部工具 實現起來很尷尬,難以調試,運行時發現
    代碼解析器 更容易實現,編譯期發現 緩慢的預構建步驟
    類型定義語言 最容易實現,沒有緩慢的預構建 不能反映現有的類

    順便提一下,UE的反射是以代碼解析器為主結合宏為輔的混合方式。

    導出游戲的類型定義的流程如下:

    上面的示例代碼中的類型SimpleVehicle經過代碼解析器導出的Types.xml數據如下:

    <type name=“SimpleVehicle”>
      <field name=“MaxSpeedKPH” type=“float”/>
      <method name=“Reset” returntype=“void”>
        <parameter name=“useDefaults” type=“bool”/>
      </method>
      <property name=“MaxSpeedMPH” type=“float” hasget=“true” hasset=“true”/>
    </type>
    

    導出工具的類型定義和游戲的稍有不同:

    其中生成的C#代理類型如下:

    [ProxyType(“SimpleVehicle”, 0x81c37132)]
    public partial class SimpleVehicle : Entity
    {
       public float MaxSpeedKPH
       { 
          get { return this.Instance.GetField(“MaxSpeedKPH”).Get<float>(); }
          set { this.Instance.GetField(“MaxSpeedKPH”).Set<float>(value); } }
       }
       public float MaxSpeedMPH { ... }
       public void Reset(bool useDefaults) { ... }
    }
    

    反射在工具中的主要用途有序列化、客戶端-服務端遠程處理、生成GUI:

    客戶端-服務器遠程處理流程和步驟:

    存在的問題和解決方法:

    • 類型定義不同步。檢測類型校驗和不匹配,及早發現問題,自動同步類型信息,數據自動遷移。
    • 與游戲緊密耦合的工具。避免過度使用生成的代理類,盡可能使用生成的UI,使用多態代理類。

    • 內存使用過多。基于使用情況的剝除類型的無用信息,自動檢測未使用的反射類型。

    反射的其它用途有多處理器架構的編組事件,在線客戶端-服務器遠程處理,保存游戲數據的序列化。

    下圖是育碧的內容框架:

    獲得良好的結果有快速工具開發,適用于所有類型內容的出色工作流程,具有改進的可重用性和對變化的彈性的解耦系統。

    The Asset pipeline for Just Cause 2: Lessons learned概述了游戲Just Cause 2中使用的資產調節管線,包含分析管線需求和設計一個可以消除關鍵瓶頸、提供穩健環境并提高吞吐量的系統的過程,討論了識別可以簡化管線的關鍵底層系統的過程。在這些系統中,有一個獨立于*臺和語言的數據管理層、一個資產依賴解析器和使用Python的編譯器腳本框架,還討論了一種以受控方式處理新編譯器部署的系統,基于這個新基礎重建現有編譯器管線的過程、好處、副作用、可維護性、穩健性,以及改進的反饋水*和監控管道統計數據的能力。Just Cause 1的工作流如下圖:

    這個工作流程有一些重大缺陷。首先,JustEdit的手動導出步驟太多,更糟糕的是,它導出了游戲就緒格式,一個實體的變化會影響很多位置或任務,因此需要再次重新導出所有資產。

    到了Just Cause 2,內容創建者訂購了許多新工具,編寫了詳細的設計文檔,每個工具都分配了一個“客戶”,程序員與客戶保持密切溝通,客戶負責測試工具并批準它,作為JustEdit插件的工具,WTL用于編輯器/插件中的GUI。

    項目開始時有10個新工具!!代碼沒有在應該完成的90%-100%完成。工作流程中的缺陷,并非所有工具都按預期使用,易于編輯的內容在性能和內存方面不太適合游戲。設計文檔按“原樣”接受,它們本質上是內容創作者的愿望清單。

    顧客不是開發團隊的一員,溝通受到影響,經常計劃全職從事其它任務,責任不明。插件導致了不穩定的C++接口,應該與網絡通信一起去,過于復雜。圖形用戶界面:WTL和C++,WTL太簡單了,而C++減慢了迭代速度。

    C++中的編譯器,使用C++編寫腳本并不總是很方便,構建時間長,調整起來很尷尬,一些編譯器包含所有游戲代碼:非常特定于*臺,如除了其它的事情,帶了DirectX的Win32繁重,無法在沒有桌面的自動構建器上運行。

    程序員實現的東西略有不同,導致整體行為異常,打破依賴檢查,編譯器中的硬編碼參數。文件格式不夠穩定,如果出現問題,編譯器可能會崩潰,或者更糟糕的是,損壞的數據進入了游戲。

    不愿使用編譯器,管線被認為有點魔法,缺乏文檔,整體流程不容易概覽,調試經常在游戲代碼中完成的損壞數據,程序員已經設置了所有代碼和數據,這有時會導致完成運行時修復,而不是修復編譯器中的錯誤。

    沒有中心代碼,集成時不使用中心代碼意味著問題,修復丟失了,必須重新找到它們,內置編譯器在Perforce中進行版本控制,需要時不會自動重建和使用,仍然可以使用舊的錯誤代碼。

    數據格式方面,有許多不同的文件類型(約30),許多格式都是基于xml的,將“屬性名稱”映射到“值”,格式對編輯器比對游戲更優化,未檢測到讀/寫錯誤,沒有版本號,無法記錄正確的錯誤消息,很多東西要改進。

    內容構建系統是有點粗略的解決方案,很慢的依賴檢查,不完整的依賴樹,時間戳不夠好。由于行為中的錯誤和異常,用戶不能100%信任它,沒有信任意味著完全清理和完全重建。

    為解決JC2的問題,提出了全新的目標:重建對管道的信任,增量構建,沒有神奇的工作流程,易于配置,使用依賴檢查,100% 準確率,簡化開發,將通用代碼移至中央存儲庫,減少創建工具時的周轉時間。

    重大決定,需要一個中央代碼的構建/部署系統,內容構建系統也需要大力推動,需要一種簡單易用但功能強大的通用數據格式,開發必須與 JC2 一起完成,需要致力于任務才能真正快速到達任何地方。

    需要依賴解析器和部署系統,內部使用python編寫,非常輕量級,處理包體:構建庫 (.h + .lib/.so)、構建的可執行文件、捆綁的 python 腳本(或任何你想要的)、第三方庫、可執行文件。將包部署到中央存儲庫,獲取包到本地系統。

    編碼過程的巨大變化,代碼庫向更小的庫發展,真正快速的編譯時間,幫助跟蹤版本沖突,允許非常快速的開發和測試。

    用于代碼和數據的輕量級構建系統,開源 (BSD),源代碼約為80kb,依賴檢查,多核支持,易于維護和擴展,替換內部的項目編譯器系統,用Python編碼。

    雪崩數據格式,序列化框架:C/C++, Python 支持、Xml <-> 二進制支持,二進制文件的基于哈希的版本控制,錯誤處理,從Python和C/C++訪問數據的能力實現了無縫的跨語言交換,跨*臺支持,例如將數據從工具傳遞到控制臺/從控制臺傳遞數據。

    ADF和Python使讀取和寫入數據變得非常容易,讀取一個類型庫,然后從xml格式加載源文件。接下來,更改第一個對象的名稱,最后,以二進制格式(big endian)寫回所有內容。下圖是文件如何相互關聯的示意圖:

    C++用于共享庫,從Python加載,移除了對舊游戲代碼的依賴,用標準代碼替換內部代碼,fopen() 而不是CFile(),降低復雜性,增加可移植性。腳本和邏輯用python,數據處理用Python或C++,例如使用C++讀取/寫入/處理Havok文件。

    Python用于極端的周轉時間,大量內置模塊,經常使用optparse、ctypes、md5、numpy、cStringIO…許多模塊都有用C++實現的后端,大多數編譯器被完全重寫,減少很多依賴,代碼大小降至1/10。

    依賴檢查方面,使用Waf處理機制,掃描程序找到依賴項,Waf緩存結果,MD5校驗和命令行參數,源/依賴文件內容,新的編譯器路徑將觸發編譯,使用Needy更新軟件包時非常方便。

    增量自動生成器,從版本控制同步,只構建必要的東西,全自動生成器,將增量構建與完整構建進行比較,發現依賴問題,查找數據格式中的錯誤。

    2011年,Multi-Core Memory Management Technology in Mortal Kombat分享了在游戲真人快打中使用的多核內存管理技術。

    《MK vs DC》主要使用了兩個內存管理器:虛幻內存管理器 (FMalloc),引擎端資源,基于C/C++的內存管理。“游戲”內存管理器,游戲端資源,面向控制臺。

    Unreal內存管理器的限制有LibC++功能集,不支持多堆,不是原生線程安全/多核,非線程安全內存分配器受“全局鎖”保護,“MK vs DC”在內部使用DLMalloc,某些操作會導致較長的卡頓。

    游戲內存管理器的限制有不是線程安全的,不是“虛擬內存感知”,僅支持靜態固定后備庫,非常慢的O(N) 次操作,容易碎片化(。

    全局鎖定并非良策,未經過多核優化,所有操作都可能導致其它線程上的輕微停頓或上下文切換,某些操作可能會導致大系統范圍的停頓,例如大型應用程序分配請求、堆后備存儲分配、重新分配操作。

    下圖是全局鎖定重新分配,線程1的重新分配會鎖定內存塊,導致線程2一直等到線程1完成內存分配之后才能進行鎖定并執行內存操作:

    良好粒度的鎖定重新分配可以減少等待:

    非阻塞的重新分配:

    虛擬內存解決內存碎片:

    多核下的內存操作默認情況下是線程安全的,盡可能無鎖(且直接),需要時首選非阻塞鎖:非排他鎖(例如讀寫器)、細粒度鎖定、條紋鎖(Striped Locking)。也可以考慮單線程的高性能,無競爭的訪問不會出現明顯的性能損害。

    新的內存管理器優化線程安全和多核,為游戲和虛幻引擎統一單獨的內存管理器,支持具有額外功能的多個堆,提高性能(CPU 周期和內存使用效率),通用跟蹤和調試實用程序。

    并發堆具有最小的線程“串擾”,可以在單個堆上同時分配/釋放多個線程(如果堆類型支持,大多數堆類型都可以!),后臺存儲和內部堆查詢操作通常同時運行(使用無鎖、條帶化或讀寫器鎖),Realloc()在復制發生時從不阻塞。簡化的內存管理架構如下:

    在實現堆時,Heap API使用虛函數,Backstore和OS Allocs的通用支持API,Global Free() “知道”返回的堆內存。易于制作不同的堆實現,直接操作系統堆,最佳擬合堆(使用紅黑樹),小塊堆(無鎖分配/無條紋),固定塊堆(無鎖 - 用于MK游戲對象)。

    可以使用混合主堆,即主堆使用混合方法來處理分配:大型分配直接通過操作系統以最大程度地減少碎片(但在內部進行跟蹤),中等分配進入最佳適合堆,小塊分配由它們自己的堆處理。C++ new/delete和C malloc/free調用路由到主(混合)堆。

    UE的內存管理就是使用這種混合主堆的方法,詳見1.4.3 內存分配

    下圖顯示了內存分配的占比中,小堆占約23%,中堆占約52%,大堆占約25%;而在分配次數上,小堆占約96.7%,中堆占約3.2%,大堆占約0.26%,可見在次數上,小堆占了絕大部分,管理好小堆的分配等操作至關重要!!

    下圖顯示了不同大小的堆分配的次數,基本上越小的堆次數越多:

    SBMM = 小塊內存管理器,它的特點是非常低的線程爭用,支持許多同時操作,分箱分配器(尺寸化的箱、Lock Striping = Lock Per Bin),(大部分是)無鎖的Alloc(),后備緩存使用無鎖分配的victim(Lockfree Lookaside cache for a Block’s Items,塊項目的無鎖后備緩存)塊,快速的條紋鎖定(Stripe-Locked)釋放。SBMM的裝箱過程如下圖:

    SBMM的內存布局如下圖:

    SBMM主要是無鎖分配,LockFree freelist緩存“受害者”塊的項目,為空時,獲取Bin條帶鎖,并從下一個帶有空閑項目的Block建立新的空閑列表,是一個非常快的操作,直到所有塊都用盡為止,在這種罕見的情況下,必須從超級塊中取出一個新塊,并為這些項目初始化一個空閑列表, 如果所有的SuperBlock都用完,則從OS請求一個新的SuperBlock。

    SBMM的釋放本來是無鎖的,但需要延遲GC,條紋鎖定 == 輕松修剪(無延遲GC),查找塊和箱的大小,快速鎖定箱,推送內存項目并檢查計數,如果需要修剪,拉取塊,釋放鎖定,修剪,否則釋放鎖定,無競爭的案例與無鎖的速度非常相似,條紋一如既往無競爭。

    無鎖的NR-Pool是用于MK內存系統中的簡單控制結構:

    Mega Meshes - Modeling, Rendering and Lighting a World Made of 100 Billion Polygons分享了用于研發游戲Milo and Kate(下圖)的引擎在模型和管線、構建、壓縮和流、虛擬紋理、實時GI等方面的內容。

    與傳統的環境模型管線不一樣的是,本文的引擎使用了大型網格工具,管理具有數十億個多邊形的模型,支持多個用戶,聯合了DCC工具、VC和雕刻等工具,窗口可在可變的細節級別編輯,負責協調構建過程。

    在物理內存中存儲如此大的數據集是不現實的,幾何數據分層存儲,連續的細分關卡以差異數據存儲,按需加載的關卡。過程見下面系列圖:





    分級存儲的好處可以在低細分級別編輯世界的區域,并保留高頻率的細節,可以改變地形的宏觀地形而不丟失所有細節,只需修改較低的細分層,可以做出大比例的色調調整。為了解決多人編輯的關卡的縫隙,需要額外的步驟生成運行時網格:

    稀疏虛擬紋理(Sparse Virtual Texture)的出現是為了解決高內存占用很多多的全局通道,需要良好的流/渲染系統,可以使用虛擬紋理,在紋理空間虛擬化。下面幾幅圖分別是Milo and Kate早期的虛擬紋理圖集、大型紋理和mipmap分拆到tile、虛擬紋理的物理內存加載過程:



    虛擬紋理的細節:數組中的多個虛擬紋理,16 * 32k * 32k的地址空間,128 x 128的tile,8:8位的UV tile地址,一些角色/道具的虛擬紋理,其余的則被劃分到世界的不同區域,可以在關卡邊界解除未使用的綁定,允許連續的流式世界,2048 x 4096 (28mb)的物理頁面。

    下圖展示了編譯巨型紋理的流程圖:

    渲染時,渲染線程、紋理緩存線程、GPU線程的交互圖如下:

    此外,該引擎在實現基于SH的GI的流程圖如下:

    Game Worlds from Polygon Soup: Visibility, Spatial Connectivity and Rendering描述了Halo系列游戲的虛擬世界內的Polygon Soup(無組織的多邊形組)在可見性、空間鏈接及渲染方面的技術內容。

    在虛擬環境方面,Halo應用了單元格和門戶(portal)、防水殼幾何形狀、藝術家手動放置傳送門、從殼幾何構建BSP樹、Floodfill BSP進入單元格、建立單元格連接等技術。這樣的技術選型優點是統一的可見性/空間連接、精確的空間分解、內部/外部測試、非常適合帶有自然門戶的室內空間。缺點是手動門戶化并非易事!、水密性對內容創作來說是痛苦的、強制進行早期設計決策、僅針對室內場景進行了優化。

    下面依次是門戶化、多邊形組的圖例:


    Polygon Soup是只是一些多邊形塊聚在一起,非防水結構,沒有手動門戶,增量構建/快速迭代,允許后期設計更改。可用于細分場景、體素化細分體積、將體素分割成區域、構建區域之間的連接圖、從體素區域構建簡化的體積等。下面圖是2D的尋路應用案例:

    從左到右、上到下依次是:輸入場景、體素化、可行走體素、距離場、分水嶺變換、輪廓。

    最終生成的導航網格。

    上述圖例是2D空間,實際在3D空間存在諸多問題,包含3D更難/更慢、過度分割(小區域)、對場景變化敏感、簡化表示并非易事、能見度如何表達等。

    解決方法是與Umbra協作,自動生成門戶,增量/本地更新,基于CPU的解決方案,低延遲,相同的可見性和空間連通性解決方案,處理門和電梯,精確圍繞用戶放置的門戶,快速運行時間/低內存占用。Umbra解決方案的流程如下:

    總體分為將場景離散為體素、確定與輸入幾何相關的體素連通性、傳播連接以查找連接的組件、確定本地連接組件之間的門戶等步驟。

    瓦片體素化的過程。

    將體素轉換成單元格和入口。

    構建單元格和門戶。

    Halo Reach游戲循環的特點如下:

    • 粗粒度并行。
    • 線程上的系統。
    • 通過狀態鏡像顯式同步。
    • 主要是手動*衡負載。

    Halo Reach細粒度并行。

    這個并行系統在幀結束時需要鏡像(復制)整個游戲狀態(Halo Reach約20MB),以便模擬線程可以自由地處理下一幀,渲染線程可以將當前幀提交給 GPU。鏡像游戲狀態的原因之一是在復制游戲狀態之后計算可見性。復制20MB游戲狀態的串行任務很耗時(約3ms),需要對整個游戲狀態進行雙重緩沖。 注意,此處的游戲狀態僅指確定性游戲狀態數據。

    Halo Reach的并行系統在幀尾需要復制整個游戲狀態。

    下圖展示了各個線程的普遍利用率:

    有沒辦法改進這一痛點?

    觀察#1:Halo Reach不需要整個游戲狀態來渲染。在Reach中,游戲狀態提取發生在進行可見性計算之前,這就是為什么必須復制整個游戲狀態,開銷大(以毫秒和內存占用為單位)。可見性占據渲染線程上的大量CPU時間,然而,CPU時間未得到充分利用,未充分利用的硬件線程。但是實際上可以反轉那個操作,僅將可見對象的數據復制到游戲狀態之外,僅提取將要渲染的對象的數據。

    更好的做法是根據可見性結果推動游戲提取和處理,僅提取可見對象的數據(靜態和動態),無需雙緩沖整個游戲狀態,僅為可見對象的每幀瞬態緩沖游戲數據,更小的內存占用。

    更好的負載*衡做法:首先將可見性計算拆分為每個視圖的作業,包括玩家、陰影、反射視圖的可見性計算,可見性作業可以具有視口到視口的依賴關系,可以重用一個可見性作業計算的結果作為另一個的輸入。

    減少輸入延遲的做法:在游戲對象更新的同時錯開可見性計算,在幀中盡早使用預測相機開始靜態可見性,在對象更新之前執行。

    改善CPU延遲:僅為可見對象運行昂貴的CPU渲染操作,只要確保在可見性之后運行它,僅渲染操作(蒙皮、布料模擬、多邊形排序)——不會影響游戲玩法。改進游戲循環和并行方式后的運行情況如下:

    收益是將游戲狀態遍歷與繪圖分離,通過交錯的可見性計算提高CPU利用率,渲染線程成為流線型內核處理器。下圖是一個簡單的小作業樹:

    Culling the Battlefield: Data Oriented Design in Practice分享了Battlefield 3中采用的面向數據的設計和實踐。

    以往的裁剪剔除方法已經有層次球體樹、靜態剔除樹、動態剔除樹等。

    但Battlefield 3依然打算重構之,原因有動態剔除樹縮放、子關卡、管線依賴、難以擴展、每個視錐體一個作業等。

    新系統的要求是更好的縮放、可破壞、實時編輯、更簡單的代碼、子系統的統一等。

    不能在這些系統上良好地工作的技術有:非局部數據、分支、寄存器類型之間的切換(LHS)、基于樹的結構通常是分支繁重、要解決最重要的數據。可以在這些系統上良好地工作的技術有:局部數據、(SIMD) 計算能力、并行度。

    新的裁剪剔除為了應對游戲場景中海量的物體(最多有約15000個),第一次嘗試是僅使用并行蠻力,比舊剔除快3倍,1/5的代碼大小,更容易進一步優化。線性數組規模大,可預測的數據,很少分支,充分利用計算能力,可以達到5倍的速度提升:

    新的裁剪剔除通過簡單的網格提高性能,就是一個AABB,分配給一個帶有球體的“單元格”,單獨的網格用于靜態渲染、動態渲染、靜態物理和動態物理。其數據數據布局如下:

    增加物體時,可以從中獲取數據的預分配的數組:

    刪除物體時,使用“交換技巧”,數據無需排序,只需與最后一個條目交換并減少計數。

    渲染裁剪時,先看看渲染的數據:

    struct EntityRenderCullInfo
    {
        Handle entity;    // handle to the entity
        u16 visibleViews; // bits of which frustums that was visible
        u16 classId;      // type of mesh
        float screenArea; // at which screen area entity should be culled
    };
    

    裁剪節點代碼如下:

    while (1)
    {
        uint blockIter = interlockedIncrement(currentBlockIndex) - 1;
        if (blockIter >= blockCount) break;
        u32 masks[EntityGridCell::Block::MaxCount] = {}, frustumMask = 1;
     
        block = gridCell->blocks[blockIter];
        foreach (frustum in frustums, frustumMask <<= 1)
        {
             for (i = 0; i < gridCell->blockCounts[blockIter]; ++i)
             {
                 u32 inside = intersect(frustum, block->postition[i]);
                 masks[i] |= frustumMask & inside;
            }
        }
        
        for (i = 0; i < gridCell->blockCounts[blockIter]; ++i)
        {
            // filter list here (if masks[i] is zero it should be skipped)
             // ...
        }
    }
    

    相交檢測代碼如下:

    bool intersect(const Plane* frustumPlanes, Vec4 pos)
    {
        float radius = pos.w;
        
        if (distance(frustumPlanes[Frustum::Far], pos) > radius)
        return false;
        if (distance(frustumPlanes[Frustum::Near], pos) > radius)
        return false;
        if (distance(frustumPlanes[Frustum::Right], pos) > radius)
        return false;
        if (distance(frustumPlanes[Frustum::Left], pos) > radius)
        return false;
        if (distance(frustumPlanes[Frustum::Upper], pos) > radius)
        return false;
        if (distance(frustumPlanes[Frustum::Lower], pos) > radius)
         return false;
        
        return true;
    }
    

    以上代碼出現很多問題,如非局部數據和浮點分支:

    該怎樣改進?點積對SIMD不太友好,通常需要隨機打亂數據才能得到結果,(x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1),將數據從AoS(Array of Struct,結構體數組)重新排列到SoA(Struct of Array,數組結構體):

    現在只需要3個指令就可以完成4個dot!

    新的相交性測試代碼下每個循環兩個*截頭體與球體相交、4*3 個點積、9條指令,遍歷所有視錐體并合并結果。

    Vec posA_xxxx = vecShuffle<VecMask::_xxxx>(posA);
    Vec posA_yyyy = vecShuffle<VecMask::_yyyy>(posA);
    Vec posA_zzzz = vecShuffle<VecMask::_zzzz>(posA);
    Vec posA_rrrr = vecShuffle<VecMask::_wwww>(posA);
    // 4 dot products
    dotA_0123 = vecMulAdd(posA_zzzz, pl_z0z1z2z3, pl_w0w1w2w3);
    dotA_0123 = vecMulAdd(posA_yyyy, pl_y0y1y2y3, dotA_0123);
    dotA_0123 = vecMulAdd(posA_xxxx, pl_x0x1x2x3, dotA_0123);
    
    Vec posAB_xxxx = vecInsert<VecMask::_0011>(posA_xxxx, posB_xxxx);
    Vec posAB_yyyy = vecInsert<VecMask::_0011>(posA_yyyy, posB_yyyy);
    Vec posAB_zzzz = vecInsert<VecMask::_0011>(posA_zzzz, posB_zzzz);
    Vec posAB_rrrr = vecInsert<VecMask::_0011>(posA_rrrr, posB_rrrr);
    // 4 dot products
    dotA45B45 = vecMulAdd(posAB_zzzz, pl_z4z5z4z5, pl_w4w5w4w5);
    dotA45B45 = vecMulAdd(posAB_yyyy, pl_y4y5y4y5, dotA45B45);
    dotA45B45 = vecMulAdd(posAB_xxxx, pl_x4x5x4x5, dotA45B45);
    
    // Compare against radius
    dotA_0123 = vecCmpGTMask(dotA_0123, posA_rrrr);
    dotB_0123 = vecCmpGTMask(dotB_0123, posB_rrrr);
    dotA45B45 = vecCmpGTMask(dotA45B45, posAB_rrrr);
    Vec dotA45 = vecInsert<VecMask::_0011>(dotA45B45, zero);
    Vec dotB45 = vecInsert<VecMask::_0011>(zero, dotA45B45);
    // collect the results
    Vec resA = vecOrx(dotA_0123);
    Vec resB = vecOrx(dotB_0123);
    resA = vecOr(resA, vecOrx(dotA45));
    resB = vecOr(resB, vecOrx(dotB45));
    // resA = inside or outside of frustum for point A, resB for point B
    Vec rA = vecNotMask(resA);
    Vec rB = vecNotMask(resB);
    masksCurrent[0] |= frustumMask & rA;
    masksCurrent[1] |= frustumMask & rB;
    

    額外的剔除包含視錐體和AABB、將AABB投影到屏幕空間、軟件遮擋。將AABB投影到屏幕空間時,計算屏幕空間中AABB的面積,如果面積小于設置就跳過它,由于FOV取距離不起作用。

    軟件遮擋已在Frostbite中使用了3年,跨*臺,藝術家制作的遮擋物,主要用于地形。使用軟件遮擋的原因有:想要去除CPU時間而不僅僅是GPU,盡早剔除,GPU查詢由于落后于CPU很棘手,必須支持破壞系統,易于藝術家控制。具體做法是使用軟件渲染將PS1風格的幾何圖形渲染到Z緩沖區,其中Z緩沖區是256x114的浮點。具體步驟包含遮擋三角形設置、地形三角設置、光柵化三角形、剔除。

    上:場景遮擋體;下:光柵化遮擋體的結果。

    并行裁剪作業圖例如下:

    以下是并行設置遮擋體三角形的圖例:


    Z緩沖區測試過程是計算對象的屏幕空間AABB,獲取單個距離值,根據Z緩沖區測試正方形:

    總之,準確和高性能的剔除至關重要,減少低級系統/渲染的壓力,一切都與數據有關,簡單的數據通常意味著簡單的代碼,充分了解目標硬件的特性、架構和參數。

    Firaxis LORE And other uses of D3D11是系列游戲文明5(Civilization V)的引擎利用D3D11實現多線程繪制的架構改造和優化技巧。

    文明5游戲截圖。

    早期目標是希望圖像引擎能夠“經得起時間的考驗”,使用D3D11 Alpha建立引擎原生的D3D11架構,并向后兼容到DX9。

    第一步:降低開銷。著色器開始于Firaxis著色語言(FSL)的HLSL超集,編譯到CPP和頭文件,所有的著色器常量是映射到結構體,分組到包中所有的包具有相同的綁定,模型代碼被模板化——FSL生成的頭文件被綁定到模板代碼中,結果是少量的代碼,填寫所需的著色,在性能分析幾乎沒有影響。

    第二步:抽象渲染。仍然需要支持DX9,未來可能需要支持主機,可能需要寫一個“驅動程序”,解決方案是讓DX9看起來像DX11。

    渲染封裝層的特點:無狀態渲染,比D3D簡單得多;一個命令集可以包含一個要渲染的表面列表,每個表面都有一個shader常量負載;一個表面是一個由IB、VB、紋理、著色器定義等組成的不可變包(bundle),命令引用這些狀態包之一;整幀被隊列化,最小化每幀分配。只有5種命令:

    • COMMAND_RENDER_BATCHE
    • COMMAND_GENERATE_MIPS
    • COMMAND_RESOLVE_RENDERTEXTURE
    • COMMAND_COPY_RENDERTEXTURE
    • COMMAND_COPY_RESOURCE

    渲染封裝層架構圖。

    基于作業的多線程并行系統。

    為什么要對整個幀執行排隊?看起來像是額外的開銷,但性能分析顯示是凈收益,內部命令設置超級便宜,只是一些內存拷貝,引擎緩存一致性要好得多,D3D驅動程序緩存一致性在一個巨大的轉儲中要好得多,提交時間占總CPU時間的百分比非常低,允許過濾冗余的D3D調用,即使在DX9中也很快。

    實現優勢:一旦掌握了“無狀態”的概念,代碼維護變得容易。幾乎沒有狀態泄漏(閃爍的alpha、紋理等),因為渲染是分組的,每個任務之間很少或根本不需要通信,沒有線程錯誤。

    線程化的D3D11命令提交的問題:批量提交的驅動開銷通常很高,但是,D3D11有多線程提交命令流不一定1:1映射到CommandList,文明5可以通過設置配置文件來改變它的提交方式。

    線程化的D3D11命令提交。

    總之,高吞吐量渲染是可能的,前提是:小心降低應用程序開銷,基于作業、基于載重的渲染,過濾冗余狀態和調用,使用D3D11命令列表,引擎可以在97%的情況下充分利用12個線程(無驅動)。

    DirectX 11 Rendering in Battlefield 3分享了2011年的Frostbite 2引擎利用DirectX 11的特性,在引擎中實現諸多渲染特性,包含延遲渲染、分塊渲染、立體3D渲染、各類抗鋸齒和性能分析等等。

    當時Frostbite 2面臨的渲染選擇有:切換到延遲著色,BF3中豐富的戶外 + 室內 + 城市環境組合,想要更多的光源。為什么不用前向渲染?燈光剔除/著色器排列效率不高,昂貴且更難的貼花/破壞遮蔽。為什么不使用Light Pre-pass?CPU和GPU上的2倍幾何通道太高開銷,能夠將BRDF推廣到足夠的幾個變體,看到了基于分塊的延遲著色的巨大潛力。

    傳統延遲照明/陰影的缺點是擁有大量大光源時的大量透支和ROP成本,在光照著色器中擁有多個逐像素材質的成本很高,MSAA照明可能很慢(不連貫,額外的BW)。

    Frostbite 2的解決方案是使用基于分塊的延遲渲染(Tile-based Deferred Shading)

    1、將屏幕分成瓦片(tile),確定哪些燈影響哪些瓦片。

    2、僅對像素應用可見光源。具有多個光源的自定義著色器,降低帶寬和設置成本。

    使用計算著色器的基于圖塊的延遲著色,主要用于解析光源(點光源、聚光燈、線光源),沒有陰影,需要計算著色器5.0。可使用混合圖形/計算著色管道:圖形管線光柵化不透明表面的gbuffer,計算管道使用gbuffers、剔除燈光、計算光照并與著色相結合,圖形管線在上面渲染透明表面。

    計算著色器的第一步是設置輸入輸出數據,相關代碼如下:

    // 輸入:gbuffers、深度緩沖區和燈光列表
    Texture2D<float4> gbufferTexture0 : register(t0);
    Texture2D<float4> gbufferTexture1 : register(t1);
    Texture2D<float4> gbufferTexture2 : register(t2);
    Texture2D<float4> depthTexture : register(t3);
    
    // 輸出:完全合成和點亮的 HDR 紋理
    RWTexture2D<float4> outputTexture : register(u0);
    
    // 每像素1個線程,16x16線程組
    #define BLOCK_SIZE 16
    [numthreads(BLOCK_SIZE,BLOCK_SIZE,1)]
    void csMain(
       uint3 groupId          : SV_GroupID,
       uint3 groupThreadId    : SV_GroupThreadID,
       uint groupIndex        : SV_GroupIndex,
       uint3 dispatchThreadId : SV_DispatchThreadID)
    {
        (...)
    }
    

    第二步是加載 gbuffers & depth,計算threadgroup/tile中的min & max z,在組共享變量上使用 InterlockedMin/Max,原子僅適用于整數,可以將float轉換為int(z始終為+):

    groupshared uint minDepthInt;
    groupshared uint maxDepthInt;
    // --- globals above, function below -------
    float depth =
     depthTexture.Load(uint3(texCoord, 0)).r;
    uint depthInt = asuint(depth);
    minDepthInt = 0xFFFFFFFF;
    maxDepthInt = 0;
    GroupMemoryBarrierWithGroupSync();
    InterlockedMin(minDepthInt, depthInt);
    InterlockedMax(maxDepthInt, depthInt);
    GroupMemoryBarrierWithGroupSync();
    float minGroupDepth = asfloat(minDepthInt);
    float maxGroupDepth = asfloat(maxDepthInt);
    

    第三步是裁剪,確定每個tile的可見光源,針對*截頭體剔除所有光源,輸入全局的 燈光列表、截錐體和SW遮擋剔除,輸出可見光源和可見光源索引列表。

    struct Light 
    {
        float3 pos; float sqrRadius;
        float3 color; float invSqrRadius;
    };
    
    int lightCount;
    StructuredBuffer<Light> lights;
    groupshared uint visibleLightCount = 0;
    groupshared uint visibleLightIndices[1024];
    
    // --- globals above, cont. function below ---
    uint threadCount = BLOCK_SIZE*BLOCK_SIZE;
    uint passCount = (lightCount+threadCount-1) / threadCount;
    for (uint passIt = 0; passIt < passCount; ++passIt)
    {
        uint lightIndex = passIt*threadCount + groupIndex;
        // prevent overrun by clamping to a last ”null” light
        lightIndex = min(lightIndex, lightCount);
        if (intersects(lights[lightIndex], tile))
        {
            uint offset;
            InterlockedAdd(visibleLightCount, 1, offset);
            visibleLightIndices[offset] = lightIndex;
        }
    }
    
    GroupMemoryBarrierWithGroupSync();
    

    最后的步驟是對于每個像素,累積來自可見光的光照,從群組共享內存中的瓦片可見光索引列表中讀取。結合光照和陰影反照率,輸出為 MSAA HDR紋理,在頂部渲染透明表面。

    float3 color = 0;
    for (uint lightIt = 0; lightIt < visibleLightCount; ++lightIt)
    {
        uint lightIndex = visibleLightIndices[lightIt];
        Light light = lights[lightIndex];
        color += diffuseAlbedo * evaluateLightDiffuse(light, gbuffer);
        color += specularAlbedo * evaluateLightSpecular(light, gbuffer);
    }
    

    對于帶MSAA的計算著色器光照,只有邊緣像素需要完整的每個樣本照明,但是邊緣的屏幕空間一致性很差,效率低。Compute Shader可以構建高效的連貫像素列表,評估每個像素的照明(樣本0),確定像素是否需要按樣本照明,如果是,添加到共享內存中的原子列表,當所有像素都完成后,同步,遍歷并點亮樣本1-3以獲取列表中的像素。性能可以大幅提升!

    在Frostbite 2的地形渲染中,使用實例化(instancing)進行優化。DX9風格的流實例化很好,但有限制,例如額外的頂點屬性、GPU開銷,不能(有效地)與蒙皮結合,主要用于微小的網格(粒子、樹葉)。DX10/DX11帶來了對著色器緩沖區對象的支持,頂點著色器可以訪問SV_InstanceID,可以完全任意加載,不限于固定元素,可以支持每實例數組和其他數據結構。

    實例化數據包含多種對象類型(剛體、蒙皮、復合網格)、多種物體照明類型(小型/動態:光照探針、大/靜態:光照貼圖),擁有的不同類型的實例數據:變換float4x3、蒙皮變換float4x3數組、SH光照探針float4x4、光照貼圖UV縮放/偏移float4。可以將所有實例化數據打包到一個大緩沖區中!

    // 實例化示例:變換矩陣+SH
    
    Buffer<float4> instanceVectorBuffer : register(t0);
    
    cbuffer a
    {
        float g_startVector;
        float g_vectorsPerInstance;
    }
    
    VsOutput main(
        // ....
        uint instanceId : SV_InstanceId)
    {
        uint worldMatrixVectorOffset = g_startVector + input.instanceId * g_vectorsPerInstance + 0;
        uint probeVectorOffset = g_startVector + input.instanceId * g_vectorsPerInstance + 3;
        float4 r0 = instanceVectorBuffer.Load(worldMatrixVectorOffset + 0);
        float4 r1 = instanceVectorBuffer.Load(worldMatrixVectorOffset + 1);
        float4 r2 = instanceVectorBuffer.Load(worldMatrixVectorOffset + 2);
        float4 lightProbeShR = instanceVectorBuffer.Load(probeVectorOffset + 0);
        float4 lightProbeShG = instanceVectorBuffer.Load(probeVectorOffset + 1);
        float4 lightProbeShB = instanceVectorBuffer.Load(probeVectorOffset + 2);
        float4 lightProbeShO = instanceVectorBuffer.Load(probeVectorOffset + 3);
    
        // ....
    }
    
    // 實例化示例:蒙皮
    
    half4 weights = input.boneWeights;int4 indices = (int4)input.boneIndices;
    
    float4 skinnedPos = mul(float4(pos,1), getSkinningMatrix(indices[0])).xyz * weights[0];
    skinnedPos += mul(float4(pos,1), getSkinningMatrix(indices[1])).xyz * weights[1];
    skinnedPos += mul(float4(pos,1), getSkinningMatrix(indices[2])).xyz * weights[2];
    skinnedPos += mul(float4(pos,1), getSkinningMatrix(indices[3])).xyz * weights[3];
    
    // ...
    float4x3 getSkinningMatrix(uint boneIndex)
    {
        uint vectorOffset = g_startVector + instanceId * g_vectorsPerInstance;
        vectorOffset += boneIndex*3;
        float4 r0 = instanceVectorBuffer.Load(vectorOffset + 0);
        float4 r1 = instanceVectorBuffer.Load(vectorOffset + 1);
        float4 r2 = instanceVectorBuffer.Load(vectorOffset + 2);
        return createMat4x3(r0, r1, r2);
    }
    

    實例化的好處在于每個對象類型而不是每個實例的單個繪制調用,對CPU的最小沖擊以獲得較大的CPU增益,蒙皮時實例化不會中斷,更具確定性和更好的整體性能。最終結果通常是1500-2000次繪制調用,無論藝術家放置了多少對象實例!

    DX11的關鍵特性包含通過將D3D調度擴展到更多內核來提高性能,減少幀延遲。為了利用此特性,每個硬件線程的DX11延遲上下文,渲染器為幀的每個渲染“層”構建我們想要執行的所有繪制調用的列表,將每一層的繪制調用拆分為約256個塊,與延遲上下文并行調度塊以生成,使用命令列表,呈現到即時上下文并執行命令列表。

    當時仍然沒有高性能的驅動程序,原因是大型驅動程序代碼庫需要時間來重構,IHV(Independent hardware vendor,獨立硬件供應商)與微軟的困境,重驅動線程與游戲線程沖突。運行原理是驅動程序不創建自己的任何處理線程,游戲將工作負載并行提交到多個延遲上下文,驅動程序確保幾乎所有需要的處理都發生在延遲上下文的繪圖調用上,游戲在即時上下文中調度命令列表,驅動程序使用它做的工作絕對最少。

    對于資源流,即使使用具有大量內存的現代GPU,通常也需要資源流,不能要求1+GB顯卡,BF3關卡擁有超過1 GB的紋理和網格,減少加載時間。但是在幀內創建和銷毀DX資源從來都不是一件好事,可能導致非確定性和大型驅動程序/操作系統停止,在DX中一直是個問題。

    已與Microsoft、Nvidia和AMD合作,以確保可以在DX11中對GPU資源進行無停頓的異步資源流處理,不希望CPU和GPU性能受到影響,關鍵基礎是DX11的并發創建。

    資源創建流程:流系統確定要加載的資源(紋理mipmap或網格LOD),將DX資源創建添加到單獨的低優先級線程上的隊列中,線程使用初始數據創建資源,發信號給流系統,資源已創建,游戲開始使用它。在驅動程序中啟用異步無停頓DMA!資源銷毀流程:流系統刪除D3D資源,驅動程序使其在內部保持活動狀態,直到使用它的GPU幀完成,沒有停頓!

    Secrets of CryENGINE 3 Graphics Technology分享了2011年的CryEngine 3的渲染技術,包含渲染管線、位置重建、覆蓋緩沖區、延遲照明、陰影、屏幕空間技術、延遲技術、批量HDR后處理、立體渲染等。

    文中提到Z緩沖區的注意事項,Z值以雙曲線分布,在著色器中使用之前需要轉換為線性空間:

    // Constants
    g_ProjRatio.xy = float2( zfar / (zfar-znear), znear / (znear-zfar) );
    // HLSL function
    float GetLinearDepth(float fDevDepth)
    { 
        return g_ProjRatio.y/(fDevDepth-g_ProjRatio.x);
    }
    

    問題是第一人稱視圖(FPV)對象,深度緩沖區可以用于防止FPV對象與場景的其余部分重疊,不同的 FOV 和*/遠*面(藝術特定選擇),不同的深度范圍,以防止實際重疊,導致延遲技術不能100%用于此類對象。解決方案是修改深度重建功能,將硬件深度轉換為線性深度,第一人稱視圖對象的不同深度比例,根據深度選擇:

    float GetLinearDepth(float fDevDepth) 
    {
           float bNearDepth = step(fDevDepth, g_PS_DepthRangeThreshold);
         float2 ProjRatio.xy = lerp(g_PS_ProjRatio.xy, g_PS_NearestScaled.xy, bNearDepth);
         return  ProjRatio.y/(fDevDepth-ProjRatio.x);
    }
    

    從深度重建位置的思路:將VPOS從屏幕空間S直接線性變換到目標齊次空間W(陰影空間或世界空間),從屏幕剪輯空間到齊次矩陣的直接轉換,VPOS是渲染延遲光量的最簡單方法,s3D單獨調整。

    float4 HPos = (vStoWBasisZ + (vStoWBasisX*VPos.x)+(vStoWBasisY*VPos.y) ) * fSceneDepth;
    HPos += vCamPos.xyzw;
    

    覆蓋緩沖區(Coverage Buffer)作為主要的遮擋剔除系統,本質上是低分辨率深度緩沖區,用于可見性Z測試的對象AABB/OBB的粗略CPU光柵化,在CPU上準備完全詳細的C-Buffer太慢,巨大的計算成本,必須在軟件中復制完整的渲染管線以獲得C-Buffer的所有細節。

    在CPU上回讀前一幀的GPU深度緩沖區,G-Buffer通道后縮小GPU上的ZBuffer(最大過濾器),通過在單獨的CPU線程中光柵化BBox來完成剔除。

    左:正常場景;右:覆蓋緩沖區。

    覆蓋緩沖區用于X360/PS3和DX11硬件,PC上的回讀延遲較高但仍可接受(最多 4 幀),C-Buffer 大小在控制臺上的孤島危機 2 中限制為256x128。問題:前一幀/當前幀相機之間的不匹配,導致錯誤的可見性測試。

    解決方案是對覆蓋緩沖區重投影(Coverage Buffer Reprojection)。使用上一幀相機中的C-Buffer CPU重投影,重投影片元的點濺射。相機信息被編碼到C-Buffer數據中,CPU回讀和重新投影在單獨的線程中,在SPU上約2毫秒,在帶有矢量化代碼的Xbox 360上約3-4毫秒。重投影后在C-Buffer內縫合孔,3x3的擴大通道,剩余的C-Buffer洞:假設對象是可見的。重投影大大提高了剔除效率,解決各種遮擋測試偽影,檢測到無效區域,以更高的幀率更高效地工作。

    覆蓋緩沖區重投影,紅色是重投影之后仍然存在的洞。

    CryEngine 3的延遲光照包含環境光、環境探針、GI、SSDO、RLR、光源等。

    CryEngine 3的延遲陰影對太陽使用了陰影遮蔽(Shadow mask),特殊渲染目標累積陰影遮擋,陰影遮罩在使用實際陰影之前將多種陰影技術相互疊加。點光源陰影直接渲染到光照緩沖區。

    級聯陰影從孤島危機1開始使用,級聯拆分方案:*似對數紋理像素密度分布,陰影截頭體調整為保守地覆蓋相機視圖截頭體,陰影截頭體的方向在世界空間中是固定的。更多級聯允許是由于更好地*似對數分布,提高了紋素密度,減少了粉刺并改進了更寬陰影范圍的自陰影。對于每個級聯,將陰影截頭體捕捉到SM的紋理網格。

    級聯陰影的通道:以延遲方式渲染的級聯/點光源的陰影通道,通過渲染截錐體在模板緩沖區中標記的潛在陰影接收區域,允許將更復雜的拆分為級聯,在重疊區域中選擇具有最高分辨率的級聯,避免浪費陰影貼圖空間。

    級聯陰影的緩存:并非所有級聯都在單幀中更新,更新成本分布在多個框架中,性能原因(尤其是 PS3),允許更多的級聯——更好的陰影貼圖密度分布,緩存陰影貼圖使用緩存陰影矩陣,遠距離級聯更新頻率較低,最后級聯使用 VSM 并與陰影蒙版相加混合,允許從巨大的遙遠物體獲得大半影。

    點光源陰影:總是將泛光燈分成六個獨立的投影器,每個投影器的陰影貼圖單獨縮放,基于陰影投影覆蓋率,最終比例是對數陰影圖密度分布函數的結果,使用覆蓋率作為參數。大紋理圖集,可在縮放后每幀打包所有陰影貼圖,永久分配紋理圖集以避免內存碎片,模板標記的接收區域。

    實時局部反射(RLR):光柵化的反射很昂貴,通常是*面反射或立方體貼圖,需要重新渲染場景,標準反射受限,無論是*面,立方體貼圖的小區域,通常沒有曲面,光線追蹤直接反射,屏幕空間中的光線追蹤以*似局部反射。

    實時局部反射基本算法:計算每個像素的反射向量,使用延遲的法線和深度目標,沿反射向量Raymarch,采樣深度并檢查光線深度是否在場景深度的閾值內,如果命中,重新投影到前一幀的幀緩沖區和樣本顏色,結果相對便宜,隨處可見的局部反射(即使在復雜表面上),由于屏幕空間中的數據有限而導致大量問題案例。

    實時局部反射實現技巧:非常有限的屏幕空間數據,與其破碎的倒影不如倒影,如果反射矢量面向查看器,則*滑淡出,因為在這種情況下沒有可用數據,在屏幕邊緣*滑淡出反射樣本,將抖動添加到步長以隱藏明顯的步長偽影,在孤島危機 2 中采樣的HDR顏色目標,基于表面光澤度的抖動或模糊。

    接觸陰影:首先生成遮擋信息,在SSAO過程中計算和存儲彎曲法線 N',彎曲法線是*均未遮擋方向,需要干凈的SSAO,沒有任何自遮擋和相對較寬的半徑。然后對于每一盞燈,像往常一樣計算 N dot L和 N' dot L,通過遮擋量乘以兩個點積之間的鉗位差來衰減照明。

    屏幕空間自陰影:無法承受每個角色的陰影貼圖(內存),解決內存不足的問題是通過簡單的技巧/*似:射線沿屏幕空間光矢量行進,所有角色的宏觀自陰影細節。

    角色的屏幕空間自陰影。

    Bokeh DOF效果采用了另一種內核和權重:

    Rendering in Cars 2是迪斯尼繼Toy Story 3之后的又一次分享,介紹了游戲汽車總動員2的渲染技術,包含光照探針、HDR色彩精度、提前模板陰影剔除及PS3后期處理。

    光照探針的目標是同時支持4個玩家,作用于所有世界幾何體的光照貼圖,匹配實時照明。

    光照探針的處理過程是從空間中的一點捕捉光照,反彈光照,環境映射。反彈光照數據存儲為球諧函數,3階SH = 每個探針108字節,可以免費在直接照明中打包(pack)。

    光照探針捕捉時,在GPU上渲染立方體貼圖,另存為16F用于HDR,用于速度的圖集,反彈照明(立方體貼圖到SH投影)。

    輻照度體積是帶有一堆光照探針的體積,允許在世界范圍內使用各種反射光,非常流行與光照貼圖一起使用。

    體積的選擇,用于賽車游戲,每個世界2-5英里的軌道,大部分在外面,許多薄而彎曲的區域,覆蓋不是必需的。

    均勻的網格體積是盒子體積,可以旋轉和縮放以適應任何地方,具有可變切片數量的框拆分(密度 x/y/z),沿著體積切片放置的光照探針。結構簡單,易于實現,整個數據保存到一個連續的數組中,帶有長方體相交測試的樣本,可以通過偏移量訪問每個探針,O(1) 的網格內采樣,成本只花在體積,但浪費空間。

    網格還需要處理過渡區域、無效的點、體積查找等。對于體積查找,有CPU和GPU兩種方式,基于CPU:每個網格分配/混合最*的SH,將SH數據傳遞到GPU;基于GPU:逐像素或逐頂點,GPU上的采樣探針。

    著色器常量是逐實例的,在著色器中計算顏色,分解大物體,可以通過頂點顏色混合將網格分開,世界照明有同樣問題。

    對于體積的重疊,需要進行混合,但*滑混合重疊體積很復雜。由于要與全局探測器混合,不能只收集最*的點并*均照明,重疊的區域會產生難以隱藏的瘋狂過渡。

    可以采用時間*均的方法,將最后一幀的SH的%混合到當前的SH中(逐世界可調),三線性過濾替代品,避免混合第一幀。

    無體積的探針非常適合道路反射,將環境映射探針分配給體積。分配環境貼圖時,如果在體積內,則使用體積的環境貼圖,否則,使用全局探針,基于衰落區域的切換,重疊體積通過共享立方體貼圖避免跳變。

    渲染直接光照時,將直接照明打包進探頭中,可以評估SH中的照明并添加到反彈,沒有額外的性能成本,取決于網格密度。

    還提供了光照覆蓋,如果在體積內,則添加定向和環境光,允許藝術家控制照明,包含二維體積、一維體積、區域光燈類型。

    均勻網格簡單快捷,使用很少的內存并且可以很好地適應4個玩家,與藝術家有很大的靈活性。

    為了同時支持4個玩家操作,需要對GPU的管線進行優化,例如降低HDR渲染的成本,降低陰影成本,為4人分屏縮放陰影貼圖,使用多分辨率、延遲渲染、陰影遮蔽等。

    首先考慮的是渲染紋理的格式,下表是不同格式的具體說明:

    下圖是不同格式產生的顏色誤差:

    可見LogLuv的誤差最低且穩定但消耗ALU,7e3較高但較穩定,RGBM高且不穩定!

    利用以上特殊格式還可能造成圖像的色階問題:

    造成這個問題的原因是HDR處理管線中,從場景渲染到Render Target時損失了精度:

    可以將色調映射組件分為兩部分,曝光校正和色調映射運算符將其縮放到 [0,1] 范圍。它們是可分離的,可以在不同的時間完成。注意,曝光校正是用于Cars 2的,但色調映射運算不是。Cars 2使用Hable提到的基于ALU的電影色調映射算子。

    將色調映射分拆之后的HDR管線如下:

    獲得的結果對比:

    對動態物體,還需要從光照貼圖接收陰影,進出陰影時只需要粗略的過渡。所以使用了低分辨率的陰影圖:使用256x256陰影貼圖,超級便宜(約0.1ms),使用簡單的代理幾何。繪制陰影圖時,僅在兩個級聯中繪制動態對象,減少陰影距離,重投影偽影可接受,因為軌道位于2d*面上。

    對于延遲的陰遮蔽,減少處理的像素數,以1/4尺寸渲染RT,采用雙邊濾波上采樣到正常分辨率。

    不可避免的偽影是邊緣偽影,較低的分辨率,太明顯而無法忽略。

    提前模板裁剪(early stencil culling)在到達像素著色器之前剔除片元,支持PS3、360和現代PC顯卡,PC是自動的,PS3和360手動控制,編寫和測試之間的延遲。不過需要注意的是,當時的Early階段的像素是4x4的像素塊。

    結合了提前模板裁剪的延遲陰影的過程如下:

    1、以1/16分辨率渲染陰影。

    使用有限過濾以1/16分辨率渲染陰影遮罩,需要擴大陰影邊緣,因為高分辨率下的邊緣與低分辨率下的邊緣不同,擴大的寬度可以根據它覆蓋邊緣的程度來配置。

    2、用1/16陰影遮蔽填充全分辨率的early stencil。

    點采樣1/16的RT,打開提前模板寫入,如果它在擴張區域內,則texkill。

    3、使用提前模板測試以全分辨率重新渲染陰影邊緣。

    打開提前模板測試,提前模板剔除先前通道中填充的像素,僅渲染約30%的像素。兩個通道的雙邊模糊,保持開啟提前模板測試,僅模糊約30%的像素。

    雙邊模糊效果對比,右邊是模糊后的效果。

    對于邊緣陰影下次,也可以使用提前模板測試解決。提前模板只是一個遮罩,擴大不覆蓋模糊區域,僅發生在具有大范圍擴張的極端特寫鏡頭中。

    陰影值為0或1,級聯選擇,大多數像素處于交叉雙邊濾波器中。渲染到精度有限的目標時,預曝光顏色非常有效,動態對象的低分辨率陰影貼圖很便宜,延遲陰影遮罩渲染時間有效地減少了一半。

    該文還詳細描述了基于SPU的后處理管線和優化以及立體3D渲染的雙攝像頭渲染優化(如遮擋、視錐體合并)等。

    立體3D渲染的遮擋體瑕疵優化。

    DX11 Performance Gems詳細闡述了DX11的高性能優化技術和建議,給出了案例不透明度映射實現和優化(曲面細分加速光照、GatherRed加速上采樣、SV_SampleIndex改善AA、軟粒子的只讀深度等)。

    DX11的延遲上下文是用于構建命令列表的類似設備的接口,DX11使用相同的ID3D11DeviceContext接口進行“即時”API調用,即時上下文是最終向GPU提交工作的唯一方法,通過ID3D11Device::GetImmediateContext()訪問,ID3D11Device沒有提交API。

    DirectX11的多線程命令生成和提交機制。圖中有兩個帶有延遲上下文的線程,它們各種記錄和生成命令,生成的命令列表被線程間同步、整理/排序/緩沖,然后被渲染主線程提交給即時上下文,再由即時上下文提交給GPU。

    DX11內部結構比較靈活的,DX11運行時有內置實現,但是驅動程序可以負責并使用自己的實現,例如命令列表可以構建在較低級別,將更多的CPU工作轉移到提交線程上。延遲上下文的優化建議:

    • 嘗試通過上下文/線程*衡工作負載。但提交工作量很少情況是可預測的,粒度有幫助(如果提交線程能夠動態處理工作),如果可能,先做較重的提交工作量,每個內核約12 個CL(命令列表),每個CL約1ms是一個很好的目標。
    • 保證合理的命令列表大小。想一想命令列表中的繪制調用數量很像繪制調用中的三角形數量,即每個列表都有開銷,約相當于幾十個API調用。
    • 留一些空閑的CPU時間。讓所有線程忙碌會導致CPU飽和并阻止來自線程渲染的服務線程(游戲引擎不要使用超過N-1個 CPU內核),“忙”包括忙等待(即輪詢),始終為圖形驅動程序留一個。
    • 留意內存!每個Map()調用都將內存與CL相關聯,釋放CL是釋放內存的唯一方法,可以在2GB的虛擬地址空間中變得緊張!

    文中使用DX11的案例是不透明度圖。使用DX11曲面細分在DS中以中間“最佳點”速率計算光照,高頻分量可以根據需要保持在每像素或每采樣率,如不透明度、可見度。

    PS、VS、DS光照的fps和效果對比如下:

    自適應曲面細分提供兩全其美,類VS計算頻率,與屏幕像素頻率的類PS關系(1:15 在這種情況下效果很好),適用于任何緩慢變化的著色結果(GI、其他體積算法)。主要瓶頸是曲面細分操作之后的填充率,所以將粒子渲染到低分辨率離屏緩沖區,顯著優勢,即使使用 曲面細分(GTX 560 Ti / HD 6950 為 1.2 倍至 1.5 倍)。但是,從低分辨率進行簡單的雙線性上采樣會導致邊緣出現偽影。

    相反,使用最*深度上采樣(nearest-depth up-sampling),概念上類似于交叉雙邊過濾,將高分辨率深度與相鄰的低分辨率深度進行比較,在深度不連續處來自最*匹配鄰居的樣本(否則為雙線性)。

    最*深度上采樣計算過程。

    效果對比。

    使用SM5的GatherRed()一次有效地獲取2x2低分辨率深度鄰域:

    float4 zg = g_DepthTex.GatherRed(g_Sampler, UV);
    float z00 = zg.w;    // w: floor(uv)
    float z10 = zg.z;    // z: ceil(u), floor(v)
    float z01 = zg.x;    // x: floor(u), ceil(v)
    float z11 = zg.y;    // y: ceil(uv)
    

    在每個樣本運行時,最*深度的上采樣與AA配合得很好,而且性能驚人!(FPS影響 < 5%)

    float4 UpsamplePS( VS_OUTPUT In,
                       uint uSID : SV_SampleIndex // 樣本序號
                     ) : SV_Target
    

    軟粒子(基于深度的Alpha漸變)需要從場景深度讀取,對于DX11之前,意味著要么犧牲深度測試(以及任何相關的加速度)要么保持兩個深度表面(以及所需的任何復制)。DX11的解決方案是使用D3D11_DSV_READ_ONLY_DEPTH聲明的深度模板緩沖:

    軟粒子的步驟如下:

    1、將不透明對象渲染到深度紋理。

    2、使用深度測試渲染軟粒子。

    最終效果對比如下:

    整體性能提升5到10倍,DX11曲面細分給了大部分貢獻,但是以降低的分辨率進行渲染會降低填充率并讓曲面細分發光,GatherRed()和RO DSV也節省了周期。

    High Performance Post-Processing談及了后處理的特點、瓶頸及優化技術,并提供了幾個實戰案例。

    文中提到DX11的新資源類型:緩沖區/結構化緩沖區、無序訪問視圖 (UAV):RWTexture/RWBuffer,允許從PS和CS進行任意讀寫,“分散”的能力提供了新的機會,必須意識到危險和訪問模式。

    新增的DirectCompute在任意線程上運行的新著色器模式,將處理從圖形管道的限制中解放出來,完全訪問傳統Direct3D資源。DispatchIndirect從設備緩沖區而不是從CPU中獲取調度參數,讓計算工作驅動計算!仍受CPU約束以發出DispatchIndirect調用,與Append緩沖區結合使用以生成動態工作負載時非常好。以下是DX11的內存類型和屬性表:

    內存容量 速度 可見性
    全局內存(緩沖區、紋理、常量) 最長的延遲 全部線程
    共享內存(groupshared) 單個線程組
    局部內存(寄存器) 非常快 單個線程

    對于線程間通信,組中的線程可以通過共享內存進行通信,線程執行不能依賴其它組!并非所有組同時執行,組可以在Dispatch中以任何順序執行,組間依賴可能導致死鎖,如果一個組依賴于另一個組的結果,建議將著色器拆分為多個調度。

    對于數據危險和停頓,重新綁定用作UAV的資源可能會停止硬件以避免數據危害,必須確保所有寫入完成,以便下次調度可以看到它們,驅動程序可能會重新排序不相關的Dispatch調用以隱藏此延遲。

    上下文切換存在開銷,必須注意上下文切換成本:圖形和計算之間的懲罰切換,通常很少,除非反復刺激,連續(Back-to-Back)調度避免了這種情況,所以分組調用。

    常見的陷阱有內存限制和計算限制。內存限制包含低效的訪問模式、低效的格式、數據太多。計算限制包括分支(Divergent)線程、錯誤的指令組合、硬件利用率低。

    DX11的內存架構有點特殊,引入了復雜的內存系統。緩存行為取決于訪問模式,對于線性訪問,緩沖區會更好地命中緩存,紋理更適用于組內更不可預測/更多的2D訪問。

    使用特殊的分層采樣(Stratified Sampling)。下圖是兩個稀疏采樣模式,分布在一個2x2的線程塊中。左邊的圖案是一個簡單的抖動,它從像素給定半徑內的隨機位置收集四個樣本。每個樣本僅使用隨機徑向偏移,導致相同訪問的相鄰像素之間的位置可能有很大差異。因此,雖然它們可能存在從相似鄰域獲取的某些局部性,但隨機偏移量意味著并發訪問不太可能命中紋理緩存中的同一塊,從而增加了帶寬需求。在右側,在最大半徑內看到類似的4-tap稀疏采樣模式。但是,這種方法不是使用任意偏移,而是使用分層采樣,使得所有線程中的訪問對應于圓的同一個扇區,因此同時訪問更有可能命中同一個緩存區域,并且可以合并。

    分層采樣示意圖。左側隨機采樣,可能導致命中率低下;右側使用分層采樣,使得所有線程中的訪問對應于圓的同一個扇區,提示命中率,并且可以合并讀寫操作。

    與紋理不同,緩沖區是線性內存,確保盡可能讀取映射到緩沖區的二維數組的間距!

    Buffer<float> srvInput;
    [numthreads(128,1,1)]
    void ReadCS(
      uint3 gID : SV_GroupID
      uint3 tID : SV_DispatchThreadID)
    {
      float val;
    
      // 好: 沿間距讀取。
      val = srvRead[128*gID.x + tID.x];
      // ... Use data ...
    
      // 不好: 不沿間距讀取。
      val = srvRead[128*tID.x + gID.x];
      // ... Use data ...
    }
    

    理論上線程獨立執行,實際上它們以并行的wavefront執行,在wavefront執行時,線程被“屏蔽”以用于未執行分支中的指令。不同的wavefront可以免費分支,wavefront大小取決于硬件(NV:32、AMD:64)。

    分支的圖例。該計算著色器被定義為一組由兩個wavefront組成。第一種條件情況將每個wavefront分成兩個交錯的集合,使它們發散。因此,每個線程都執行兩個分支,wavefront基本上是50%空閑。第二個條件導致線程組內的分支; 但是,在這種情況下,一個wavefront中的所有線程都采用一個分支,而第二個wavefront中的所有線程都采用另一個。 因此,每個wavefront只執行一個分支,沒有空閑線程。

    在PS中,線程被分組為2D樣本簇,如果它們在圖像中是連貫的,則分支是可以的,特別是如果它可以節省工作!

    在PS中,分支可能會降低著色效率,但是否有分支并不重要,重要的是它們在一個小區域內分叉多少。事實上,如果分支允許減少昂貴的操作(例如使用更少的紋理樣本),則可以大大提高著色器的性能。

    為了提升利用率,創建足夠的工作以使硬件飽和,幾十個線程組差不多是甜蜜點;最大化每組的線程數,需要足夠的時間來隱藏硬件中的延遲,256-512是一個很好的目標;嘗試共享內存使用,更多共享內存 = 更少的組/處理器,當共享內存/線程增加時嘗試使組更小。

    計算線程可以通過“groupshared”內存進行通信和共享數據,預加載組中每個線程使用的數據(解包值、動態規劃),節省帶寬和計算,共享常見任務的工作量,計算集合的總和/最大值/等,比共享原子更有效。

    預加載數據到組內共享內存的示例。可分離卷積:將內核的整個足跡讀入共享內存,從共享緩沖區中獲取值并乘以每個像素的內核,盡量少讀取,效率更高!



    幾種在著色器中求和的算法和圖例。性能從上到下依次提升。

    文中還舉了具體的例子加以說明后處理的優化技巧,包含SAT DOF、Scattered Bokeh DOF等。


    在研究和工業中開發的大量渲染和圖形應用程序都是基于場景圖,傳統的場景圖封裝了完整3D場景的層次結構,并結合了語義和渲染方面。Separating Semantics from Rendering:A Scene Graph based Architecture for Graphics Applications提出了場景圖的語義和渲染部分的清晰分離,可獲得一種普遍適用的圖形應用程序架構,該架構松散地基于眾所周知的模型視圖控制器 (MVC) 設計模式,用于分離應用程序的用戶界面和計算部分。還探索了這種新設計對各種渲染和建模任務的好處,例如渲染動態場景、大型場景的核外渲染、樹木和植被的幾何生成以及多視圖渲染。最后,展示了在大型框架中使用該軟件架構的過程中已經解決的一些實現細節,用于快速開發可視化和渲染應用程序。在傳統設計中,應?程序可以維護對部分場景圖的引?,以便動態修改場景圖。

    狀態也可以直接存儲在場景圖中,使遍歷變得復雜。

    以上兩種設計都會導致?型或復雜應?的缺陷。在第?種情況下,狀態與場景圖分離,場景圖的層次結構不?定反映在應?程序中狀態存儲的?式上。為了克服這個問題,渲染場景圖的結構可以在應?程序中部分實現并行結構,導致重復?作,或者不同的結構狀態可能難以維護。如果場景圖的不同部分之間存在依賴關系,則與第?種情況?樣,在場景圖中存儲狀態可能會導致復雜性顯著增加。

    通過將語義與渲染場景圖完全分離并引?拆分場景圖架構來尋求更完整的解決?案來解決這些問題:

    • 語義場景圖(semantic scene graph):體現了??建模的場景。在純渲染應?程序中,此圖在其初始創建后永遠不會被修改。
    • 渲染場景圖(rendering scene graph):傳統意義上的場景圖,?成顯?場景所需的渲染操作序列,它的結構受所使?的渲染后端的影響。

    ?個典型的圖形應?程序就像?個編譯器,它將?個真實的或隱含的語義場景圖作為輸?,并為3D輸出?成渲染場景圖。在這個翻譯操作期間,語義場景圖的單個節點通常被翻譯成渲染場景圖的多個連接節點(下圖)。

    典型的圖形應?程序通過轉換真實或隱含語義場景圖的節點來構建渲染場景圖。

    通過使用從語義場景圖生成渲染場景圖的相同技術,渲染場景圖將成為小場景圖片段的森林,這些片段在遍歷語義場景圖時根據需要動態生成(下圖)。在某種程度上,對語義和渲染場景圖的分離讓人想起作為總線系統的場景圖,然而,渲染場景圖塊的森林可以完全從語義場景圖重建,并代表一個擴展的場景圖或翻譯版本。

    在動態翻譯期間,語義場景圖被翻譯成渲染場景圖片段的森林。

    為了使渲染場景圖真正動態化,在轉換步驟中引入狀態,并允許遍歷場景圖來修改現有渲染場景圖片段以反映新狀態(下圖)。已通過創建翻譯規則字典來實現,該字典包含規則對象的創建者函數,這些規則對象包含翻譯的當前狀態。這樣一個規則對象的每個構造函數都會構建一個渲染場景圖片段,該片段對應于翻譯后的語義場景圖節點,并存儲對它的引用。

    每個語義場景圖節點的動態翻譯會創建一個包含當前翻譯狀態的規則對象,每個規則對象都包含對其渲染場景圖片段的引用,這種結構可以看作是模型-視圖-控制器(MVC)設計模式的一種變體。

    文中還詳細闡述了實現細節及應用案例,有興趣的童鞋自行點擊原文閱讀。

    Scaling the Pipeline分享了Frostbite 2引擎的資產管理及管線的伸縮。

    Frostbite 2引擎的資產管線的目標是支持多站點協作(上海、歐洲、北美)、大型團隊(在某些情況下有400多個工作人員)、多個VCS分支、許多目標*臺(個人電腦、PS3、Xbox 360)、內容豐富的游戲。

    Battlefield 3的規模達到500GB的原始DCC資產,80GB的原生Frostbite資產,10萬個文件,約18GB目標數據 (PC),10萬個單獨的構建步驟 (PC),當時正在開發的游戲更大。

    Frostbite引擎的資產管線如下圖,采用結構化存儲,以構建為中心,單一資產加載路徑,始終在目標上預覽,支持資產熱插拔,直接調整路徑,游戲中的一些顯式實時編輯代碼。

    資產打包模型見下圖:

    • 捆綁包(Bundle)。資產的線性流(通常),關卡、子關卡(流式傳輸),線性只讀(推送)。
    • 數據塊(Chunk)。免費的流數據塊,紋理mips、電影、網格,隨機訪問(拉取)。
    • 超級捆綁包(Superbundle)是容器文件,存儲捆綁包和數據塊。一旦安裝了超級捆綁包,里面的數據就可見了。

    在開發過程中,布局存儲在Avalanche Storage Service中,存儲捆綁包和超級捆綁包的完整描述,存儲為帶有塊引用的包,游戲/工具請求時(通過 HTTP)即時組裝捆綁包,游戲不知道網絡和磁盤構建的區別(單路徑)。每次構建過程都會執行完整的打包邏輯,包括迭代構建!所以必須非常快。

    資產管線目標:等待構建所花費的時間 = 浪費,優化引導時間(初始構建),構建吞吐量,優化反饋時間(迭代構建),大型游戲需要高度可擴展的解決方案,具有挑戰性的!還有一點吃力不討好的任務……如果人們注意到你的工作,可能是因為你弄壞了東西,或者太慢了!

    以下是存儲架構及其延遲、吞吐率:

    存儲類型 延遲 吞吐率
    寄存器 < 1 ns -
    緩存 < 10 ns > 100 G/s
    內存 < 500 ns > 1 G/s
    網絡緩存 < 50 μs -
    SSD < 200 μs > 200 M/s
    HDD < 20 ms > 50 M/s

    這是一個緩存層次結構,更大的緩存有助于提高性能,可用系統RAM用作緩存。不要忘記將大量內存放入工作站,它將減少I/O的影響,工作集適合免費RAM -> 好!如果工作集不適合系統緩存,性能就會下降,就像不在L1/L2/L3緩存中時CPU工作一樣。

    構建緩存實現:

    • 從構建輸入生成的密鑰。輸入文件內容 (SHA1),其它狀態(構建設置等),構建函數版本(“手動”哈希)。

    • 可緩存的構建函數分為兩個階段。第一階段記錄所有輸入,第二階段執行工作。

    • 構建調度程序。執行第一階段,查詢緩存,如果可用,使用結果——否則運行第二階段。

    構建模型是應用函數將源數據映射到目標:

    \[\text{資產}_\text{目標}= f(\text{資產}_\text{來源}, \ \ ...) \]

    目標:純功能,無副作用!簡單的并行性

    資產數據庫:在Avalanche存儲服務中管理的數據,建立商店的類似實現,即日志結構化,由將數據“導入”到數據庫的映射過程產生,非常像常規的數據構建過程!數據可以從原生格式文件導入... 或其它數據源(SQL、Excel 等),保存涉及將數據庫資產“導出”回文件,即反向映射。

    這種數據庫的好處是在構建之前無需保存到磁盤(或簽出);構建的快照隔離;用于創建多個會話的廉價分支,即并排預覽相同的級別/對象/著色器,不同的設置;與構建系統緊密集成;快速同步,幾秒鐘即可啟動并運行,延遲獲取等等。

    Cutting the Pipe: Achieving Sub-Second Iteration Times提出了一種迭代流程,以便快速響應需求迭代,提示生產力和質量,優化流水線延遲。文中提出的迭代流程如下圖:

    需要更快迭代時間的原因是提升生產力,降低等待構建的時間;提升質量,增加更多調整,在控制臺上在游戲中測試的資產。

    在編輯場景時,不用緩存,而是實時編輯,但實時編輯的有:游戲并不總是最好的編輯器,如果游戲數據是正在使用的二進制圖像,版本控制很棘手,協同工作和合并變更也很棘手,適合編輯的數據格式沒有最佳的運行時性能。

    兩全其美的快速迭代是快速游戲和快速工作流。快速游戲需具備二進制資源、就地裝載、沒有尋道時間,快速工作流程需要編譯時間短,熱重載,即時反饋。進攻戰略是盡可能快地編譯并用重新加載替換重啟:

    重新編譯和重新加載所有數據 (>1 GB) 的速度永遠不夠快,必須分小塊工作:將游戲數據視為個體資源的集合,每個資源都可以單獨編譯,然后在游戲運行時重新加載,按類型+名稱標識,兩者都是唯一的字符串標識符(經過哈希處理),名稱來自路徑,但可將其視為 ID(僅通過相等比較)。

    編譯資源時,每個資源都編譯為特定于*臺的運行時優化二進制塊,由名稱哈希標識。

    加載資源時,資源被分組到用于加載的包中,包由后臺線程流入,在開發過程中,資源存儲在以哈希命名的單個文件中,對于最終版本,包中的文件捆綁在一起以進行線性加載。

    重新加載資源:運行游戲偵聽TCP/IP端口,消息是JSON結構,來自內部工具的典型命令,啟用性能HUD,顯示調試行,Lua REPL(讀取-評估-打印-循環),重新加載資源,也用于所有的工具可視化。

    重新加載資源的細節:加載新資源,根據類型通知游戲系統,指向新舊資源的指針,游戲系統決定做什么,如刪除實例(聲音)、停止和啟動實例(粒子)、保留實例,更新它(紋理),銷毀/卸載舊資源。

    // 重新加載資源示例
    if (type == unit_type) 
    {
        for (unsigned j=0; j<app().worlds().size(); ++j)
        {
             app().worlds()[j].reload_units(old_resource, new_resource);
        }
    }
    
    void World::reload_units(UnitResource *old_ur, UnitResource *new_ur)
    {
        for (unsigned i=0; i<_units.size(); ++i) 
        {
              if (_units[i]->resource() == old_ur)
                 _units[i]->reload(new_ur);
        }
    }
    
    void Unit::reload(const UnitResource *ur)
    {
        Matrix4x4 m = _scene_graph.world(0);
        destroy_objects();
        _resource = ur;
        create_objects(m);
    }
    

    存在的問題:將數據部署到控制臺,處理大量資源,編譯緩慢的資源,重新加載代碼。其中部分問題解決如下:

    • 大資源。永遠無法快速編譯和加載非常大的資源 (>100 MB),找到合適的資源粒度,不要將所有關卡的幾何圖形放在一個文件中,在單獨的文件中存放具有實體的幾何圖形,讓關卡對象引用它使用的實體。

    • 緩慢的資源。冗長的編譯使快速迭代變得不可能(光照貼圖、導航網格等),將烘焙與編譯分開,烘焙始終是一個明確的步驟:“立即制作光照貼圖”(編輯器按鈕),烘焙數據保存在源數據中并檢入存儲庫,然后像往常一樣編譯(從原始紋理到*臺壓縮)。

    • 重新加載代碼。重新加載是最棘手的資源,有四種代碼(著色器(Cg、HLSL)、二進制流(可視化腳本)、Lua、C++),流和著色器被視為普遍資源,只是二進制數據。

    ?

    實時重新加載LUA如下圖:

    重新加載C++代碼:工具支持“重啟Exe”,exe已重新加載,但仍然在同一位置看到相同的對象,只是使用新的引擎代碼,狀態由工具持有。沒有達到<1s 的目標,但仍然非常有用,小尺寸的exe有所幫助。

    快速編譯圖例:

    還支持增量編譯:查找自上次編譯以來修改的所有源數據,確定依賴于這些文件的運行時數據,重新編譯必要的部分,重要的是過程堅如磐石,信任來之不易,也容易失去,“完全重新編譯是最安全的”。

    依賴性是一個挑戰。base.shader_source包括common.shader_source,如果common.shader_source改變需要重新編譯,如果不讀取每個文件,我們怎么能知道這一點?解決方案:編譯數據庫,存儲以前運行的信息,在啟動時打開,在關閉時保存更新。編譯文件時,將其依賴項存儲在數據庫中,通過跟蹤open_file()自動確定它們。

    二進制版本也是個挑戰。如果紋理資源的二進制格式發生變化,每個紋理都需要重新編譯。解決方案是重用數據庫:將每個編譯資源的二進制版本存儲在數據庫中,檢查數據編譯器中的當前版本,如果不匹配,重新編譯,為數據編譯器和運行時使用相同的代碼庫(甚至相同的 exe),因此二進制版本始終保持同步。

    啟動和關閉編譯器,僅在啟動和關閉編譯器進程上花費了幾秒鐘。解決方案:重用該過程!作為服務器運行,通過TCP/IP接收編譯請求。

    掃描源文件,可以是以下代碼:

    foreach (file in source)
        dest = destination_file(file)
        if mtime(file) > mtime(dest)
              compile(file)
    

    這種方式很慢,檢查每個項目文件的mtime,而且碎片化(視日期而定)。可嘗試顯式編譯列表,工具發送一個它想要重新編譯的文件列表,工具跟蹤已更改的文件,紋理編輯器知道用戶更改的所有紋理,快速但碎片化:在工具svn/git/hg update之外不起作用,在Photoshop中編輯的紋理,在文本編輯器中編輯的Lua文件。

    解決方案:目錄觀察者。在服務器啟動時執行完整掃描,初始掃描后,使用目錄監視來檢測更改,ReadDirectoryChangesW(...),無需進一步掃描,使用數據庫避免脆弱性。將上次成功編譯的mtime存儲在數據庫中,如果掃描期間mtime或文件大小不同 - 重新編譯,如果目錄觀察程序通知更改 - 重新編譯。但會引發條件競爭,可由下圖的方式解決競爭:

    對于依賴項,由于不破壞進程,可以將依賴數據庫保存在內存中,只需要在服務器啟動時從磁盤讀取,可以將數據庫作為后臺進程保存到磁盤,當要求重新編譯時,不必等待數據庫被保存,稍后編譯器空閑時保存。

    最后處理:處理請求時唯一的磁盤訪問是:編譯修改后的文件,創建目錄觀察者“fence”文件,否則一切都發生在內存中。結果如下:


    通用規則:

    • 考慮資源粒度。大小合理,適合單獨編譯/重新加載。
    • TCP/IP是你的朋友。優先通過網絡做事而不是訪問磁盤,將進程作為服務器運行以避免啟動時間。
    • 使用數據庫+目錄觀察器來跟蹤文件系統狀態。數據庫還可以在編譯器運行之間緩存其它信息,保留在內存中,在后臺反映到磁盤。

    Putting the Plane Together Midair談到了游戲中AI行為樹的常見幾種設計模式。腳本、層級有限狀態機、行為樹的優缺點和特點:

    類型 優點 缺點
    腳本 完整和直接的游戲控制
    可廣泛應用于諸多模塊和系統
    難以調試和優化
    需要設計師的大量工程專業知識
    層級有限狀態機 直觀的設計師
    不錯的低級控制
    難以擴展和重用
    難以達到目標導向
    行為樹 利用腳本的強大功能和靈活性,使其成為設計師的簡單視覺語言。
    采用分層 FSM 的直觀和反應能力,使其可重用和目標導向

    行為樹圖例。

    行為樹的節點以下有幾種類型:

    • 序列(Sequence):與(&&)。
    • 選擇器(Selector):或(||)。
    • 裝飾器(Decorator):FOR循環。
    • 條件(Condition):游戲狀態檢查。
    • 動作(Action):玩法互動。

    實現行為樹的設計模型有:

    • 組合(Composite)。用戶定義的節點和行為樹。

    • 輕量級(Flyweight)。使用相同行為樹定義的多個AI角色。

    • 訪問者(Visitor)。需要每個AI角色能夠走同一棵樹并保持自己的狀態。

    還存在事件驅動樹(Event-Driven Tree),其目標是讓設計師學習并成為單一語言的專家,快速執行基于更新的AI腳本和基于事件的關卡腳本。事件驅動樹就像更新驅動樹(Update-Driven Tree),只是它們只tick一次。

    2012年,Accelerating Rendering Pipelines Using Bidirectional Iterative Reprojection描述了利用迭代重投影和雙向重投影來加速渲染管線的技術。

    當前的圖形架構需要對每一幀進行強力渲染,因此它們不能很好地擴展到高幀速率。然而,由于時間相干性,附*的幀通常非常相似,通過重用相鄰幀的渲染結果,可以在不執行光柵化和著色的情況下合成一個合理的幀。

    幀間插值示意圖。

    涉及實時重投影的策略有:

    • 從目標視點柵格化場景并從源視點采樣著色。(Nehab2007)
    • 使用每像素圖元將現有幀扭曲到目標視點。(Mark1997)
    • 使用某種*似。(Andreev2010,Didyk2010)
    • 使用迭代搜索扭曲幀。(Yang2011, Bowles2012)

    假設有一個使用渲染器生成的渲染幀,如何在給定渲染幀的情況下合成新幀?MV(運動向量)是渲染管線常見的衍生數據,提供從源幀到目標幀的映射。

    迭代投影示意圖。

    基于圖像的迭代重投影過程如下:

    • 通過方程知道每個像素的映射:

      \[p_{tgt} = p_{src} + V(p_{src}) \]

    • 在目標幀上運行GPU著色器:\(p_\text{tgt}\)已知,如何解析\(p_\text{src}\)

    • 可以迭代地解析:

    • 定點(Fixed Point)迭代。算法如下:

      • 選擇一個起點:(例如\(??_{??????}\))。
      • 應用遞歸關系直到收斂:

    ?

    第1次到第4次迭代依次是:左上、右上、左下、右下。

    幾種方法的性能對比。

    迭代初始化,簡略地說,FPI貪婪地收斂到最*的解。如下圖的Source圖像所示,兩個正在移動的球體的示例,并最終在目標圖像中重疊配置。三種解決方案——源圖像中的三個表面點位于目標圖像中,運動邊界在MV中產生不連續性,以紅色顯示。在每個區域中開始迭代返回最接*的解決方案,想從這里開始迭代。

    細分為四邊形并在扭曲位置柵格化:

    有一種特殊的情況,在靜止的汽車像素和快速移動的道路像素之間存在很大的運動不連續性,導致汽車后面的區域在源視圖中被遮擋。(下圖)

    之前已經有一些方法解決不連續的問題:

    • 重新著色 (Nehab2007)。需要再次遍歷場景。
    • 修復 (Andreev2010, Bowles2012)。基于圖像,取決于該區域的孔大小和視覺顯著性。
    • 雙向重投影(Yang2011)。

    而文中提出了自己的解決方案:從兩個源圖像重投影。

    場景:幀插值:渲染I幀(幀內或關鍵幀),插入插值B幀(雙向插值幀),這就是雙向重投影(Bireproj)

    • 為每對\(I\)幀生成運動流場。
    • 對于\(B\)幀中的每個像素$ t + α$:
      • 在正向流場\(??_??^??\)中搜索以重新投影到\(I\)\(t\)
      • 在反向流場\(??_{??+1}^??\)中搜索以重新投影到$ I \(幀\) t +1$。
      • 從幀$ t $和 $t +1 $加載和混合顏色。

    • 運動流場映射 I 幀 $t $和 \(t +1\) 之間的像素,獨立于??。

    • 假設\(t\)\(t +1\)之間的運動是線性的:將向量縮放 ?? (或 1??? )。

    • 使用迭代重投影解決\(??_{??+??}\)

    后續還需要生成運動向量場、選擇正確的像素、額外搜索初始化、分區渲染等步驟。

    選擇正確的像素

    額外搜索初始化

    分區渲染

    其存在的限制:

    • 動態著色插值。
      • 不好:僅在一個來源中可見時不起作用。
      • 好:每個B幀分離和渲染有問題的組件。
    • 快速移動的薄物體可見性。
      • 不好:重投影可能未正確初始化。
      • 好:使用健壯的初始化(使用DX 10+級別的硬件)。
    • Bireproj引入了一個小的延遲。
      • 不好:位置延遲小于一個(I 幀)時間步長。
      • 好:響應延遲最小化 (約等于0)。

    總之,重用著色結果以減少冗余計算;基于圖像的迭代重投影純基于圖像(無需遍歷場景),速度快,在PS3 (1280x720) 上為0.85毫秒,給定適當的初始化時非常準確的重投影;雙向重投影幾乎消除了遮擋偽影,將幀速率提高* n(插值幀數)倍,插入動態著色變化。

    [Deus Ex is in the Details](http://twvideo01.ubm-us.net/o1/vault/gdc2012/slides/Programming Track/DeSmedt_Matthijs_Deus Ex Is.pdf)講述了DirectX 11的特點及應用。

    DirectX 11 GPU優化包含只讀深度緩沖區、計算著色器本地存儲、收集指令。早期的CPU是瓶頸,場景中有許多獨特的對象,非常靈活的材質系統,狀態變化過多,最小化drawcall之間的狀態變化(實例化、狀態對象、常量緩沖區、池化靜態頂點和索引緩沖區)。

    狀態對象綁定到持久對象,如材質中的BlendState,哈希表中的JIT創建和緩存,哈希創建參數比Create…State更高效,創建仍然需要時間,可在啟動期間預熱狀態對象。常量緩沖區綁定到對象,如燈光狀態(用于前向渲染)、材質參數、實例參數,按更新頻率劃分的其它常數(可繪制、場景)。

    用DirectX 11實現的效果有抗鋸齒、SSAO、景深、曲面細分、軟陰影等。

    DX11實現的幾種抗鋸齒。

    高斯模糊在PS和CS的性能對比,卷積核越大,CS加速越明顯。

    SSAO在控制臺 SSAO模糊深度,PC則在半球中采樣,更少失真, 開銷更大,類似于星際爭霸2。SSAO雙邊模糊在DX9上采用9x9內核的像素著色器,DX11則使用19x19內核的可分離計算著色器,更流暢、降低噪點、性能影響小。SSAO自遮擋的問題:深度緩沖區不是法線映射的,夸張的法線貼圖導致半球與*面幾何體相交,沒有可用的頂點法線。解決方案:深度緩沖區包含幾何,想要視圖空間頂點法線,SSAO內計算視圖空間位置,ddx() 和 ddy() 返回任何變量的斜率,視圖空間頂點法線重建:normalize(ddx(viewpos)×ddy(viewpos))

    曲面細分裂縫(下圖)由多個子網格組成的角色、多個頂點位置相同、不連續法線導致。曲面細分裂縫解決方案:生成“Tessellation Normal”通道,均衡該位置所有頂點的法線,按三角形大小加權的法線,修復網格邊界上的裂縫,低開銷。

    鑲嵌凸起問題:硬邊改為*滑法線,部分機型出現此問題,通過*均法線來修復裂縫!Phong曲面細分創建圓形幾何圖形。解決方法:藝術家在邊緣添加額外的多邊形。

    細分優化:對約10m的距離啟用曲面細分,禁用前淡出曲面細分,保持Hull著色器簡單快速,曲面細分僅基于距離,最大曲面細分系數3.0,剔除因子為0.0的背面三角形。

    軟陰影需要SM5著色器,9x9濾波器內核來完成軟PCF,使用GatherCmpRed獲取4個樣本。軟陰影的問題:使用前向照明渲染的所有陰影投射燈,9x9內核只需幾秒鐘即可編譯,導致著色器構建時間爆炸,延遲所有光源的風險太大。解決方案:渲染屏幕空間中延遲的軟陰影,游戲只需要一個著色器,前向光照期間從軟陰影緩沖區中采樣,不用于半透明。

    多顯示器渲染使用供應商特定的API擴展,應該支持所有配置,如何處理擋板上的十字準線?使用偏心投影矩陣保持主顯示器的原始視野,FOV高時在裁剪*面附*拉回,增加貼花的深度偏差。

    立體渲染使用供應商特定的API擴展,為每只眼睛渲染幀,只進行一次剔除,立體的投影矩陣。

    Mastering DirectX 11 with Unity由NVIDIA和Unity的研發人員共同分享的主題,關于DirectX 11在Unity的渲染器、新功能和效果。

    當時Unity新增的功能包含Unity DirectX 11 渲染器、Unity“基于物理”的著色器、修改后的照明管線、Catmull-Clark曲面細分、混合形狀、PointCache (PC2) 支持、反射(立方體貼圖、四邊形)探針、后期處理、增量光照貼圖烘焙。

    Unity“基于物理”的著色器:受 Mental Ray “架構” (MIA) 著色器的啟發,足以用于高質量離線渲染大多數硬表面材料(金屬、木材、玻璃、粘土),熟悉CG/非游戲美術師,基于物理減少了參數的數量,能量守恒(有點)不可能打破物理定律設置材質,可預測的結果避免直觀的Hack影響,幾乎所有東西都只有1個著色器。

    漫反射用Oren-Nayar,1個參數:粗糙度,蘭伯特(粗糙度 == 0),不同的*似值。

    鏡面反射:Cook-Torrance,2個參數:粗糙度(光澤度)、反射率,能量守恒,基于物理的微觀層面理論。

    菲涅耳曲線 - 反射率如何取決于視角,2個參數:面對相機(0度),90度到相機,使用 Schlick *似插值:\(lerp (Refl_{at0},\ Refl_{at90},\ (N\cdot V)^5)\),在MIA中稱為BRDF(雙向反射分布函數)。

    能量守恒:漫反射 + 反射(+ 折射)<= 1,熱力學第一定律,反射率從漫反射和透明度中獲取能量,增加反射率將減少漫射能量,100% 反射永遠不會是漫反射或透明的!透明度從漫反射中吸收能量,標準Alpha混合。Cook-Torrance和Oren-Nayar都是能量守恒的,不像原來的Blinn-Phong,強烈的亮點很窄,寬的亮點不太強烈。

    模糊的反射,通過采樣不同的miplevel (LODbias) 實現低質量模糊,DX9/GL需要跨立方體貼圖邊緣進行修復,不適用于*面反射,只有方框過濾。通過多次采樣提高模糊質量 (1..8),彎曲表面法線,模擬粗糙表面的微面,根據“彎曲”法線反射視圖方向,可以模擬各種法線分布。

    組合2個法線貼圖:藝術家想要詳細的法線貼圖,混合2個法線貼圖只會“壓*”兩者,想要像混合2個高度圖一樣獲得結果。扭曲(Warp)第二張法線貼圖,使用第一個法線貼圖的法線:

    float3x3 nBasis = float3x3(
        float3 (n1.z, n1.x,-n1.y),
        float3 (n1.x, n1.z,-n1.y),
        float3 (n1.x, n1.y, n1.z ));
    n = normalize (n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);
    

    組合2個法線貼圖示例。從左到右:沒有法線、僅細節法線、組合法線。

    優化“基于物理的”著色器:

    • 小的代碼內核:易于優化,皮膚和汽車著色的代碼也相同。
    • 消耗:*均90條ALU指令,最多270條ALU指令,最多24個紋理提取。
    • 排列:根據參數值選擇。如果漫反射粗糙度 = 0,則使用Lambert而不是OrenNayar,不同的OrenNayar*似,光澤度值影響反射紋理樣本的數量,ETC。

    全局照明:Unity支持使用Beast進行光照貼圖烘焙,只有間接光照存儲在光照貼圖中。光照探頭用于動態物體的照明,反射探頭包含立方體貼圖(無限距離反射)和四面體(局部反射)。

    光照探針的四面體(tetrahedras)。

    環境光遮蔽:基于地*線的環境光遮蔽 (HBAO),由英偉達開發,環境光遮擋不應影響直接照明,根據定義!否則看起來很臟。盡早計算 SSAO,將結果輸入像素著色器:min (bakedAO, objectAO, SSAO),僅適用于間接光照貼圖、光照探針和反射。

    文中提到的DX11的特效包含APEX破壞、頭發管線、體積爆炸、運動模糊。

    NVIDIA APEX的可擴展動態框架。

    APEX破壞的工作流。

    頭發管線:頭發和皮毛是游戲角色的下一個大挑戰,隨著動畫和皮膚著色變得更好,低質量的頭發更加明顯,Unity頭發系統被設計為盡可能類似于離線系統,發型可以在Softimage XSI / Maya / MAX中建模,使用PC2點緩存格式導出到Unity,使用DirectX 11細分渲染,渲染頭發幾何體的生成完全在硬件中完成,幾何放大,GeForce GTX 580曲面細分硬件非常快。

    頭發的渲染使用了曲面細分,期間會增加噪點、結塊以提升真實感。頭發著色方面,系統支持細幾何頭發或帶有多根頭發圖像的寬紋理條帶。8x MSAA效果出奇的好,使用帶有隨機抖動的Alpha-To-Coverage,無需混合即可提供OIT,使用著色器覆蓋輸出的透明度超級采樣也將可能,但開銷太大。著色器包括在根部和尖端的淡入淡出,使用Kajiya-Kay各向異性著色模型,支持自陰影、假邊緣照明效果。

    運動模糊:提高快速移動場景中的“可讀性”,帶來電影般的外觀。Unity具有現有的“運動模糊”圖像效果,但不是真正的運動模糊,只是通過在舊框架上混合新框架來在物體后面留下痕跡。

    速度緩沖運動模糊:生成速度緩沖區,著色器計算屏幕空間中先前位置和當前位置之間的差異,Unity已提供,之前的模型矩陣 (UNITY_MATRIX_PREV_M)、蒙皮對象的先前世界空間位置 (POSITION1),從深度緩沖區和先前的模型視圖投影矩陣計算的相機運動模糊,必須小心相機剪輯第一幀的運動模糊!使用這些速度在運動方向上模糊當前場景的圖像。

    重建濾鏡運動模糊:“用于合理運動模糊的重建過濾器”,使用當前幀圖像和深度,加上屏幕空間速度緩沖區,模擬分散作為聚集,使用深度來解釋遮擋,正確模糊外部物體輪廓,還是有一些失真,特別是在tile邊界。

    總之,DirectX 11為Unity帶來了最先進的圖形,來自離線CG世界的技術正在成為可能,旨在使Unity盡可能對藝術家友好。

    Effects Techniques Used in Uncharted 3: Drake's Deception分享了神秘海域3的特效系統、工具、運行時等技術內容。

    神秘海域的粒子系統基于布局(Scheme)的宏語言,全能著色器,PPU和SPU之間的處理拆分:

    新系統大幅縮短迭代時間,更快的迭代==更多的效果和潤色,更多數據驅動,更好的動態,具有現代功能的更靈活的著色器,100%異步SPU代碼。

    構建效果時,從節點中提取的信息用于導出(粒子定義、字段、曲線和斜率表、表達式),將表達式編譯成VM字節碼,以DC格式寫出所有數據。粒子到游戲階段,粒子器使用插件生成交互式DC會話,DC堅持加快未來構建以實現快速迭代。

    下圖是粒子涉及的相關接口:

    對于粒子的運行時設計,系統設計考慮了硬件和引擎,GPU的粒子周期預算非常有限,SPU提供復雜計算的能力,更小的粒度,但處理的數量增加。


    粒子的一幀涉及的階段:

    graph LR A(預計算) --> B(更新) --> C(裁剪) --> D(構建幾何體)--> E(排序)--> F(構建渲染列表)

    每個階段的詳情如下:

    • 預計算:碰撞。

    逐粒子與環境碰撞開銷太大,只能用*似。每個粒子都存儲一個碰撞*面,讓粒子與之碰撞。每一幀,選擇一個碰撞粒子子集 (10) 并為它們選擇光線投射與環境的對比,下一幀返回結果,并存儲在粒子數據中。粒子*面以循環方式更新,每幀成本低,結果可以接受,一些粒子會穿過幾何體,但實際上并不明顯。

    • 更新。

      • 應用場。場包含重力、湍流、阻力、體積軸、徑向、渦旋等,烘焙動畫參數,測試與音量,對速度施加力。
      • 字節碼執行。具有16個向量寄存器的VM,非分支,創建和更新表達式。
      (particle-expr runtime
          (1const 0 2) 
          (1const 12)
          (lconst 2 2)
          (store 10 0)
          (store 11 1) 
          (store 122)
          (ramp 0 1 0)
          (store 9 0)
          (ramp 0 0 0)
          (store 13 0)
          (load 0 13)
          (store 14 0)
      )
      
    • 裁剪和構建幾何體。

    剔除與視錐體,構建頂點,創建索引和渲染數據結構,寫入內存。

    • 排序。

    對粒子中的每個發射器集進行排序,距離、生成順序、反向生成順序,藝術家可以在particler(粒子器)中設置發射器繪制順序。

    • 構建命令列表。

    RSX的輸出命令列表,設置視口、著色器、渲染狀態、頂點格式等,緩存著色器和設置。

    后面需要設置作業鏈,想要在所有SPU上運行,不想讓PPU在啟動之后才參與,每個階段必須在下一階段運行之前完成,不知道每個階段需要多少工作。使用設置作業收集結果并設置新作業,作業鏈如下:

    文中還給出了沙子足跡的特殊用例,過程大致是向地面投影粒子,構建粒子幾何體,粒子投射,變換深度到世界空間再到粒子空間。

    沙子足跡效果。

    2012年的主流游戲引擎都已經支持延遲渲染,但Forward Rendering Pipeline for Modern GPUs反其道而行之,闡述了在GPU上實現前向渲染,充分發現它的潛力和優勢,實現驚人的效果。

    前向渲染支持復雜材質、多種燈光類型、硬件抗鋸齒,高效的內存使用,良好的緩存使用,更好地將渲染數據與引擎分離。但是,由于硬件寄存器和API限制,以前無法支持大量光源,分支太慢。

    動機是想要更接*離線著色器的著色樣式,如V-ray、Maxwell、Renderman等,藝術家和技術藝術家友好, 從想法到視覺結果的快速迭代,從DirectX 11及更高版本開始。 利用現代GPU標量架構,至少在理論上,視覺質量沒有限制,一個地方完整的“渲染方程”。在跳轉到基于計算的完整路徑跟蹤之前的良好光柵化設計。

    Forward+渲染是前向渲染器的變種,為光源剔除添加計算著色器, 修改主光照循環。照明和陰影在同一個地方完成,所有信息都被保留, 有助于將復雜的著色器抽象為漂亮的“構建塊”,以便以不同的方式組合。對燈光和材質的參數沒有限制,如全方位、點光源、電影(任意衰減,barndoor)、每個材質實例的唯一 BRDF。設計簡單,渲染數據與運行時c++引擎解耦,可以在Maya中動態添加新的復雜材質類型。

    Forward+步驟:為每個“間接光”生成 RSM(可選),生成VPL和/或創建動態燈光并更新現有燈光(可選),Z預通道,光源剔除,著色。

    Forward+總覽。

    其中光源裁剪的著色器代碼如下:

    //    1. prepare
    float4 frustum[4];
    float minZ, maxZ;
    {
        ConstructFrustum( frustum );
        minZ = thread_REDUCE(MIN, depth );
        maxZ = thread_REDUCE(MAX, depth );
        ldsMinZ = SIMD_REDUCE(MIN, minZ );
        ldsMaxZ = SIMD_REDUCE(MAX, maxZ );
        minZ = ldsMinZ;
        maxZ = ldsMaxZ;
    }
    
    //    2. overlap check, accumulate in LDS
    __local u32 ldsNLights = 0;
    __local u32 ldsLightBuffer[MAX];
    
    for(int i=threadIdx; i<nLights; i+=WG_SIZE)
    {
        Light light = fetchAndTransform( lightBuffer[ i ] );
        if( overlaps( light, frustum ) && overlaps ( light, minZ, maxZ ) )
        {
            AtomicAppend( ldsLightBuffer, i );
        }
    }
    
    //    3. export to global
    __local u32 ldsOffset;
    if( threadIdx == 0 )
    {
        ldsOffset  = AtomAdd( ldsNLights );
        globalLightStart[tileIdx] = ldsOffset;
        globalLightEnd[tileIdx] = ldsOffset + ldsNLights;
    }
    
    for(int i=threadIdx; i< ldsNLights; i+=WG_SIZE)
    {
        int dstIdx = ldsOffset + i;
        globalLightIndexBuffer[dstIdx] = ldsLightBuffer[i];
    }
    

    著色階段:

    • 渲染場景材質。基礎光累積功能,使用屏幕xy位置確定tileID,從tileID獲取燈光開始和結束索引和從開始索引到結束索引循環,條目是光源數組的索引,累積光源擊中的像素,返回擊中像素的總直接和間接光。
    • 材質著色器。決定如何處理總入射光,例如傳遞到材質的BRDF,使用著色構建塊,環境光、 基礎光累積、BRDF等放在一起形成最終的像素顏色,可以在不影響底層著色器的情況下添加新的光源類型和BRDF 還可以根據*臺實現更低開銷的版本。
    // 光源累積偽代碼
    
    StructuredBuffer<float4>  LightParams          : register(u0);
    StructuredBuffer<uint>    LowerBoundLights  : register(u1);
    StructuredBuffer<uint>    UpperBoundLights  : register(u2);
    StructuredBuffer<int2>    LightIndexBuffer     : register(u3);
    
    uint GetTileIndex(float2 screenPos)
    {
       float tileRes  = (float)m_tileRes;
       uint numCellsX = (m_width + m_tileRes - 1)/m_tileRes;
       uint tileIdx   = floor(screenPos.x/tileRes)+floor(screenPos.y/tileRes)*numCellsX;
    
       return tileIdx;
    }
    
    StartHLSL BaseLightLoopBegin    // THIS IS A MACRO, INCLUDED IN MATERIAL SHADERS
    
     uint tileIdx    = GetTileIndex( pixelScreenPos );
     uint startIdx  = LowerBoundLights[tileIdx]; 
     uint endIdx   = UppweBoundLights[tileIdx];
    
     [loop]
     for ( uint lightListIdx = startIdx; lightListIdx < endIdx; lightListIdx++ )
     {
        int lightIdx = LightIndexBuffer[lightListIdx];
        
        // Set common light parameters
        float ndotl = max(0, dot(normal, lightVec));
            
        float3 directLight   = 0;
        float3 indirectLight = 0;
    
        if( lightIdx >= numDirectLightsThisFrame )    {
           CalculateIndirectLight(lightIdx , indirectLight);
        }    else      {
            if( IsConeLight( lightIdx ) )        {            //    <<==  Can add more light types here
                CalculateDirectSpotlight(lightIdx , directLight);
            }     else     {
             CalculateDirectSpherelight(lightIdx , directLight);
         }
     }    
    
     float3 incomingLight = (directLight + indirectLight)*ndotl;
     float shadowTerm = CalcShadow();
    
    EndHLSL
    
    StartHLSL BaseLightLoopEnd   
          }
    EndHLSL
    
        
    // 材質著色器模板
        
    #include "BaseLighting.inc"
    
    float4 PS ( PSInput i ) : SV_TARGET    
    {
        float3 totalDiffuse = 0;
        float3 totalSpec    = GetEnvLighting();;
    
    $include BaseLightLoopBegin
        totalDiffuse += GetDiffuse(incomingLight);
        totalSpec    += CalcPhong(incomingLight);
    $include  BaseLightLoopEnd
    
        float3 finalColor = totalDiffuse + totalSpec;
        return float4( finalColor, 1 );
    }
    

    基于計算的延遲渲染與Forward+的性能對比。

    深度預通道關鍵點:像素過繪制削弱了Forward+,因此需要深度預通道,深度預通道是使用MRT生成后期特效和其它渲染特效所需的其它全屏數據的好機會。XBOX 360具有良好的帶寬,因此考慮到前向渲染的限制,延遲渲染很有意義。然而,ALU計算的增長速度比帶寬快,只做計算比讀寫大量數據更可行。動態分支對性能的損害不像以前那么糟糕,作為一種優化,計算著色器可以按光照類型進行排序,例如以最小化光照分支損失。可以放棄所有用于決定哪些光源照射到每個對象以設置常量寄存器的“光源管理”CPU端代碼!

    總之,修改后的前向渲染器,可處理具有1000多個光源的場景,自動的硬件抗鋸齒 (MSAA) ,帶寬友好,充分利用GPU的ALU能力(增長速度快于帶寬),著色器可以類似于離線著色器以獲得高視覺質量。

    Advanced Procedural Rendering with DirectX 11闡述了基于DX11的程序化效果,如網格生成、GPU流壓縮、幾何體生成、網格改進、動態流體、照明等內容。

    距離函數是返回從給定點到表面的最*距離,有符號距離函數是返回點到表面的最*距離,如果點在形狀之外,則返回正數,如果在形狀內,則返回負數。有符號距離場用于程序幾何創建的有用工具,易于在代碼中定義,“隨機公式”的合理結果,可以從網格、粒子、流體、體素創建,CSG、失真、重復、變換等一切都變得輕松,不關心幾何拓撲,只需在空間中定義字段,稍后進行多邊形化。利用SDF添加網格細節的過程和圖例如下:

    以上圖例對應的偽代碼如下:

    // 1: 一個長方體
    Box(pos, size)
    {
        a = abs(pos-size) - size;
        return max(a.x,a.y,a.z);
    }
    
    // 2: 用布爾值切割
    d = Box(pos)
    c = fmod(pos * A, B)
    subD = max(c.y,min(c.y,c.z))
    d = max(d, -subD)
    
    // 3: 更多布爾值
    d = Box(pos)
    c = fmod(pos * A, B)
    subD = max(c.y,min(c.y,c.z))
    subD = min(subD,cylinder(c))
    subD = max(subD, Windows())
    d = max(d, -subD)
    
    // 4: 重復的布爾值
    d = Box(pos)
    e = fmod(pos + N, M)
    floorD = Box(e)
    d = max(d, -floorD)
    
    // 5: 切割孔洞
    d = Box(pos)
    e = fmod(pos + N, M)
    floorD = Box(e)
    floorD = min(floorD,holes())
    d = max(d, -floorD)
    
    // 6: 組合結果
    d = Box(pos)
    c = fmod(pos * A, B)
    subD = max(c.y,min(c.y,c.z))
    subD = min(subD,cylinder(c))
    subD = max(subD, Windows())
    e = fmod(pos + N, M)
    floorD = Box(e)
    floorD = min(floorD,holes())
    d = max(d, -subD)
    d = max(d, -floorD)
    
    // 7: 重復空間
    pos.y = frac(pos.y)
    d = Box(pos)
    c = fmod(pos * A, B)
    subD = max(c.y,min(c.y,c.z))
    subD = min(subD,cylinder(c))
    subD = max(subD, Windows())
    e = fmod(pos + N, M)
    floorD = Box(e)
    floorD = min(floorD,holes())
    d = max(d, -subD)
    d = max(d, -floorD)
    
    // 8: 重復空間
    pos.xy = frac(pos.xy)
    d = Box(pos)
    c = fmod(pos * A, B)
    subD = max(c.y,min(c.y,c.z))
    subD = min(subD,cylinder(c))
    subD = max(subD, Windows())
    e = fmod(pos + N, M)
    floorD = Box(e)
    floorD = min(floorD,holes())
    d = max(d, -subD)
    d = max(d, -floorD)
    
    // 9: 增加細節
    AddDetails()
    
    // 10: 增加光照和色調映射
    DoLighting()
    ToneMap()
    
    // 11: 增加貼花和耶穌光
    AddDeferredTexture()
    AddGodRays()
    
    // 12: 藝術調整,最終成像
    MoveCamera()
    MakeLookGood()
    

    實踐中的程序化SDF:

    • 生成的場景可能不會取代3D藝術家。
    • 生成的SDF是真實網格的良好代理。
    • 結合一些比藝術數據更便宜的圖元的代碼。
    • 結合藝術家構建的網格轉換為SDF。
    • 布爾、修改、剪切、程序變形。

    來自三角形網格的SDF:

    • 將三角形網格轉換為3D紋理中的SDF。
      • \(32^3\)\(256^3\) 體積紋理典型。
      • SDF插值很好,一般用雙三次插值。
      • 低分辨率3D紋理仍然可以正常工作。
      • 與多邊形數量無關(處理時間除外)。
    • 經常可以離線完成。

    將網格轉換為64x64x64的SDF并進行多邊形化。

    • 初級的方法是計算每個單元格到每個三角形的距離,非常緩慢但準確。

    • 將網格體素化為網格,然后掃掠(sweep)并不是好方法,掃描計算體素到細胞的有符號距離,*表面的體素化太不準確,但*地表距離很重要 - 插值。

    • 結合精確的三角距離和掃掠。

    幾何階段:綁定3D紋理目標,VS轉換到SDF空間,幾何著色器將三角形復制到受影響的切片,將三角形展*為二維,將位置輸出為紋理坐標,每個頂點的所有3個位置。

    像素著色器階段:計算從3D像素到三角形的距離,計算三角形上最*的位置,使用重心評估頂點法線,使用加權法線評估距離符號,寫有符號距離到輸出顏色,距離到深度,深度測試保持最*距離。

    后處理步驟:網格表面周圍的單元格現在包含準確的符號距離,網格的其余部分為空,在后期處理CS中填寫網格的其余部分,快速掃描算法。

    快速掃掠:需要能夠讀取和寫入相同的緩沖區,每行一個線程,線程的讀寫不重疊,無需聯鎖,在同一軸上先前后后掃描,依次掃描各軸。

    d = maxPossibleDistance
    for i = 0 to row length
        d += cellSize
        if(abs(cell[i]) > abs(d))
            cell[i] = d
        else
            d = cell[i]
    

    SDF還可以來自粒子系統,也可以對SDF可視化,此處忽略。

    現在聊聊GPU上的流壓縮。流壓縮的過程是取一個稀疏的數組,將所有填充的元素推到一起,記住計數和偏移映射,現在只需要處理數組的填充部分。

    計數通道 - 并行減少,迭代地將數組大小減半(如mip鏈),寫出父單元數的總和,直到達到最后一步:1個單元格、總計數。偏移通道- 迭代回走,單元格偏移 = 父位置+兄弟位置,組織金字塔(Histopyramid):3D流壓縮。

    組織金字塔(Histopyramid):以塊為單位累加mip鏈,從基數向上計數以計算偏移量。

    使用組織金字塔:使用活動蒙版填充網格體積紋理(0為空,1有效),在mip鏈中向下生成計數,對單元格位置使用第二體積紋理,遍歷mip鏈。

    壓縮實戰:使用組織錐體壓縮活性細胞,也知道活動單元格數,GPU只為活動單元格數量的調度drawcall,使用DrawInstancesIndirect,GS根據單元格索引確定網格位置,為此使用組織金字塔,為GS中的單元格生成行進立方體。對蠻力的巨大改進,從11ms下降到約5ms,大大提高并行度,減少繪圖調用大小,幾何圖形仍然在GS中生成,為每個渲染通道再次運行,無索引/頂點重用。

    DX11還可用于在渲染管線利用生成幾何、頂點等數據,以及*滑網格(下圖)。

    此外,DX11還可用于*滑粒子流體動力學、AO光線追蹤、基于SDF的AO等渲染技術。

    Survey of Physics Engines for Games調查了2012年的主流物理引擎的基礎知識、特點、特性、使用比例等內容。

    游戲應用程序上下文中的物理引擎。

    當時的主流物理引擎包含PhysX、Havok、ODE、Bullet等,前三者的使用情況如下:

    它們的特性如下:

    The Technology Behind the “Unreal Engine 4 Elemental demo”詳細分享了2012年的Unreal Engine在圖形功能、間接照明、陰影、后期處理、粒子等方面的渲染、實踐和優化技術。

    當時的UE4考慮一個通用的實時GI解決方案,受Interactive Indirect Illumination and Ambient Occlusion Using Voxel Cone Tracing [Crassin11]的啟發,最終選用了體素化的方案。

    體積射線投射:從一些開始偏差開始,內容自適應步長,查找輻射和遮擋,通過遮擋累積光,如果被遮擋或足夠遠就停止。錐形追蹤,局部錐體寬度的Mip級別,逐步增加步長。

    對GI使用體素錐追蹤:

    • 類似“光線追蹤到一個簡化的場景”。
    • 漫反射GI:多個方向取決于法線,錐數的張角。
    • 鏡面反射:來自鏡像眼睛矢量的方向,高光power的張角。
    • 不如光線追蹤精確,但分數幾何相交、無噪點、LOD。

    椎體追蹤示意圖。其中紅色代表鏡面反射,綠色代表漫反射GI。

    • 可以進一步優化/*似。較低的體素分辨率,在體素照明通道中聚集而不是分散,自適應采樣,樣本復用。
    • 額外的好處。著色IBL,發光材質的著色區域燈。

    體素錐追蹤挑戰:穿過薄薄的墻壁,寬錐體顯示偽影,但窄錐體速度較慢,Mip貼圖需要依賴于方向,從三角形網格創建體素數據,運行時內存管理,GPU硬件上的高效實現,稀疏數據結構。

    稀疏體素八叉樹:映射函數允許本地更高分辨率,世界3D位置 <=> 索引和本地3D位置。在GPU上完全維護。訪問渲染階段特定數據的索引,每個節點/葉子數據,2x2x2 體素數據(放置在八叉樹節點角落),6x 3x3x3 體素數據(如帶有附加邊框的 2x2x2)。

    體素照明管線:

    上圖的Voxelization:所有靜態區域的關卡加載后,動態對象移動時;Lighting:聚光明暗;Filtering:創建方向相關的體素和mip;Finalize:創建冗余數據并復制到體積紋理。

    • 體素化。

      • 在區域中創建體素幾何數據,輸入:八叉樹、三角形網格、實例數據、材質、區域,輸出:八叉樹、2x2x2材質屬性、法線。
      • 區域重體素化。幾何變化、重大變化、分辨率變化。
      • 針對少數動態對象進行了優化。按需重體素化,區域保持靜態體素數據分開。
      • 渲染方法:
        • 方法1:使用硬件光柵化器的像素著色器通道,每個軸(X、Y、Z)一次光柵化,以避免孔洞,著色器評估藝術家定義的材質,輸出:跟隨CS處理的分片隊列。
        • 方法2:計算著色器通道,更新八叉樹數據結構(并行),在葉子中存儲體素數據。
        • 方法2有更好的占用率(2x2四邊形),著色器編譯時間(重用CS)。
    • 體素光照。

      • 計算著色并存儲 Radiance。輸入:2x2x2材質屬性、法線,輸出:2x2x2HDR顏色和不透明度。
      • 累積輻照度和陰影。使用陰影貼圖添加直射光,添加環境色,結合反照率顏色,添加發光顏色。

    • 過濾體素并完成。

      • 生成mip-maps,創建冗余邊框,壓縮。輸入:2x2x2 HDR顏色、遮擋和法線,輸出:HDR乘法器,6 x 3x3x3LDR顏色和遮擋。
      • 生成方向相關的體素。在體素法線的葉子級別,僅在同一方向的節點級別。

    鏡面采樣:

    • 每像素局部反射,Specular Power的錐角,單錐通常已足夠,可能的復雜BRDF。
    • 自適應以獲得更好的性能,高光亮度,深度差,法線差異。


    • 使用Dispatch()進行上采樣。
    • 分散通道使用DispatchIndirect()。

    漫反射采樣:

    • 類似于Final Gathering [Jensen02]。
    • 問題:很少的樣本以獲得良好的性能,足夠的質量樣本(錐角),在半球上分布均勻以減少誤差,不想要噪音,不想模糊正常的細節。
    • 漫反射多為低頻。
    • 一致性對效率很重要。

    體素光照對比圖。

    接下來聊聊UE的著色。

    UE使用了新的鏡面power編碼:IBL更高的鏡面反射功率,共享值的更多定義,調整以在寬度為1000像素的遠距離球體上提供像素清晰的反射。

    OldEncode(x): sqrt(x / 500)
    OldDecode(x): x * x * 500
    
    NewEncode(x): (log2(Value) + 1) / 19
    NewDecode(x): exp2(Value * 19 - 1)
    

    高斯鏡面反射用于減少鋸齒[McKesson12],以經驗*似:

    Dot = saturate(dot(N, H))
    Threshold = 0.04
    CosAngle = pow(Threshold, 1 / BlinnPhongSpecularPower)
    NormAngle = (Dot - 1) / (CosAngle – 1)
    LightSpecular = exp(- NormAngle * NormAngle) * Lambert
    

    區域光鏡面反射,軟球區域光:

    LightAreaAngle = atan(AreaLightFraction / LightDistance)
    ACos = acos(CosAngle) 
    CosAngle = cos(ACos + LightAreaAngle)
    

    能量守恒(*似值):

    SpecularLighting /=  pow(ACos + LightAreaAngle, 2) * 10
    


    新的后處理圖:

    • 圖形:創建每一幀,沒有用戶界面,依賴項定義執行順序,按需RT、引用計數、lazy釋放。
    • 節點:種類多但功能固定,多個輸入和輸出,定義輸出紋理格式。

    SSAO:

    • 經典SSAO [Kajalin09]。環境遮擋計算為后期處理,只需要z緩沖區和3d點樣本,很少有樣本以小屏幕對齊模式排列。

    • UE的技術基于二維點樣本。基于角度的類似于HBAO[Sainz08],使用GBuffer法線進一步提高質量,用高頻細節補充體素照明。

    • 采樣:在半分辨率z緩沖區中使用6個樣本對 = 12個樣本,16次旋轉,以4x4模式交錯的模式:

    • 逐像素法線進一步限制角度:

      A) Given: z buffer in the sample direction
      B) Get equi-distant z values from samples
      C) AO (so far) = min((angle_left+angle_right)/180,1)
      D) Clamp against per pixel normal
      E) AO (per pixel normal) = (angle_left+angle_right)/180AO ~= 1-saturate(dot(VecA,Normal)/length(VecA))
      

      效果對比:

    HDR直方圖:

    • 64個Bucket,對數,無原子。

    • Pass 1:并行生成屏幕局部直方圖(CS)。

      清除組共享直方圖float4[64][16]
      同步
      并行累積直方圖
      同步
      將多個直方圖累積到一個float4[16]
      在16個紋素中每行輸出一個直方圖
      
    • Pass 2:將所有行合二為一,64個Bucket存儲在16個ARGB中。

    人眼自適應:

    • 從直方圖計算*均亮度(藍線),僅考慮明亮區域(例如 >90%),拒絕少數非常明亮的區域(非常明亮的發射區,例如 >98%)。
    • 計算整個視口的單個乘數,與最后一幀*均值*滑融合(白條),綁定在用戶指定區域(綠色),應用于色調映射器(白色曲線)。
    • 在色調映射VS中讀取結果,作為插值器傳遞給PS。

    GPU加速的粒子:

    • CPU:生成粒子(任意復雜的邏輯),固定大小緩沖區中的內存管理(單位:16 個粒子),發射器管理(索引緩沖區、繪圖調用排序)。
    • GPU:牛頓力學的運動(固定函數),來自非定向體積級聯的照明(3D 查找),如果需要,GPU 基數深度排序 [Merrill11] [Satish09],渲染,來自矢量場的附加力(3D 查找),粒子曲線來調整粒子屬性(一維查找)。

    Bloom:

    • 目標:大型、優質、高效

    • 下采樣:

      A = downsample2(FullRes)
      B = downsample2(A)
      C = downsample2(B)
      D = downsample2(C)
      E = downsample2(D)
      
    • 下采樣期間的模糊可避免鋸齒:

    • 重組(隨著分辨率的增加):

      E’= blur(E,b5)
      D’= blur(D,b4)+E’
      C’= blur(C,b3)+D’
      B’= blur(B,b2)+C’
      A’= blur(A,b1)+B’
      
    • 上采樣時模糊,提高質量,幾乎不影響模糊半徑:

      blur(blur(X,a),b) ~= blur(X,max(a,b)) 
      
    • 組合臟紋理(dirt texture):

       J*const + tex2d(Dirt,ScreenUV)*const
      

    GBuffer模糊:

    • 智能模糊:*均5個像素,按正常加權,按深度差加權。

    • 應用:減少鏡面反射材質的鋸齒(運動中明顯),減少環境光遮擋中的高頻抖動偽影,可以使用IBL或體素照明提高性能。

    • 盡可能對深度、AO使用 Gather() 。

    • 輸出:SpecularPower、Normal、AmbientOcclusion。

    • 降低鏡面反射功率 [Toksvig05] [Bruneton11]:

      L = saturate(length(SumNormal) * 1.002)
      SpecularPower *= L / (L + SpecularPower * (1 - L))
      

    圖的輔助:

    • 后處理體積:線性混合后期處理屬性,優先級取決于相機位置,具有混合半徑的軟過渡,可以遠程控制權重。
    • 渲染目標池(Render Target Pool):按需分配,引用計數,延遲釋放,查看中間緩沖區的工具。

    Destiny: From Mythic Science Fiction to Rendering in Real-Time描述了Destiny引擎的渲染技術和特效。

    當時的Destiny引擎支持和的特性有:矢量地形移動樹,河流和湖泊,可定制的齒輪、實時布料、面部技術、高質量的實時陰影、特效、公告板系統、曲面細分、高質量的大型AO (GI)、可見性、一天的動態時間,還有很多很多......

    Destiny引擎的目標是在藝術風格選擇和現實主義之間取得良好的*衡,高質量的視覺效果,但成為通用圖形引擎不少目標。渲染關鍵領域有高效的內容創建流程和管道、可信而復雜的角色高度互動、動態的世界處理復雜問題的能力、充分利用下一代硬件的強大功能、可擴展到當前一代的控制臺。引擎技術包含基于作業的多線程、數據并行和緩存一致性、執行在任意類型的核心上。

    為了加速渲染,減少了幾何通道,保持小的渲染目標尺寸,統一的光照+材質模型,簡化著色器。Halo: Reach采用了混合延遲渲染管線:

    Pre-pass延遲渲染管線:

    Destiny引擎的延遲渲染管線:

    其GBuffer數據布局如下:

    材質庫,材質模型參數存儲在表中在G-Buffer中,只在表中存儲單個索引,可表達且可定制:表被存儲為一個紋理,指定使用創作的曲線或繪制的紋理。

    鏡面反射瓣指數(10 位存儲在法線旁邊),控制鏡面高光的形狀,4位指定了藝術家繪制的波瓣形狀,并且6位指定粗糙度變化,這些變化在導入過程中自動計算。



    光照和著色過程如下系列圖:




    各種延遲渲染的性能對比如下:

    Destiny透明光照的目標是光線不透明的一致性,陰影與不透明的一致性,應用于大氣中。*似方法是動態地將光探頭放置在透明的地方,每一幀都為探測器構建一個低階球面諧波,要考慮光線和陰影。

    傳統的輻照度體積在CPU進行,不過Destiny移到了GPU。處理如下:

    • 每幀構建探針列表。在處理可見透明對象時,CPU會構建一個光探測點列表,點被寫入一個快速的線程安全的無鎖緩沖區,在任務中構建對象列表。XYZW每個組件都是32位的浮點數,光源探針半徑存儲在W中。

    • 每幀提交光源探針到GPU緩沖區。限制數量為1024個,編碼進64x64的RGBA32F紋理,使用雙緩沖以阻止停頓。

    • 光源探針GPU生成。設置MRT與SH光照環境表面:3 x 64 x 16 RGBA16F渲染目標,每個渲染目標為一個顏色通道編碼4個SH系數。為太陽渲染一個四邊形到SH表面,將定向光映射到SH系數。

      • 陰影。采樣級聯陰影映射以確定每個探針的陰影,在光照點緩沖位置執行PCF,使用基于探針半徑的樣本半徑。
      • 光照環境。光源可以被藝術家標記為影響透明度,光源只是藝術家可選擇的一組著色器組件。對每一個影響透明的光源:渲染一個四邊形到SH表面,將光源的參數映射到給定的光源探針點緩沖位置的SH系數。
    • 渲染半透明。半透明在后置的主要延遲通道被渲染,在應用延遲光照和陰影之后。當渲染一個半透明物體時,采樣SH表面,應用光源到像素顏色。環境光照模型低開銷且且之后再計算。

    限制:SH不是不透明照明的完美搭配,但是合理的足夠的,當物體是透明的時,大多數瑕疵不明顯。需要很多樣本來獲得*滑的陰影響應,由于每個物體的陰影因素,有利的一面是在64x16的緩沖區上操作速度很快。

    粒子是超低(über-Low)分辨率的VDM(方差深度圖)粒子。

    左:全分辨率;中:使用雙線性上采樣的1/4分辨率;右:使用VDM的1/4分辨率

    2014年,Solving Visibility and Streaming in The Witcher 3: Wild Hunt with Umbra 3闡述了巫師3利用Umbra組件實現可見性和流式的功能。Umbra是2007年成立的專門做遮擋剔除的小型公司,它的工作流程如下:

    巫師3的要求是大型開放世界→ PVS,手動操作是不可能的,Umbra是自動的,流數據,LOD。數據流的挑戰有獨立區塊、邊界匹配、速度。Umbra3中的LOD,之前的場景由單個對象實例組成,問題有需要多個LOD級別、關卡*間的自遮擋、LOD層次結構?解決方案如下:

    LOD挑戰;距離參考點、LOD選擇的其他標準、更智能的LOD遮擋體。

    遮擋數據流:

    • 從特定的攝像機位置確定所需的tile組。
    • 如果新確定的集合與當前使用的集合不同,則異步計算開始。
    • 流式輸入預烘焙緩沖區(僅適用于尚未流式輸入數據的tile)。
    • 創建Tome對象(僅適用于尚未創建此對象的tile)。
    • 一旦所有的Tome都存在,就會從中創建TomeCollection對象。
    • 新創建的集合將被發送到渲染器以替換當前使用的集合。
    • 不再需要的tile會破壞其Tome對象,并取消流(unstream)預烘焙緩沖區的tile,銷毀之前的收集對象。

    《天涯明月刀》引擎開發是騰訊北極光工作室的安柏霖在GDC中國2014上的一個分享,講解了水銀(QuickSilver)引擎的場景、材質、光影等方面的技術。

    在場景方面,要滿足4kmx4km的可玩區域和12kmx12km的可視區域,挑戰是開發&運行效率。在運行效率上采用了可見性檢測和剔除、LOD、實例化、多線程渲染,而開發效率則使用了管線工具、協助編輯、外部互通工具:

    在可見性檢測方面,主線程跳過粒子、動畫等計算,使用基于Actor/Entity級別的檢測。渲染線程跳過DrawCall,執行通用可見性檢測(場景管理、視錐體剔除、軟光柵剔除、貢獻度剔除)、反射剔除、陰影剔除等。

    在通用可見性檢測上,場景管理使用了兩個層級的Cell/BVGrid,以獲得快速的內存訪問,采用了SSE2指令集、軟件光柵化(地形、內包圍盒),且剔除占屏幕非常小的物件(角色和普通物件使用不同的閾值)。

    通過以上手動,場景的Draw Call提升了47.8%。

    在執行反射剔除時,將水面上的地形當作遮擋體,以剔除鏡像相機不可見的地上物體:

    對于陰影剔除,計算量很大,范圍大(2kmx2km的范圍),靜態物件和植被會產生投影。由于光的方向變化是固定的,可以離線計算不需要投影的情況,已經被陰影覆蓋的地形、靜態物件和植被,投影但是沒有物體接受。


    反射進一步簡化,避免水中的反射非常清晰的情況,只在Z方向投射,不再有多向投射,MipmapBias = 2。


    光照和材質方面,支持通用、頭發、眼睛、皮膚的材質,使用了PBR光照。光照拆分為漫反射和鏡面反射項,鏡面反射使用了兩層高光:主高光、次高光。

    主鏡面反射使用了主流的Cook-Torrance模型,其中D : blinn-phong、F : Schlick’s approximation、G : Neumann-Neumann GAF。次鏡面反射使用了離線烘焙的IBL。頭發使用了Kajiya-Kay,皮膚使用了5S,眼睛使用了基于Jimenez12的改進和優化版本。環境光使用3個半球光(Sky、Ground、SunScatter)。

    角色光照進行了特殊處理,因為需要任何情況下看起來都很棒。光照參數和場景分開,由角色美術單獨調試,渲染是兩個pass,不同參數,使用stencil區分。增加攝像機燈光—Camera View方向的一個光照,頭發在陰影中DirectLighting保留%30到%40。

    光照管線采用了Deferred/Forward混合模式。

    級聯陰影有4個cascade,保證*滑過渡,Pack到一個LightViewBuffer中,前3個Cascade的每幀更新,覆蓋大約200米,第4個cascade覆蓋超過2km。沒有*滑過渡的話,為了保證完整性,Frustum需要完整包含Cascade的包圍球,而Frustum最外側部分只是為了很小部分cascade椎體。有了*滑過渡,可以把Frutum縮小到原來的0.85x0.85=0.72,少部分覆蓋不到的地方由下一個cascade混合處理:

    第4級陰影覆蓋超過2km,分幀更新,每20秒一切換(隨光的方向,位置的移動),切換過程中,*滑過渡。陰影緩存可以節省約%70的static mesh&terrain sector的shadow中渲染,用在第三個Cascade里,在第1、2兩個cascade中意義不大,值不回顯存的成本。

    此外,使用了Snapping處理抖動,以及Shadow Mask 對ShadowMap構建pass達到%10到%60的優化。

    Rendering in Codemasters’ GRID2 and beyond Achieving the ultimate graphics on both PC and tablet陳述了游戲超級房車賽2在兼容PC和*板設備的渲染技術,包含像素著色順序、OIT、自適應體積光照、可編程混合、粒子光照等等新興渲染技術。

    2014年的顯示屏分辨率和GPU都有較大的變動,分辨率變得越來越高清,1080p占據了主導地位,而Intel的集成顯卡依然是主導地位,GTX次之:

    當時的行業目標是使中等設置與當前控制臺匹配,從控制臺質量向上和向下擴展:

    像素著色順序(Pixel Shader Ordering):屏幕位置的像素著色器互斥,速度很快,因為只有沖突的線程被序列化。保證執行順序類似于Alpha混合規則,以V_PrimitiveID順序寫入的像素,像素著色器排序將這種保證的排序移動到像素著色器中。可以用它做任何想要讀修改寫每像素數據結構的東西。

    左:沒有像素著色順序,重疊像素可以并行執行;右:使用了像素著色順序,重疊的像素不能再并行地執行,變成了串行。

    OIT(順序無關的透明度):可以表示多個透明層,而不存在排序問題,如密集/柔和的葉子(尤其是在距離較遠的地方,因為mips),改進了其它Alpha測試幾何體。

    在UAV表面上,將可視性函數存儲為順序的固定大小節點數組,每個紅色節點對應一對深度和透射率值:為了壓縮可見性,移除了產生最小面積變化的節點:

    GRID2中的透明度覆蓋率(Alpha Coverage):葉子上有很多半透明的部分(下圖左紅色區域),Alpha混合不是一個選項,最初的系統使用Alpha 2x覆蓋率,但需要4xMSAA才能看起來不錯。在沒有混合或A2C的情況下,會得到帶鋸齒的結果:

    自適應體積陰影(Adaptive Volumetric Shadow Mapping,AVSM):可以用一個類似的想法來*似穿過參與介質的透光率嗎?從光源的角度渲染OIT,可用于渲染體積煙霧效果,例如下圖的輪胎被霧氣覆蓋:

    發光粒子:最初的研發專注于優化陰影圖讀/寫,粒子的每像素照明時間>=10毫秒…逐頂點照明太粗糙,帶有屏幕空間細分的逐頂點實際上看起來更好!每個頂點的細分速度快2-3倍。

    可編程混合(Programmable Blending):對數編碼到R10G10B10A2后緩沖區的HDR照明值,編碼值的固定函數alpha混合無效,結果是在透明物體后面失去了高動態范圍,解決方案是融入線性空間。

    GPU和CPU優化:不同于使用離散圖形優化系統,有些優化是違反直覺的,優化功率和帶寬在獲得預期性能方面發揮了重要作用。CPU和GPU共享系統的熱設計功率(TDP)額定值。CPU和GPU有最大允許頻率,可以得到一個或另一個,而不是同時得到兩個!在圖形基準測試中,負載共享如下所示:

    游戲看起來更像下圖!TDP在CPU和GPU之間的共享更加均勻。音頻、人工智能和更高的圖形API開銷都會導致更高的CPU使用率,更高的CPU要求意味著可能很難達到最大Gfx頻率。

    降低TDP,更積極的權衡。在較低的TDP下,最大CPU和GPU頻率可能不會有太大變化,但不能同時獲得這兩個頻率

    那么優化圖形時會發生什么呢?如果分析告訴你是GPU受限的,那么優化GPU將提高性能,對嗎????節省20%的GPU工作量,你有20%的額外FPS嗎??額外的FPS通常需要更多的CPU來驅動工作負載。

    GPU受限?優化CPU!!聽起來很瘋狂,但越來越普遍。GPU和CPU共享電源預算,在運行時根據工作負載動態調整頻率,優化其中一個會給另一個帶來更多動力,基本CPU頻率可能會產生誤導……

    博主注:文中的有些描述是帶有時間性的,不知道現在的移動或*板設備是否還如此,需要另外查資料或針對性地實測!

    能量不是唯一共享的東西!共享的還有高達1.7G的系統內存,通過環形總線連接到CPU,共享LLCache,CPU和GPU之間共享的系統帶寬。

    好戲還在繼續,隨著TDP的增加,外部帶寬變化不大,增加GPU或CPU工作負載會增加帶寬需求:

    你能足夠快地給系統供電嗎?如果更高的TDP不能提供更高的性能,請檢查GPU有多忙。EU暫停通常可以直接由等待RAM造成,也可以通過采樣器間接造成。Intel GPA可以檢測相關數據。

    SSAO改進前,當研發規模沒有縮小時的中等設置,僅有15-20%的幀數,成本過高。基于CS,難以跨多個硬件供應商進行優化,內存非常密集,每個遮擋結果2個深度樣本,智能交叉雙邊模糊從深度讀取以確定邊緣,以1/2 x 1/2屏幕分辨率工作。

    SSAO改進后,基于圖像空間地*線的環境遮擋(Based on Image-Space Horizon-Based Ambient Occlusion),完全基于PS,仍然是1/2 x 1/2分辨率,法線和邊緣檢測的基本成本+一個遮擋結果的一個深度樣本,智能交叉雙邊模糊使用上一個過程中的邊緣,不讀取深度。下圖是改進前(上)后(下)的性能對比,:

    可見改進后的SSAO的性能有巨大的提升,普遍提升了好幾倍。

    MSAA性能:像素著色器每個采樣運行一次,覆蓋率和遮擋率更高。子樣本級別所需的存儲增加了帶寬和內存需求,成本因硬件和工作量而異,但從來都不是免費的。

    后處理AA作為替代方案,評估了兩種最常見的方法:SMAA 1x和FXAA 3.11用于GRID2。FXAA 3.11的性能良好,但開發人員發現它太模糊:(文本和高頻紋理上的細節丟失);SMAA 1x在正向渲染方面,成本比MSAA高一點,仍然有點模糊;從“形態抗鋸齒”開始,后期處理,通過分析顏色不連續(邊緣)來檢測鋸齒,并應用智能模糊來減少鋸齒。

    輸入保守形態AA(CMAA):基于MLAA,但僅求解對稱Z形,而不是U形、Z形和L形,更好地保存*均圖像顏色和時間穩定性。確定和修剪邊緣的保守方法,“如果不確定,不要模糊”,與FXAA 3.11相比,總體損傷更小,AA質量更高,為英特爾Haswell量身定制:快于FXAA 3.11,是SMAA 1x的兩倍。

    全屏陰影通道:像素著色器上的塊,EU像素著色器停頓 = 42.3%,最初在4種陰影紋理中讀取,一個著色器用于所有質量設置,在較低的設置下清理紋理讀取。添加模板遮罩以移除選定區域,例如天空,在中等及以下級別使用不同的著色器,從粒子陰影紋理中刪除讀取。

    其它優化方法:可以刪除高端PC功能,*板電腦GPU的性能最初為每幀約53ms,但有更大的空間進行積極的改進,可擴展性(更多圖形菜單選項!),更選擇性地使用鏡面反射和法線貼圖等。更便宜的著色器,想在主場景中使用環境貼圖著色器?該渲染過程本質上是主顏色通道的低質量版本,在某些情況下,質量太低,但可節省20毫秒的GPU時間!

    紋理LOD偏差,視覺質量下降非常快,目前的測試顯示,增益微乎其微。較低的幾何細節層次,更*的繪制距離,樹木/人群的廣告牌LOD,降低頂點成本和照明成本,簡化后處理:只需要色調映射(需要bloom),運動模糊、鏡頭光斑等都消失了。

    低分辨率粒子渲染:對控制臺/PC進行有效優化,以較低的分辨率渲染粒子,并將其與主幀緩沖區相結合,從而降低填充率,1/4的寬度和高度,*板電腦的固定成本管理費用要高得多,創建下采樣深度緩沖區,上采樣下采樣顏色緩沖區,更高效地以全分辨率渲染粒子,并犧牲粒子數,高粒子數僅在碰撞或偏離軌道時出現。

    總結:新的擴展允許視覺差異化,并且節能;現有算法可以顯著優化;正常的GPU優化規則是微妙的不同,帶寬和功率意味著事情并不總是直觀的;CMAA是一種適用于所有硬件的低成本后處理抗鋸齒解決方案,適用于擔心模糊/圖像退化的情況,與SMAA相比,AA的效果不如SMAA(尤其是與更昂貴的變體相比);你是否在電源受限的硬件上做出了最佳的視覺權衡?

    Authoring Tools Framework: Open Source from Sony's Worldwide Studios提到了一種特殊的設計模型——文檔物體模型(Document Object Model,DOM),內存中可觀察的類XML的數據庫,DomNode樹的根通常是一個文檔,DomNodes有屬性和子節點,由DomNodeType指定(類似于模式類型),與XML中的屬性一樣,屬性是簡單類型(int、float、string、引用)或簡單類型的數組。節點是可觀察的,例如子節點添加事件或移除子事件、屬性更改事件等。

    DomNode層級:每個DomNode都有特定的屬性和子節點,由DomNode的DomNodeType指定,可以通過編程或加載模式文件來創建DomNodeType,事件從子節點傳給父節點。

    節點適配器:客戶端的“業務類”源于DomNodeAdapter,是為特定的DomNodeTypes定義的。首先創建DomNode,然后自動創建其DomNodeAdapter,但會根據需要進行初始化。在根DomNode上調用InitializeExtensions,初始化整個樹的所有DomNodeAdapter。

    上下文:通常每個文檔一個。SelectionContext跟蹤用戶的選擇并有更改事件;HistoryContext跟蹤DOM對子樹的更改以進行撤消/重做;TransactionContexts是HistoryContext的基類,跟蹤一組更改的時間開始和結束,以便可以在正確的時間執行驗證邏輯。InstancingContext實現復制、粘貼和刪除。

    注冊表,每個應用程序各有一個。DocumentRegistry–跟蹤文檔,公開文件清單,添加和刪除文檔,活動文檔。ContextRegistry–跟蹤上下文,可用“上下文”列表,添加和刪除上下文,活動上下文。IControlRegistry、IControlHostService客戶端注冊控件,以便它們出現在停靠框中,跟蹤主動控制。

    Introduction to PowerVR Ray Tracing闡述了2014年的PowerVR的移動端GPU的光線追蹤硬件架構。

    沒看錯!2014年!移動端GPU!!超前的技術和探索!

    下圖是PowerVR的硬件架構圖:

    上圖的架構中,和光線追蹤有關的單元是:Ray Tracing Unit、Scene Hierarchy Generator、Ray Data Master等。下圖是渲染效果:

    當時主要應用于AR、VR及輕量級游戲。其中VR涉及透鏡畸變和像差校正(aberration correction):

    游戲中的混合渲染:陰影,反射,透明度,使用多個動態光源進行更好的縮放,光柵化的實時光照圖更新,易于集成。

    完全的光線追蹤圖形:蠻力的路徑追蹤,輕松實現照片真實感,幾乎需要對所有3D內容進行光線跟蹤,在當今的控制臺/桌面技術中,使用向導是可能的,對于未來幾代人來說,在移動設備中完全實時使用可能不太現實,適合亞實時使用。2014年的功率和帶寬曲線圖:

    Imagination的移動端圖形能力如下:

    實例是頂點、像素或OpenCL線程,任務共享計劃中的所有實例、uniform、參數等。

    核心內的ALU、寄存器、通用存儲區結構如下:

    每個統一著色簇(Unified Shading Cluster)有12個周期的延遲,其內部結構如下:

    整體能力:Series6為最新移動設備提供超過100Gflops的著色效果,OpenGL ES 3.0支持,巨大的性能提升,允許使用數學重著色器,移動圖形是真正的創新所在。

    新著色器類型:當光線與三角形相交時,會調用光線著色器,著色器類型可以發射任意數量的光線,OpenRL有一個框架著色器來發射主光線,現有片段/像素著色器也可以發射光線!GLSL編程模型相同,擁有內置函數、參數等。

    圖元對象:封裝一個網格的渲染狀態,包括VBO、制服、著色器程序、紋理綁定等。光線追蹤單元將光線分類到具有公共圖元對象的任務中,幀間持久,客戶提供的可變對象。

    限制:著色器不能等待單個光線跟蹤操作的結果,著色器必須提供有關子光線數的最壞情況估計,必須仔細管理每根射線用戶數據有效載荷。

    *行度取決于光線而不是像素:


    光線的AABB測試:6條線條構成了AABB的輪廓,射線原點和每個邊向量的6個*面,*面法線和射線方向向量的點積,6個標志必須匹配且為負數。

    USC指令組打包,下圖顯示了26條指令,如果使用了壓縮數據格式則是32條:

    此功能的面積減少44倍:

    光線追蹤單元和一致性引擎(Coherency Engine):

    下圖顯示了升序虛擬內存地址存儲了大約100兆字節的數據,用于1百萬個三角形,包括變量:

    圖元物體的層級結構如下:

    體素化的圖元物體:

    一致性隊列:

    自動查找一致性路徑:

    場景層次生成器:

    局限性:場景由三角形表示——與常規一樣,BVH是一種為構建和便利而優化的定義格式,三角形順序通常必須遵循空間連貫的流程,需要大致的場景規模估計,幾何體著色器不與光線跟蹤管線內聯。

    優勢:著色簇工作負載不高于頂點著色器,只需處理在世界空間中實際移動的幾何體,獨特的算法僅將工作集限制為內部寄存器,單程操作:與頂點著色器執行一致,很好地處理“長而瘦的三角形”問題,流式寫入外部存儲器,由于構建算法,輸出格式被無損壓縮,精簡邏輯。建在稀疏的log2八叉樹形的層級上:

    光線追蹤的三角形處理流程如下:

    處理一些三角形之后,結構體圖例:

    組裝父節點之后更復雜:

    在組裝一個級別的所有父節點之后:

    硬件單元的各個性能指標如下:

    Practical Techniques for Ray Tracing in Games也是Imagination探索游戲中光線追蹤的實用技術。

    之前關于光線追蹤的謬論:光線跟蹤僅用于照片級真實感/物理精確渲染,光線跟蹤與光柵化圖形不兼容,光線跟蹤是渲染給定數量像素的效率較低的方法。實際上,光線跟蹤是一個對象的著色能夠感知其它對象的幾何體的技術,利用光線追蹤可以方便地實現更加真實的陰影、反射、折射、AO、GI等效果:

    光線跟蹤允許模擬光線的行為:

    如何將光線追蹤添加到游戲中?使用混合的游戲引擎。在世界空間場景相交處的光線追蹤細節:

    基于光柵的現代游戲引擎,大多數現代游戲引擎都使用延遲著色:

    混合渲染使用G緩沖區設置光線:

    光線追蹤可以實現軟陰影,但半影渲染要求每個像素有多條光線,為每個表面點發射幾條光線(而表示1條),每條光線的行為與硬陰影情況相同,*均每個像素的所有光線的結果:如果所有光線都被遮擋,則表面將被完全遮擋,如果所有光線都到達光源,則光源表面將完全照亮,如果一些光線被遮擋,一些光線到達光線,則表面處于半影區域。

    選擇光線方向:如果某個區域中的光源發光,則將光線分布在從表面可見的光源橫截面上,要使用無限遠的*行光*似日光,請從表面選擇一個錐形光線:為了表示完全晴朗的一天,圓錐體的立體角為零,表示云層較大的日光時,立體角變大。估計到達表面點的入射光,為了得到好的估計,樣本應該均勻地覆蓋域。

    GBuffer連續性:需要大量光線才能準確地對軟陰影進行采樣。對于大多數圖像,從一個像素到相鄰像素,表面屬性變化很小,因此,從G緩沖區的一個像素發送的光線可能會與從相鄰像素發送的相同光線擊中同一對象。當然有一種方法可以利用這一事實來減少光線數量,同時保持視覺準確性?

    交錯采樣(Interleaved Sampling),利用相鄰像素的陰影光線數據。在幀緩沖區上*鋪\(N^2\)射線方向的二維正方形陣列,基于網格發射陰影光線,生成的圖像具有一個關鍵特性,即對于圖像的任何NxN區域,都可以表示整個\(N^2\)射線方向數組,所以使用一個盒子過濾器來去除圖像中的噪聲,每個輸出像素是\(N^2\)個相鄰輸入像素的*均值。必須處理圖像中的不連續性。

    反射:當光線擊中一個完美的鏡面時,它會以與入射角相同的角度反射,公元前3世紀,歐幾里德首次編纂了基本物理定律。在現實世界中,反光物體很常見,而不僅僅是金屬球!

    光線追蹤反射:從反射表面發射一條額外的光線,反射光線的方向根據入射光線的方向使用反射定律計算,當光線擊中場景中的對象時,使用與直接可見表面相同的照明計算對該表面進行著色。

    光線追蹤透明度,“真實”透明度不是alpha混合!在現實光學中,當光線通過半透明物體時,有些光線被吸收,有些則不被吸收。從表面的背面發射光線,像反射光線一樣的著色,順序無關。

    透明度和陰影:當陰影光線擊中一個透明的物體時,它會繼續朝向光線。投射到透明對象上的陰影光線應進行著色并重新發射,就像它是非陰影光線一樣,陰影光線將穿過表面的完全透明區域,陰影光線從半透明對象獲取顏色,陰影光線與透明物體相互作用!

    總之,光線追蹤很容易,使逼真的光源模擬變得簡單,與現有基于光柵的引擎輕松結合。

    Next Generation Post Processing in Call of Duty分析了COD上的次世代后處理流程、技術、應用和優化。其后處理管線包含了運動模糊、Bokeh DOF、次表面散射、泛光、陰影采樣等。

    COD的方法是基于后處理特效過濾作為輸入,如顏色緩沖區、深度緩沖區、速度緩沖器,沒有屏幕空間后處理DOF可以完全準確。信息缺失:在運動模糊中,當對象移動時,背景會顯示出來,在DOF背景中,從不同的視點積累視圖。

    使用分散技術自然地實現,收集即分散(scatter-as-you-gather)允許基于過濾器的方法,但其存在的問題有:如何確定采樣區域,檢測哪些樣本真正起作用,如何恢復背景。

    收集即分散的運動模糊案例。在這種情況下,采樣速度足夠大,以實際覆蓋當前像素,樣本也比當前像素更接*相機。所以,這兩種情況都意味著樣本會起作用。

    對于運動模糊的如何確定采樣區域的問題,可以采用3個Pass:

    第1個Pass[Tile最大值]:以Tile為單位計算最大速度(20x20像素)。

    第2個Pass[Tile鄰域]:計算每個Tile在3x3內核中的最大速度。

    第3個Pas[運動模糊]:在Tile速度方向上應用全分辨率的可變寬度模糊。

    下面是不同方法的運動模糊效果圖:

    從左到右:McGuire2013方法、準確的樣本貢獻、準確的樣本貢獻+鏡像背景重建。

    在采樣上,嘗試了幾種方法,包括:均勻、白噪聲、抖動、時間抖動。

    運動模糊的優化:計算每個瓷磚的最小值/最大值(與最大值相比),如果[max-min]很小,可以使用更簡單的版本,如果[max-min]等于零,則該算法收斂到常規顏色*均值。

    實現提示:使用可分離方法計算最大速度(更快),從[width x height]到[20 x height]緩沖區的水*通道,從[20 x高度]到[20 x 20]的垂直通道。與[Sousa2013]的啟發類似,將速度整合到一個深度,在COD中,是一個R11G11B10緩沖區,將每個樣本的點擊次數從3(顏色、速度、深度)減少到2(顏色、速度/深度)。與[McGuire2013]類似,抖動用于訪問tile的紋理坐標,有助于減少tile瑕疵,使用點采樣以避免溢出。

    DOF概覽:運動模糊的許多想法適用于DOF,收集即分散對雙方都有用。呈現的“聚集即散射”的運動模糊方法僅覆蓋單個層(單個方向),許多可能發生并重疊,但在COD中,運動模糊不是什么大問題,它們更容易看到,移動對象會隱藏問題。因此,開發了一種兩層方法。想要一直都好看的東西,使用真實的相機控件(光圈、焦距…)會使創作中的DOF問題更難解決,由玩家觸發的DOF,不受創作控制。

    傳統的DOF問題:

    • 收集即分散問題。
      • 和運動模糊一樣,鄰域像素可以滲入當前像素,類似的解決方案。計算tile中的最大COC,主二維濾波器半徑與之成比例。
      • 計算每個樣本的貢獻,對樣本進行分類和混合,背景重建。
    • 過濾質量問題。欠采樣,需要正確使用雙線性過濾。
    • 性能問題。很多樣本,半分辨率渲染。

    收集即分散的理想算法:

    • 檢測一個樣本COC是否和當前像素重疊。
    • 檢測它是否前景。
    • 對重疊和前景像素進行從后向前排序。
    • 分配一個透明度給每個樣本,\(\alpha = \cfrac{1}{\pi c^2}\),其中\(c\)是COC半徑。
    • 混合它們。
    • 排序的開銷異常大。

    DOF樣本的側視圖(左)和頂視圖(右)。

    COD的排序方法:

    • 與[Lee2008]類似,性能簡化。
    • 將樣本分為兩層:背景、前景。
    • 對每層:
      • 疊加\(\alpha\)混合*均值。
    • 計算前景Alpha。
    • Alpha混合前景/背景。
    • 松散的前景/背景分類漸變。
      • 距離0英寸:前景。
      • 距離100英寸:背景。

    COD的前景、后景、當前樣本的側視圖和頂視圖。

    用雙線性進行采樣時,會產生顏色溢出(下圖右):

    通常使用COC預乘求解,適用于極端情況,介于兩者之間的情況可能會失敗,特別適用于高動態范圍圖像。需要RGBA16緩沖區,更多內存,與只使用顏色的循環不兼容(tile優化)。以較小的數字編碼的顏色會降低精度。

    雙線性過濾的具體方法:

    • 預過濾。
      • 顏色緩沖區用雙線性采樣。
      • 深度緩沖區用Gather4 + 計算最小雙邊權重。
    • 主通道。
      • 顏色緩沖區用點采樣+隨機偏移。
      • 預排序緩沖區用點采樣+隨機偏移。
      • 但對于快速tile(僅彩色循環)用雙線性采樣+隨機偏移。

    在陰影方面,由于需要60FPS,陰影過濾的限制非常具有挑戰性的,因為沒有很多樣本的預算。COD用每像素隨機旋轉的泊松圓盤做實驗,發現它們的結果質量不高,樣本數量適中(8個),運動時非常不穩定。

    通過實驗和優化噪點發生器,發現了一個噪點函數,可以將其分類為介于抖動和隨機之間的一半,稱之為交錯梯度噪聲(Interleaved Gradient Noise)

    float3 magic = float3( 0.06711056, 0.00583715, 52.9829189 );
    return -scale + 2.0 * scale * frac( magic.z * frac( dot( sv_position, magic.xy ) ) );
    

    用于旋轉樣本:

    sincos( 2.0 * PI * InterleavedGradientNoise( sv_position ), rotation.y, rotation.x );
    float2x2 rotationMatrix = { rotation.x, rotation.y, -rotation.y, rotation.x };
    ...
    float2 sampleOffset = mul( offsets[i], rotationMatrix );
    

    兩全其美:產生豐富的值范圍,類似于隨機噪聲,產生時間上一致的結果,類似于抖動方案。這種噪聲會產生交錯的梯度,意味著以恒定速度移動的對象將*滑地旋轉樣本,要使其適用于靜態圖像,可以滾動水*位置:

    sv_position.x += scale * time;
    

    交錯梯度噪點和交錯梯度。

    交錯梯度噪點的空間一致性允許通過模糊更容易地*滑,在COD的例子中不能模糊它,但它在其它情況下可能有用。

    把以上所有概念放在一起,獲有一個隨時間*滑旋轉的螺旋,考慮到高幀速率,它比實際擁有的樣本更多:

    Crafting a Next-Gen Material Pipeline for The Order: 1886講述了游戲《秩序:1886》的次世代材質管線。

    《秩序:1886》引擎的光照管線:使用了分塊前向渲染,透明物體使用漫反射+鏡面反射的完整照明。靜態幾何使用光照圖,使用Optix在GPU農場上烘焙,基于H-方向變化的基。SH動態探針使用三階(9個系數),預卷積鏡面探針,立方體貼圖在引擎中渲染,在計算著色器中卷積。

    環境光遮蔽:定向AO地圖(H基)烘焙角色和靜態幾何,靜態幾何僅用于阻擋探頭的鏡面反射,動態幾何將AO應用于SH探針的擴散。

    核心著色模型:默認鏡面反射BRDF為Cook Torrance,D項是Walter等人的GGX分布(匹配在同一篇論文中導出的Smith G項)、Schlick對菲涅耳的模擬、蘭伯特漫反射(與鏡面反射強度*衡)。GGX+Cook Torrance==大量數學運算,可以優化,不要使用三角形,將Smith的G項折疊成分母,讓藝術家使用sqrt(roughness),更直觀,更適合混合。

    // Helper for computing the GGX visibility term
    float GGX_V1(in float m2, in float nDotX) 
    {
        return 1.0f / (nDotX+ sqrt(m2 + (1 -m2) * nDotX* nDotX));
    }
    
    // Computes the specular term using a GGX microfacetdistribution. m is roughness, n is the surface normal, h is the half vector, and l is the direction to the light source
    float GGX_Specular(in float m, in float3 n, in float3 h, in float3 v, in float3 l) 
    {
        float nDotH= saturate(dot(n, h));
        float nDotL= saturate(dot(n, l));
        float nDotV= saturate(dot(n, v));
        float nDotH2 = nDotH* nDotH;
        float m2 = m * m;
        // Calculate the distribution term
        float d = m2 / (Pi * pow(nDotH* nDotH* (m2 -1) + 1, 2.0f));
        // Calculate the matching visibility term
        float v1i = GGX_V1(m2, nDotL);
        float v1o = GGX_V1(m2, nDotV);
        float vis= v1i * v1o;
        // Multiply this result with the Fresnel term
        return d * vis;
    }
    

    其它可用的BRDF:Beckmann、各向異性GGX、頭發(Kajiya Kay)、皮膚(預集成漫反射)、布料。

    皮膚:最昂貴的著色器!基于\(N \cdot L\)的逐光源的紋理查找,多個鏡面反射波瓣。沒有使用基于著色器梯度的曲率,瑕疵太多,使用曲率貼圖。

    頭發著色:不以身體為基礎,使用Kajiya Kay光照模型。調整菲涅耳曲線,使用切線方向進行SH漫反射,各向異性表面的分析切線輻照度環境貼圖[Mehta 2012]。次級鏡面反射葉沿切線向尖端移動,采用反照率顏色。

    偏移貼圖以分解高光,沿切線方向額外移動:

    定義切線方向的流向圖:

    布料著色:參考數碼相片的觀察結果:柔和的鏡面反射波瓣,具有較大的*滑衰減,邊緣因粗糙度散射而產生絨毛,前向角度的低鏡面反射貢獻,有些面料有兩種色調的鏡面反射顏色。

    用于粗糙度散射的反向高斯,從原點進行*移,以在前向角度提供更多鏡面反射,沒有幾何項:

    普通的Cook-Torrance+法線貼圖會產生明顯的高光鋸齒,修改粗糙度貼圖以減少鋸齒,使用基于Han等人“頻域正態圖濾波”的技術。

    將NDF表示為球形高斯分布(vMF分布),以SH表示的*似BRDF為高斯分布,兩個高斯函數的卷積是一種新的高斯函數,使用關系計算新的粗糙度。


    為了獲得逼真的材質,使用了3D掃描的技術:

    材質 = 文本資源,使用自定義數據語言(radattr),語言支持:類型、繼承、用戶界面布局、元數據。材質創作是基于功能的,沒有著色樹,用于實時編輯的材質編輯器工具,也可以托管在Maya內部。

    用于制作模板的radattr繼承,基礎材質中共享的通用參數,衍生材質僅存儲基礎材質的變化,更快地創建資產,全局變化可以在單一資產中進行。

    著色器:手寫的Uber著色器,很多#if,主要材質特征=宏觀定義,硬編碼到著色器中的參數(規格強度、粗糙度等),動畫或合成時除外,一些代碼是自動生成的,用于處理紋理和動畫參數。著預定義的“排列”,排列=宏定義+入口點,蒙皮、混合形狀、實例化、光照貼圖等的排列,可視化/調試的調試排列,基于排列+材質在構建管線中編譯的著色器。

    優點:著色器針對材質進行了優化,優化器具有完全訪問權限,游戲本身沒有運行時編譯(由工具使用)。缺點:要編譯的著色器太多了!緩存了所有內容,但迭代可能會很慢,整體著色器很難調試,大量依賴編譯器。

    材質組合:主要是離線流程,材質資產指定組合棧,堆棧中的每一層都有:引用材質、混合Mask、混合參數,遞歸構建+組合參考材質,逐層逐組合使用像素著色器。從材質和混合貼圖生成參數貼圖,支持合成BRDF的子集,布料、GGX和各向異性,合成布料需要應用2層BRDF。

    // Compositing pixel shaderrun on a quad covering the entire output texture
    CompositeOutputCompositePS(in float2 UV : UV) 
    {
        CompositeOutputoutput;
        float blendAmt= BlendScale* BlendMap.Sample(Sampler, UV);
        float4 diffuseA= DiffuseTintA* DiffuseMapA.Sample(Sampler, UV);
        float4 diffuseB= DiffuseTintB* DiffuseMapB.Sample(Sampler, UV);
        float diffuseBlendAmt= blendAmt* DiffuseContribution;
        output.Diffuse= Blend(diffuseA, diffuseB, diffuseBlendAmt, DiffuseBlendMode);
        // Do the same for specular, normals, AO, etc.
        return output;
    }
    

    材質層:最多4層,源自基本材質,每層單獨的合成鏈,由頂點顏色驅動。

    LayerParamscombinedParams;
    
    [unroll]
    for(uinti= 0; i< NumLayers; ++i) 
    {
        // Build all layer paramsfrom textures and hard-coded
        // material parameters
        LayerParamslayerParams= GetLayerParams(i, MatParams, Textures);
        // Blend with the previous layer using vertex data and blend masks
        combinedParams= BlendLayer(combinedParams, layerParams, vtxData, BlendMode, Textures);
    }
    
    // Calculate all lighting using the blended params
    return ComputeLighting(combinedParams);
    

    Rendering Techniques in Ryse講述了游戲Ryse: Son of Rome所用的引擎CryEngine采納的各類渲染技術,包含PBS、遮擋、SSDO、反射遮擋、AO顏色溢出、圖像穩定性、幾何鋸齒、高光鋸齒、LOD選擇、粒子著色、陰影(太陽、點光源陰影、角色陰影、靜態陰影圖、粒子陰影、影視級陰影)、大規模AO等內容。

    PBS方面,基于真實世界行為建模光與物質的相互作用,注重一致性,一切都遵循一個定義良好的規則集,從圖形編程中排除了很多猜測,對幾個領域的重大影響。材質模型:為資產定義清晰的規則,從而實現更高的藝術/內容一致性,強制執行合理的材質參數,并阻止不切實際的設置。光照模型:更復雜的BRDF、菲涅耳、鏡面高光標準化、總體能量守恒,微*面BRDF仍然只是有限的現實模型。照明模型:必須小心保護整個管線的材質完整性,基于物理的著色只有在所有區域都得到尊重的情況下才能很好地工作。

    著色模型的限制:為模型的數學正確性付出了大量努力,主要是在學術研究方面,普通分析鏡面微*面BRDF只是現實的有限模型,不考慮多次光反彈,忽略粗糙表面上任何與波長相關的吸收。

    光照模型注意事項:需要注意保持材質的完整性,如果光源可以在不影響鏡面反射的情況下隨機添加漫反射貢獻,那么真實世界中的反射比將毫無用處,添加漫反射而不添加相應數量的鏡面反射將顯著*坦材質,因為它會有效降低材質反射率F0。刪除了Ryse的所有僅漫反射常量和半球形環境項,對純鏡面反射表面(金屬)無影響,由具有漫反射和鏡面反射立方體貼圖的局部環境探測器捕獲的所有間接照明,探頭延伸到更大的區域可能會缺少局部光強度變化,從而導致環境*坦。引入了倍增光(“環境光”)作為實用工具,供燈光藝術家設置反彈照明和遮擋[SCHULZ14],環境光同樣影響間接漫反射和鏡面反射,并保持反射率。

    遮擋是全局照明的重要組成部分,AO是理解物體之間空間關系的重要線索,小范圍遮擋:SSDO、屏幕空間反射,大規模遮擋:帶有負環境光的局部探頭、基于簡單陰影圖的遮擋系統。特殊解決方案:用于眼睛的預烘焙遮擋貼圖、一些資產的預烘焙AO圖。

    SSDO:用統一的屏幕空間定向技術完全取代SSAO,基于簡單類SH基的方向遮擋編碼,兩個獨立使用的波段:恒定部分和方向部分,應用于間接漫反射照明的常數項(如SSAO中),用于直接照明的方向項,提供簡單的接觸陰影,使用光源方向進行查找,應用于燈光的漫反射和鏡面反射貢獻,這兩部分都用于反射遮擋,使間接鏡面反射變暗,使用視圖反射向量進行查找。

    反射遮擋開啟(左)和關閉(右)的對比圖。

    AO顏色溢出:去除環境光的主要部分可能看起來太暗,在吸收率低的明亮表面(包括白種皮膚)尤其明顯,AO沒有考慮到發生多次光反彈,限制最大AO變暗有助于,但AO在深色表面上會變得太弱,需要根據表面反照率調整遮擋量。

    簡單顏色溢出*似:最后一分鐘的功能,必須非常便宜,生成低頻版本的反照率緩沖區,作為非常粗糙的局部散射*似,作為gamma函數應用于遮擋值,Ryse中夸張的顏色變化,給人一種輕微的GI印象:

    float3 occlColor = pow(occlTerm, 1 - min( bleedColor * bleedColor * bleedColor * 3, 0.7 ));
    

    顏色溢出關閉(上)和開啟(下)的對比圖。開啟后,物體凹槽處會有輕微的提亮。

    圖像穩定性:干凈且暫時穩定的圖像對感知質量至關重要,視覺噪點會分散注意力,需要最小化的各種形式的鋸齒,包含幾何體鋸齒著色鋸齒鏡面鋸齒

    著色鋸齒

    • 由于欠采樣、過采樣、多分辨率渲染等原因,如射線行進(SSR)、陰影圖采樣等。
    • 要由算法級別的特定解決方案解決,通常傾向于使用時間穩定的方法,而不是在靜態圖像中提供稍高質量的方法。

    幾何鋸齒

    • 空間鋸齒。臭名昭著的鋸齒狀/樓梯瑕疵,基于后處理的方法,如MLAA、FXAA和SMAA,效果非常好,Ryse[JIMENEZ12]中使用的SMAA。

    • 時間鋸齒。更嚴峻的挑戰之一,亞像素大小的三角形在一幀中光柵化,但在另一幀中不光柵化,導致閃爍/微光。每像素需要更多樣本(MSAA、超級采樣),在Ryse中完全支持超級采樣,用于預錄制的電影,可在PC版本中使用。

    • 時間幾何鋸齒。

      • MSAA在Xbox One上不是延遲著色的選項,帶寬大幅增加,遇到ESRAM大小問題,2倍MSAA不夠高質量,需要依賴前一幀的數據。
      • 最終使用的解決方案是新的SMAA 1TX[SOUSA13],累積多個幀以獲得更好的時間穩定性,跟蹤前一幀中的幾何圖形,但限制信號變化,避免動態對象中的像素無法準確重投影時出現重影,避免圖像過于*滑的關鍵是根據信號頻率選擇累積采樣數,圖像低頻部分的采樣數越少,高頻部分的采樣數越多。

    鏡面鋸齒

    • 基于物理的著色非常容易出現鏡面反射鋸齒,標準化BRDF的高亮度值與高頻正常信息相結合。
    • Ryse中的法線和粗糙度嚴格耦合。從概念上講,法線代表宏觀尺度上的表面凹凸,微觀尺度上的粗糙,粗糙度存儲在源資源的法線貼圖alpha通道中,按引擎拆分為2個紋理(BC5表示法線,BC4表示粗糙度),粗糙度貼圖中mips的法線方差[HILL12],用于估計方差和推導新粗糙度的Toksvig因子[TOKSVIG04]。
    • 在GBuffer中修改粗糙度時仍存在問題(貼花、雨水濕度),通過在屏幕空間中應用法線方差濾波器解決[SCHULZ14]。

    LOD選擇:改進的LOD選擇,計算LOD轉換的最佳觀察者距離,通過避免小三角形有助于減少鋸齒(還可以提高性能)。計算*均屏幕空間三角形大小,當屏幕上的投影尺寸低于閾值時,網格過于詳細,應使用下一個LOD網格,離線預計算的*均網格三角形面積,各種可能的指標:*均值、中位數、幾何*均值等。選擇幾何*均值(對數*均值),許多小三角形會減少值,而少數大三角形幾乎不會增加值。剛剛為第一個LOD過渡計算的開關距離,后續LOD使用該距離的倍數,防止完全跳過某些LOD網格。

    上:使用物體尺寸的LOD選擇;下:使用三角形尺寸的LOD選擇。

    太陽和點光源陰影:用于太陽的級聯陰影貼圖,*似對數分裂格式,點光源展開為單個投影器,每個投影器從大型陰影地圖集中渲染成塊,塊分辨率取決于投影儀的重要性。陰影遮擋在全屏過程中進行評估,并累積到“陰影遮罩”紋理數組中。每個燈光一個顏色通道,如果可能,通道共享,允許對陰影接收區域進行有效的模板剔除,減少tile著色過程中的GPR壓力,通過逐像素旋轉的泊松盤進行過濾。

    角色陰影:第三人稱視角,主角始終處于焦點,高質量的自陰影,自定義陰影貼圖集中在緊密的邊界框上,使用“max”操作符混合到陰影遮罩(shadow mask)中。

    靜態陰影圖:

    • 在早期的開發中,Draw Call是一個主要的瓶頸,尤其是陰影,是項目的主要風險。大多數陰影繪制通道都花在遠距離的級聯上,世界覆蓋率呈指數級增長,甚至在高度優化的資產上也是如此,聚合距離剔除,盡可能禁用陰影投射。優化折衷是遠處沒有動態陰影。
    • 簡單方法:用“靜態陰影貼圖”替換最遠的級聯,8192 x 8192像素,每像素16位: 128MB視頻內存,每個對象只渲染一次,零陰影繪制需要在靜態陰影貼圖填充后替換級聯,世界空間分辨率(每米像素)匹配或超過第一級,在分辨率方面沒有質量損失,由于偏離對數紋素分布而產生新的欠采樣失真,在Ryse中相當次要。
    • 選擇哪種級聯?對數分割方案導致固定世界空間面積的指數存儲需求,CryEngine選擇了cascade 4作為最佳選擇,能夠覆蓋大約1.3公里×1.3公里的區域,而不會造成質量損失。

    1km x 1km區域所需的紋理大小,保持第一次替換級聯的分辨率不變(對數比例)。

    • 由設計師和燈光藝術家控制的地圖放置和更新,關卡檢查點,在區域之間切換。在XBox One上進行大約10-15毫秒的完整更新,避免幀速率峰值的時間切片更新策略,每幀的繪制調用數上限,流選項:渲染對象完全流化后。在優化的資產上節省大約40%-60%的陰影繪制調用。

    影視級陰影:太陽陰影需要高質量的陰影解決方案,從樣本分布陰影圖開始[LAURITZEN11]。有很好的結果,但如何擺脫回讀?上一幀數據中的瑕疵太多,避免回讀需要使用幾何體著色器(速度慢!),或者在GPU上全部剔除和繪制調用,但GPU的做法太危險。最終采取了非常務實的解決方案:在cutscene創作工具中暴露*距離/遠距離陰影,*/遠之間的完全對數分割,2048 x 2048陰影貼圖將超過屏幕分辨率。

    大規模AO:最終使用了類似于[SWOBODA10]的方法,將場景自上而下渲染到陰影貼圖中,生成高度貼圖*似值。全屏通道:每個像素
    投影到陰影貼圖空間,對周圍像素進行采樣,并使用所選AO算法計算遮擋。

    主要關注捕捉最大規模的特征,低分辨率陰影貼圖(大約每像素0.5米),2048 x 2048足夠覆蓋1km x 1km的區域。AO內核采樣半徑:7.5米,Ryse沒有那么大比例的移動對象,與靜態陰影貼圖相同的更新策略。非常低頻的效果,每像素4個(交錯)樣本,可以很容易地在一半或四分之一的時間內完成,通過與常規SSDO通道合并,獲得了略好的性能。

    大規模AO關閉(左)和開啟(右)的對比。

    效果出奇地好,相當低開銷,在XBox One上0.4毫秒。在天空大部分被遮擋的區域,適當地降低立方體貼圖中天空的貢獻,例如透過窗戶看房子。不過只能在戶外,當高度貼圖表示與場景不匹配時會出現問題,重疊結構。

    Achieving the Best Performance with Intel Graphics Tips, Tricks, and Clever Bits闡述了在Intel的GPU芯片上進行性能優化的技巧。

    高效的GPU編程需要充分利用管線,如IA軟件堆棧中的優化、特定于應用程序和通用的,應用程序優化的最大影響。

    GPU編程優化包含應用程序、驅動和GPU三層。

    繪圖調度和資源更新:注意已調度操作的內存訪問模式,如三維/二維作業調度、狀態/著色器更改、資源位置。

    排序繪制調用時,影響因素最大的且具有相同資源的Draw Call排在一起。例如,RT影響最大,故而圖下將0和1安排在一起;然后是引用了相同資源的(圖中)。

    圖形只是謎題的一部分,獨特的架構特征:動力與性能、內存層次結構,成對*臺:中央處理器、系統存儲器,其它制約因素:熱量、功率等。

    CPU/GPU之間的關系,CPU或GPU瓶頸,CPU可以限制GPU......

    橫坐標是總功率,豎坐標是各個硬件單元的功率。左:隨著總功率的提升,GPU的功率先下降后提升;CPU相反,而未被使用的功率先略微提升,后下降。右:隨著總功率提升,GPU頻率先降低接著持*后提升,CPU則相反。

    以上圖表只針對2014年前后的Intel硬件架構,目前及其他GPU廠商是否依然如此有待查證。

    緩存位置是王道:優化CPU和GPU的內存訪問,內存帶寬限制,層次結構因*臺而異,可選的CPU+GPU緩存:末級緩存(LLC)、嵌入式DRAM(eDRAM)。

    架構組件:

    • 非切片。
      • 固定函數:變換、裁減
    • 切片。
      • 普通切片:光柵化、著色器調度、顏色后端
      • 子切片:著色器執行

    架構擴展/擴展組件:

    • 切片:并行圖元處理。
    • 子切片:并行寬度(span)處理。

    采樣器:每個子切片1個采樣器本地紋理緩存,由L3緩存作為后盾支持。

    采樣器性能:還記得緩存位置嗎?吞吐量:格式、采樣模式,糟糕的訪問模式會增加內存帶寬和延遲。

    填充率:逐切片通用,如像素后端、顏色緩存(RCC$)。

    填充率性能,輸出顏色:

    • 吞吐量。
      • 格式。
      • 維度+區域。
    • 其它因素。
      • 光柵化。
      • 提前Z/S。
      • 像素著色器執行。
      • 后期Z/S。
      • 混合函數+模式。

    表面格式:為顏色范圍選擇適當的格式,中間/最終渲染目標,不必要選擇更高精度的格式,否則會降低填充率,增加內存帶寬。

    算術邏輯:逐子切片的塊,包含執行單位(EU)、指令緩存(IC$)。

    算術邏輯性能:算法復雜性,如控制流、數學、擴展數學、最大并發寄存器數。

    著色器優化:基于意圖的最優代碼,著色器伸縮,通用著色器的情況,產生未使用的產出。

    幾何著色器:單個非切片,固定函數:VS、HS、TE、DS、GS、SOL,剪切,設置前端。

    優化幾何以提高算法復雜度,單個幾何體的最佳定義,基于*臺的質量擴展,意圖:燈光,深度,動畫…

    幾何體的軟邊、硬邊的做法和效果對比。

    內存帶寬:都是關于內存,因*臺而異,關注的原因是從內存中讀出,寫入內存。

    采樣器吞吐量:不同的架構和*臺,測量所有用例,包含維度數、格式、過濾模式。

    填充率:多種表面類型,包含渲染目標:格式、維度、混合/非混合,深度:讀/寫,模板:讀/寫。

    幾何吞吐量:固定功能帶寬與算術邏輯,固定功能(剪輯/剔除、光柵化),幾何變換:ALU。

    時間來到了2015年,這一年,以DirectX 12、Vulkan為代表的新一代圖形API正式發布,緊接著,有不少文獻闡述了它們的特點和應用。D3D12 A new meaning for efficiency and performance就是其中之一。文中對Command List、Root Signature、資源同步、屏障、并發、多線程、多隊列、渲染應用和性能分析等等方面闡述得由淺入深、鞭辟入里,適合入手和進階。想了解更多的可查閱原文或剖析虛幻渲染體系(13)- RHI補充篇:現代圖形API之奧義與指南

    Visual Effects in Star Citizen分享了游戲星際公民的視覺效果,該游戲由CryEngine研發。

    該游戲具有非常高端的視覺效果,長期關注質量,高系統規格,當時僅限DX11。艦體擁有復雜的網格數量、材質和貼花效果。

    其中航艦的破壞效果的渲染流程如下:

    添加破壞效果的具體步驟如下:

    由于游戲中的MMO部分,需要很多環境,因此選擇了模塊化方法。模塊化的“套件”易于組裝,簡化藝術管道(例如外包),對于關卡設計師來說非常靈活。

    期間遇到了許多性能問題,由于期望的保真度、多邊形數量、紋理密度太高,無法烘焙紋理,*鋪紋理意味著每個網格有許多繪制調用,大量的網格來建造一個房間,空間站需要更多的網格和繪制調用。

    紋理數組是一種潛在的解決方案,分辨率限制意味著流數據很困難,相反,只對LOD使用低分辨率紋理數組,無需流式傳輸單個紋理–256x256的整個紋理數組的級別小于15Mb,只需一次繪制調用即可渲染LOD!頂點緩沖區按材質ID排序,因此如果需要,仍然可以使用高分辨率紋理。

    類似KillZone的網格合并解決方案,為每個單獨的模塊化資產構建LOD,迭代啟發式算法,結合LOD,構建具有最小繪制調用和內存的層次結構。依賴于積極的LOD,無需藝術家手動工作即可大幅減少繪制調用。

    Rendering the World of Far Cry 4分享了Fay Cry 4的綜合渲染技術,包含材質、光照、植被、抗鋸齒、地形等。

    在光照方面,FC4支持天空遮擋、環境圖、間接光照等特性。天空光使用Bruneton天空模型和Preetham太陽模型,生成三階SH照明。對于天空遮擋,將直接天空照明與間接照明分離高分辨率“自上而下”天空遮擋,從高度場創建可見性二階SH,使用了類似SSAO的方法來計算遮擋。

    FC4為了讓單個cubemap在一天中的每個時間都有合適的強度,對cubemap每幀進行重照明,主流程如下:

    在間接光方面,使用延遲輻射傳輸體積[Stefanov2012],存儲輻射傳輸信息的光照探針。目標是上一代和當前一代使用相同的光照探針,擴大間接照明的范圍,通過將CPU工作轉移到GPU來加快更新速度。主流程如下:

    • 離線:烘焙探針,二階SH中的輻射傳輸信息。
    • CPU:流化探針數據,上傳到GPU并更新頁面表。
    • GPU:計算輻射傳輸,將探針插入剪輯圖并在延遲照明中采樣。

    其中在整個單元格列表執行輻射傳輸的過程如下:

    植被是FC4的主要渲染關注點,使用了和以為完全完全不同的數據集。目標是*距離視覺逼真度,改進的LOD和替代物(imposter)以及各類模擬。其中骨骼和物理模擬的流程如下:

    對于替代物(imposter),從九個角度截圖,八根垂直于這棵樹,另外一個自頂向下:

    G-Buffer屏幕截圖:捕捉反照率、法線和材質屬性。

    深度公告板:公告板幾何圖形細分為16x16網格,在GBuffer截圖期間捕獲深度,根據原始樹深度置換頂點。深度數據和渲染圖如下:

    此外,還要為植被生成AO體積:尺寸范圍從16x16x16到64x64x64,從體積周圍的32個方向捕獲陰影貼圖,采樣陰影和*均值。

    從視覺上看,植被很難完全正確,模擬了部分特性:光在草葉間反射,光在樹葉中散射等等,需要TA的魔法。

    抗鋸齒上,使用了HRAA,詳見14.4.3.5 特殊技術Hybrid Reconstruction Anti Aliasing部分。

    SIMD at Insomniac Games分享了Insomniac公司的游戲所使用的SIMD技術,包含SSE、技巧、最佳實際等。

    SIMD編程在Insomniac公司的工作室中有悠久歷史,如PS2 VU、PS3 SPU+Altivec、X360 VMX128、SSE(+AVX),關注本周期的SSE編程,當PC+主機共享ISA時,更大的激勵,當使用SIMD時,PC工作站的速度快得離譜,許多舊的最佳實踐不適用于SSE。

    當時的趨勢是都是GPGPU,但許多問題太小,無法轉移到GPU,并且不息在控制臺上浪費x86內核。永遠不要低估暴力+線性訪問,CPU SIMD可以大大提高性能,不能只把性能留在PC上。當時SSE和AVX SIMD的選項有:

    • 編譯器自動向量化。烏托邦式的想法在實踐中并不奏效,編譯器是工具不是魔杖,在維護期間經常中斷,只獲得一小部分性能提升!編譯器支持/保證=糟糕,VS2012中沒有支持,VS2013中有些支持,不同的編譯器有不同的怪癖。
    • 英特爾ISPC。SSE/AVX類著色器編譯器,編寫標量代碼,ISPC生成SIMD代碼,需要在另一個抽象層次上進行投入,注意:容易生成低效的加載/存儲代碼。主要優點:SSE/AVX自動切換,例如英特爾的BCT紋理壓縮器, 在AVX工作站上自動運行速度更快。
    • 內部函數。掌握控制權,而不必去匯編,Insomniac游戲中編寫SIMD的首選方式,可預測,沒有無形的性能退化,靈活地公開所有CPU函數。難以學習和實踐,但不是真正的反對理由,所有好的編程都很難(而糟糕的編程很容易)。
    • 匯編。永遠是一個選擇!64位VS編譯器上沒有內聯匯編,需要外部匯編程序(例如yasm)。對于初學者來說,有很多陷阱:在OS之間保持ABI可移植性很難、相對穩定(Non-volatile)的寄存器、64位Windows的異常處理、堆棧對齊、調試…

    為什么SSE沒有被更多地使用?對個人電腦領域碎片化的恐懼,每個x64 CPU都支持SSE2,但通常支持更多, “它不符合我們的數據布局”,傳統上,PC引擎不太重,在OO設計中嵌入SIMD代碼很尷尬,“我們試過了,但沒用”。

    // SSE版Vec4聲明
    class Vec4 
    {
        __m128 data; // 有X/Y/Z/W,是4D向量
        
        operator+ (…)
        operator- (…)
    };
    
    // 【不正確】的SSE版Vec4點積
    Vec4 Vec4Dot(Vec4 a, Vec4 b)
    {
        __m128 a0 = _mm_mul_ps(a.data, b.data);
        __m128 a1 = _mm_shuffle_ps(a0, a0, _MM_SHUFFLE(2, 3, 0, 1));
        __m128 a2 = _mm_add_ps(a1, a0);
        __m128 a3 = _mm_shuffle_ps(a2, a2, _MM_SHUFFLE(0, 1, 3, 2));
        __m128 dot = _mm_add_ps(a3, a2);
        return dot; // WAT: the same dot product in all four lanes
    }
    
    // 【良好】的SSE版Vec4點積
    __m128 dx = _mm_mul_ps(ax, bx); // dx = ax * bx
    __m128 dy = _mm_mul_ps(ay, by); // dy = ay * by
    __m128 dz = _mm_mul_ps(az, bz); // dz = az * bz
    __m128 dw = _mm_mul_ps(aw, bw); // dw = aw * bw
    __m128 a0 = _mm_add_ps(dx, dy); // a0 = dx + dy
    __m128 a1 = _mm_add_ps(dz, dw); // a1 = dz + dw
    __m128 dots = _mm_add_ps(a0, a1); // dots = a0 + a1
    

    不要把時間浪費在SSE類上,試圖用AOS數據抽象SOA硬件,注定是笨拙和緩慢的。SSE代碼想要自由,實現無包裝器或框架的最佳性能,只需根據需要編寫小的助手例程。“它不符合我們的數據布局”,空粒子(float pos[3],…),存儲在結構粒子{float pos[3];…},在SSE中使用粒子數組很難,所以避免這樣做。保留spawn函數,更改內存布局,問題在于結構粒子,而不是SSE。內存中的結構粒子(AOS):

    內存中的粒子(SOA):

    數據布局選擇:對于SSE代碼來說,SOA形式通常要好得多,自然映射到指令集,SOA SIMD代碼與標量參考代碼緊密映射。AOS形式通常更適合于標量問題,尤其是對于查找或索引算法,單緩存未命中以獲取一組值。如果需要,在轉換中局部地生成SOA數據,通過調整輸入/輸出來*衡SIMD效率。

    舉個具體的案例——門。自動打開的門,當右翼演員“忠誠”在某個半徑范圍內時,想想《星際迷航》,典型博弈問題,最初是作為面向對象解決方案實現的,開始出現在性能雷達上,約100扇門 x 約30個角色測試 = 3000次測試!下面是有性能問題的版本及解析:

    上圖的原始更新中輸入數據的內存關系如下:

    SIMD準備工作:將門數據移動到中心位置,實際上只是SOA形式的一包價值觀,很好的方法,因為門很少被創建和破壞,每個門都有一個進入中央數據倉庫的索引,在更新中本地構建參與者表,每次更新一次,而不是100次, 隱藏在堆棧上的簡單數組中(分配用于可變大小)。

    // 門更新數據設計
    
    // In memory, SOA
    struct DoorData 
    {
        uint32_t Count;
        float *X;
        float *Y;
        float *Z;
        float *RadiusSq;
        uint32_t *Allegiance;
        // Output data
        uint32_t *ShouldBeOpen;
    } s_Doors;
    
    // On the stack, AOS
    struct CharData 
    {
        float X;
        float Y;
        float Z;
        uint32_t Allegiance;
    } c[MAXCHARS];
    

    SIMD門新的更新可以一次完成所有的門,在內部循環中測試4個門和1個參與者,數據布局帶來的巨大好處,所有的計算都會自然而然地以SIMD操作的形式出現。

    // 外循環
    for (int d = 0; d < door_count; d += 4) 
    {
        // 加載4扇門的屬性,清除4個“打開”累積器
        __m128 dx = _mm_load_ps(&s_Doors.X[d]);
        __m128 dy = _mm_load_ps(&s_Doors.Y[d]);
        __m128 dz = _mm_load_ps(&s_Doors.Z[d]);
        __m128 dr = _mm_load_ps(&s_Doors.RadiusSq[d]);
        __m128i da = _mm_load_si128((__m128i*) &s_Doors.Allegiance[d]);
        __m128i state = _mm_setzero_si128();
            
        // 內循環
        for (int cc = 0; cc < char_count; ++cc) 
        {
            // 加載1個角色的屬性,廣播到所有4個線程(lane)
            __m128 char_x = _mm_broadcast_ss(&c[cc].x);
            __m128 char_y = _mm_broadcast_ss(&c[cc].y);
            __m128 char_z = _mm_broadcast_ss(&c[cc].z);
            __m128i char_a = _mm_set1_epi32(c[cc].allegiance);
            
            // 計算角色和四扇門之間的*方距離
            __m128 ddy = _mm_sub_ps(dy, char_y);
            __m128 ddz = _mm_sub_ps(dz, char_z);
            __m128 dtx = _mm_mul_ps(ddx, ddx);
            __m128 dty = _mm_mul_ps(ddy, ddy);
            __m128 dtz = _mm_mul_ps(ddz, ddz);
            __m128 dst = _mm_add_ps(_mm_add_ps(dtx, dty), dtz);
                
            // 對比開門半徑和忠誠=>或進入狀態
            __m128 rmask = _mm_cmple_ps(dst, dr);
            __m128i amask = _mm_cmpeq_epi32(da, char_a);
            __m128i mask = _mm_and_si128(_mm_castps_si128(amask), rmask);
            
            state = _mm_or_si128(mask, state);
        }
        
        // 為這4扇門存儲“應該開門”,為下一組4扇門做好準備。
        _mm_store_si128((__m128i*) &s_Doors.ShouldBeOpen[d], state);
    }
    

    內循環的匯編代碼生成如下:

    vbroadcastss xmm6, dword ptr [rcx-8]
    vbroadcastss xmm7, dword ptr [rcx-4]
    vbroadcastss xmm1, dword ptr [rcx]
    vbroadcastss xmm2, dword ptr [rcx+4]
    vsubps xmm6, xmm8, xmm6
    vsubps xmm7, xmm9, xmm7
    vsubps xmm1, xmm3, xmm1
    vmulps xmm6, xmm6, xmm6
    vmulps xmm7, xmm7, xmm7
    vmulps xmm1, xmm1, xmm1
    vaddps xmm6, xmm6, xmm7
    vaddps xmm1, xmm6, xmm1
    vcmpps xmm1, xmm1, xmm4, 2
    vpcmpeqd xmm2, xmm5, xmm2
    vpand xmm1, xmm2, xmm1
    vpor xmm0, xmm1, xmm0
    add rcx, 10h
    dec edi
    jnz .loop
    

    結果獲得了20-100倍加速比,更可能的是,現在可以對數據進行推理,蠻力SIMD代表“合理的事物”,游戲中有很多“合理的”問題!刪除緩存未命中+SIMD ALU可能是一個巨大的勝利,解決“千刀之死”(death by a thousand cuts)問題,這種類型的轉換通常會讓它遠離雷達。

    文中還例舉了過濾數據的案例。

    最佳實踐:分支。一般避免分支,預測失誤的分支在大多數H/W上仍然非常昂貴,不想在內部循環中很難預測分支,如果非常可預測,可以進行分支,分支應正確預測99%以上才能有意義,例如,數據海洋中的一些昂貴的東西。如果在SSE2上,請_mm_movemask_X(),還可以考慮m_testz_si128()和SSE4.1+。

    分支的替代方案:GPU風格的“計算兩個分支”+選擇,用于許多較小的問題,每個問題單獨輸入數據+內核,盡可能產生最佳性能,考慮對索引集進行分區,運行fast內核將索引數據劃分為多個集合,在每個子集上運行優化的內核,除非訪問了大多數索引,否則預取可能很有用。

    最佳實踐:預取。對上一代硬件來說絕對必要,盲目地將其推廣到x86不是一個好主意,準則:不要預取線性數組訪問,在某些硬件上可能存在嚴重的TLB未命中成本機會,芯片已經在緩存級別免費預取。指南:可能預取即將發布的PTR/索引,如果知道他們之間的距離足夠遠/不規則,AMD/Intel之間的預取指令有所不同,仔細測試是否從所有硬件中受益。

    最佳實踐:展開。在VMX128/SPU樣式代碼中常見,為了讓機器隱藏延遲,很有意義,也有很多寄存器!對于SSE/AVX來說,通常不是個好主意,只有16個(命名)寄存器——硬件內部有更多寄存器,無序的執行在一定程度上為你展開。指南:僅展開至整個寄存器寬度,例如展開2x 64位循環以獲得128位循環,但不能再展開,可以根據需要對非常小的循環進行例外。

    最佳實踐:流式讀寫。一定要使用流式讀取(>SSE 4.1)和寫入,有助于避免緩存垃圾,特別是對于使用大型查找表的內核,但別忘了圍欄!!針對不同體系結構的不同選項_mm_fence()總是有效,但速度很慢,流繞過了強大的x86內存模型,如果不加以限制,微妙的數據競爭就會發生。

    結論:SIMD不是魔法,所有人都可以成為性能的英雄!小投資可以帶來巨大的收益,現代SSE好處多多!

    Strategies for efficient authoring of content in Shadow of Mordor分享了游戲Middle-earth: Shadow of Mordor中的內容創作策略和效率,包含同步、加載、性能、資產處理、內容依賴等內容。

    隨著游戲日益復雜,游戲的資源尺寸、數量、數據記錄等都呈數十倍的增長曲線,其中紋理占據約55%,音頻占據35%,其它約10%。而紋理和音頻中,占用尺寸從高到低依次是動畫、關卡、模型、數據記錄、行為、特效、著色器。(下圖)

    文中采用了智能加載的策略,檢查源文件格式,強調最小占用、最大加載速度,如數據進入內存(磁盤)的速度有多快,數據的解釋速度有多快(CPU)等。LTA(LTA–Lith Tech ASCII)源文件格式,類似xml的文本,人類可讀、文件大、解讀緩慢,使用編碼、壓縮的ASCII碼,磁盤上更小,更快地進入內存,解讀速度較慢。壓縮二進制表示法,磁盤上更小,速度快得多,無需解析文本,獨立壓縮的文件樹根(Zlib),并行或部分加載/解壓縮,CRC檢查暴露文件損壞,提供轉換為人類可讀格式的實用程序。對于壓縮格式,占用小10倍,加載快10倍!

    按需加載:在需要之前不加載大多數數據,需要用戶操作來提示額外加載,非常適合獨立的工作流程,樹控制對此很有效。需要適當的源文件粒度,很難處理單個文件。延遲加載時間(快15倍):

    在后臺加載,僅提前加載部分數據,讓用戶在加載初始塊后開始編輯,需要數據,但不是立即需要,如有必要,請用戶屏蔽,例如視覺輔助、Visual Studio智能提示。后臺加載可以提升5倍的速度。

    千刀之死:需要加載大量小文件,硬盤在這方面的性能很差,異步IO將有所幫助,建立一個“檢查點”,單個文件,壓縮后占用最小,在其過期的情況下進行修補,90k文件/800m到30m的檢查點。

    磁盤SSD可以提速序列化,但容量更小。CPU線程池實現并行化,不適用于任何地方,復雜有開銷。

    線程池可以減少CPU等待時間,可以提高線性速度,如果合適容易丟棄,缺點是不會抵消糟糕的算法選擇,核心競爭,更復雜,只和最慢的作業一樣快。

    文中提及的資源構建管線如下:

    還采用了數據繼承:

    Piko: A Framework for Authoring Programmable Graphics Pipelines分享了一種可編程的GPU渲染管線。文中提到當前市面上高效的圖形管線有:

    渲染器 *臺 算法
    Unreal Engine 4 GPU 延遲著色的光柵化
    Unity 5 GPU 前向和延遲著色的光柵化
    Disney Hyperion 多核CPU 延遲著色的路徑追蹤
    Pixar RenderMan 多核CPU 光線追蹤的Reyes
    Solid Angle Arnold 多核CPU 路徑追蹤
    Media Molecule Dreams GPU 基于點的延遲著色渲染

    但問題是高效的圖形管道實現很難編寫,設計空間也很難探索。

    GPU上的軟件光線有:

    引入靈活的圖形管線,在類型中抽象各個階段,通過隊列抽象通信。

    高性能的基礎是并行、執行局部性、數據局部性、生產者消費者局部性。而Piko框架真是解決以上問題的橋梁:

    Piko的運行流程如下:

    管線中的每個階段都有三個步驟:


    Piko管線易于表達和定制:

    利用空間分塊,可以提升并行度和局部性,從而提升圖形管線效率:


    Rendering the Alternate History of The Order: 1886分享了游戲The Order: 1886的渲染迭代歷程。

    該游戲引擎使用了深度預通道、頂點法線和速度,按分塊列表計算,使用深度緩沖區剔除,透明材料的單獨列表,異步計算->基本免費,更低開銷的MSAA。生成每材質像素著色器,完全優化的材質+照明管道,更難手動優化所有案例。存在一些GPR問題,照明所用參數的函數。

    造成畫面閃爍的原因有高頻信號、欠采樣(時間或空間)、移動采樣、不良的過濾方法(需要考慮頻率響應,可以使用后處理抗鋸齒)。

    重建過濾器是重采樣(上采樣、下采樣、過采樣)的重要部分,生成輸出(影響漸變、影響感知的細節、影響鋸齒)。有時無法選擇重建過濾器,因為它是整個系統固有的東西,其中一個例子是顯示器。顯示器會采集離散采樣信號,并將其轉換為連續信號。通過查看屏幕上的實際物理模式,可以了解正在使用什么樣的過濾器。在LCD顯示器中,矩形像素圖案的普遍使用是我們有時認為像素是“小正方形”的原因之一。

    以下是常見的幾種重建過濾器,分別是盒子、三角形和Sinc:



    鋸齒的來源:光柵化(幾何鋸齒)、鏡面反射(鏡面鋸齒)、陰影、紋理、SSAO、后處理特效和采樣!

    幾何鋸齒的來源:幾何采樣不足(通常頻率非常高,無法預過濾三角形),光柵化器是一個階躍函數,二進制-開/關,丑陋的樓階梯圖案,相機移動時的時間瑕疵,改變覆蓋范圍=閃爍!可以用MSAA進行過采樣。

    鏡面鋸齒的來源:低粗糙度=非常高的頻率,移動采樣點將閃爍,采樣不足的幾何體+法線貼圖變得更糟,可以使用LEAN/CLEAN/Toksvig進行預過濾,很難解釋所有法線方差的來源,但你絕對應該這么做!

    The Order使用的抗鋸齒是EQAA(2倍片元、4倍覆蓋率、質量與性能/內存的*衡)、自定義解析(高階過濾、用一些細節換取穩定)、TAA(進一步減少閃爍,與MSAA解決方案整合)。

    使用了MSAA的中間通道有:延期貼花,累積到非MSAA RT;低分辨率透明和AO,放大每深度子樣本,合成一次;Alpha測試,按深度子樣本測試,或使用A2C;景深,使用所有子樣本中最小的CoC。

    帶MSAA的DOF。

    MSAA自定義解析:使用計算著色器而不是硬件解析,樣本顏色片元和覆蓋率樣本,2像素寬的立方濾波器,覆蓋相鄰像素的子集,沒有負波瓣–HDR的振鈴(ringing)太多。更多請參閱:MSAA Resolve Filters

    The Order想要選擇更寬、更*滑的重建濾波器,對比了點、盒子和高斯重建濾波器之后,最終選用了后者,因為它更寬更*滑,在時域中轉換為更*滑的過渡,從而提供更好的穩定性。

    HDR的MSAA:非線性色調映射,在極端情況下“殺死”AA,夾緊(clamp)特別糟糕!需要在postFX之后進行色調映射,后處理需要HDR,昂貴的解決方案:MSAA分辨率的后處理,在色調映射后立即解析。廉價的解決方案:色調映射子樣本,解析,然后逆轉色調映射,復雜的運算符不容易逆轉。更便宜的解決方案:使用簡單運算符的*似色調映射(Reinhard),基本上按1 / (1 + Luminance)來計算樣本權重,好處是抑制小高光以減少閃爍。

    左:HDR下的MSAA;右:逆轉亮度過濾器。

    還需要需要考慮曝光!不想過度渲染高光,以更好地匹配最后的色調映射步驟,仍然可以將曝光偏移1或2檔,*衡過度壓暗和抑制高光。

    左:逆轉亮度過濾器,中:逆轉亮度過濾器(曝光+10),右:修復的逆轉亮度過濾器(曝光+10)。

    // 解析過濾樣本代碼
    float3 sample = InputTexture.Load(uint2(samplePos), subSampleIdx).xyz;
    float weight = Filter(sampleDist);  // Bicubic, Gaussian, etc.
    float sampleLum = Luminance(sample);
    sampleLum *= exposure * exp2(ExposureOffset); // ExposureOffset ~= -2.0
    weight *= 1.0f / (1.0f + sampleLum);
    sum += sample * weight;
    totalWeight += weight;
    

    TAA的主要目標是減少鏡面閃爍,預過濾還不夠,主要啟發是TXAA、SMAA 1TX[Sousa13]、Dust 514[Malan12]、Killzone: Shadow Fall [Valient14]。積累多個樣本,指數移動*均數,使用速度緩沖區重新投影前一幀,使用最小/最大鄰域進行加權和夾緊,MSAA解析期間計算的最小/最大值,沒有抖動(主要是引擎團隊沒有時間集成,不管怎樣,攝像機總是在移動)。

    后處理AA銳化:寬的解析導致“柔和的外觀,與視覺風格一致,可以增加后處理AA銳化,不銳化的遮罩非常簡單,但注意不要太極端!

    從左到右的銳化值是0.0、0.5、1.0。

    陰影:16個聚光燈陰影投射,帶4個級聯的1個*行光,最多支持2個*行光。保持簡單:1個用于聚光燈的紋理數組,1個用于級聯,前向通道中采樣。緩存聚光燈陰影,基于距離的不同頻率更新,如果沒有移動,就不會重新生成陰影。

    預計算陰影可見性:用于主要可見性的系統擴展,在關卡構建期間預計算,僅適用于非移動光源。對于聚光燈,從光源POV柵格化網格ID,使用模板標記投射者(計數>=2)。對于*行光,在光源空間中將關卡拆分為NxN的tile,確定潛在的陰影投射者,對于每個攝影機采樣點:渲染攝影機可見網格,渲染潛在的陰影投射者,如果模板值>=2,則添加到最終陰影可見性列表中。

    用于所有陰影的EVSM,好處是沒有偏倚、更少條紋,硬件過濾(三線性、各向異性),預過濾非常適合正向渲染,有助于降低GPR壓力,非常適合緩存。

    采樣了SDSM來實現CSM,分析深度緩沖區,基于可見曲面約束級聯。計算視圖空間的的最小/最大XYZ,帶優化原子的單通道,理想情況下,逐聯計算光源空間中的AABB。在回讀時附帶1幀延遲,可以使用GPU路徑,減少瑕疵的“時間扭曲”技巧:計算每個像素的速度,預測下一幀位置,展開AABB以覆蓋下一幀位置。不穩定,需要進一步探討。

    左:普通CSM,右:SDSM。

    文中還探討了一種改良的貼花渲染方法——混合貼花:延遲通道累加,使用深度進行投影,添加混合到fp16渲染目標中,主前向通道讀取貼花緩沖區,修改材質屬性。

    Far Cry 4 and Assassin’s Creed Unity: Spicing Up PC Graphics with GameWorks由Nvidia呈現,講述了Far Cry 4和刺客信條兩款游戲利用GameWorks在PC上增強了不少圖形特性,如HBAO+、PCSS、TXAA、角色渲染、光照等。

    Far Cry 4的喜馬拉雅山脈場景。

    刺客信條的中世紀宏偉場景。

    NVIDIA GameWorks包含多個組件,如ShadowWorks、PostWorks、Godrays、HairWorks等等。其中,ShadowWorks和PostWorks非常適合Far Cry 4和刺客信條,HairWorks發型和Godrays非常適合Far Cry 4。

    NVIDIA ShadowWorks由不同的技術組成,以提供電影級陰影、HBAO+、高級軟陰影等。基于地*線的環境遮擋+(Horizon-Based Ambient Occlusion+,HBAO+)是當時最先進的SSAO方法,有最佳性能,可伸縮。調整HBAO+:半徑用HBAO內核的大小,偏移隱藏低細分瑕疵,指數遮擋衰減,細節遮擋是高頻遮擋組件的權重,粗糙遮擋是低頻遮擋分量的權重。

    高級軟陰影:最先進的軟陰影,基于更*的軟陰影百分比(PCS),支持級聯陰影圖,簡單但功能強大的界面。調整高級軟陰影的參數:光源尺寸、最大閾值、最低百分比、混合百分比、邊界百分比。修復漏光:當燈光尺寸過大時,會發生燈光泄漏,PCSS內核太寬,在級聯之外采樣,調整邊界百分比以限制內核,如果仍存在漏光,需減小燈光大小和最大閾值。

    時間抗鋸齒(TXAA)專為減少時間鋸齒而設計的膠片式抗鋸齒技術,NVIDIA PostWorks家族成員。

    NVIDIA HairWorks使用戶能夠模擬和渲染毛發,以提供真正的交互式游戲體驗,運行時庫和內容創建工具的組合。

    HairWorks集成流程:

    HairWorks允許自定義著色模型,支持正向著色和延遲著色。在Far Cry 4中,依靠Dunia渲染機制來執行著色,使用定制材質,HairWorks參數存儲在GBuffer中。

    HairWorks的GBuffer數據:壓縮的漫反射、法線、鏡面指數及縮放、切線、最終成像。

    抗鋸齒:最好的解決方案是在一個單獨的、啟用抗鋸齒的過程中渲染HairWorks毛發。在Far Cry 4中,毛發在主管線中渲染,并依賴于全局抗鋸齒,以對抗閃爍。

    左:無AA,中:4xMSAA,右:4xTXAA。

    NVIDIA Godrays可以渲染出逼真的太陽光束,巨大的調整空間,可擴展的性能,首次整合使用了游戲中的煙霧顏色,使用太陽色顯示出最好的效果。

    Godrays需要找到*衡點:場景看起來完全模糊了,事實上,只是增加了太多的密度,使光線強度依賴于白天

    <br/ >

    14.4.3.2 光影技術

    2010年,A Real Time Radiosity Architecture for Video Games闡述了Frostbite引擎實現的實時輻射光照的架構,包含介紹Enlighten的概述、架構及如何集成到Frostbite中。

    文中提到Enlighten有4個特點:分離的光照管線、帶回饋的單次反饋、光照圖輸出和來自目標幾何體的重建光照。Enlighten的管線如下圖,預計算階段包含分解場景到系統、投影細節幾何到目標幾何以重建光照、提取目標幾何以實時計算輻射,運行時階段包含GPU渲染直接光、CPU異步生成輻射、在GPU組合直接和非直接光。

    運行時的管線如下圖,

    上圖涉及的各個節點和最終組合效果如下系列圖:





    光照圖輸出如下所示:

    Frostbite集成Enlighten的因素包含工作流和工作時間、動態環境、靈活的架構。Frostbite的預計算流程如下:

    • 收集靜態和動態物體。靜態物體接受和反彈光照,而動態物體只接受光照。

    • 生成輻射系統。并行處理和更新,輸入依賴關系控制光傳輸,用于輻射粒度。

    • 參數化靜態幾何。靜態網格使用目標幾何,利用目標幾何圖形來計算輻射,投影細節網格到目標網格以獲得uv,系統打包成單獨的uv圖集。

    • 生成運行時數據。每個系統一個數據集(流友好),使用Incredibuild的XGI進行分布式預計算,數據只依賴于幾何形狀(不是光或反照率)。

    渲染時,分離直接光照和輻射度光照,CPU計算輻射度,GPU計算直接光。Frostbite使用延遲渲染,所有光源都可以動態反彈輻射度。分離光照圖和光照探針渲染,光照圖在前向Pass中渲染,光照探針被添加到3D紋理中,并在延遲渲染中執行。運行時管線分為三步:

    • 輻射度Pass(CPU)。更新非直接光照圖和光照探針,將光照探針注入到3D紋理中。
    • 幾何Pass(GPU)。增加非直接光照圖到單獨的GBuffer中,使用模板緩沖遮蔽掉動態物體。
    • 光照Pass(GPU)。渲染延遲光源,從GBuffer增加光照圖,從3D紋理中增加光照探針。

    以上幾個階段的效果圖如下:





    無獨有偶,Pre-computing Lighting in Games也探討了游戲引擎中的預計算光照。文中提到使用烘焙光的原因有3個:

    • 光照工作流。烘焙照明是一種讓藝術家訪問全局照明 (GI) 的方式,根據實際光源定義照明,沒有人工補光燈,將光照與幾何體/材質分離。烘焙照明還允許更豐富的光源集,基于物理的軟陰影,陰影投射HDR光探頭。

    • 質量。允許最高質量的光模擬算法,GI效果,多次反彈,允許高質量的直接照明。

    • 性能。運行時性能非常好,獨立于燈光設置,獨立于GI算法,好看的光照貼圖與不好看的光照貼圖具有相同的性能。

      • 性能可預測。運行時性能往往非常強大,藝術家可以根據需要添加任意數量的燈光,實時陰影貼圖性能和GI難以預測,光照角度和位置影響陰影渲染的性能,玩家位置會影響燈光需要的分辨率。
      • 性能可伸縮。可在Quake 1、手持設備、高端游戲中使用。

    烘焙光面臨的挑戰有:

    • 更改燈光設置。可以烘焙一天中的不同時間,如果引入更多可變燈,則組合會爆炸。可將移動和強度變化的燈光視為普通運行時燈光,適合組合爆炸或閃爍的燈光,沒有間接照明。

    • 移動/變形幾何。區分局部和全局更改,局部的包含在房間里移動的角色、小家具、彈孔,全局的包含被毀的建筑物、被毀的墻壁。

      局部幾何體改變時存在兩個問題:物體如何受環境影響?物體如何影響其環境?

      樸素的方法只是為移動的物體添加直接照明,但會使使角色看起來格格不入,同樣在沒有直射光的區域,角色是完全黑色的。Light probes優雅地解決了這些問題,并為照亮角色和其它移動物體提供了一個很好的管線(下圖)。一些游戲將關鍵燈置于光探頭之外,并將它們添加為更傳統的直射燈。

      對于移動物體上的入射光,在房間里烘焙光照探頭,使用最*的來照亮物體,將入射照明*似為整個物體的一個光照探頭,與環境相比,適用于較小的物體,非常大的物體可能需要特殊處理。編碼可采用球面諧波(通常為3階),每個面使用1個像素的立方體貼圖,只有單一的環境色。

      移動物體也可以影響環境,光照探頭中的直接照明照明可選,允許對動態對象進行自陰影,允許對象在環境上投射陰影,也可以從光探頭中提取最強的光方向,也可能可以提供間接照明的自我陰影,有些方法只為環境上的角色陰影很重要的燈光烘焙間接光,角色的間接照明通常微不足道。

      對于全局幾何體改變,高動態游戲傾向于避免全局烘焙光照,其它子系統也傾向于依賴靜態幾何或在靜態幾何上表現更好(路徑尋找,碰撞檢測,游戲故事通常需要玩家遵循某些路徑)。

    • 內存占用。照明是全局性的,包含材質紋理(實例共享材質紋理,多個對象可以共享紋理,紋理可以*鋪和鏡像)、照明紋理(每個實例必須是唯一的,不能*鋪、鏡像等,可根據分辨率要求優化分辨率)。

      法線貼圖非常適合增加幾何細節級別,法線貼圖在光照中引入高頻細節,高頻照明需要高紋理分辨率。

      對于定向光照圖,細節在幾何體中,而不是在入射光中,將每個紋素的入射光半球存儲在光照貼圖中,允許*似不同法線方向的照明。定向光照圖的典型編碼有輻射度法線貼圖 (RNM)、SH(一般為 2 個波段,4 個分量)、每像素環境光和定向光、SH基,允許使用真正的BRDF,半球會模糊的,但也可以從中獲得合理的鏡面反射效果。



      上:烘焙光;中:法線貼圖;下:低分辨率定向光照貼圖與法線貼圖相結合。

    • 燈光重建時間。可以采用混合的方案:

      • 僅烘焙間接照明。間接光通常比直接照明更*滑,銳利的陰影需要更高的紋理分辨率。

      • 太陽的特殊處理。陽光往往是對戶外場景影響最大的光線,陽光直射通常是銳利陰影和動態范圍差異的來源,僅從太陽烘焙間接光,直接光作為運行時光添加。

    該文提出的烘焙光管線如下:

    管線的影響有:

    • 光源構建階段可能很耗時。在CPU小時為單位的數量級,取決于算法、分辨率、關卡大小、燈光設置、反彈次數等。
    • 加快速度的工具,選擇性燈光構建,預覽質量構建,預覽工具,相機渲染工具,漸進式光照貼圖生成,分布。
    • 自動重建以確保照明始終是最新的。
    • 用于管理GI特定光源屬性的工具。直接和間接照明的比例因子,以放大和分離光的貢獻。
    • 用于管理GI特定材質屬性的工具。在屏幕上產生良好發光效果和正確外觀的東西并不一定會在環境中產生所需的光發射,增加或減少場景的整體反射率。
    • 紋理烘焙形狀需要唯一的UV。可以在一定程度上實現自動化,易于展開的內容更可取,如果可能,將細節保留在法線貼圖層中。
    • 頂點烘焙很常見。由于紋理分辨率不足而沒有接縫,法線貼圖和定向光照貼圖有助于在低分辨率光照下提供細節,不適合多邊形內的陰影和其他照明不連續性。

    Real-time Diffuse Global Illumination in CryENGINE 3提出了級聯光照傳播體積(Cascaded Light Propagation Volumes,CLPV)的技術。CLPV的核心思想在于:

    1、采樣照明表面,將它們視為輔助光源。為GI采樣場景時,使用面元(又名point、disk, Surfel == 表面元素),所有光照面元都可以在光源空間中展*為2D映射圖,使用反射陰影貼圖(RSM)進行照明,RSM是在GPU上對光照面元進行采樣的最快方法,甚至過度快O_O!

    2、將樣本分簇成一個統一的粗糙3D網格(grid),累加并*均每個單元格(cell)的輻射亮度(Radiance)。分簇面元時,以虛擬點光源(VPL)表示的光照面元,將每個面元分布到最*的單元格中(類似于PBGI, light-cuts and radiosity clustering),將所有VPL轉換為輸出輻射分布,以較低頻帶的球面諧波表示,在擁有者的單元格的中心進行累加,使用光柵化完全在GPU上完成。

    3、迭代地將輻射亮度傳播到相鄰單元格(僅適用于漫反射)。RSM是一組從燈光位置規則地采樣的場景VPL,通過規則網格和SH離散地初始VPL分布,將光照從一個單元格迭代傳播到另一個單元格。(下圖)

    跨3D網格的局部單元格到單元格的傳播,類似于參與媒體照明的SH離散坐標法[GRWS04],6個軸向方向,輪廓面作為傳播波前(wave front),將得到的SH系數累加到目標單元格中以進行下一次迭代。

    4、用生成的網格點亮場景。使用LPV進行最終場景渲染,使用硬件三線性插值在特定位置查找生成的網格3D紋理,將輻照度與被照表面法線的余弦波瓣進行卷積,應用抑制因子(dampening factor)以避免自溢出(self-bleeding),計算朝向法線的方向導數,基于與強度分布方向的梯度偏差進行抑制。

    注入后迭代8次的效果:

    為了穩定光照結果,可以采用以下方法:

    • 空間穩定。將RSM捕捉一個像素以進行保守光柵化,通過一個網格單元捕捉LPV以實現穩定注入。
    • 自發光(Self-illumination)。在RSM注入期間偏移半單元格VPL到法線方向。
    • 時間連貫性和重投影。對RSM注入執行重投影的時間SSAA。

    此方法的局限性:僅漫反射相互反射,稀疏空間和低頻角度*似(光擴散:光傳輸濺射在各個方向,空間離散化:對于遮擋和非常粗糙的網格可見),次級AO信息不完整。

    可以采用多分辨率方法,以不同的分辨率渲染多個嵌套的RSM,受級聯陰影貼圖技術的啟發,在GPU上模擬不均勻的多分辨率渲染,根據對象的大小將對象分配到不同的RSM。將RSM注入相應的LPV,創建綁定RSM視錐體的嵌套LPV網格,獨立進行傳播和渲染,從內部LPV傳播到外部。

    LPV還可以擴展到:

    • 透明物體。
    • 用于大規模光照*似的光照緩存,將分析輻射注入被光線覆蓋的網格單元。
    • 具有附加遮擋網格的二級遮擋,使用相同的技巧可以多次反彈。
    • LPV中部分匹配的光澤反射。
    • 參與介質照明,來自傳播過程的本質。

    CLPV效果這么好的原因有:

    • 人類對間接照明的感知。對接觸照明非常敏感(角落、邊緣等),間接照明主要是低頻,即使是間接陰影,*滑漸變而不是陰影中的*坦環境,*似為參與媒體的擴散過程。
    • 級聯:基于重要性分簇。發射器根據其大小分布在級聯中。

    離線的PBRT和實時的LPV的對比如下圖,兩者差異不太明顯:

    光照圖、PRT、LPV在圖像質量、內存、動態光照支持、動態物體支持、次級遮蔽、多反射、區域光等參數的對比如下表:

    LPV還可以和其它技術相結合:

    • 與SSAO相乘以添加微遮擋細節。
    • 延遲環境探針。結合后可增強遠距離GI。
    • 間接光源和延遲光源。在某些地方使用間接光燈模擬GI,對GI風格化的藝術家很重要。

    總之,LVP是全動態方法,改變場景/視圖/照明,GPU和控制臺友好,極快(在PlayStation 3上大約需要1毫秒/幀),符合生產要求(用于實時調整的豐富工具集),高度可擴展,與質量成正比,穩定、無閃爍,支持復雜的幾何形狀(例如樹葉)。

    Physically-Based Shading Models in Film and Game Production是Naty Hoffman等人在Siggraph上分享的PBR實時化的演講,演講中詳細地闡述了PBR的物理理論基礎和數學化建模,以及如何在GPU中實現出來。

    不同物質對光的吸收和散射的表現。

    基于微觀幾何建模的光照模型。

    漫反射和次表面散射的轉變關系。

    經典的Cook-Torrance BRDF公式。

    Crafting Physically Motivated Shading Models for Game Development也是Naty Hoffman的演講,涉及了PBR在游戲引擎的實現、改進和優化等內容。文中說到使用PBR的原因有:更容易實現照片寫實/超寫實,在照明和觀察變化下保持一致,更少的調整和“捏造因素”,為藝術家提供更簡單的材質界面,更容易排除故障,更容易擴展。

    PBR需要一些前置基礎,包含伽瑪校正渲染、支持HDR值,良好的色調映射(最好是電影)。伽瑪校正渲染的特點是著色輸入(紋理、淺色、頂點顏色等)自然創作、預覽和(通常)使用非線性(伽馬)編碼存儲,最終幀緩沖區也使用非線性編碼,這樣做是有充分理由,感知一致等于有效使用比特,還有歷史遺留原因(如工具、文件格式、硬件)。

    如果著色默認為Gamma空間,著色結果不正確,產生“1+1=3”的效果:

    高動態范圍 (HDR) 可以產生逼真的渲染,但需要處理遠高于顯示白色 (1.0) 的值,著色前:光照強度、光照貼圖、環境貼圖,著色產生影響光暈、霧、景深、運動模糊等的高光,存在廉價的解決方案。

    文中對比了Phong和Blinn-Phong的效果,發現在某些情形Blinn-Phong的效果更真實:


    文中提到對鏡面高光,除了菲涅耳項之外,還引入了歸一化因子\((\alpha_p+2)/8\),歸一化因子非常重要,若沒有它,鏡面反射亮度會從4倍太亮到數千倍太暗,具體取決于\(\alpha_p\)的值,誤差如此之大,菲涅耳因子變得無關緊要。沒有歸一化使得創建看起來逼真的材質變得非常困難,尤其是當每個像素的\(\alpha_p\)變化時。下面分別是有無歸一化的曲線和效果對比圖:


    筆者的另一篇文章已經詳細深入地探討過PBR:由淺入深學習PBR的原理和實現

    OIT And Indirect Illumination Using Dx11 Linked ListsReal-Time Order Independent Transparency and Indirect Illumination Using Direct3D 11講述了使用DirectX 11的特性來實現間接光的效果。文中提到了沒有間接陰影的間接光方案:

    1、繪制場景G-Buffer。

    G-Buffer需要允許重建:世界/相機空間位置、世界/攝影機空間法線、顏色/反照率,DXGI_FORMAT_R32G32B32A32_FLOAT位置可能需要用于間接陰影的精確光線查詢。

    2、繪制反射陰影圖(RSM)。RSM顯示從光源接收直射光的場景部分。

    RSM需要允許重建:世界/相機空間位置、世界/攝影機空間法線、顏色/反照率,僅繪制間接光源的發射器,間接陰影的光線精確查詢可能需要DXGI_FORMAT_R32G32B32A32_FLOAT的位置。

    3、以1/2的分辨率繪制間接光緩沖區。RSM紋素用作G-Buffer像素上的光源,用于間接照明。步驟如下:

    • 延遲渲染1/2分辨率的間接光(IL)。
    • 將G-Buffer像素轉換為RSM空間。
      • G-Buffer像素的空間轉換順序:Screen Space -> Light Space -> 投影到RSM紋素空間。
    • 使用RSM紋素的內核作為光源。
      • RSM紋素也稱為虛擬點光源 (VPL)。
      • 內核大小取決于所需速度、想要的效果外觀、RSM分辨率。

    在G-Buffer的一個像素上計算IL,然后累加內核中所有VPL的貢獻:

    下面的計算項與輻射度形狀系數(form factor)計算中使用的項非常相似:

    *滑IL的簡單解決方案需要考慮四個中心位于t0、t1、t2 和t3的VPL內核:

    大的VPL內核的計算速度很慢:

    可以采用下圖的技巧:

    4、上采樣間接光 (IL)。

    間接光緩沖為1/2的分辨率,執行雙邊上采樣步驟,結果是全分辨率的IL。

    5、繪制添加IL的最終圖像。

    組合直接照明、間接照明和陰影。

    左:沒有間接光;右:組合了間接光。

    添加間接陰影的步驟:

    • 使用CS和鏈表技術。

      • 將IL的遮擋幾何圖形(使用遮擋者的三角形)插入到3D列表網格中,查看備用數據結構的備份。

    • 再讀取一個VPL的內核。

    • 只累加被遮擋者三角形遮擋的VPL的光。

      • 通過3d網格追蹤光線以檢測被遮擋的VPL。
      • 僅渲染低分辨率緩沖區。
    • 從IL緩沖區中減去被遮擋的間接光。

      • 使用了低分辨率遮擋的IL的模糊版本,模糊是雙邊模糊/上采樣的組合。

    上排:3D網格、非直接光緩沖區、被遮擋的非直接光;下排:非直接光緩沖區、減去被遮擋的非直接光、最終成像。

    [Uncharted 2: Character Lighting and Shading](http://advances.realtimerendering.com/s2010/Hable-Uncharted2(SIGGRAPH 2010 Advanced RealTime Rendering Course).pdf)闡述了神秘海域2使用的角色渲染技術,包含皮膚、頭發、布料等材質的渲染。

    神秘海域2中不同角色的渲染效果。

    其中皮膚采用了次表面散射模型。其中下圖是NV使用了紋理空間的模糊來*似次表面散射效果:

    然后通過RGB分量各不相同的卷積核來累加獲得最終的次表面散射效果:

    // 直接光:像素自身的權重最大,并且B > G > R.
    diffColor = direct*float3(.233,.455,.649);
    
    // 散射光:lm1~lm5就是上圖模糊后的紋理.
    diffColor += lm1 * float3(.100,.336,.344);
    diffColor += lm2 * float3(.118,.198,.0);
    diffColor += lm3 * float3(.113,.007,.007);
    diffColor += lm4 * float3(.358,.004,.0);
    diffColor += lm5 * float3(.078,0,0);
    

    從左到右:僅直接光、僅散射光、組合了兩者。

    由于NV在模糊的過程使用了太多通道,性能和銷毀無法滿足要求,為此,神秘海域2采用了12-Tap的*似方法:

    12-Tap抖動的權重如下:

    float3 blurJitteredWeights[13] = 
    {
        // 像素自身的權重.
        { 0.220441, 0.437000, 0.635000 }, 
        // 12-Tap的權重.
        { 0.076356, 0.064487, 0.039097 }, 
        { 0.116515, 0.103222, 0.064912 }, 
        { 0.064844, 0.086388, 0.062272 }, 
        { 0.131798, 0.151695, 0.103676 }, 
        { 0.025690, 0.042728, 0.033003 }, 
        { 0.048593, 0.064740, 0.046131 }, 
        { 0.048092, 0.003042, 0.000400 }, 
        { 0.048845, 0.005406, 0.001222 }, 
        { 0.051322, 0.006034, 0.001420 }, 
        { 0.061428, 0.009152, 0.002511 }, 
        { 0.030936, 0.002868, 0.000652 }, 
        { 0.073580, 0.023239, 0.009703 }, 
    }
    

    12-Tap有兩種實現方法:

    • 分離模糊(Separate Blur)。
      • 渲染到光照貼圖。
      • 12-Tap模糊光照貼圖。
      • 渲染最終場景。

    • 組合模糊。
      • 渲染到光照貼圖
      • 渲染最終場景,從光照貼圖中使用12-Tap采樣。

    以上兩種方法都有不錯的渲染效果:

    左:分離模糊;右:組合模糊。

    Uncharted 2還嘗試了彎曲法線(Bent Normal),以偽裝R/G/B各分量來自不同的法線,R更接*幾何,G/B更接*法線貼圖(下圖),漫反射計算3次。

    但是,對R/G/B使用不同的法線似乎會導致一些藍色斑點:

    出現藍色斑點的原因是點積在極端角度,可能會遇到diffuseR=0diffuseB=1的情況,或相反亦然。

    另一種方法是混合法線(Blended Normal),對幾何和法線映射法線進行漫反射計算,并在它們之間從幾何法線中獲取更多紅色,從法線映射法線中獲取更多綠色/藍色。具體做法是:Diffuse(L, G)、Diffuse(L, N) 然后Lerp,藍色/綠色保持不變、紅色溢出,可以有紅色但沒有藍色/綠色,不能有藍色/綠色但沒有紅色。這種方法可以顯著降低藍色斑點:

    在頭發渲染上,Uncharted 2使用了Kajiya-Kay的光照模型,實現的細節和特點如下:

    • 輕微環繞漫反射(Slight Wraparound Diffuse)。
    • Kajiya-Kay鏡面反射。
    • 沒有自陰影。看起來像具有額外偏差的最大級聯。
    • 漫反射貼圖作為高光遮罩。部分去飽和。
    • 使用Blinn-Phong的延遲光照。

    在布料上,Uncharted 2使用了邊緣光 + 內部光 + 漫反射的組合:

    布料光照。從左到右:邊緣光、內部光、漫反射、組合光。

    布料光照的偽代碼如下:

    VdotN = saturate( dot( V, N ) );
    Rim = RimScale * pow( VdotN, RimExp );
    Inner = InnerScale * pow( 1-VdotN, InnerExp );
    Lambert = LambertScale;
    
    ClothMultiplier = Rim + Inner + Lambert;
    FinalDiffuseLight *= ClothMultiplier;
    

    但以上布料的實現方式忽略了光線方向,因此布料光照不能隨光源方向的改變而改變。

    CryENGINE 3: reaching the speed of light主要是CryENGINE 3在控制臺上紋理壓縮和延遲照明的改進。

    紋理壓縮改進:

    • 顏色紋理。創作精度、最佳色彩空間、DXT塊壓縮的改進。

      建議根據直方圖選擇正確的顏色空間,按照經驗是如果75%以上的像素高于中值(線性空間是116/255=0.45),則使用線性空間。

    • 法線貼圖紋理。法線精度、3Dc法線貼圖壓縮的改進。

      以前,藝術家將法線貼圖存儲到8bpc紋理中,導致法線從一開始就被量化了!將工作流更改為始終導出16bpc法線貼圖!修改工具以默認導出,對藝術家透明。

      上:8bpc法線紋理;下:16bpc法線紋理。顯然后者的高光更細膩*滑。

      可以改進用于法線貼圖的3Dc編碼,3Dc比ARGB8好很多,在多數GPU上以16位精度生成插值!

      常規的3Dc編碼器:將x和y獨立壓縮為兩個alpha通道——不將x-y視為法線!

      建議改進3Dc編碼器:將兩個alpha塊視為一個整體x-y法線,計算正常而不是“色差”的誤差:

      \[\triangle N = \arccos\bigg(\cfrac{(N_c \cdot N)}{||N_c|| \ ||N||} \bigg) \]

      為了加速壓縮,可以采用自適應方法:壓縮為2個alpha塊,測量法線的誤差。如果誤差高于閾值,則運行高質量編碼器。

      a:原始紋理;b:常規編碼;c:建議編碼;d:誤差。

    CryEngine 3的遮擋剔除使用軟件z緩沖區(又名覆蓋緩沖區),步驟如下:

    • 在控制臺上縮小前一幀的z緩沖區。使用保守遮擋避免錯誤剔除。
    • 創建mip并使用分層遮擋剔除,類似于Zcull和Hi-Z技術,使用AABB和OOBB測試遮擋。
    • 在PC上:手動放置遮擋物并在CPU上光柵化,CPU和GPU之間的延遲使z緩沖區無法用于剔除。

    SSAO的改進包含將深度編碼為2通道16位值 [0;1],作為有理數的線性深度:depth=x+y/255。以半屏分辨率計算SSAO,將SSAO渲染到同一個RT(另一個通道),雙邊模糊同時獲取SSAO和深度。具有4個樣本的體積遮擋,簡單重投影的時間累積,整體性能是在X360上1ms,PS3上1.2ms。

    對于顏色分級(color grading),將所有全局顏色轉換烘焙到3D LUT,事實證明16x16x16 LUT已足夠,盡量使用硬件3D紋理,顏色校正通道是一種查找:newColor = tex3D(LUT, oldColor)

    CryEngine 3使用Adobe Photoshop作為色彩校正工具,從Photoshop讀取轉換后的顏色LUT:

    文中談到了延遲渲染管線的問題包含

    • 不支持抗鋸齒,MSAA對于延遲管線來說過于繁重,后處理抗鋸齒不會完全消除鋸齒,大多數情況下需要超采樣。
    • 有限的材料變化,無各向異性材料。
    • 不支持透明對象。

    延遲渲染的GBuffer的每像素數據越小越好,CryEngine 3最小化GBuffer到64 bits / pixel,其中RT0存儲Depth 24bpp和Stencil 8bpp,RT1存儲Normals 24bpp和Glossiness 8bpp。用于標記照明組中的對象的模板:門戶/室內、自定義環境反射、不同的環境和間接照明。光澤度不可延遲,照明累積通道需要,否則鏡面反射是非累積的。這種G-Buffer布局的問題:僅Phong BRDF(正常 + 光澤度)、沒各向異性材質、24bpp的法線過于量化、照明帶狀/低質量。

    對于著色的法線精度,24bpp的法線過于量化,光照質量低。24bpp的精度本應該足夠了,為什么會出現光照質量低?原因是存儲了標準化的法線!立方體是256x256x256個單元格 = 16777216個值,在這個立方體中只使用單位球體上的單元格:16777216個中的約289880 個單元格,即約1.73%!!

    我們有一個包含\(256^3\)個值的立方體!最佳擬合是找到一條光線誤差最小的量化值,可以離線執行,使用[3D-DDA](光線追蹤小記:空間加速結構Regular Grid與3DDDA)中的約束優化。將其烘焙到結果的立方體貼圖中,立方體貼圖應該足夠大(顯然 > 256x256)。

    提取這個對稱立方體貼圖最有意義和唯一的部分,保存為2D紋理,在G-Buffer生成期間查找它,縮放法線,將調整后的法線輸出到G-Buffer。

    法線的最佳匹配支持Alpha混合,盡管最合適的會被破壞,但通常不是問題,重構只是一種歸一化!可以應用于一些選擇性*滑的物體,例如禁用帶有細節凹凸的對象,不要忘記為結果紋理創建mip-maps!

    幾種法線存儲技術的對比如下表:

    法線存儲技術 有效單元格 有效單元格占比
    Normalized normals 約 289880 / 16777216 約 1.73 %
    Divided by maximum component 約 390152 / 16777216 約 2.33 %
    Proposed method (best fit) 約 16482364 / 16777216 約 98.2 %

    標準化法線(上)和最佳匹配(下)法線的渲染對比圖。

    標準化法線(上)和最佳匹配(下)法線的對比圖。

    該文還談到了一種用于光照的計算:裁剪體積(Clip volume)。沒有陰影的延遲光照往往會溢出,但陰影開銷很大。解決方案:使用藝術家定義的剪裁幾何體——剪裁體積,除了光照體積遮罩之外,還由遮罩模板。非常低開銷,提供四倍的模板標記速度。

    裁剪體積示例。左上:裁剪體積幾何體;右上:模板標記;左下:光照累積緩沖;右下:最終成像。

    CryEngine 3為了高效地實現各項異性材質,還將BRDF復雜度與光照復雜度解耦,BRDF復雜性完全從光照通道中消除。(下圖)

    在抗鋸齒上,CryEngine 3使用了混合抗鋸齒的解決方案。

    • *處物體用后處理AA。不超采樣,適用于邊緣,使用MLAA。
    • 遠處物體用TAA。進行時間超級采樣,不區分表面空間陰影變化。
    • 用模板和無抖動相機將它們分開。

    距離分離保證了遠處物體的視圖矢量的微小變化,減少反向時間重投影的基本問題:著色域中的視圖相關變化。原因是重投影是基于深度緩沖區的,因此,不可能考慮物體的著色空間局部變化。如果將它應用到特寫物體上,可能會導致重影、反射等。

    在逐物體基礎上分開,一致的物體空間著色行為,使用模板標記物體以進行時間抖動。


    上:用于遠處物體的TAA;下:用于*處物體的后處理AA。

    Sample Distribution Shadow Maps是Intel提出的一種改進的陰影渲染方法。

    Sample Distribution Shadow Maps(SDSM)是樣本分布陰影圖,通過分析陰影樣本分布,找到緊湊Z的最小值/最大值,基于緊湊Z邊界的對數分區,無需調整即可適應視圖和幾何形狀。計算緊湊的光源空間邊界,每個分區都有緊湊的軸對齊邊界框,大大提高有用的陰影分辨率。


    上:PSSM(*行陰影圖)的分區、光源空間、光源空間分區;下:SDSM的分區、光源空間、光源空間分區。

    分區變體有:

    • K均值(K-means)的分簇。在Z中有大量樣本的地方放置分區,*均誤差的好結果,但有玻璃鉗口(glass jaw)。
    • 自適應對數。與基本對數類似,但要避免Z中的間隙,只適用于特殊情形,通常不值得嘗試。

    上面的方案需要深度直方圖。

    SDSM有兩種不同的實現:

    • 對數的簡單“減少”實現。可以在DX9/10硬件上的像素著色器中實現。
    • 一般深度直方圖實現。共享內存原子使這成為可能,太慢且依賴于DX11之前的硬件。

    SDSM產生更緊密的光源空間截錐體,渲染到陰影貼圖中的幾何體更少。

    在GPU上生成的分區邊界數據,CPU不能用于截錐體剔除!阻塞并讀回分區邊界數據(非常小),聽起來糟糕,但就是當時所做的,而且速度相當快。未來在GPU上進行截錐剔除。

    關于時間一致性的說明:

    • 改變分辨率會導致時間鋸齒。
    • 量化光源空間中的分區邊界僅適應于定向燈?根本無法移動或調整分區大小,存在一些相機變換的問題,過于限制和次優。
    • 將分區量化為2的冪大小?可以工作,但苛刻,且浪費了很多分辨率。
    • 以亞像素陰影分辨率為目標。需要足夠的分區分辨率(約等于屏幕分辨率),使用良好的過濾和陰影貼圖抗鋸齒!

    未來的改進方向:

    • 更好的分區方案?雖然嘗試了很多方法,但可能有更好的算法在實踐中運行良好。
    • 解決投影鋸齒的混合算法。在誤差高的地方使用更昂貴的算法。

    Toy Story 3: The Video Game Rendering Techniques是介紹了迪斯尼的游戲玩具總動員3所使用的SSAO、環境光及陰影等渲染技術。

    文中提到,SSAO面臨的挑戰及對應的解決方案如下:

    • 如何以及在哪里采樣。

    使用線性積分。

    藍點仍然是正在采樣的像素,想象一個圍繞它的概念體積球體,是在2D中采樣而不是在3D中采樣。每個樣本都有相應的體積,并且根據樣本的深度,該體積的一小部分將被遮擋。

    使用線積分,每個樣本都會產生一小部分遮擋與非遮擋,因此遮擋量會*滑地變化。

    線性積分的算法過程:采樣 (x,y) 坐標,計算沿 [0,1] 的距離,樣本的對應行是將該數量乘以相應的體積以獲得樣本的遮擋貢獻。

    • 如何偽造以獲得更多樣本。

    使用2D隨機旋轉。先創建具有2D旋轉的紋理(使用4x4 G16R16F紋理來編碼每個角度的正弦和余弦)。

    以4x4紋理編碼的旋轉:

    具有4x4偏移的順序旋轉:

    為了避免旋轉的樣本過于集中或規則,可以隨機旋轉:

    隨機旋轉后的效果:

    還可以對旋轉添加抖動(下圖左沒有抖動,右添加了抖動):

    有沒旋轉樣本的對比(左無右有):

    • 如何處理大距離的深度差。

    以往(如CryTek)的做法是對于大距離深度差的AO直接設為0,但問題是如下圖所示的*面應該被1/2遮擋,因此將不可用的樣本設置為零會使結果偏向過于未遮擋,從而導致光暈。

    當*面與視圖*面*行時,使用0.5效果很好,但會因傾斜表面而失效。在下圖中,結果將是遮擋太少,如果遮擋物在另一側,則會導致遮擋過多。

    為了能夠以估算丟失的樣本,需要對采樣模式施加約束,即每個樣本都是一對的一部分(配對采樣),解釋了之前看到的奇怪的“獵戶座”采樣模式:

    在環境光方面,輻照度光源使用了SH,每個軸沿 +/- 的一個定向光,單色環境光,僅用于環境照明。可實時調整,負光源(negative light),SH可以實時混合。其中負光源主要用于從負y方向指向上方的光,使光源的底部變暗,并給一切帶來了輕微的陰影。

    渲染Wii上的環境光采用SH,且每一幀在視圖空間中生成一個可以用法線查找的球面貼圖。環境存在局部區域的問題,某些地方漏光導致過亮,原因是每個世界只有一個環境配置(ambient rig),預期的效果是無論位置如何,一切都呈現在相同的氛圍中。可能的解決方案是在兩個環境配置之間混合,基于相機距離的混合,根據位置切換環境配置,烘焙環境照明,實時輻射度或全局照明,指定的環境光。

    但沒有采取以上方法,而是增加約束:只有兩種類型的光源,即暗光源和亮光源。

    該解決方案的工作原理是將體積渲染到延遲緩沖區中,并根據緩沖區的值在明暗環境綁定之間進行混合。主要優點是藝術家可以更好地控制環境照明。

    延遲技術的步驟:

    • 使用體積。
    • 將體積渲染到單獨的渲染目標中。
      • 輸出顏色表示從體積中心到像素的距離。
    • 在主場景通道中混合亮的和暗的環境色。

    上述的體積包含了多種幾何類型和操作:立方體、球體、旋轉和縮放。

    下面是有無使用體積的對照圖:


    在陰影方面,文中還分享了沒有光照貼圖的動態陰影、用于主角的投射陰影(Drop Shadow)、柔和并保留陰影的形狀、以犧牲整體距離為代價*距離獲得更高質量的陰影。

    在柔和并保留陰影的形狀方面,傳統的方案是以絕對最高分辨率渲染陰影貼圖、添加過濾以減少瑕疵。但存在需要更柔和的陰影,ToyStory 3包含多達300萬個頂點的場景,在300m的距離上拉伸4個級聯非常昂貴,有限的LOD和遮擋剔除技術。ToyStory 3也考慮過虛擬陰影圖(Virtual Shadow Map,VSM),但VSM也在當時也存在諸多限制,如模糊高分辨率陰影貼圖過于昂貴,沒有 2x 深度寫入,藝術家不喜歡的視覺效果,漏光很難管理等,最終未被采納。最終采用了組合解決方案:3個640x640陰影貼圖、4x4的高斯PCF、5x5交叉雙邊濾波。(下圖)

    此外,ToyStory 3還采用了延遲陰影(Deferred shadow)的技術,R通道存儲SSAO,G通道存儲世界陰影,B通道存儲角色陰影。

    延遲陰影著色步驟如下:

    • 渲染全屏四邊形。
    • 從視圖空間深度緩沖區重新生成世界位置。
    • 包圍盒級聯選擇。
    • 動態深度偏差計算。
    • 4x4 高斯PCF到最終陰影值。

    文中還對陰影的條紋、深度偏差等瑕疵進行了優化和改善。

    Real-Time Order Independent Transparency and Indirect Illumination Using Direct3D 11分享了基于DX11的OIT透明渲染和帶有間接陰影的全局光照技術。間接陰影可以幫助感知場景中發生的細微動態變化,為深度感知添加有用的提示,場景像素上的間接光貢獻更準確,當環境光線昏暗或動作發生在遠離直射光的情況下,這對于視覺體驗和游戲玩法尤其重要。

    Dynamic lighting in GOW3講述了游戲戰神3(God of War III)所使用的動態光照技術,包含環境光、點光源、定向光等。

    其中環境光被組合成一個RGB插值器,該文并不涉及。點光源和定向光表示為混合頂點光源(Hybrid vertex light)

    混合頂點光源可支持1個與像素燈相同的光源,也支持多光源:計算每個頂點的距離衰減,每個頂點組合成一個聚合光,插值每個像素的聚合光位置,在片元程序中執行\(N\cdot L\)\(N\cdot H\)等,就好像有一個單像素光一樣。

    在插值任意三角形兩個點的位置時,使用默認的插值會產生錯誤的結果,需要特別處理光源方向:

    對于光源的衰減函數,希望它是光滑的、便宜的,希望一階導數接*0,因為函數本身接*0。下圖是相同的定向燈直射而下的衰減,右邊是文中采納的衰減,衰減函數設置為在相同距離處達到零:


    為了更好地表示聚合的光源,從每個世界光位置減去世界頂點位置以創建相對向量,計算長度和重量(記住兩個燈的光強度都是 1),將相對向量乘以權重以進入方向域,添加燈光方向,并累積權重,將聚合方向乘以累積權重以返回位置域,最終得到了聚合光的相對光向量,將頂點世界位置添加到它以獲得聚合光的世界位置。

    相關的計算公式、符號說明、圖例如下:

    解決計算聚合光位置的方法,選擇了合適的衰減函數之后,需要解決背面光源,因為光源是在不考慮陰影的情況下聚合的,消除背向頂點的燈光的貢獻很重要。采用以下公式:

    接下來還需要解決不同方向的兩個光源的過渡問題。假設下圖是完全對稱的,在片元程序中計算的N dot L將正好是1,比A或B處的N dot L值高得多,因此將在P處看到意想不到的亮紫色高光:


    修復以上問題的過程是:在片段程序中,得到從頂點插值的光位置,然后計算光向量并在計算 N dot L 之前對其進行歸一化,如果插值光向量短于閾值,則停止歸一化,可以很好地解決上述問題。

    接下來處理聚合光源顏色的問題。用于計算“物理上正確”值的數據丟失,需要解決一些在片段程序中插值時會給出合理結果的東西。計算聚合光位置,計算歸一化光方向,計算點積:



    相加多個向量,其結果的長度等于投影的總和:

    最后擬合出的公式如下:

    擴展到RGB:

    GOW3實現時,在EDGE作業中將每個頂點的光照計算作為自定義代碼運行,高度優化,仍然保持PPU版本運行以供參考和調試。

    [Physically-based Lighting in Call Of Duty: Black Ops](http://advances.realtimerendering.com/s2011/Lazarov-Physically-Based-Lighting-in-Black-Ops (Siggraph 2011 Advances in Real-Time Rendering Course).pptx)陳述了在不斷發展的使命召喚圖形的背景下基于物理的照明和陰影以及經驗教訓。

    COD的運行時光照策略是:所有主要照明都在著色器中計算,每個主要的運行時陰影貼圖會覆蓋相機周圍半徑中的烘焙陰影。因此,主要可以改變顏色和強度,移動和旋轉小范圍,仍然看起來正確,靜態和動態陰影很好地融合在一起。

    對于漫反射,主要漫反射使用經典的蘭伯特項,由陰影和漫反射反照率調制。次級漫反射由具有逐像素法線的光照貼圖/光照網格二次輻照度重建,由漫反射反照率調制。

    對于鏡面反射,主要鏡面反射使用微*面BRDF,由陰影和“漫反射”余弦因子調制。次級鏡面反射從具有逐像素法線和菲涅耳項的環境探針重建,也與二次輻照度相關,基于與主要高光相同的BRDF參數。

    采用模塊化方法,早期實驗性使用Cook-Torrance,然后嘗試了不同的選項以獲得更逼真的外觀和更好的性能,由于BRDF的每個部分都可以單獨選擇,因此嘗試了各種“樂高積木”(即組合)。

    其中,D(法線分布)采用了Beckmann方程:

    F(菲涅爾)采用了以下方程:

    G(幾何遮蔽)采用了Schlick-Smith聯合公式:

    對于環境貼圖,以前有幾十個環境探針來匹配照明條件,由于內存限制,分辨率低,過渡問題、鏡面反射流行、大型網格的連續性。對于Black Ops,希望解決這些問題,并擁有更高分辨率的環境貼圖來匹配高鏡面反射指數。解決方案:

    • 歸一化(Normalize)——通過捕獲點的*均漫射照明來劃分環境貼圖。

    • 去歸一化(De-normalize)——將環境貼圖乘以從光照貼圖/光照網格中重建的每個像素的*均漫反射光照。

    歸一化允許環境貼圖更好地適應不同的光照條件,戶外區域只需一張環境圖就可以逃脫,室內區域需要更多特定位置的環境貼圖來捕捉次級鏡面光照。使用AMD/ATI的CubeMapGen預過濾和生成Mipmap,HDR角度范圍過濾,面邊修正。

    根據材質光澤度選擇mip:

    texCUBElod(uv, float4(R, nMips - gloss*nMips));
    

    對于非常光滑的表面,可能會導致紋理損壞,某些GPU具有獲取硬件選擇的mip的指令。環境貼圖“菲涅耳”:

    但會導致高光過多,可以采用法線方差(Normal Variance)解決。方差貼圖可以直接對來自mipping法線貼圖的丟失信息進行編碼,方差圖需要高精度和額外的成本才能在著色器中存儲、讀取和解碼,如果離線將它們與光澤貼圖結合起來會怎樣?

    可以從法線貼圖中提取投影方差,總是從頂部mip中提取,最好使用NxN加權濾波器:

    添加創作的光澤,轉換為方差:

    將方差轉換回光澤度:

    這種方法解決了大部分的高光強度問題,也可以用于鏡面反射的抗鋸齒,在對環境貼圖的mip進行光澤控制時,最大限度地減少紋理損壞的機會。

    基于物理的著色相對更昂貴(ALU *均增加10-20%),使用特殊情況著色器對性能有所幫助,對于紋理綁定著色器,可以隱藏額外的ALU成本,為特定情況使用快速的Lambert著色器仍然是個好主意。

    基于物理的著色是完全值得的,使鏡面反射真正成為“下一代”,準備好在工程和藝術方面付出相當大的努力以獲得收益。

    Lighting the Apocalypse: Rendering Techniques for RED FACTION: ARMAGEDDON分享了游戲紅色兵團(Red Faction)使用的光照技術,如推斷光照。

    推斷光照(Inferred Lighting)是延遲光照的一個變種,也叫光照預通道渲染(Light Pre-Pass Rendering)。將照明與場景復雜性隔離開來,對于處理場景破壞至關重要,推斷光照 = Light Pre-Pass++,可調照明分辨率、支持MSAA、Alpha照明。

    1年后,Lighting & Simplifying Saints Row: The Third分享了推斷照明的最新迭代,新增了幾項優化和功能,以及自動化LOD管線,包含網格簡化和實際執行問題。

    原來的推斷照明支持許多完全動態的光源、集成Alpha照明(無前向渲染)、硬件MSAA支持(即使在 DX9上)。而本文在此基礎上新增了雨滴照明(需要IL)、更好的樹葉支持(僅適用于IL)、屏幕空間貼花(由IL增強)、徑向環境光遮蔽 (RAO)(由IL優化)。詳見14.4.4.1 Inferred lighting小節。

    對于LOD,以前的方法是主要由藝術家創作,耗時,實際創建的LOD并不多,大多選擇淡入“細節集”。新方式實現了全功能網格簡化器,在crunchers中運行,而不是在DCC應用程序中運行。大部分是自動生成的LOD,但藝術家可以調整:建筑物、人物、車輛,完全自動化(無藝術家干預):地形,還使用簡化器生成地形碰撞船體、構建陰影代理。

    網格簡化主要使用誤差度量,誤差度量衡量網格*似的“糟糕”程度,用于計算收縮誤差,確定哪個邊先收縮,放置結果頂點的位置。二次誤差度量概述:

    實現的過程中可能出現UV邊界的拉伸問題:

    原因是UV不連續:

    可以采用UV鏡像,以便讓邊界不那么明顯:

    更好的做法是保持邊界,以相同方式保留任何類型的邊界,通過邊界邊緣添加“虛擬”*面:

    連續區域:在每個頂點,跟蹤具有連續UV的區域。連續區域問題是UV可能在頂點處是連續的……即使地區是分開的

    材質數量:隨著LOD變得更簡單,材料成本占主導地位:

    減少材質數量:積極尋找“小”面積材質,更換為同一網格上使用的較大材質,數量有所減少,但不會有大的節省。

    補充細節層次,可將每個可流區域烘焙成單個網格,更加簡化(大約是原始頂點的 5%),用頂點著色替換幾乎所有材質。

    CSM Scrolling: An acceleration technique for the rendering of cascaded shadow maps講述了一種陰影優化技巧,通過滾動CSM和區分動態、靜態物體來優化CSM的渲染。

    對于陰影,大多數時候,相機不會跨幀進行徹底的改變,大多數幾何圖形在幀之間是相對靜態的。可以識別從前一幀發生變化的幾何圖形,跨幀的光線方向相對穩定,空間查詢的結果可以與陰影渲染在同一幀中使用,幾何被分成小實例。CSM滾動步驟可結合下圖加以說明:

    1、在緩存陰影圖中存儲來自上一幀的靜態幾何體。

    2、滾動緩存陰影圖以匹配相機視圖的變化。

    3、在滾動過程中暴露的邊緣中渲染額外的靜態幾何體(比如數字3旁邊陰影圖右上側的圓柱體),然后在緩存區域中渲染新的靜態幾何體(比如數字3旁邊陰影圖左上側的圓形)。

    (以上階段都是在持久的緩存陰影圖中操作,以下階段則是在臨時的當前幀陰影圖中操作)

    4、復制緩存陰影圖到當前幀的最終陰影圖。

    5、渲染非靜態幾何體到當前幀的最終陰影圖。

    現在假設相機沒有移動或移動很少,則涉及4個主要階段(下圖數字標注)。

    • 將前一幀的“靜態”幾何圖形存儲在緩存地圖中(“靜態” = 在 t 時間內沒有移動,例如5秒)。(1和2)
    • 每幀將非靜態幾何圖形渲染到緩存副本。(3和4)
    • 但是,上一幀的陰影圖緩存在相機移動、相機FOV變化、“靜態”幾何體移動的情況下會失效。涉及階段1。
    • 解決方法是針對階段2中新的靜態幾何體(紅色圓形):
      • 在緩存區域中渲染新的“靜態”幾何圖形。
      • 查詢“靜態”幾何體的狀態,區分當前“靜態”與以前“靜態”查詢結果。
      • 使用動態遮擋系統。
    • 創建復制新的陰影圖緩存以使用此幀。(3)
    • 渲染動態”幾何體到臨時陰影圖。(4)

    現在假設相機移動了很多(但很慢),此時將涉及5個階段(下圖數字標注)。

    • 插入CSM緩存:滾動陰影圖,渲染到暴露的邊緣。(2、3)

    • 滾動緩存陰影圖以考慮相機視圖的變化。(2)

    • 從前一幀采樣陰影紋理。(2)

    • 滾動區域是鉗制到邊框的(下圖白色區域)。(3)

    • 由于相機運動是3D,涉及橫向滾動、深度滾動。(2)

      • 橫向滾動:垂直于光線的*移。

        // input是已在delta相機在光源坐標系中轉換的UV。
        float ScrolledDepth_LateralOnly(float3 input)
        {
            float2 uv = input.xy;
            // 簡單的紋理查找(點采樣)
            return SampleShadow(uv);
        }
        
      • 深度滾動:*行于光線的*移。

        // input是已在delta相機在光源坐標系中轉換的UV。
        float ScrolledDepth(float3 input)
        {
            float2 uv = input.xy;
            // 深度滾動需要額外的處理. input.z是光源坐標系中的相機深度的差異值。
            float depth_offset = input.z;
            float old_depth = SampleShadow(uv);
            // 抵消所有先前的深度(滾動深度)
            float new_depth = old_depth + depth_offset;
            // 防止深度超出遠*面。
            return (old_depth < 1.0f) ? new_depth : 1.0;
        }
        
    • 在滾動過程中暴露的邊緣中渲染額外的靜態幾何體(右上側紅色圓柱體)。(3)

      • 滾動區域被劃分為板塊(薄的OBB,下圖)。

      • “靜態”幾何體具有重疊邊界體積:

      • 幾何體相對于視圖的粗糙度:

      • 有的具有大量的重疊體積(下圖左),有的只有少量的重疊體積(下圖右),有的具有明顯的鋸齒(下圖中):

    • 在緩存區域中渲染新的“靜態”幾何體。(3)

    • 復制陰影圖以用作當前幀的最終陰影圖。(4)

    CSM的滾動涉及2、3、4,渲染效果如下:

    總之,直接添加到CSM緩存,關鍵是像2D位圖一樣滾動,可以減少約70%靜態幾何體渲染到CSM。

    Practical Physically Based Rendering in Real-timeBeyond a simple physically based Blinn-Phong model in real-time詳細闡述了實時渲染領域的PBR的理論、依賴知識、特點、實現及優化。

    實時PBR使整個渲染管道基于當前控制臺的物理基礎,包含以下幾點:

    • 基于物理的著色模型。基于物理的BRDF模型
    • 基于物理的光照。基于物理的量,膠片模擬(基于頻譜的色調映射)。
    • 基于物理的相機模擬。基于真實相機系統的鏡頭模擬。

    基于物理的光照要求使用的物理量:

    • 正確的色彩空間。
      • 在光譜域 (380nm – 1000nm) 處理膠片模擬(色調映射)。
      • 基于真實電影資料的電影數據庫,曝光、顯影、復制、打印和投影。
    • 基于瓦特。
      • 其他單位(勒克斯、流明、色溫)在引擎中轉換為瓦特。
      • 光源面積。用于延遲和前向光源的基于圖像的照明和偽光源大小,金屬或光澤物體不再需要環境貼圖著色器。

    基于物理的相機模擬要求基于真實相機系統:

    • 基于鏡頭數據庫的光學模擬:
      • 真正的散景模擬。
        • 基于透鏡方程。真實的相機參數和鏡頭,對焦模擬。
        • 孔徑模擬。葉片數、圓形光圈、光圈機制。
        • 暗角。桶形暗角,光學暗角。
      • 支持其它光學效果。

    基于物理的著色模型(已實現或正在研究的模型):

    • 基于物理的 Blinn-Phong。
      • 各向同性、各向異性、光譜。
      • 布林-貝克曼。
    • Ashikhmin。
    • 分層材質。
    • Oren-Nayar、改良的Oren-Nayar。
    • 逆向反射材料。
    • 其它特殊材料。Marschner、金屬、玻璃、印刷、NPR。

    基于物理的Blinn-Phong:


    文中還詳細地剖析了基于物理的IBL的理論、公式、推導及實現。


    基于物理的IBL公式推導及*似。


    輻射率環境圖(REM)的生成過程。


    IBL效果。


    鏡面AO及效果對比。

    不同漫反射模型的效果和性能對比。

    Realtime global illumination and reflections in Dust 514講解了利用特殊的高度場光線追蹤來完成間接光和反射的計算。下面三幅圖像從左到右分別顯示了原始環境、理想的卷積環境和實時*似:

    理想的間接項是使用前面描述的朗伯卷積離線計算的,計算需要幾秒鐘。右側是錐形軌跡*似,在像素著色器中實時評估。過渡是基于表面法線的Z/up分量的天空顏色和地面顏色之間的線性混合;底色是通過對具有大量mip偏移的地面紋理進行采樣獲得的。毫無疑問,可以通過花費更多周期來建立更好的*似值。

    無論如何,關鍵是有一種方法可以為空間中的任何點和任何表面法線方向提供間接項,為所有環境提供了一個解決方案,可以用一個*面水*面和一個天空立方體來*似。

    在對高度場進行光線追蹤時,將光線與一個水**面相交,該*面的高度是通過對光線原點下的高度場進行采樣來確定的。(下圖)

    另一種方法是使用單獨的偏向光線進行向上和向下跟蹤,因此從不顯示水*光線跟蹤的結果。

    單樣本光線追蹤步驟在大多數情況下運行良好,但在某些情況下完全失敗,例如下圖的橫截面。黑色矩形表示一個物體,例如一座橋; 橙色線是從下方渲染場景時生成的高度場。左下角的箭頭表示一些樣本射線,例如從下方經過的車輛頂部的反射。問題是當光線原點在屋頂下傳播時,交點會不連續地變化,在該車輛的頂部,橋邊緣下方的反射會出現明顯的不連續性。雖然仍然可以對不準確的交點進行合理的反映,但不連續性非常明顯并且顯然是錯誤的。

    解決方案是對高度場應用后處理,以確保高度不會突然變化,將產生下圖顯示的橫截面。意味著反射永遠不會有不連續性,因此會得到更合理的反射,還具有一步光線跟蹤*似的好處。高度場過程是增量完成的; 高度場需要多次通過才能收斂到此處顯示的結果, 這是它的工作原理。

    高度場細化過程:

    整個跟蹤過程:計算上/下偏向光線向量,在射線原點采樣壓縮高度場,計算每一層的交點,計算每一層的mip偏差,采樣四層紋理和天空紋理,合成結果以產生上下顏色(天空、天花板、下面的橋梁、地板、上面的橋),根據查詢光線方向混合向上/向下顏色。

    細化:時間切片分層更新,為陰影重用CSM,邊緣淡出,量化運動。

    總之,提供通用間接項,提供具有可變模糊的通用反射項,快速,不支持任意高度復雜度的場景,一般情況下,無法應對墻壁和垂直表面,動態對象不會促成間接或反射,反射質量有限。

    三重緩沖的目標是GPU永遠不會停止等待頁面翻轉發生,并且翻轉發生在vblank上,因此不會出現撕裂。

    兩個全高清緩沖區A和B之間的放大和累積步進乒乓球。由于以30fps運行,并且只在幀的最后寫入全高清緩沖區,因此要求沒有限制寫入之間至少有16.6毫秒的間隔。一旦完成了對其中一個緩沖區的寫入,就請求在下一個vblank上翻轉。在正常的事件過程中,它會在主720p渲染期間的某個時間點發生,當準備開始放大下一幀時,可以確信翻轉將會發生。

    需要一個柵欄來強制執行這種延遲,因為在RSX幾乎無事可做的情況下,例如菜單和加載屏幕。從三個720p緩沖區變為兩個1080p緩沖區和一個720p緩沖區,因此所需的額外內存約為9MB。

    累積步驟:如果確定正在處理的當前像素是靜態的,那么需要找出所有低分辨率樣本(下圖的紅點)落在正在處理的高分辨率像素的區域內。如果有,希望將其混合到運行*均顏色中,如果沒有,則保持當前像素不變。

    累積的效果對比:


    [Rock-Solid Shading](http://advances.realtimerendering.com/s2012/Ubisoft/Rock-Solid Shading.pdf)闡述了PBR的基礎理論,分析了導致著色失真的問題,以及如何提升材質的真實可信度。

    讓東西看起來不錯的因素有穩定、干凈、沒有鋸齒,材質類型的表現力,簡單、直觀的模型。主要工具:Blinn-Phong、Banks、Ashikhmin-Shirley,大多數材質無論是否風格化都可以用簡單的BRDF表示。

    當前的著色模型存在的問題:物體太亮。鋸齒:采樣問題可能會導致法線突然成為亮點,閃閃發光,在 HDR 綻放過程中導致失真,亮點可能完全錯過,防止使用高的鏡面指數,經常會看到使用環境貼圖完成的規范以獲得清晰的亮點。

    為什么離線渲染不會出現以上問題?采樣率經常被鎖定——例如REYES,即使是錯誤的,樣本也是幀到幀的,從方程中消除了大部分時間鋸齒。一切都過采樣,每個像素一百個樣本并不少見,在無窮大采樣大多數問題都會消失。解決了嗎? 不,但蠻力使之減緩。

    沒有達到目標的原因:不穩定——分辨率對大尺度效果影響很大,鋸齒——時間和空間,缺乏表現力——無法使用廣泛的力量。如何正確實現Blinn-Phong?可以做一個基于紋理的照明方法——一個la REYES,可以找到一個類似的BRDF,真實地表現自己嗎?

    LEAN映射的機制。LEAN(Linear Efficient Anti-aliased Normal Mapping)是線性高效抗鋸齒法線貼圖,在Sid Meier的文明V中應用過,為未來的所有資產生產進行部署。好處:時間穩定,分辨率穩定,可以使用高鏡面指數(例如10000+),Blinn-Phong內容可以輕松轉換,自動各向異性。

    給定凹凸貼圖的法線:N = (N.x, N.y, N.z),然后創建另一個圖M:M = (B.x^2, B.x*B.y, B.y^2),其中: B = (N.x/N.z,N.y/N.z)。不是冗余數據,需要這些項的線性過濾版本!

    存儲5個通道:X、Y與中心凹凸的偏移量,可以是8位,\(X^2\)\(Y^2\)\(X*Y\),如果想要很好的高鏡面反射功率,需要16位。壓縮也許可行,但由于使用了線性過濾,不能犧牲它。接下來要介紹的中間解決方案。

    從Blinn Phong初始化內容:沿\(X^2\)\(Y^2\)項添加的基本鏡面反射功率s為1/s,所以M映射$ (X^2, Y^2, XY) $變為 \((X^2 + 1/s, Y^2 + 1/s, XY)\),存儲逆冪意味著需要16位精度來獲得大于256的冪,觀察:即使使用Blinn-Phong,將功率存儲為1/s也會導致MIP濾波器正常運行。

    它與Blinn-Phong的*似程度如何?對于低power(例如 < 16),LEAN映射的響應與Blinn-Phong不同,可能需要重新調整一些內容

    清晰映射:5個值可能開銷大:修改,丟棄各向異性:

    存儲3個值:X、Y、(X^2 + Y^2)/2,只有 (X^2 + Y^2)/2需要以高精度存儲。

    對于高光過亮的問題,在最小化濾鏡下高光更穩定;對于之前提出的鋸齒問題,無鋸齒。

    達到目標。穩定:渲染的分辨率不影響大尺度效果,抗鋸齒:線性硬件濾波器工作正常,表現力:可以使用大功率,并支持各向異性。還有一些問題:在低功率下與Blinn-Phong的分支,存儲空間要求更高。

    接下來闡述著色器鋸齒匿名(Shader Aliasing Anonymous)。選項:紋理空間著色,關鍵思想是MIP照明!但開銷大,用虛擬紋理緩存?可選項1: 擬合,關鍵思想是找到最合適的參數,例如法線、粗糙度、反射率,緩慢、脆弱、不連續。可選項2:直接方差估計,關鍵思想是估計方差 -> 新的粗糙度:





    烘焙方差過程:對于每個MIP:

    • 對雙線性應用小過濾器。
    • 計算方差。
    • 存儲結果:
      • 直接,或調整光澤圖。
      • 允許編輯?

    調整光澤度:多種多樣……鏡面AA “無處不在”!

    • 動態反射:生成MIP,MIP偏移的查找,或是DX11:可變高斯? 圖像空間收集?
    • 體素錐追蹤 [Crassin 11]
    • 反射公告牌 [Mittring11]
    • 反射遮擋?

    選項:預過濾(LEAN),更好:更準確的結果,各向異性效果。

    缺點是內存、額外的著色成本、切線空間。

    LEAN的雙變量法線分布:

    可視化圖:

    LEAN的內存,烘焙,和Toksvig 一樣……雙線性模擬仍然很重要!存儲協方差矩陣:[Σx, Σ y, Σz]?

    可能有精度問題。可改成存儲兩個光澤值:

    可以使用BC5或DXT5,可選擇存儲相關性:\(\rho = \cfrac{\sum_z} {\sqrt{\sum_x \cdot \sum_y}}\)

    除了LEAN,還涉及詳細法線貼圖、幾何物體、漫反射、環境貼圖。

    其中幾何物體鋸齒(Geometry AA)是另一個方差來源!

    想法1——預過濾幾何法線:擴大,生成MIP,使用Toksvig,需要圖集!

    想法2——像素四邊形消息傳遞 [Penner11A],訪問鄰居?*均?方差,*均代碼:

    float2 dir = 0.5 - frac(vpos*0.5 - 0.25)*2;
    
    float3 n0 = N;
    float3 n1 = ddx_fine(n0)*dir.x;
    float3 n2 = ddy_fine(n0)*dir.y;
    float3 n3 = ddy_fine(n1)*dir.y;
    
    float3 nn = n0 + n1 + n2 + n3;
    

    想法3——來自 Kaplanyan & Valient:結合法向錐(曲率)和鏡面反射波瓣錐,將規格功率轉換為錐角,添加曲率角:

    float3 dN = fwidth(N);
    float3 new_normal = normalize(N + dN);
    float curvature = acos(dot(new_normal, N))/(pi*0.5);
    

    轉換回新的power,已投入實際應用中!優化后的代碼:

    float3 dN = fwidth(N);
    float3 new_normal = normalize(N + dN);
    float curvature = sqrt(1 - dot(new_normal, N));
    
    float angle = 4.11893/sqrt(power) + curvature;
    power = 16.9656/(angle*angle);
    

    類似的結果。下面是不同方法的著色效果對比:

    當前的漫反射存在誤差,原因是完整的漫反射積分方程是:

    而實際上,目前使用的漫反射積分方程是:

    也就是忽略了\((n_a\cdot \bold l)\)的積分。實際上,如果要正確計算漫反射,可以使用完整積分的等同變體:

    漫反射積分的解決方案:法線方差?圓錐,*均法線 (Na) 周圍法線錐的光照積分,錐角公式:cos(θ) = 2*length(Na) - 1

    可以預計算積分,就像預積分的皮膚著色 [Penner11B]一樣:

    float len = length(Na);
    float3 N = Na/len;
    tex2D(LUT, float2(dot(N, L)*0.5 + 0.5, len));
    

    結果樣例:

    縮小LUT,重要區域:0~25度(下圖)。

    也可以避免 LUT,使用曲線貼合:\(x^2\)

    float DiffuseAA(float3 N, float3 L)
    {
        float a  = dot(N, L);
        float w  = max(length(N), 0.95);
        float x  = sqrt(1.0 - w);
        float x0 = 0.373837*a;
        float x1 = 0.66874*x;
        float n  = x0 + x1;
        return w*((abs(x0) <= x1) ? n*n/x : saturate(a));
    }
    

    約18 條指令 (fxc) ,可以說太合適了,獲得了大致*似。但有類似Toksvig的問題:壓縮法線,存儲預過濾的長度/方差。

    另外,LEAN還可用于環境圖中:

    Calibrating Lighting and Materials in Far Cry 3闡述了育碧的Far Cry 3的基于物理的光照模型及優化。

    為了獲得更加接*物體本身的基礎色(反照率),Far Cry 3使用數碼相機對物體進行掃描,然后刪除光照和鏡頭畸變,獲得了更加物理的反照率貼圖。

    捕捉漫反射反照率時,使用麥克白顏色檢測器(Macbeth ColorChecker)作為參考,由X-Rite制作,有24個已知sRGB值的色塊。

    ColorChecker旁邊的照片材質,照明必須一致,使用ColorChecker的塊尋找變換。

    變換有兩種:仿射變換(Affine Transform)和多項式變換(Polynomial Transform)。仿射變換的優點是消除通道間的串擾,缺點是線性變換。多項式變換的優點是精確調整級別,缺點是通道獨立。

    顏色校正工具:命令行工具,由Photoshop腳本啟動,在xyY顏色空間中運行,應用以下變換[Malin11]:

    下面是校正前(左)后(右)的對比:

    天空的著色模型采用了CIE的模型:

    光照模型也是Cook-Torrance,其中D項是:

    F項有兩種:Schlick*似和球面高斯*似:

    可見性項:

    然后簡化了G項:

    為了減少鏡面反射的鋸齒和保留其細節,采用了Toksvig的公式來縮放鏡面power:

    將這些縮放數據存儲在紋理中,理想情況下,用于調整光澤貼圖。添加額外紋理的成本太高,不是每個著色器都使用光澤貼圖,無法將Toksvig貼圖與現有光澤貼圖組合。DXT5壓縮法線貼圖中存在自由通道,藝術家在法線貼圖的alpha通道中繪制光澤,將光澤與Toksvig組合儲存在R通道中:

    法線的y分量的壓縮受到影響:

    光澤貼圖是可選的,如果有,便與Toksvig結合,如果沒有,將Toksvig*均為單個值。

    Deferred Radiance Transfer Volumes: Global Illumination in Far Cry 3詳細介紹了FarCry 3中使用的動態全局照明的*似值,分為兩部分:首先是理論概述和離線預計算,其次是實時渲染實現和著色細節。使用稀疏體積的輻射傳輸探針,每個探針存儲球諧系數矩陣,可以在運行中重新照亮探針,從而支持在一天中的時間變化下的照明,以及槍口閃光和爆炸等局部照明。使用重新照亮探針的著色是在屏幕空間中的GPU上完成的。使用混合CPU/GPU實現來確保系統在當前一代控制臺上運行良好,在內存和性能方面都是高效的,提供詳細的性能統計數據以及代碼片段來說明系統的內部結構。

    延遲輻照亮度傳輸體積的特點是*似全局光照,輕量級、主機友好,實時重照明,混合CPU/GPU。其中全局光照是低頻的輻照亮度傳輸,支持太陽和天空反彈及天空直接光。

    實時重照明是用太陽/天空顏色更新的全局光照,支持一天的周期時間,直接的藝術家反饋。

    整個系統的概述。

    離線時,將探針放置在世界中并為它們預先計算輻射轉移,這一過程也稱為烘焙。在游戲中,每當光照環境發生變化時,都會實時重新點亮探測器。將動態生成新的輻照度值插入到許多體積紋理中,然后GPU使用這些來遮蔽屏幕空間中的所有內容。探針烘焙時,預計算輻射傳輸和天空的能見度。

    PC的局部輻射亮度傳輸:來自動態光源的全局照明,假設光源與探針處于同一位置,在每個探針位置存儲白光PRT。

    實時重照明:由太陽和天空驅動的照明,投影到二階SH,藝術家創作梯度。

    計算每個探針的光貢獻,結果是顏色/強度數組,每個基本方向都有一種顏色,可以添加更多的基礎方向,以獲得更好的準確性。

    體積紋理:GPU快速濾波,可以逐像素完成,適用于大型對象。連接到第一人稱攝像機,體素是四個基礎強度,96x96x16的RGBA,完全更新約7ms,占用5個SPUS。時間攤銷:只更新那些沒有數據可用的,使用包裝避免偏移現有的切片。

    體積紋理環繞:環繞采樣器狀態昂貴,模擬具有frac()的環繞,用于正確過濾的重復邊界。

    環境照明方面考慮了室內、遠距離環境等因素。

    下圖是PS3的實現概覽:

    Practical Clustered Shading介紹了分簇著色的特點和實現。

    分簇著色(Clustered Shading)是Olsson、Billeter、Assarsson等人在HPG 2012的論文Clustered Deferred and Forward Shading提出的一種擴展了分塊著色的技術。它的特點是實時、魯棒性好,支持的光源多,與視圖低相關,可以處理嘈雜的深度分布。

    分簇著色帶來的海量光源,可帶來全局光照、復雜的光源類型(如面光、體積光)、藝術家無約束、帶光照的特效等。假設有以下的樣例場景:

    對于分塊著色,支持延遲或前向,簡單,某些情況快速,2D分塊。

    分塊著色(Tiled Shading)的最大問題在于游戲是3D,而它是在2D*面上分塊,導致單個塊會和改塊在深度上的所有光源相交,即便較遠的光源被前面的物體遮擋!簡而言之,分塊是2D,幾何樣本、片元/像素是3D,光密度也是3D,視圖依賴,不可預測的著色時間。

    分簇著色(Clustered Shading)的核心思想是增加第三維,也在深度方向分塊 = 分簇,也可大于3維(例如法線)。

    分塊和分簇在空間上的分布如下兩圖:


    分簇著色的步驟如下:

    • 光柵化G緩沖區。前向:pre-z通道。
    • 分簇分配。

    分簇鍵:$ck = (i, j, k) \(的整數元組,i, j = 2D分塊的id,即gl_FragCoord.xy,\)k = \log(z_\text{viewSpace})$。

    • 尋找唯一的簇。

    全屏通道,標記使用的分簇,讀取深度,計算ck,將網格中的單元格設置為1。向前:具有副作用的幾何通道。

    精簡非零的分簇值,獲得非空簇的列表,并行前置和Compute Shader。

    • 將光影分配給簇。

    許多簇和光源,可分層方法:光源的層次結構、32叉樹(匹配GPU的SIMD),在GPU上動態重建,用BV測試給每個簇遍歷光源樹。

    • 著色視圖樣本,延遲:全屏通道,前向:幾何通道。

    以下是在Crytek Sponza場景有樹有10000+光源的情況下,不同方法的性能對比圖:

    上圖顯示,Tiled著色受著色時間支配,即使使用非常簡單的像素著色器也是如此,意味著提高tile速度不會太大改變結果。另一方面,分簇著色在著色和算法的其它部分之間更加*衡。圖中還顯示了三個基于法線的更復雜剔除的變體,然而測試實現中沒有得到回報,因為更復雜的剔除和更多簇的成本超過了著色成本的降低。隨著更昂貴的著色和更好的分簇和剔除實現,它們可能仍然值得。

    分塊前向著色(Tiled Forward Shading)可用于透明物體,存在的問題是視圖依賴、退化為二維、全屏不連續!

    分簇前向著色(Clustered Forward Shading)在預幾何通道執行,標記使用的分簇,片元著色器中的副作用,將網格中的單元格設置為1。

    以下是分簇前向著色的性能數據:

    總之,分簇著色高性能,低視圖依賴性,良好的最壞情況性能,全動態,支持透明度,支持前向或延遲,或兩者兼而有之。潛在的優勢有:樣本的快速體素化,如陰影、錐體追蹤的起點、*似陰影、自適應著色及其它用途。

    Moving Frostbite to Physically Based Rendering 3.0闡述了Frostbite引擎改進了PBR關照,系統地梳理了其理論基礎、相關技術、推導、優化及應用。PBR的范圍包含了光照、材質和相機(之前的關注點只在光照和材質上)。

    80%的外觀類型是標準材質,鏡面反射用帶有GGX NDF的微面模型,漫反射用迪士尼模型,其它材質類型有次表面材質、單層涂層材料。其中鏡面反射的公式如下:

    下圖是GGX-Smith和Height-Correlated Smith的G項對比效果:

    上圖顯示差異很小,但對于高粗糙度值(右側)來說很明顯:

    漫反射項上,不再使用蘭伯特,用迪斯尼漫反射取而代之,因為后者使用了漫反射和鏡面反射之間的耦合粗糙度和反向反射(retro-reflection)。

    它們的漫反射對比如下圖,在低粗糙度時有點暗,在高粗糙度時更亮,很微妙,但可以帶來不同。

    最初的迪士尼漫反射項存在一個問題,即能量不守恒:在某些情況下,反射光可能高于入射光。Frostbite應用了一個簡單的線性校正,以確保當鏡面反射和漫反射項相加時,半球方向的反射率小于1。


    對于鏡面反射和漫反射的輸入值,Frostbite再次使用了Burley的*似方法,解耦了金屬和非金屬,更易于資產創作。

    Frostbite選擇向藝術家展示“*滑度”而不是粗糙度,因為白色的*滑對他們來說更直觀。還嘗試了各種重新映射函數,以獲得感知線性度,最后,再次使用了Burley的方法來*方化粗糙度。


    在光照方面,爭取光照一致性——所有BRDF必須與所有光源類型正確集成,所有光源都需要管理直接照明和間接照明,所有照明均正確組合(SSR/本地IBL/...),所有光源之間的比例都正確。光照存在多種單位和參考系,常見的光度單位制如下:

    而Frostbite使用了以上4種單位,它們的應用場景具體如下:

    Frostbite支持四種不同的形狀:球體、圓盤、矩形和管狀。每種光源都可以有一個更簡單的版本,但只有點和點使用準時(punctual)光照路徑,因為它們更頻繁,成本較低。

    下面是關于準時光、光度光、區域光的描述:




    對于IBL,關注點在*距離光照探針和遠距離光照探針。其單位和光照來源和公式如下:


    在運行時,不使用表面的鏡像方向,而采用略有偏移的主導方向(dominant direction,下圖青色箭頭),有助于提高積分*似的精度。


    對于攝像機,依舊考量了基于物理的模擬,納入將場景亮度轉換為像素值、光圈、感光元件、鏡頭、快門等因素:

    到達傳感器的場景亮度將由曝光確定,然后將曝光轉換為像素值:

    過渡到PBR的步驟:

    1、標準材質 + 觀察者優先 + 培養關鍵藝術家。

    2、PBR/非PBR并行,自動轉換。

    3、向游戲團隊宣傳PBR + 驗證工具。

    Real-time lighting via Light Linked List闡述了使用光源鏈表來實現和優化海量光源的技術。使用Light Linked List(光源鏈表,LLL)可以獲得半透明順序正確的光照效果:

    使用Light Linked List的前(左)后(右)對比。

    將光源存儲在逐像素鏈接列表中,其結構體如下:

    struct LightFragmentLink
    {
        float m_LightDepthMax; // 光源最大深度
        float m_LightDepthMin; // 光源最小深度
        int   m_LightIndex;    // 光源索引
        uint  m_Next;          // 下一個光源
    };
    
    // 壓縮版本
    struct LightFragmentLink
    {
        uint  m_DepthInfo; // 深度信息
        uint  m_IndexNext; // 下一個光源
    };
    

    分辨率越低越好:四分之一、八分之一等等…內存消耗:4個緩沖區,分辨率為八分之一:2個RWByteAddressBuffer、1個RWStructuredBuffer、1個深度緩沖區(可選),*均每像素預分配40個光源。總成本:900P:約7.25megs,1080P:約10.15megs。

    Insomniac引擎的渲染流程如下:

    其中填充鏈表的消耗如下:

    LLL的深度緩沖:生成較小的深度緩沖區,使用保守的深度選擇,使用GatherRed

    LLL的著色步驟:軟件深度測試、獲取最小和最大深度、分配一個LLL片元。

    LLL的深度測試:正面通過深度測試,背面未通過深度測試,禁用硬件深度剔除。

    // 軟件測試正面.
    // 如果正面Z測試失敗,跳過片元.
    if((pface = true) && (light_depth > depth_buffer))
    {
        return;
    }
    

    如果兩種深度都穿越,哪個深度優先?邊界的RWByteAddressBuffer,編碼深度+ID(16位ID、16位深度),

    uint new_bounds_info = (light_index << 16) | f32tof16(light_depth);
    

    使用InterlockedExchange交換新舊的邊界值:

    使用一個RWStructuredBuffer來存儲:

    struct LightFragmentLink
    {
        uint  m_DepthInfo; // 深度信息,高位是最小深度,低位是最大深度。
        uint  m_IndexNext; // 下一個光源。
    };
    RWStructuredBuffer<LightFragmentLink> g_LightFragmentLinkedBuffer;
    
    // 增加當前的計數
    // 分配.
    uint new_lll_index = g_LightFragmentLinkedBuffer.IncrementCounter();
    // 不要越界
    if(new_lll_index >= g_VP_LLLMaxCount)
    {
        return;
    }
    
    // 填充鏈接光源的片元并保存。
    // 最終輸出
    LightFragmentLink element;
    element.m_DepthInfo = (light_depth_min << 16) | light_depth_max;
    element.m_IndexNext = (light_index << 24) | (prev_lll_index & 0xFFFFFF);
    // 存儲光源鏈表信息
    g_LightFragmentLinkedBuffer[new_lll_index] = element;
    

    計算光照的步驟:繪制全屏四邊形、訪問LLL、應用光源。訪問LLL時,獲取第一個鏈接元素偏移:第一個鏈接元素以較低的24位編碼。

    uint src_index = LLLIndexFromScreenUVs(screen_uvs);
    unit first_offset = g_LightStartOffsetView[src_index];
    // 解碼首個元素索引
    uint elemen_index = (first_offset & 0xFFFFFF);
    

    啟動照明循環:等于0xFFFFFF的元素索引無效。

    while(element_index != 0xFFFFFF)
    {
        LightFragmentLink element = g_LightFragmentLinkedView[element_index];
        element_indx = (element.m_IndexNext & 0xFFFFFF);
    }
    

    解碼光源的最小和最大深度,比較光源的深度。

    // 解碼光源邊界
    float light_depth_max = f16tof32(element.m_DepthInfo >> 0);
    float light_depth_min = f16tof32(element.m_DepthInfo >> 16);
    // 執行深度邊界檢測
    if((l_depth > light_depth_max) || (l_depth < light_depth_min))
    {
        continue;
    }
    
    // 獲取完整的燈光信息
    uint light_index = (element.m_IndexNext >> 24);
    GPULightEnv light_env = g_LinkedLightsEnvs[light_index];
    switch(light_env.m_LightType)
    {
        // ......
    }
    

    對于陰影,使用紋理數組,分配子區域。

    Multi-Scale Global Illumination in Quantum Break說明了游戲Quantum Break的多種規模的全局光照,包含大規模光照和屏幕空間光照。

    文中提到可能的全局光照解決方案有:

    • 動態方法:

      • Virtual Point Lights (VPLs) [Keller97]

      • Light Propagation Volumes [Kaplaynan10]

      • Voxel Cone Tracing [Crassin11]

      • Distance Field Tracing [Wright15]

    • 基于網格的預計算:

      • Precomputed Radiance Transfer (PRT) [Sloan02]
      • Spherical Harmonic Light Maps
    • 無網格預計算:

      • Irradiance Volumes [Greger98]

    文中經過對比之后,選用了Irradiance Volumes。

    輻照度體積原理示意圖。

    全局照明體積的好處是沒有UV,適用于LOD模型,體積光照,與動態對象一致,但不適用于鏡面,由于數據量太大。混合反射探頭圖例如下:




    自動化放置探針,最大化可見表面積,盡量減少到地面的距離,選擇K個最佳探針位置:

    對于全局光照數據的存儲(如鏡面探針圖集),可選的方案有:

    • GPU體積紋理:由于壓縮,無法使用原生插值。

    • GPU稀疏紋理:對于細粒度樹結構,頁面太大,可能無法在未來游戲的目標*臺上使用。

    • 自適應體積數據結構有:

      • Irradiance Volumes [Greger98, Tatarchuk05]
      • GigaVoxels [Crassin09]
      • Sparse Voxel Octrees [Laine and Karras 2010]
      • Tetrahedralization, e.g., [Cupisz12], [Bentley14], [Valient14]
      • Sparse Voxel DAGs [K?mpe13]
      • Open VDB [Museth13]

    自適應體素樹:隱式空間劃分,64的分支因子,多尺度數據。

    體素樹結構:

    樹遍歷:

    體素樹可視化:

    無縫插值:

    文中的SSAO基于Line-Sweep Ambient Obscurance(LSAO)[Timonen2013],LSAO定位了最有貢獻的遮擋體。

    掃描36個方向,長步(~10px)和短線間距(相隔約2倍),GPU的調度友好,在Xbox One上,720p的掃描速度為0.75毫秒。對樣本增加抖動,額外的*場樣本(距離約2倍),樣本垂直于加緊的遮擋體。

    36個方向太貴,無法按像素采集,在3x3鄰域上交錯(4個方向/像素),使用深度和法線感知3x3盒過濾器進行收集。

    屏幕空間漫反射照明,LSAO樣本是“最可見的”,很適合對入射光進行采樣,無法根據定義進行遮擋(提供自遮擋)。

    效果對比:

    屏幕空間反射:GGX分布的每像素1條光線,針對所有表面進行評估,線性搜索(7步),步進形成一個幾何級數。

    深度緩沖樣本的處理,需要支持不同的粗糙度,計算圓錐體覆蓋率,需要適應遮擋和顏色采樣,還可以找到單色樣本位置。深度厚度=a+b*(沿射線的距離),深度場延伸至/自攝影機,而不是沿視圖z!

    將線性項匹配視圖空間的步長,否則,匹配實心幾何體上的孔:

    對于遮擋,計算圓錐體的最大覆蓋率,將圓錐體的下限夾緊到曲面切線!

    對于顏色,需要一個樣本位置,首先選擇覆蓋大部分圓錐體的樣品。將反射光線對準覆蓋的中心,并與最后兩個樣本之間的直線相交,低采樣密度:向相機方向插值(藍色)。

    光線上方的上一個示例:不插值。

    優化交叉點,如果相鄰光線的方向相同,交叉搜索,采取最*的命中距離。

    Hybrid Ray-Traced Shadows闡述了混合光線追蹤的陰影技術,以獲得高質量更接*基準真相的陰影效果。常規的陰影圖存在粉刺、彼得*移和鋸齒等瑕疵:

    傳統的邊界體積層次結構可以跳過許多光線三角形命中測試,需要在GPU上重建層次結構,對于動態對象,樹遍歷本身就很慢。

    存儲用于光線跟蹤的圖元,而無需構建邊界體積層次!對于陰影貼圖,存儲來自光源的深度,簡單而連貫的查找。同樣地存儲圖元,一個深層圖元圖,逐紋素存儲一組正面三角形。深度圖元圖繪制(N x N x d)包含3個資源:

    • 圖元數量圖(Prim Count Map):紋理中有多少個三角形,使用一個原子來計算相交的三角形。
    • 圖元索引圖(Prim index Map):圖元緩沖區中三角形的索引。
    • 圖元緩沖區(Prim Buffer):后變換的三角形。

    d夠大嗎?可視化占用率:黑色表示空的,白色表示滿了,紅色則超出限制,對于一個已知的模型,很容易做到這一點。

    GS向PS輸出3個頂點和SV_PrimitiveID:

    [maxvertexcount(3)]
    void Primitive_Map_GS( triangle GS_Input IN[3], uint uPrimID : SV_PrimitiveID, inout TriangleStream<PS_Input> Triangles )
    {
        PS_Input O;
        [unroll]
        for( int i = 0; i < 3; ++i )
        {
            O.f3PositionWS0 = IN[0].f3PositionWS; // 3 WS Vertices of Primitive
            O.f3PositionWS1 = IN[1].f3PositionWS;
            O.f3PositionWS2 = IN[2].f3PositionWS;
            O.f4PositionCS = IN[i].f4PositionCS; // SV_Position
            O.uPrimID = uPrimID; // SV_PrimitiveID
            Triangles.Append( O );
        }
        Triangles.RestartStrip();
    }
    

    PS哈希了使用SV_PrimitiveID的繪制調用ID(著色器常量),以生成圖元的索引/地址。

    float Primitive_Map_PS( PS_Input IN ) : SV_TARGET 
    { 
        // Hash draw call ID with primitive ID 
        uint PrimIndex = g_DrawCallOffset + IN.uPrimID; 
        // Write out the WS positions to prim buffer 
        g_PrimBuffer[PrimIndex].f3PositionWS0 = IN.f3PositionWS0;     
        g_PrimBuffer[PrimIndex].f3PositionWS1 = IN.f3PositionWS1; 
        g_PrimBuffer[PrimIndex].f3PositionWS2 = IN.f3PositionWS2; 
        // Increment current primitive counter uint CurrentIndexCounter; 
        InterlockedAdd( g_IndexCounterMap[uint2( IN.f4PositionCS.xy )], 1, CurrentIndexCounter ); 
        // Write out the primitive index 
        g_IndexMap[uint3( IN.f4PositionCS.xy, CurrentIndexCounter)] = PrimIndex; return 0; 
    }
    

    需要使用保守的光柵來捕捉所有與紋素接觸的圖元,可以在軟件或硬件中完成。硬件保守光柵化:光柵化三角形接觸的每個像素,在DirectX 12和11.3中啟用:D3D12_RASTERIZER_DESC、D3D11_RASTERIZER_DESC2。

    軟件保守光柵化:使用GS在裁減空間中展開三角形,生成AABB以剪裁PS中的三角形,參見GPU Gems 2-第42章。

    光線追蹤:計算圖元坐標(與陰影貼圖一樣),遍歷圖元索引數組,對于每個索引,取一個三角形進行射線檢測。

    float Ray_Test( float2 MapCoord, float3 f3Origin, float3 f3Dir, out float BlockerDistance )
    {
        uint uCounter = tIndexCounterMap.Load( int3( MapCoord, 0 ), int2( 0, 0 ) ).x;
        [branch]
        if( uCounter > 0 )
        {
            for( uint i = 0; i < uCounter; i++ )
            {
                uint uPrimIndex = tIndexMap.Load( int4( MapCoord, i, 0 ), int2( 0, 0 ) ).x;
                float3 v0, v1, v2;
                Load_Prim( uPrimIndex, v0, v1, v2 );
                // See “Fast, Minimum Storage Ray / Triangle Intersection“
                // by Tomas M?ller & Ben Trumbore
                [branch]
                if( Ray_Hit_Triangle( f3Origin, f3Dir, v0, v1, v2, BlockerDistance ) != 0.0f )
                {
                    return 1.0f;
                }
            }
        }
        
        return 0.0f;
    }
    

    左:3k x 3k的陰影圖;右:3k x 3k的陰影圖 + 1K x 1K x 64的PM。

    為了抗鋸齒,使用額外的光線可行嗎?開銷太大了!簡單技巧——應用屏幕空間AA技術(FXAA、MLAA等)。

    混合方法;將光線跟蹤陰影與傳統的軟陰影相結合,使用先進的過濾技術,如CHS或PCS,使用阻擋體距離計算lerp系數,當阻擋體距離->0時,光線跟蹤結果普遍存在。插值因子可視化:

    L = saturate( BD / WSS * PHS ) 
    
    L: Lerp factor 
    BD: Blocker distance (from ray origin) 
    WSS: World space scale – chosen based upon model 
    PHS: Desired percentage of hard shadow 
    
    FS = lerp( RTS, PCSS, L ) 
    
    FS: Final shadow result 
    RTS: Ray traced shadow result (0 or 1) 
    PCSS: PCSS+ shadow result (0 to 1)
    

    使用收縮半影過濾,否則,光線跟蹤結果將無法完全包含軟陰影結果,將導致在兩個系統之間執行lerp時出現問題。

    效果對比:

    不同圖元復雜度的效果、消耗及性能如下:

    局限性:目前僅限于單一光源,不會擴大到適用于整個場景,存儲將成為限制因素,但最適合最接*的模型:當前的焦點模型、最*級聯的內容。總之,解決傳統的陰影貼圖問題,AA光線跟蹤硬陰影的性能非常好,混合陰影結合了這兩個世界的優點,無需重新編寫引擎,今天的游戲速度足夠快!

    Advancements in Tiled-Based Compute Rendering闡述了基于分塊的計算著色器的渲染,如當前的技術、剔除改進、分簇渲染等。

    文中改進的目標有Z-Prepass(前向+)、深度邊界、光源剔除、顏色通道。

    首先來分析深度邊界,以往的做法是根據每個tile確定深度緩沖區的最小和最大界限,原子操作的最小、最大值。以往的實現往往存在不少性能上的問題:

    可以改成并行規約(Parallel Reduction),原子有用,但不是高效的,需要計算友好算法,當前已經有了很好的資料:

    • Optimizing Parallel Reduction in CUDA [Harris07]
    • Compute Shader Optimizations for AMD GPUs: Parallel Reduction [Engel14]

    實現細節:第1個pass讀取4個深度樣本,需要單獨的通道,寫入邊界到UAV,也許對其它操作也有用。

    效率對比:

    顯卡 Atomic Min/Max Parallel Reduction 變化
    AMD R9 290X 1.8 ms 1.6 ms 提升11.1%
    NVIDIA GTX 980 1.8 ms 1.54 ms 提升14.4%

    在3840x2160分辨率和2048個光源情況下的深度邊界和光源剔除的綜合成本,并行規約過程約需0.35ms,比測試的GPU上的原子最小值/最大值更快。

    接下來分析光源剔除。

    光源剔除需要涉及球體-視錐體的相交檢測,存在以下幾種方式:

    上:裁剪*面;中:圍繞長視錐體的AABB;下:圍繞短視錐體的AABB。

    除了以上幾種,還有Arvo[Arvo90]相交測試方式,其代碼如下:


    左:球體-視錐體相交測試;右:Arvo相交測試。明顯Arvo的更緊湊精確。

    剔除聚光燈時,不要在聚光燈原點周圍放置邊界球體,在半徑為r的球體內P處的緊湊包圍聚光燈:

    在深度裁剪時,存在2.5D和HalfZ方式,而文中使用了改良的HalfZ方式:像往常一樣計算最小和最大Z,然后計算HalfZ,分別使用HalfZ和Max&Min的第二組最小值和最大值,測試*邊界和遠邊界,寫入其中一個列表或者兩者,在深度邊界通道重復一次,最壞情況收斂于HalfZ。

    從上到下:2.5D、HalfZ、改進的HalfZ。

    Unreal Engine 4的Infiltrator演示和不同方法的光源剔除可視化。

    剔除結論:帶有AABB的改良HalfZ通常效果最好,盡管生成MinZ2和MaxZ2會增加一些成本,即使在兩個AABB而不是一個AABB中剔除每個光源,32x32的tile在剔除階段節省了大量時間,以推送更多光源時的顏色通道效率為代價。

    對于分簇渲染的光源剔除,視圖空間AABB在二維網格上工作得最好,但在16切片時很糟糕,視圖空間視錐體*面更好,逐tile*面計算,然后測試每個切片的*距離和遠距離,(可選)然后測試AABB。

    VRAM的使用:16x16像素的2D網格需要numTilesX x numTilesY x maxLights,1080p:120 x 68 x 512 x uint16 = 8MB,4k: 240 x 135 x 512 x uint16=32MB,每種燈光類型(點和聚光)的列表:64MB,32片:僅點光源為1GB,或者使用更粗的網格,或者使用壓縮列表。

    壓縮列表的選項1:在CPU上進行所有剔除,但其中一些燈光可能是由GPU產生的,CPU是寶貴的資源!選項2:在GPU上剔除,跟蹤TGSM中每個切片的燈光數量,在燈光列表標題中寫入偏移表,每個分塊只需要maxLights x “安全系數”。

    Z Prepass非常依賴場景,通常被認為開銷太大,DirectX12有助于降低提交成本,應該已經有一個超級優化的陰影深度路徑!僅位置流,索引緩沖區和材質一起批處理,部分Prepass確實有助于減輕幾何體負載。

    結論:并行規約比原子最小值/最大值更快,AABB球體測試結合改良的HalfZ是一個不錯的選擇。分簇著色可能會大大節省tile的剔除,低光源數量的開銷更小,與2D分塊相比,它還提供了其它好處。聚合裁剪是非常值得的,為昂貴的場景顏色提供了最佳的優化。

    如何打造一款秒級別的全局光烘培軟件由網易研究院在GDC中國2015呈現,講述了實現快速烘焙的全局光照渲染。文中提到需要自己研發的原因是當時市場上的商業烘培軟件烘培慢、包體大、材質有限,提出了CloudGI的烘焙流程:

    使用了基于點云的GI:

    基于點云的GI主要有3個步驟:點云生成、構建八叉樹、計算間接光照。

    點云實際是用的面元,包含了法線、位置、輻射率、面積等信息,使用了DX1的UAV 和原子相加的指令。對于面元的面積,在幾何著色器中執行,公式如下:

    \[\text{Area} = \cfrac{\text{TriangleArea}}{\text{UVArea/UVPerPixel}} \]

    構建八叉樹使用了GPU,高度并發,無需同步,不浪費顯存。創建葉節點,3位一層編碼,32位表示10層:

    計算間接光照的過程:遍歷八叉樹,得到id映射表,然后從ID計算輻射率,最后用下面的公式獲得*均值并乘以PI:

    另外,采用了分塊烘焙的方法來提升速度。


    14.4.3.3 移動*臺

    The Benefits of Multiple CPU Cores in Mobile Devices闡述了移動設備對多核的需求、SMP多處理器、Tegra 2、Dual Core ARM Cortex A9架構等內容。

    移動設備執行各種各樣的任務,例如We瀏覽、視頻播放、移動游戲、SMS文本消息和基于位置的服務。由于高速移動設備和Wi-Fi網絡可用性的增長,移動設備也將用于以前由傳統PC處理的各種性能密集型任務。下一代智能手機(稱為“超級手機”)和*板電腦將用于各種任務,例如播放高清1080p視頻、基于Adobe Flash 的在線游戲、基于Flash的流式高清視頻、視覺豐富的游戲、視頻編輯、同步高清視頻下載、編碼和上傳以及實時高清視頻會議。

    當前這一代移動處理器并非旨在應對這種高性能用例的浪潮。當用戶同時運行多個應用程序或運行性能密集型應用程序(如游戲、視頻會議、視頻編輯等)時,基于單核CPU的設備的體驗質量會迅速下降。為了提高CPU性能,工程師采用了多種技術,例如使用更快和更小的半導體工藝、提高內核工作頻率和電壓、使用更大的內核以及使用更大的芯片緩存。

    增加CPU內核或高速緩存的大小只能將性能提高到一定水*,超過該水*的熱量和散熱問題使得進一步增加內核和高速緩存大小變得不切實際。從基本的半導體物理學中我們知道,提高工作頻率和電壓可以成倍地增加半導體器件的功耗,即使工程師可以通過增加頻率和電壓來擠出更高的性能,但性能的提高會大大縮短電池壽命。此外,消耗更高功率的處理器將需要更大的冷卻解決方案,從而導致設備尺寸的意外擴大。因此,提高處理器的工作頻率以滿足移動應用不斷提高的性能要求,從長遠來看并不是一個可行的解決方案。

    為了滿足移動設備對性能和外形時尚度快速增長的需求,業界已開始采用更新的技術,例如對稱多處理(Symmetrical Multiprocessing,SMP)異構多核(Heterogeneous Multi-core)計算。 NVIDIA Tegra是當時世界上最先進的移動處理器,從頭開始構建為異構多核 SoC(片上系統)架構,具有兩個ARM Cortex A9 CPU內核(下圖)和其它幾個專用內核來處理特殊任務,例如音頻、視頻和圖形。

    ARM Cortex A9 CPU內核架構圖。

    與用于音頻、視頻和圖形處理等任務的通用處理內核相比,專用內核需要的晶體管更少、工作頻率更低、性能更高、功耗更低。(下圖)

    雙核CPU的電壓和頻率擴展優勢。

    對稱多處理(SMP)技術使移動處理器不僅能夠提供更高的性能,而且還能滿足峰值性能需求,同時保持在移動電源預算之內。 具有SMP的多核架構由以下特征定義:

    • 架構由兩個或更多相同的CPU內核組成。
    • 所有內核共享一個公共的系統內存,并由一個操作系統控制。
    • 每個CPU都能夠在不同的工作負載上獨立運行,并且盡可能與其它CPU共享工作負載。


    單核(上)和雙核(下)運行網頁瀏覽的對比圖。

    搭載ARM Cortex A9 CPU的移動設備和搭載其它芯片的移動設備的性能表現。圖中表明前者有2.5倍的提升。

    游戲Dungeon Defender開啟雙核之后,FPS是單核的2倍多。

    總之,芯片制造商意識到,頻率和內核尺寸的不斷增加導致功耗呈指數級增長和過度散熱, 因此,CPU制造商開發了多核CPU架構,以繼續提供更高性能的處理器,同時限制這些處理器的功耗。智能手機和*板電腦等移動設備比PC設備從多核架構中獲益更多,因為電池壽命和續航的收益巨大。該文預測雙核處理器將在2011年成為標準,四核將在不久的將來出現。

    為了進一步提高性能并保持在移動電源預算內,所有移動處理器最終都將不可避免地具有多核處理器。Android、Windows CE和Symbian等移動操作系統能夠在多核環境中運行,并具有有效利用底層硬件的多個處理核心所需的功能。此外,流行的Web瀏覽器和大多數PC游戲已經是多線程的,如果將這些應用程序移植到基于多核CPU的移動處理器上運行,用戶將看到性能的巨大改進。

    NVIDIA Tegra旨在利用對稱多處理的強大功能,提供非凡的Web瀏覽體驗、響應速度快的用戶界面、有效的多任務處理以及極大的電池續航時間。

    在2012年的GDC中國上,《調教三國》產品研發及技術感悟分享了國內具有代表性意義的移動端游戲的研發過程、技術和經驗教訓。下圖是該游戲的服務端使用的架構:

    下圖是該游戲使用的引擎架構:

    在移動端游戲的引擎選擇上,文中給的建議是不盲目選擇商業引擎(Unity、Unreal、Flash),流行的開源引擎會更加符合實際,也不要盲目跨*臺,iOS和Android足夠。當時的Cocos2D-X是多少移動端游戲的首選,并建議不要自己重新造輪子寫引擎。下圖是當時移動端游戲引擎的常見模塊和架構:

    對客戶端技術及語言的選擇,建議在Windows開發環境,減少對于Object C的依賴(iOS),減少對于Java的依賴(Android),C/C++作為主要編程語言。

    在游戲品質分級上,建議的分辨率是高端1136x640(iPhone 5)、中端960x640(iPhone 4S)、低端480x320(iPhone 3GS),內存控制在150M以內,包體大小在高中低端上分別是小于50M、約80M、大于100M。

    用戶體驗上注重特殊操作方式(觸摸點大小、操控范圍)、適應新興屏幕特點(小屏幕、高清晰)、性能優化(加載時間、內存局限)、網絡優化(時延、斷線及壓縮)。

    iOS and Android Development with Unity3D講述了2012年用Unity開發移動*臺游戲的攻略。當時付費下載過移動端app的情況如下圖,其中40%多ios用戶選擇了是,而安卓30+%:

    為了以后*臺遷移,選擇解決方案要考慮代碼最少的大多數*臺、不會吃掉利潤的許可模式、廣泛的社區支持等因素。同時對比了H5、Cocos2D、UDK、Flash、Corona等開發套件。說明選擇Unity的原因:對關鍵*臺的最佳支持,包含移動設備(iOS、Android)、網頁(NaCL、Flash、網頁播放器)、桌面(Steam、Mac App Store)、主機及原生插件、廣泛的社區等。

    2012年的Unity游戲畫面。

    插件方面,主要使用跨*臺插件,訪問*臺特定功能(游戲中心等)。重構*臺特定代碼只花了幾天,更換了適用于Android的iOS插件,運行時*臺檢查和#IF 編譯器指令的組合,AndroidJava類。具備跨*臺的導出工具,每個*臺的資產設置,如壓縮設置、過濾、緩存服務器、多*臺工具包、特定于*臺的資產、構建時資產更改。

    總之,Unity有最佳商業模式,最廣泛的*臺支持,最佳社區支持,非常簡單的移植過程。

    Bringing AAA Graphics to Mobile Platforms闡述了移動端硬件的幕后工作原理及案例研究,軟件如何應用這些知識將控制臺圖形引入移動*臺。

    移動圖形處理器的功能有著色器、渲染到紋理、深度紋理、MSAA,性能也在慢慢變好。

    移動GPU架構:基于Tile的延遲渲染(TBDR),與臺式機或控制臺截然不同,常見于智能手機和*板電腦,ImgTec SGX GPU屬于這一類,還有其它基于tile的GPU(例如 ARM Mali)以及其他移動GPU類型(NVIDIA Tegra更傳統)。

    基于Tile的移動GPU:TLDR將屏幕分割成tile(例如16x16或32x32像素),GPU適合整塊芯片,處理一個tile的所有繪制調用,對每個tile重復以填滿屏幕,每個tile在完成時被寫入RAM。

    ImgTec處理過程。

    頂點前端:頂點前端從GPU命令緩沖區讀取,將頂點圖元分布到所有GPU核心,將繪制調用拆分為固定的頂點塊,GPU核心獨立處理頂點,持續到場景結束。

    頂點處理如下圖:

    各階段描述如下:

    • 頂點設置。從頂點前端接收命令。
    • 頂點預著色。獲取輸入數據(屬性和uniform)。
    • 頂點著色器。通用可擴展著色器引擎,執行頂點著色器程序,多線程。
    • 參數緩沖區。存儲在系統內存中,但不能溢出這個緩沖區!

    像素前端:讀取參數緩沖區,將像素處理分配給所有內核,一次一整塊tile,在一個GPU內核上完整處理一個tile,tile在多核GPU上并行處理。

    像素處理(每個GPU核心)如下圖:

    其中:

    • 像素設置。從Pixel Frontend接收tile命令,從參數緩沖區獲取頂點著色器輸出,三角光柵化,計算插值器值,深度/模板測試, 隱藏表面剔除(HSR)。
    • 像素預著色。填充插值器和統一數據,啟動非依賴紋理讀取。
    • 像素著色器。多線程ALU,每個線程可以是頂點或像素,每個GPU核心中可以有多個USSE。
    • 像素后端。當tile中的所有像素都完成時觸發,執行數據轉換、MSAA下采樣,將完成的tile的顏色/深度/模板寫入內存。

    著色器單元注意事項:沒有動態流控制的著色器程序:每條指令4個頂點/像素,具有動態流控制的著色器程序:每條指令1個頂點/像素, 著色器中的Alpha混合:不分離專用硬件,切換狀態時可能會發生著色器修補。

    手機是新的PC,廣泛的功能和性能范圍,可伸縮的圖形回歸,用戶圖形設置回歸,低/中/高/超,渲染緩沖區大小縮放,100個SKU測試回來了。

    渲染目標已死,MSAA開銷低且使用更少的內存,只有在內存中的解析數據,MSAA大約消耗0到5ms,當心緩沖區(顏色或深度)回存!Alpha混合沒有帶寬消耗,低開銷的深度/模板測試。

    “免費”去除隱藏表面(ImgTec SGX GPU專用),消除所有背景像素,消除過繪制,僅用于不透明。

    移動與控制臺:OpenGL ES API 的CPU開銷非常大,100-300個繪圖調用時的最大CPU使用率,避免每個場景的數據過多,頂點和像素處理之間的參數緩沖區,節省帶寬和GPU刷新,著色器打包,某些渲染狀態會導致著色器被驅動程序修改和重新編譯,例如alpha混合設置、頂點輸入、顏色寫入掩碼等。

    Alpha測試/丟棄:有條件的z寫入可能非常慢,在像素著色器確定當前像素的可見性之前,“像素設置”(PDM) 不會提交更多片段,而不是提前寫出Z。使用alpha-blend而不是alpha-test,使幾何體適合可見像素。

    渲染緩沖區管理:

    • 每個渲染目標都是一個全新的場景,避免來回切換渲染目標!

    • 可能導致完全恢復(restore):在場景開始時將全部顏色/深度/模板從RAM復制到Tile Memory。

    • 可能導致完全解析(resolve):在場景結束時將完全的顏色/深度/模板從Tile Memory復制到RAM。

    • 避免緩沖區恢復。清除一切:顏色/深度/模板,清除只是在寄存器中設置一些臟位。

    • 避免緩沖區解析。使用丟棄擴展(GL_EXT_discard_framebuffer)。

    • 避免不必要的不同FBO組合,不要讓驅動程序認為它需要開始解析和恢復任何緩沖區!

    紋理查找:不要在像素著色器中執行紋理查找!讓“pre-shader”提前排隊,即避免依賴紋理查找。不要用數學操作紋理坐標,將所有數學運算移至頂點著色器并向下傳遞。不要將.zw組件用于紋理坐標,將作為依賴紋理查找處理,僅使用.xy并在.zw中傳遞其它數據。

    移動端材質系統:完整的虛幻引擎材質太復雜,初步構想是預渲染為單個紋理,目前的解決方案是將組件預渲染為單獨的紋理,添加特定于移動設備的設置,由藝術家推動的功能支持。

    移動材質著色器:一個手寫的uber shader,所有功能都有很多#ifdef,在藝術家UI中顯示為固定設置,如復選框、列表、值等。

    著色器離線處理:離線運行C預處理器,減少游戲內編譯時間,在離線時消除重復。

    著色器編譯:啟動時編譯所有著色器,避免在運行時掛起在GL線程上編譯,在Game線程上加載,編譯還不夠,必須觸發虛擬draw call!記住某些狀態如何影響著色器!可能需要嘗試避免著色器補丁,例如alpha混合狀態、顏色寫入掩碼。

    文中談及了耶穌光的渲染和具體步驟。其移動端優化包含:

    • 將所有數學運算移至頂點著色器。沒有依賴紋理讀取!
    • 通過插值器傳遞數據。但插值器數量有限。
    • 將徑向過濾器拆分為4個繪制調用,4x8 = 總共32次紋理查找(相當于 256 次)。
    • 從30毫秒縮短到5毫秒。

    文中還涉及了角色陰影。投影、調制的動態陰影,相當標準的方法,生成陰影深度緩沖區,模板潛在像素,比較陰影深度和場景深度,使受影響的像素變暗。具體步驟:

    • 從光源角度投影角色深度。
    • 重新投影到相機視圖中。
    • 與SceneDepth進行比較并進行調制。
    • 在頂部繪制角色(無自陰影)。

    陰影優化:

    • 幀中陰影深度優先。避免渲染目標切換(解析和恢復!)

    • 解析陰影之前的場景深度:

      • 將tile深度寫入RAM以作為紋理讀取。
      • 保持在同一個tile中渲染。
      • 不幸的是,OpenGL ES中沒有這方面的API。
    • 優化陰影的顏色緩沖使用:

      • 只需要深度緩沖區!
      • 不必要的緩沖區,但在OpenGL ES中是必需的。
      • 清除(避免恢復)并禁用彩色寫入。
      • 使用glDiscardFrameBuffer()避免解析。
      • 可以用F16/RGBA8顏色編碼深度。
    • 繪制屏幕空間四邊形而不是立方體,避免依賴紋理查找。

    工具提示:在PC上使用OpenGL ES包裝器。幾乎“所見即所得”,在Visual Studio中調試。Apple Xcode GL調試器,iOS 5,完整捕獲一幀,在單獨的窗格中顯示每個繪制調用、狀態,顯示每個drawcall使用的所有資源,顯示著色器源代碼+所有統一值。

    ImgTec 6xxx 系列:100+ GFLOPS(可擴展到 TFLOPS 范圍),DirectX 10、OpenGL ES “Halti”,PVRTC 2,提高內存帶寬使用率,改進的延遲隱藏。

    Accelerate your Mobile Apps and Games for Android? on ARM由Arm呈現,講解了在Android系統上如何優化App的技術。

    移動應用程序需要特殊的設計考慮,這些考慮因素并不總是很清楚,解決日益復雜的系統的工具也很有限。動畫和游戲丟幀,聯網、顯示、實時音視頻處理耗電,應用程序不適合內存限制。幸運的是,谷歌、ARM 和許多其他公司正在開發這些問題的分析工具和解決方案。應用程序是CPU/GPGPU受限、I/O 或內存受限或省電的嗎?能用什么方式來修復它?

    Java SDK Android應用分析:使用SDK Lint工具進行靜態分析,使用DDMS進行動態分析(分配/堆、進程和線程利用率、Traceview(方法)、網絡),層次結構查看器,系統跟蹤。

    這個性能瓶頸是否可并行化?是Java還是Native? 反過來會更好嗎?以前有這樣做過嗎? 不要重新發明輪子。對資源使用很智能嗎?應該針對哪個版本的Android?靜態分析:LINT(下圖):

    超越靜態分析Dalvik Debug Monitor Server (DDMS),DDMS 線程分析(類似“top”但更好):

    DDMS:Traceview追蹤每種方法消耗多少CPU時間。

    分配和HEAP是否以高頻方法進行分配:

    Dumpsys gfxinfo:將dumpsys數據列放入電子表格并可視化……例如動畫是否會掉幀:

    Systrace:已經盡所能在應用程序內部進行分析,但仍然找不到瓶頸,Systrace來救援!Systrace.py將生成一個5秒的系統級快照:


    ARM DS-5社區版免費Android原生分析器和調試器:

    流線型的概覽:

    Mali GPU的圖形分析工具:

    應用程序資源優化器 (ARO),免費/開源以網絡為中心的診斷工具(由AT&T提供,但不需要AT&T設備),pcap/數據收集需要root,設備上的APK,用于捕獲數據分析的Java桌面應用程序。

    網絡資源:

    • 關閉連接。80% 的應用程序在完成后不會關閉連接,LTE功率增加38%(3G功率增加18%)。
    • 緩存數據。17 的移動流量是重復下載相同的未更改HTTP內容 ,“這只是一個6 KB的徽標”——6KB * 3DL/會話 * 10000個用戶/天 = 3.4GB/月,從本地緩存讀取比從Web下載快75-99%,即使支持緩存 - 默認情況下它是關閉的。
    • 管理每個連接。將連接分組,節省電池,加快應用程序。

    緩存方法:每個文件都有一個唯一標簽,在服務器上為每個請求重新驗證。高性能網站:規則1 – 減少HTTP請求 ,添加連接會耗盡電池電量,增加500-3000毫秒的延遲。仔細分配Max-Age時間很重要,在達到Max-Age之前,應用程序不會檢查服務器上的文件,檢索是嚴格的文件處理時間。

    分組連接:下圖,紅色:每60秒下載一張圖片,藍色:每60秒下載一個廣告,綠色:每60秒向服務器發送一次分析。

    未分組:使用了38J的能量,分組:使用了16J的能量,節省58%的電量!!

    其它網絡優化:刪除對文件的重定向,它們每次請求大約2-3秒,預取常用文件,線程文件下載而不是串行下載,不應出現4xx、5xx的http響應錯誤代碼,小心定期連接,定期3分鐘輪詢更新可能會在一天中保持連接1.2 小時,消耗大約20%的電池電量。

    適用于ARM的原生開發套件 (NDK):NDK是一個全面的工具包,使應用程序開發人員能夠直接為ARM處理器編寫。

    SMP和并行化:當今市場上幾乎所有的Android和移動設備都是多核的,而且這一趨勢將繼續下去——設計多線程應用程序Davlik Java線程和IPC,AsyncTask通常是將任務快速推送到后臺工作線程的最簡單方法,且IPC復雜性極低,仿生C庫實現了Pthreads API的一個版本,大多數pthread和sem_函數都已實現,但沒有SysV IPC,如果它在pthread.h 或 semaphore.h中聲明,它將大部分按預期工作。

    OpenGL ES 2.0支持可編程嵌入式GPU的完全可編程3D圖形,免版稅的跨*臺API,2D和3D圖形,支持Android的框架API和NDK。

    為移動/嵌入式/電池編寫java:新的方法,應避免,至少不在CPU密集/頻繁的活動中,嘗試使用靜態變量或僅預先分配或在活動的自然停頓時分配,避免觸發垃圾收集(使用DDMS)。在JellyBean中為圖形使用新功能,例如用于垂直同步脈沖的android.view.Choreographer、myView.postInvalidateOnAnimation(),不要畫不會顯示的東西c.quickReject(items...)、Canvas.EdgeType.BW。

    SIMD: NEON——適用于許多應用的通用SIMD處理,支持用于互聯網應用的最廣泛的多媒體編解碼器,許多軟編解碼器標準:MPEG-4、H.264、On2 VP6/7/8、Real、AVS、……在軟件中支持所有互聯網和數字家庭標準。更少的周期,NEON將在復雜的視頻編解碼器上提供 1.6x-2.5x 的性能,單個簡單的DSP算法可以顯示更大的性能提升 (4x-8x),處理器可以更快地休眠 => 整體動態節能。直接編程,干凈的正交向量架構,適用于廣泛的數據密集型計算。不僅適用于編解碼器 - 適用于2D/3D圖形和其他處理,32個寄存器,64位寬(雙視圖為16個寄存器,128位寬),現成的工具、操作系統、商業和開源生態系統支持。

    線程文件下載(左)與串行下載(右)的對比。

    OpenGL ES 3.0 - Challenges and Opportunities分享了OpenGL ES 3.0在移動端的特性、應用和優化。

    OpenGL ES是嵌入式系統的開放式圖形庫,圖形硬件的底層軟件接口,OpenGL的子集,OpenGL ES驅動的GPU的各種使用,用于智能手機/*板電腦、電視機、汽車等等。桌面GPU計算在移動設備中還需要多長時間?計算能力和帶寬速度曲線及趨勢預測如下:


    OpenGL ES 3.0于2012發布規范,基于OpenGL3.3/4.x的功能集,減少擴展需求,完全向后兼容OpenGL ES 2.0。著色語言GLSL ES 3.00模式如下:

    ETC2紋理壓縮是標準紋理壓縮,支持Alpha、1或2個通道,消除ETC1的限制(不支持Alpha、質感差),理論上不再需要專有紋理格式,更小的文件大小,沒有不同的資產包。

    布爾遮擋查詢:用于基于硬件的可見性測試的軟件接口:

    glGenQueries
    glDeleteQueries
    glBeginQuery
    glEndQuery
    glGetQueryObjectuiv
    
    ...
    
    int qid[NUM_OBJECTS];
    unsigned int result = 0;
    // 生成查詢
    glGenQueries(NUM_OBJECTS, &qid[0]);
    
    for (int i = 0; i < NUM_OBJECTS; ++i) 
    {
        // 開始查詢
        glBeginQuery(GL_ANY_SAMPLES_PASSED, qid[i]);
        // 以低細節渲染物體.
        RenderOccluders();
        // 結束查詢.
        glEndQuery(GL_ANY_SAMPLES_PASSED);
    
        // 等待查詢結果有效.
        // 注意:這種同步等待結果的方式會導致【嚴重的性能問題】!!
        while (result == GL_FALSE) 
        {
            glGetObjectuiv(qid[i], GL_QUERY_RESULT_AVAILABLE, &result);
        }
        
        // 獲取結果。
         glGetObjectuiv(qid[i], GL_QUERY_RESULT, &result);
        if (result == GL_TRUE) 
        {
            // 以全精度渲染物體。
            RenderObjects();
         }
     }
    
    ... 
    

    遮擋查詢的CPU和GPU時序圖。

    ES3比2的遮擋查詢能夠有明顯的提升。

    實例化渲染:盡量減少繪圖調用,強大的具有大量相同幾何圖形的場景,相關接口:

    glDrawArraysInstanced(GLenum mode, Glint first, GLsizei count, GLsizei primcount)
    glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei primcount)
    glVertexAttribDivisor(GLuint index, GLuint divisor)
    gl_InstanceID 
    

    可能難以在現有(2013年)渲染管道中實現。

    // OpenGL ES 2.0
    for ( int i = 0; i < numInstances; i++ ) 
    {
        // set for each instance the model-view-projection matrix
        glDrawElements(GL_TRIANGLES,mesh->indx_count,GL_UNSIGNED_SHORT,mesh->indx);
    } 
    
    // OpenGL ES 3.0
    glDrawElementsInstanced(GL_TRIANGLES,mesh->indx_count,GL_UNSIGNED_SHORT,mesh->indx, numInstances); 
    
    // 非實例化渲染
    // Vertex shader
    #version 100
    uniform mat4 u_matViewProjection;
    attribute vec4 a_position;
    attribute vec2 a_texCoord0;
    varying vec2 v_texCoord;
    
    MVP = glGetUniformLocation( programObj, "u_matViewProjection" );
    glUniformMatrix4fv(MVP, 1, GL_FALSE, &mvpMatrix ); 
    
    // 實例化渲染
    // Vertex shader
    #version 100
    // uniform -> attribute
    attribute mat4 u_matViewProjection;
    attribute vec4 a_position;
    attribute vec2 a_texCoord0;
    varying vec2 v_texCoord;
    
    // glGetUniformLocation -> glGetAttribLocation
    MVP = glGetAttribLocation( programObj, "u_matViewProjection" );
    // 填充實例化數據.
    for (int i = 0; i < 4; i++) 
    {
        glEnableVertexAttribArray(MVP + i);
        glVertexAttribPointer(MVP + i,
            4, GL_FLOAT, GL_FALSE, // vec4
            16*sizeof(GLfloat), // stride
            &(matArray + 4*i*sizeof(GLfloat)); // offset
        glVertexAttribDivisor(MVP + i, 1);
    }
    

    #define LTP_ARRAY     0
    #define VERTEX_ARRAY 4
    
    layout(location = LTP_ARRAY)    in highp mat4 inLocalToProjection;
    layout(location = VERTEX ARRAY) in highp vec3 inVertex;
    
    void main()
    {
        gl_Position = inLocalToProjection * inVertex;
    }
    

    多重渲染目標(MRT)在一次繪制調用中渲染到多個緩沖區,提供在下一代中執行視覺效果的可能性,如延遲照明、細胞陰影、延期貼花、實時局部反射......

    // C++
    ...
    unsigned int fb;
    unsigned int initializedTexture2D_1;
    unsigned int initializedTexture2D_2;
    GLenum buffs[] = {GL_COLOR_ATTACHMENT0,
    GL_COLOR_ATTACHMENT1};
    glGenFrameBuffer(1, &fb);
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferTexture2D(GL_FRAMEBUFFER,
    GL_COLOR_ATTACHMENT0, GL_TEXTURE2D, initializedTexture2D_1, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER,
    GL_COLOR_ATTACHMENT0, GL_TEXTURE2D, initializedTexture2D_2, 0);
    glDrawBuffers(2, buffs);
    // render calls
    ...
        
    // fragment shader
    #version 300 es
    layout(location = 0) out lowp vec4 color;
    layout(location = 1) out highp vec4 normal;
    in lowp vec4 v_color;
    in highp vec4 v_normal;
    
    main() 
    {
        color = v_color;
        normal = v_normal;
    } 
    

    OpenGL ES 3.0特性的消耗收益比。

    挑戰:

    • 現有引擎中的實現并非易事。
    • 需要對產品管線進行改造。
    • 在某些情況下,OpenGL ES 3.0特性不會獲得更好的性能。
    • 圖形部門也需要理解MRT。
    • OpenGL ES 3.0設備目前比較少同時支持ES2/ES3。

    機會:

    • 更好的性能。
    • 更小的能耗。
    • OEM喜歡看到開發人員使用的最新技術。
    • 當前控制臺和移動設備之間的差距越來越小。
    • 通過擴展,一些3.0功能可在當前的通用硬件上使用。

    2013年,大型多人在線游戲 (MMOG) 等視頻游戲已成為文化媒介,手機游戲為應用市場帶來大量下載和潛在收益。盡管移動設備的處理能力增加了帶寬傳輸,但網絡連接不佳可能會成為游戲即服務 (GaaS) 的瓶頸。為了提高數字生態系統的性能,處理任務分布在瘦客戶端設備和強大的服務器之間,An Architecture Approach for 3D Render Distribution using Mobile Devices in Real Time基于“分而治之”的方法,即使用游戲中場景序列的樹KD對體積曲面進行細分,從而將曲面縮減為小點集。提高了重建效率,因為數據的搜索是在局部和小區域中進行的。流程通過一組有限狀態建模,這些狀態使用隱馬爾可夫模型構建,域由啟發式配置。進行了六個控制每個啟發式狀態的測試,包括間隔數,以驗證所提出的模型。該驗證得出的結論是,所提出的模型在一系列交互中優化了每秒的響應幀。

    節點的架構全連接(遍歷)模型。

    桌面虛擬現實(Virtual Reality,VR)歷來是消費級3D計算機圖形的主要顯示技術。*來,立體視覺和頭戴式顯示器等更復雜的技術已變得更加普及。然而,大多數3D軟件仍然僅設計用于支持桌面VR,并且必須進行修改以在技術上支持這些顯示器并遵循其使用的最佳實踐。Virtual Reality Capabilities of Graphics Engines評估了現代3D游戲/圖形引擎,并確定了它們在多大程度上適應不同類型的負擔得起的VR顯示器的輸出,表明立體視覺得到了廣泛的支持,無論是原生還是通過現有的適應。其它VR技術,如頭戴式顯示器、頭部耦合透視(以及隨之而來的魚缸VR)很少得到原生支持。但是,該文確定并描述了一些方法,例如重新設計,通過這些方法可以添加對這些顯示技術的支持。

    2013年虛擬現實顯示技術有桌面VR(串流)、立體視覺、頭部耦合透視、頭戴式顯示器等幾種,它們在模擬模型和用戶感知方面的差異如下圖:

    立體視覺(Stereoscopy)是適用于雙目視覺的桌面VR范式的擴展。立體鏡通過兩次渲染場景來實現這一點,每只眼睛一次,然后以這樣的方式對圖像進行編碼和過濾,使每張圖像只能被用戶的一只眼睛看到。這種過濾最容易通過特殊的眼鏡實現,眼鏡的鏡片設計為選擇性地通過匹配顯示器產生的兩種編碼之一。當前的編碼方法是通過色譜、偏振、時間或空間。這些編碼方法經常被分類為被動、主動或自動立體。被動和主動編碼之間的區別取決于眼鏡是否是電主動的:因此被動編碼系統是顏色和極化,而唯一的主動編碼是時間。自動立體顯示器是不需要眼鏡的顯示器,因為它們在空間上進行編碼,這意味著眼睛之間的物理距離足以過濾圖像。

    消費者立體顯示器與計算機的接口方式與桌面VR顯示器相同(通過VGA或DVI等視頻接口)。由于這些接口中的大多數都沒有特殊的立體觀察模式,因此將兩個立體圖像以顯示硬件可識別的格式打包成一個圖像。此類幀封裝格式包括交錯、上下、并排、2D+深度和交錯。由于這些標準化接口是軟件將渲染圖像傳遞給顯示硬件的方式,因此軟件應用程序不需要了解或適應編碼系統的顯示硬件。相反,圖形引擎支持立體透視所需要的只是它能夠從不同的虛擬相機位置渲染兩個具有相同模擬狀態的圖像,并將它們組合成顯示器支持的幀封裝格式。

    頭部耦合透視(Head-coupled perspective,HCP) 的工作原理與桌面VR和立體視覺略有不同, 定義了一個虛擬窗口而不是虛擬相機,其邊界是虛擬的窗口映射到用戶顯示器的邊緣。因此,顯示器上的圖像取決于用戶頭部的相對位置,因為來自虛擬環境的對象會沿用戶眼睛的方向投影到顯示器上。這種投影可以使用桌面VR中使用的投影數學的離軸版本來完成。

    為了做到這一點,必須實時準確地跟蹤用戶頭部相對于顯示器的位置。用于此目的的跟蹤系統包括電樞、電磁/超聲波跟蹤器和圖像- 基于跟蹤。HCP的一個限制是,由于顯示的圖像取決于用戶的位置,因此任何其他觀看同一顯示器的用戶將感知到失真的圖像,因為他們不會從正確的位置觀看。

    頭戴式顯示器(Head-mounted display,HMD)是另一種單用戶VR技術,將立體視覺的增強功能與類似于HCP的大視場和頭部耦合相結合。HMD背后的感知模型是完全覆蓋用戶眼睛的視覺輸入,并將其替換為虛擬環境的包含視圖。通過將一個或兩個小型顯示器安裝在非常靠*用戶眼前的鏡頭系統來實現的,以實現更自然的聚焦。由于顯示器非常靠*用戶的眼睛,顯示器的任何部分只有一只眼睛可見,使系統具有自動立體感。

    頭飾中還嵌入了一個方向跟蹤器,允許跟蹤用戶頭部的旋轉,允許用戶通過將虛擬相機的方向綁定到用戶頭部的方向來使用自然的頭部運動來環顧虛擬環境。它與HCP不同,HCP跟蹤的是位置,而不是方向。支持HMD的軟件要求與立體觀察相同,但附加要求是圖形引擎必須考慮HMD的方向,以及要校正的鏡頭系統引起的任何失真。

    通過確定可以使用哪些擴展機制來實現所需的VR顯示技術來衡量支持級別,已經結合了差異可以忽略不計的擴展機制(例如腳本和插件),并引入了兩個額外的級別,不需要擴展(本機支持)和沒有引擎內支持(重新設計)。擴展機制按引擎代碼相對于實現VR支持的非引擎代碼的比例排序,產生的支持級別及其排序如下:

    5、原生支持。在原生支持VR技術的引擎中,引擎的開發人員特意編寫了渲染管線,使用戶只需最少的努力即可啟用VR渲染。所需要做的就是檢查開發人員工具中的選項或在引擎的腳本環境中設置變量。除了輕松啟用該技術外,這些引擎還旨在避免常見的優化和快捷方式,這些優化和快捷方式在桌面VR顯示器中并不明顯,但隨著更復雜的技術變得明顯,一個常見的例子是渲染具有正確遮擋但深度不正確的對象,會導致立體鏡下的深度提示沖突。

    4、通過引擎內圖形定制(包括節點圖)。一些引擎的設計方式使得可以使用具有圖形界面的自定義工具來更改渲染過程,一種方法是通過節點圖,其中渲染管道的不同組件可以在多種配置中重新排列、修改和重新連接。根據支持的節點類型,有時可以配置節點以產生某些 VR 技術的效果。下圖顯示了虛幻引擎的材質編輯界面,該界面配置為將紅青色立體立體渲染作為后處理效果。

    3、通過引擎內編碼(腳本或插件)。每個引擎都可以使用自定義代碼進行擴展,使用定義明確但受限制的擴展點。兩種常見的形式是在受限環境中運行的腳本,以及引擎加載并運行外部編譯的代碼插件,兩種形式都可以訪問引擎功能的子集,但是,插件也可以訪問外部API,而腳本不能。由于通常實現特定于應用程序功能的機制,因此可用于自定義代碼的引擎功能可能更多地針對人工智能、游戲邏輯和事件排序,而不是控制確切的渲染過程。

    2、通過引擎源代碼修改。除了免費的開源引擎,一些商業引擎通過適當的許可協議向用戶提供其完整的源代碼。通過訪問完整的源代碼,可以實現任何VR技術,盡管所需的修改量可能很大。

    1、通過工程改造。對于不提供上述任何定制入口點的引擎,仍然可以通過重新設計進行一些更改。工程改造是逆向工程的一種形式,除了學習程序的一些工作原理之外,還修改了它的一些功能。對渲染管道進行完全逆向工程所需的工作量可能很大,因此更可取的是微創形式的再工程。其中一種方法是函數掛鉤,即內部或庫函數的調用被攔截并替換為自定義行為。由于很大一部分實時圖形引擎使用OpenGL或Direct3D庫進行硬件圖形加速,因此這些庫為通過函數掛鉤實現純視覺VR技術提供了可靠的入口點。事實證明,這種方法可以有效地將立體視覺添加到3D游戲。本文還展示了以這種方式實現頭耦合透視也是可能的,通過掛鉤加載投影矩陣(glFrustum和glLoadMatrix)的OpenGL函數,并用頭部耦合矩陣替換原始程序提供的固定透視矩陣。

    影響用戶體驗的因素有很多,雖然質量因素本質上與顯示硬件相關,但適當的軟件設計可以緩解這些問題,而粗心的設計可能會引入新問題。可以通過軟件減輕的硬件質量因素的示例是串擾(立體)、A/C故障(立體)和跟蹤延遲(HCP和HMD)。由于這些因素對于它們各自的顯示技術來說是公認的,因此有眾所周知的技術可以最大限度地減少它們引起的問題。解決方案分別是降低場景對比度、降低視差和最小化渲染延遲。

    不正確的軟件實現也會影響VR效果的質量,可能是由于粗心或桌面VR優化的結果。這方面的一個示例是任意位置的特殊圖層(例如天空、陰影和第一人稱玩家的身體)不同通道的深度。雖然在桌面VR中產生正確的遮擋,但在立體鏡下添加雙目視差提示會顯示不正確的深度,并在這兩個深度提示之間產生沖突。由于桌面VR的主導性質,這不是一個不常見的問題,并且可以作為另一個例子,說明簡單的第三方實現可能不如原生VR支持。從這些方面應該注意到,雖然非原生VR實現可能滿足必要的技術要求,但也必須考慮其它因素。

    下表是2013年的主流引擎對VR的支持情況:

    引擎 立體視覺 頭部耦合透射 頭戴式顯示器
    UDK 4:圖形定制。可以使用Unreal Kismet創建雙攝像頭裝備,并使用材質編輯器打包輸出。 1:工程改造。無法從引擎訪問自定義相機投影,因此如果無源代碼訪問權限,則需要工程改造。 3:引擎編碼。通過自定義實現立體化,可以通過自定義DLL獲得頭部方向并通過腳本綁定到相機。
    Unity 3:引擎編碼。 3:引擎編碼。 3:引擎編碼。
    CryENGINE 5: 原生。 3:引擎編碼。 3:引擎編碼。
    OGRE 3:引擎編碼。 3:引擎編碼。 3:引擎編碼。

    Developing Virtual Reality Experiences with the Oculus Rift詳細地分享了在Oculus Rift上進行VR開發的技術、過程及實操。

    2014年的Rift技術包含開發工具包2,1920x1080 OLED屏幕,每只眼睛一半,廣角圓形透鏡,90-110度FOV,GPU可校正鏡頭變形,低持久性–每個像素每幀的亮度小于3毫秒,1000Hz陀螺追蹤朝向,60Hz位置跟蹤:外部攝像頭可以看到HMD上的LED陣列,軟件融合、方向和位置預測。

    善待VR玩家:虛擬現實開發者每天花數小時觀看HMD,大部分時間,到處都會有bug,大腦很快就會學會忽視瘋子,但玩家不會!他們的大腦新鮮而天真,他們希望事情是真實的,希望你已經調試了所有東西,并且擁有真正的“存在感”。如果你把每件事都推到11,你會給他們帶來創傷,他們會停止玩游戲,給你一星的評價!

    每個人都大相徑庭,有些人無法忍受的事情,而其他人甚至看不見,沒有一個“VR公差”滑塊,對一個方面非常敏感的人可能會容忍另一個方面,例如上下樓梯,寬容不僅僅是一種可以學習的技能,可能會有負面反饋:人們對暴露的容忍度會降低。最佳實踐指南包含當前已知道的內容,將其用作至少需要認真思考的事項清單。

    溫和錯誤,過于激烈的虛擬現實讓人更難理解劇情和游戲機制,讓緊張體驗成為可選,盡量避免“在你臉上”的粒子和爆炸,更少、更慢的移動。默認低難度,讓更有經驗的VR用戶“選擇加入”,而不是讓新手“選擇退出”,便于隨時更改,在受“VR打擊”之后,允許降低強度以實際玩游戲。

    前庭光學反射(Vestibulo-Optical Reflex,VOR):眼球和肌肉、反射神經元、耳內半圓管等部位和交互如下圖:

    用于“固定”,如靜止物體、移動頭部,通過耳朵檢測頭部旋轉,<10毫秒后,眼睛轉動順暢,不是掃視!非常*滑,卓越的視覺質量。

    VOR增益是耳朵運動和眼睛反應之間的比率,通常給予1:1的補償,+10?頭部運動 = -10?眼動,在固定過程中獲得微調,嘗試產生零“視網膜流”,調整速度非常慢。

    如果視圖被壓縮怎么辦?一副新眼鏡,VR中的渲染比例不正確,10?頭部運動現在需要-5?保持注視的眼球運動,VOR增益現在導致視網膜流,導致定向障礙,增益適應需要1-2周(假設持續使用!)。

    保持VOR增益:顯示器上的游戲通常有一個“FOV”滑塊,監視器上可接受–不會直接影響VOR增益,顯示器不會隨頭部移動——不會發生“虛擬注視”,房間周邊視覺提供真實光流真實性檢查…但即便如此,它也會給一些人帶來問題。在Rift中,唯一需要關注的是虛擬現實,VR對象的視網膜流必須與真實世界的運動相匹配。虛擬現實中的視場比例不是任意選擇!它必須與HMD+用戶特征相匹配,“醫生,當我這樣做的時候,會傷害我的玩家的大腦……”

    Rift顯示屏有一個物理間距,即“每可見度像素數”,準確值取決于失真、用戶的頭部和眼睛位置等。通過用戶配置工具找到,SDK將幫助您精確匹配此音高,對于給定的設備和用戶大小,它將提供正確的視野和比例,避免任何改變視野或“縮放”效果,頭部旋轉10度必須產生10度光流,即使每度像素的微小變化也會給大多數用戶帶來問題。

    IPD——瞳孔間距,實際上每只眼睛有兩種成分:從鼻子到瞳孔——“半IPD”,視距——從透鏡表面到瞳孔的距離,與HMD的尺寸無關!從中心到眼向量,在用戶配置期間設置,存儲在用戶配置文件中。很少對稱,有點人的視距可能相差2毫米,下圖的那個家伙的鼻子和瞳孔相差1個像素:

    中央瞳孔-SDK報告的位置,頭盔顯示器的中心線,左右視距的*均值。大致是玩家“感覺”到自己的位置,音頻偵聽器位置,視線檢查,十字線/十字線光線投射的原點。

    保持幀率:存在是一個相當二元的東西——有或沒有,堅如磐石的高FPS對虛擬現實中的存在感至關重要,以75FPS的速度進行立體聲顯示很有挑戰性,積極刪除細節和效果,以保持幀速率和低延遲,保持狀態給玩家帶來的樂趣遠遠超過額外效果,主要成本是繪制調用和填充率。

    對于繪制調用,雙倍的眼睛,雙倍的調用,新的API應該可以降低多次提交的成本:Mantle(Vulkan的前身)、DX12等。有些事情只需要做一次:剔除——使用包括雙眼的保守*截體、動畫、陰影緩沖區渲染、一些遠距離反射/光澤貼圖/AO渲染——但不是全部!一些延遲照明技術。

    對于填充率,更改虛擬相機渲染的大小,而不是幀緩沖區大小,例如對于DK2,幀緩沖區始終為1080x1920–不要更改此設置!但相機眼睛通常每只眼睛渲染1150x1450,取決于用戶面部形狀和眼睛位置——由個人資料和SDK設置。縮放此渲染效果非常好,失真校正過程將對其進行重新采樣和過濾,每幀動態縮放也很好——幾乎看不見。如果該幀中有大量粒子/爆炸,請降低大小。使用相同的RT,只需使用一小部分即可,SDK明確支持此用例。

    Virtual Reality and Getting the Best from Your Next-Gen Engine闡述了如何在游戲引擎種集成VR渲染。文中提到可調節的眼鏡佩戴者:

    無需調整即可耐受瞳孔間距的變化:

    下圖則是關鍵的(上)和可容忍的參數示意圖:

    對于立體3D而言,在攝影立體和頭盔顯示器的固定設置下的所需的焦距要求:

    結合下圖,(a)大多數HMD的視野都很窄,(b)實現寬視場需要更高分辨率的顯示器,(c)或更大的像素。(d)如何做到兩全其美?利用眼睛的可變敏銳度,(e)使用扭曲著色器壓縮圖像的邊緣,(f)光學元件應用反向失真,使邊緣看起來再次正確,(g)中心像素較小,邊緣像素較大。

    VR開發需要注意以下事項:

    • 不要控制玩家的頭部!
    • 注意第一人稱動作。
    • 照片現實主義是沒有必要的。
    • 不要使用電影級的渲染效果!比如可變焦距、過濾器、鏡頭光斑、泛光、膠片顆粒、暗角、景深等。

    立體渲染質量檢查:

    • 左右方向正確嗎?
    • 雙眼中的元素相同嗎?
    • 兩幅圖像代表同一時間嗎?
    • 刻度正確嗎?
    • 深度一致嗎?
    • 避免快速深度變化了嗎?

    如果要在游戲引擎集成VR功能,需要注意的是每個引擎都是不同的,但總會有一些相似之處。什么是良好的虛擬現實引擎?答案如下:

    • 高質量的視覺效果。

      所說的高質量視覺效果是什么意思?沒有任何東西會分散你的注意力,讓你沉浸在游戲中,良好的著色效果(但不一定是真實照片),通常意味著良好的抗鋸齒。

      為什么良好的抗鋸齒至關重要?人類感知的本質意味著我們很容易被高頻噪點分心,分心會降低存在感,使用立體渲染時,鋸齒偽影可能會更嚴重,它們會導致視網膜競爭,良好的抗鋸齒比本機分辨率更重要。

      抗鋸齒方法有:邊緣幾何AA,通常硬件加速;圖像空間AA,非常適合大多數渲染管線,如FXAA、MLAA、SMAA等;時間AA,使用再投影進行時間超采樣。


      MSAA在高頻幾何體、幾乎垂直的線條、對角線看起來更好,但內部紋理/著色仍然有鋸齒。


      FXAA在邊緣幾何體、紋理/著色細節看起來更好,但有時會丟失高頻數據中的細節。

      超采樣反走樣渲染到更大緩沖區的效果良好……如果負擔得起的話,與良好的下采樣過濾器一起使用。

      抗鋸齒結果:鏡面AA也可以大大改善圖像,一個很好的起點是研究LEAN、Cheap LEAN (CLEAN)和Toksvig AA,扭曲著色器減少邊緣鋸齒,在某些游戲中,可能需要更多地關注LOD。

      幾種AA方法的組合可能會產生更好的結果,每種不同的AA解決方案都能解決不同方面的鋸齒問題,使用最適合引擎的方法。

    • 一致的高幀速率。

      為什么一致的高幀速率至關重要?在虛擬現實中,低幀速率看起來和感覺都很糟糕,如果沒有高幀率,測試就很困難。在整個開發過程中保持高幀率,缺少V-sync也更為明顯,因此,請確保啟用了V-sync。

      在當前的引擎中,“通道”的概念被廣泛接受,如反射渲染、陰影渲染、后處理等,每個通道都有不同的要求,每個通道都要找出瓶頸所在。CPU?DrawCall?狀態設置?資源設置?GPU?頂點處理受限?幾何處理受限?像素處理受限?

      繪制調用、狀態設置或資源設置時CPU受限?考慮如何使用幾何著色器,可以減少繪制調用的總數,陰影級聯渲染:drawCallCount/n,其中n是層疊的數量,立方體貼圖渲染:drawCallCount/6。降低資源設置成本,它還有其它特性可以幫助將處理從CPU上移開。

      幾何渲染單元將一個圖元流轉換為另一個可能更大的圖元流,在像素著色器之前發生,即在直接頂點像素繪制調用中的頂點著色器之后,如果啟用了細分,則在Hull著色器之后。

      幾何體著色器功能,渲染目標索引/視口索引,用于單程立方體貼圖渲染、陰影級聯、S3D、GS實例,允許逐圖元運行同一幾何體著色器的多次執行,而無需再次運行上一個著色器階段。

      用于立體3D渲染的幾何體著色器,一種使引擎立體3D兼容的簡單方法,為每種材質添加一個GS(或調整已有材質的GS),如下所示:

      [maxvertexcount(3)]
      void main(
          inout TriangleStream<GS_OUTPUT> triangleStream,
          triangle GS_INPUT input[3])
      {
          for(uint i = 0; i < 3; ++i)
          {
              GS_OUTPUT output;
              output.position = (input[i].worldPosition , g_ViewProjectionMatrix);
              triangleStream.Append(output);
          }
      }
      

      頂點/幾何體受限?通過壓縮屬性來減少頂點大小,在著色器階段之間打包所有屬性,如果正在使用用于放大或細分管道的幾何體著色器,這一點很重要。考慮使用延遲獲取(late fetch)法:將頂點屬性數據綁定為使用的著色器階段中的緩沖區,高度依賴硬件,始終調試性能,看看是否有影響!減少在GPU周圍移動的數據。

      像素受限?降低像素著色器的復雜性,減少每幀著色的像素數,一個使用較小渲染目標的實驗上采樣與高質量視覺沖突,引入光暈、微光和視網膜沖突。

      考慮使用重新投影來加速立體3D渲染的方面,在PlayStation 3立體聲3D游戲中獲得巨大成功,然而,它只能在視差較小的情況下成功使用。

    • 出色的跟蹤和標定。

      項目Morpheus SDK處理跟蹤,使用SDK提供的跟蹤矩陣。游戲定義的默認觀看位置和方向:

      追蹤玩家頭部與攝像機的偏移量:

      玩家眼睛相對于頭部矩陣的偏移量:

      跟蹤器重置功能:設置頭部位置,使其與游戲攝像機的位置和偏航對齊。

      標定:重置位置和方向跟蹤,重新調整游戲世界與現實世界的關系,以便固定的玩家位置,傳遞和游戲(Pass-and-play),匹配不同身高的玩家。

    • 低延遲。

      為什么減少延遲如此重要?延遲是輸入和響應之間的時間間隔,重要的不僅僅是始終如一的高幀率。不僅用于虛擬現實頭部跟蹤,提高響應能力在游戲中至關重要,游戲編程人員了解響應控制的必要性,網絡程序員了解對響應性對手的需求等等。

      多上下文渲染:從引擎的角度來看,減少延遲的一種方法是使用延遲上下文在多個線程上異步構建命令列表(又稱命令緩沖區),作為即時上下文,在命令緩沖區中將命令排隊時會產生渲染開銷,相比之下,在回放期間,命令列表的執行效率要高得多,適用于“通道”的概念。多上下文渲染允許GPU在幀中更早地開始處理,從而減少延遲。

      單上下文(上)和多上下文(下)渲染的對比。

      多上下文渲染最簡單的測試用例:為每只眼睛的視圖并行創建和提交命令列表,立即減少CPU幀時間,如果引擎受CPU受限,意味著幀延遲會立即減少。如果引擎是GPU受限,但GPU現在在幀中完成得更早,因為它啟動得更早,意味著幀延遲立即減少。

      虛擬現實的延遲考慮:是否有任何特定于虛擬現實的方法來應對延遲?采樣跟蹤數據和使用該數據渲染幀之間的時間需要盡可能短,不要使用超過兩倍的緩沖,使用最新的方向數據重新投影圖像可以改善明顯的延遲和幀速率。盡可能降低被跟蹤外圍設備的延遲,是否有任何特定于*臺的方法來應對延遲?

      預測(Prediction):帶有Project Morpheus的PlayStation 4是一個已知的系統,硬件中存在任何延遲,庫/軟件中存在任何延遲,需要想方設法減少這些延遲。提供CPU和GPU性能分析工具,使開發者能夠計算并減少游戲中的延遲,可以用它來預測圖像顯示時HMU的位置。減少引擎延遲是關鍵,但使用預測來掩蓋任何微小的剩余延遲都可以很好地發揮作用,指定的預測量越小,其質量越好。

    假設現在擁有一個非常高效、高幀率、低延遲、超高質量的下一代引擎中擁有了出色的跟蹤功能,該引擎針對虛擬現實進行了優化……引擎的工作完成了嗎?當然不是!還有特定于*臺的優化、跟蹤外圍設備、社交方面、游戲性/設計元素等工作。

    異步計算:仍受CPU限制?也許Compute可以幫助將可并行任務卸載到GPU上。仍然受限于GPU?Compute允許你從不同的、更通用的角度來思考GPU任務,在GPU未被充分利用的地方使用它,陰影渲染通常需要頂點/幾何體,因此它是安排異步計算任務的好地方。

    Diving into VR World with Oculus闡述了Oculus下的VR開發攻略。

    虛擬現實中最重要的因素有:短余輝(Low Persistence)、延遲、現實。

    Oculus SDK 0.4.x的特色:支持DK1和DK2,新款C接口; 用新款SDK來對游戲進行重新編譯。位置跟蹤,位置原點目前距離攝像頭1米,SDK用預測的Pose狀態來報告傳感器狀態,包括方向、位置和導數,超出跟蹤范圍時,給出旗標提示。依靠頭部模型,Direct-to-Rift和擴展模式,OVR配置工具。

    OVR軟件堆棧如下圖,C接口: 容易與其它語言連接,驅動程序DLL: 自動支持硬件和功能的變更,OVR服務: 在各應用之間的Rift分享和虛擬現實轉換。

    SDK渲染與游戲渲染的比較:SDK 0.2未做任何渲染,只提供適當渲染所需的參數;SDK 0.4中的新渲染后端,延續了關鍵的渲染特色,Game(App) layer gives層將SDK左、右眼紋理ovrHmd_EndFrame()。

    SDK的一般工作流程:

    • ovrHmd_CreateDistortionMesh。通過UV來轉換圖像,比像素著色器的渲染效率更高,讓Oculus能更靈活地修改失真。
    • ovrHmd_BeginFrame。
    • ovrHmd_GetEyePoses。
    • 基于EyeRenderPose(游戲場景渲染)的立體渲染。
    • ovrHmd_EndFrame。

    用后面的特性在SDK 0.4上完成渲染:利用色差和時間扭曲來實現桶形畸變,內部延遲測試及動態額預測,低延遲垂直同步(v-sync)和翻轉(用direct-to-rift,甚至會更好),健康與安全警告。

    SDK易于集成,無需創建著色器和網格,通過設備/系統指針和眼睛紋理,支持OpenGL和D3D9/10/11,必須為下一幀重新申請渲染狀態。好處:與今后的Oculus硬件和特性更好地兼容,減少顯卡設置錯誤,支持低延遲驅動顯示屏訪問,例如前前緩沖區渲染等,支持自動覆蓋:延遲的測試、攝像頭指南、調試數據、透視、*臺覆蓋。

    支持Unreal Engine 3、Unreal Engine 4、Unity等主流游戲引擎使用SDK渲染。

    擴展模式:頭戴設備顯示為一個OS Display,應用程序必須將一個窗口置于Rift監視器上,圖標和Windows在錯誤的位置,Windows合成器處理Present,通常有至少一幀延遲,如果未完成CPU和GPU同步,則有更多延遲。

    Direct To Rift:輸出到Rift,顯示未成為桌面的一部分。頭戴設備未被操作系統看到,避免跳躍窗口和圖標,將Rift垂直同步(v-sync)與OS合成器分離,避免額外的GPU緩沖,使延遲降到最低,使用ovrHmd_AttachToWindow,窗口交換鏈輸出被導向Rift,希望直接模式成為較長期的解決方案。

    延遲:Motion-to-photon的延遲,涉及多階段:動作、傳感器、處理與合并、渲染、Scanout、傳輸、像素變化時間、像素余輝。

    將延遲保持在低值是提供良好虛擬現實體驗的關鍵,目標是< 20毫秒,希望接*5毫秒。

    渲染延遲- 時間扭曲(TimeWarp):將渲染重新延遲到后面一個時間點,與變形同時進行,減少感受到的延遲,負責DK2滾動快門,SDK 0.4處理方向、位置。在幀結束前,使用傳感器是否有其它方式?時間扭曲– 預測的渲染(John首創)。



    Programming for Multicore & big.LITTLE闡述了ARM的大小核心的特殊CPU并行架構,可區別地處理不同計算密集度的任務,以達到省電和性能的均衡。

    多核和big.LITTLE之多處理的情況:*臺趨勢:從中端到高端的四核+內核明顯增加,一切都在變大——LTE、GPU、攝像頭、顯示屏,單線程性能改善正在減少——關注多核,這不僅僅關乎性能——熱量約束用例現在已經司空見慣。軟件趨勢:操作系統供應商更多地利用多核,更廣泛地了解多處理支持庫,增加設備的組合使用,例如增強現實。

    利用并行性,在核心內可以使用NEON、SIMD,使用并行的工具(OpenMP、Renderscript、OpenCL等),盡可能地多線程,從來都不容易,但越來越有必要。

    2014-2015年的多核心趨勢:Cortex-A15/Cortex-A7 big.LITTLE的2014年的高端產品,芯數范圍:4(2+2)、6(2+4)和8(4+4)核心,Cortex-A17/Cortex-A7(32b)將于2015年上市。2014年,ARMv8-A(64b)芯片組在所有細分市場中嶄露頭角,四核和八核Cortex-A53進入入門級和中端,高端移動設備預計將在2015年向A57和A53 big.LITTLE遷移,多個big.LITTLE預期的拓撲結構。新的小型處理器提供與Cortex-A9類似的性能,使用大處理器(如Cortex-A15)顯著提升性能。

    從程序員的硬件視角看big.LITTLE系統:高性能Cortex-A57 CPU集群,節能Cortex-A53 CPU集群,CCI-400保持集群之間的緩存一致性,GIC-400提供透明的虛擬中斷控制。

    big.LITTLE:來自4+4MP系統與Quad Cortex-A15的證據:

    big.LITTLE開發 / 關于全局任務調度(GTS)的一般建議:

    • 相信調度程序。Linux將為性能和效率制定時間表,所有新任務都從大屏幕開始,以避免延遲,快速適應任務的需要。
    • 除非你知道線程是密集的,但不是緊急的,可以在小核執行則永遠不要使用大核,例如可能將小核用于在單獨的線程上加載資產。
    • 小核心是偉大的。你會經常使用它們,Cortex-A53的性能比Cortex-A9高20%,大多數工作負載將運行在很少的服務器上,為其它SoC組件提供更多熱量空間。
    • 大核心是重要的動力。把它們想象成短脈沖加速器,例如基于物理的特效,在設計過程中考慮權衡。

    需要避免:

    • 共享公共數據的不*衡線程。集群一致性很好,但不是免費的。
    • 如果有實時線程,請注意實時線程不是自動遷移的,實時線程是一個設計決策,請仔細考慮親和性。
    • 避免在大內核上執行長時間運行的任務。很少需要長時間的處理能力,這個任務可以并行化嗎?

    在2014年的big.LITTLE和Global Task Scheduling(或HMP)設備:精彩的巔峰表現,針對長時間運行的工作負載的節能、可持續計算;多處理:超越單線程性能的限制,避免對性能的熱量約束。

    NEON是一種廣泛的SIMD數據處理體系結構,ARM指令集的擴展,32個寄存器,64位寬(雙視圖為16個寄存器,在ARMv7中為128位寬),NEON指令執行“打包SIMD”處理,寄存器被視為相同數據類型元素的向量,數據類型:有符號/無符號8位、16位、32位、64位、單/雙精度、浮點還是整數。指令在所有線程上執行相同的操作。

    通用SIMD處理適用于許多應用:

    • 支持用于互聯網應用的范圍最廣的多媒體編解碼器。許多軟編解碼器標準:MPEG-4、H.264、ON2VP6/7/8/9、Real、AVS…,在軟件中支持所有互聯網和數字家庭標準。
    • 需要更少的周期。NEON將在復雜的視頻編解碼器上提供1.6x-2.5x的性能,單個簡單的DSP算法可以顯示更大的性能提升(4x-8x)
      ,處理器可以更快地休眠=>整體動態節能。
    • 易于編程。清晰正交向量結構,適用于廣泛的數據密集型計算,不僅適用于編解碼器——適用于2D/3D圖形和其它處理,現成的工具、操作系統、商業和開源生態系統支持。

    NEON的優化路徑:

    • 開源庫,例如OpenMAX、libav、libjpeg、Android Skia等,免費提供的開源優化。
    • 矢量化編譯器。利用現有源代碼自動利用NEON SIMD,狀態:發布(在DS-5 armcc、CodeSourcery、Linaro gcc和現在的LLVM中)。
    • NEON指令集。NEON操作的C函數調用接口,支持NEON支持的所有數據類型和操作,狀態:發布(在DS-5和gcc中),LLVM/Clang正在開發中。
    • 匯編程序。對于那些真正想在低級別上進行優化的人,狀態:已發布(在DS-5和gcc/gas中)。
    • 商業渠道。優化并支持現成的軟件包。

    Arm的各代架構圖:

    ARMv7-A到ARMv8-A的演變:

    異常級別和交互處理:

    OpenGL ES 3.0 and Beyond: How To Deliver Desktop Graphics on Mobile Platforms闡述了用OpenGL ES 3.0在移動端開發出桌面般的圖像特性的說明。

    OpenGL ES 3.1在2014年發布,當時的安卓應用占比達62%,ES 3.0的使用率達到8%:

    OpenGL ES 3.0的新功能:

    • 主要新功能:
      • 多重渲染目標
      • 遮擋查詢
      • 實例渲染
      • 統一緩沖區對象(UBO)和統一塊
      • 變換反饋
      • 基本重啟
      • 序二進制
    • 增強的紋理功能:
      • Swizzle、3D紋理、2D陣列紋理、LOD/MIP級別夾具、無縫立方體貼圖、不變紋理、NPOT紋理、采樣器對象
    • 新的渲染緩沖區和紋理格式:
      • 浮點格式
      • 共享指數RGB格式
      • ETC/EAC紋理壓縮
      • 深度和深度/模板格式
      • 單通道和雙通道紋理(R和RG)
    • ES著色語言3.00版:
      • 完全支持32位整數/浮點數據類型(IEEE754)
      • 輸入/輸出存儲限定符,復制到/來自后續/上一管道階段的值。
      • 數組構造函數和操作
      • 新的內置函數

    其中實例化和非實例化的對比:

    英特爾Bay Trail*臺上的OpenGL ES 3.1:

    OpenGL ES 3.1-計算著色器模型:

    OpenGL ES 3.1 EXT Extensions–細分著色器:

    OpenGL ES 3.1英特爾擴展–像素同步:

    • 概念

      • 英特爾OpenGL | ES擴展:GL_INTEL_fragment_shader_ordering

      • 允許從著色器中同步無序的內存訪問

      • 在同步點向著色器添加單個內置項:beginFragmentShaderOrderingINTEL();

    • 好處

      • 使用無序內存訪問映射到同一像素的片段可能會導致數據競爭
      • 片元可以按順序進行陰影處理
    • 應用

      • OIT
      • 可編程混合
      • 自適應體積陰影貼圖
      • 等等

    Assassin's Creed Identity: Create a Benchmark Mobile Game!講述了如何制作一個高性能的移動端游戲,包含引擎選擇、內容創作、架構概述、游戲邏輯、統一編輯器擴展等內容。

    使用的引擎要求可以快速原型化支持:易于學習的環境,支持動畫驅動的游戲,編輯器框架應該易于理解。移動端友好型,藝術家驅動,專注于可擴展工具,靈活的許可條款(例如iOS和Android僅適用于部分工程師)。

    在設計架構時,遵循幾條優先級。優先事項1:協作促進跨部門工作;優先事項2:讓人們繼續工作,不要破壞構建;優先級3:保持敏捷,解耦計劃和流線處理。

    前提條件:盡可能將游戲邏輯從Unity邏輯分離,控制和調試Unity引擎的Mono行為被認為是一項挑戰。首先,是優化游戲邏輯實體的更新時間:

    (重新)使用有限數量的3D角色:

    為什么要重寫Unity引擎的實體組件模型?啟動、喚醒、更新和修復更新不允許刺客信條團隊想要的控制粒度,復合實體只能通過預置進行克隆,并且需要像下面這樣:

    public class PlayerCharacter: AssassinCharacter, IPlayerCharacterStateMachinesCarrier,IPlayerEventHandlerCarrier, IParticipantCarrier, IVisionCarrier
    {
        public PlayerCharacter() : base(entity=> newPlayerCharacterStateMachines(entity)) 
        {
            ...
    

    文中還對Unity的C#提出一些深層次的優化建議和注意視線,感興趣的童鞋可閱讀全文。

    Frostbite on Mobile分享了Frostbite在移動端化的相關技術和經驗,包含從GL遷移到Metal、著色器、光照等。

    在GL遷移到Metal過程中,有兩大挑戰:1、引擎在內存消耗方面已經開始與xbox 360時代有所不同;2、許多著色器都是用純HLSL編寫的,使用YACCGL作為著色器轉換器。

    利用Metal經驗改進OpenGL ES 3.0后端,花時間將Metal和GL后端與控制臺/PC對齊。管理磁貼內存:glInvalidateFramebuffer,glClear,所有*臺上的延遲渲染/正向渲染,ES上的大多數功能,但性能較低。

    在光照方,許多光源都支持使用燈光分塊優化,所有游戲都轉移到基于物理的渲染。光源類型有點光源、聚光燈、區域光源、陰影投射等效物、*面反射、局部反射體積等。光照分塊的向前vs延遲如下圖:


    交叉編譯許多復雜著色器,計算用于光源剔除/裝箱(binning)的著色器,在Deferred / Forward / Forward+之間切換。用于局部反射的立方體貼圖數組unw(ra/ar)ped到2d lat-long紋理數組,采樣時有些alu開銷,但支持硬件尋址/過濾/MIPMAP。

    將延遲光積累從cs重寫為vs/ps,在tile內存中累積光照,在Metal上沒有間接的drawcalls/Dispatch,使用早期的頂點著色器模擬。相關優化:

    • 后端優化。公開tiler提示api并大量使用(非tiler上的nop:s),合并盡可能多的渲染過程,減少狀態變化。
    • 著色器代碼。盡可能多地使用內部函數/內置函數,使用標量數學,仔細打包、對齊數據。

    總之,在深入細節之前先了解全貌,今天的移動硬件和api支持完整的引擎功能集,許多特定于tile內存的優化都可以在不偏離桌面/控制臺代碼基礎的情況下完成,如果為多個*臺構建,請使用交叉編譯器。新API如Vulkan/ES 3.1、spir-v,特定于tile的著色器優化(延遲著色),使用tile本地存儲進行高效渲染,特定于移動設備的著色器優化(fp16/fp32使用、alu/帶寬*衡),未來可以考慮Tesselation、異步計算、間接繪制等。

    Advanced VR Rendering闡述了Valve的VR*臺上的嘗試和改進,包含立體繪制、時序(調度、預測、VSync、GPU氣泡)、鏡面鋸齒和各向異性照明以及其它VR渲染主題。

    Valve的VR有3年多的研究,聯合了硬件和軟件工程師,專為VR設計的定制化光學元件,顯示技術——低持久性、全局顯示,跟蹤系統(基于基準的位置跟蹤、基于點的桌面跟蹤和控制器、激光跟蹤耳機和控制器),SteamVR API–跨*臺、OpenVR。

    HTC Vive開發者版規格:刷新率是90赫茲(每幀11.11毫秒),低持久性,全局顯示,幀緩沖區的分辨率是2160x1200(每只眼睛1080x1200),離屏渲染的寬高約1.4倍:每只眼睛1512x1680 = 254萬個著色像素(蠻力),FOV約為110度,360? 房間尺度跟蹤,多個跟蹤控制器和其它輸入設備。

    光學與變形:Warp通道分別為RGB使用3組UV,以考慮空間和顏色失真。

    可視化1.4倍的渲染目標。其中上圖是扭曲前,下圖是扭曲后。

    每秒著色可見像素數:30赫茲時720p:2700萬像素/秒,60Hz時1080p:1.24億像素/秒,30英寸監視器2560x1600@60赫茲:2.45億像素/秒,4k監視器4096x2160@30赫茲:2.65億像素/秒,90赫茲時的VR 1512x1680x2:4.57億像素/秒,我們可以將其降低到3.78億像素/秒,相當于非虛擬現實渲染器在100赫茲時的30英寸監視器。

    沒有“小”的影響:跟蹤允許用戶接*跟蹤體積中的任何內容,無法實現超昂貴的效果,并聲稱“這只是角落里的一個小東西”,即使是最低畫質也需要比傳統創作的更高的逼真度,如果在跟蹤體積中,必須是高保真的。

    虛擬現實渲染目標:最低GPU最低規格,希望虛擬現實取得成功,但需要客戶,最低規格越低,客戶就越多,客戶不應注意到鋸齒,客戶將鋸齒稱為“閃爍”,算法應該擴展到多個GPU上。

    立體渲染(單GPU):強力運行CPU代碼兩次(錯誤),使用幾何體著色器放大幾何體(錯誤),重新提交命令緩沖區(很好的解決方案),使用實例將幾何體加倍(更好,API調用減少一半,提高了VB/IB/texture讀取的緩存一致性),來自High Performance Stereo Rendering For VR。

    立體渲染(多GPU):AMD和NVIDIA都提供DX11擴展以加速跨多個GPU的立體渲染,AMD實現的幀速率幾乎翻了一番,但還沒有測試NVIDIA的實現。非常適合開發人員,團隊中的每個人都可以在他們的開發盒中使用多GPU解決方案,在沒有不舒服的低幀率的情況下打破幀率。

    預測(Prediction):目標是盡可能縮短HMD和控制器變換的預測時間(渲染為光子)(精度比總時間更重要),低持久性全局顯示:在11.11毫秒幀中,面板僅點亮約2毫秒。

    上面的圖像不是最佳的VR渲染,但有助于描述預測。

    管線架構:渲染當前幀時模擬下一幀:

    在提交之前,會重新預測轉換并更新全局cbuffer,由于預測限制,虛擬現實實際上需要這樣做,必須保守地在CPU上減少大約5度。

    等待VSync:最簡單的VR實現,在VSync之后立即預測,模式#1:Present(),清除后緩沖區,讀取像素;模式#2:Present(),清除后緩沖區,在查詢上自旋轉(spin)。非常適合初始實現,但避免這樣做,GPU不是為此而設計的。

    “運行開始”的VSync:怎么知道離VSync有多遠?很棘手,圖形API并不直接提供這一點。Windows上的SteamVR/OpenVRAPI在一個單獨的進程中,在調用IDXGIOutput::WaitForVBlank()時旋轉,記錄時間并遞增一個幀計數器。然后,應用程序可以調用getTimeSincellastVsync(),該函數也會返回一個幀ID。GPU供應商、HMD設備和渲染API應該提供這一點。

    “運行開始”的細節:要處理壞幀,需要與GPU部分同步,在清除后緩沖區后注入一個查詢,提交整個幀,在該查詢上旋轉,然后調用Present(),確保在當前幀的VSync的正確一側,現在可以旋轉直到運行開始時間:

    為什么查詢題很關鍵?如果有一幀延遲,查詢將在下一幀的VSync右側,確保預測保持準確(下圖橙色部分):

    開始運行總結:具有一個穩定的1.5-2.0毫秒GPU性能增益!正常情況,可以分別在NVIDIA Nsight和微軟的GPUView中看到下圖所示:

    鋸齒是VR的頭號敵人:相機(玩家的頭)永遠不會停止移動,因此,鋸齒會被放大。雖然要渲染的像素更多,但每個像素填充的角度比以前做的任何事情都大,以下是一些*均值:2560x1600 30英寸顯示器:約50像素/度(50度水*視場),720p 30英寸顯示器:約25像素/度(50度水*視場),VR:約15.3像素/度(110度視場,是非VR的1.4倍),必須提高像素的質量。

    4xMSAA最低質量:前向渲染器因抗鋸齒而獲勝,因為MSAA正好有效,如果性能允許,使用8xMSAA,必須將圖像空間抗鋸齒算法與4xMSAA和8xMSAA并排進行比較,以了解渲染器將如何與業內其它渲染器進行比較,使用HLSL的“sample”修飾符時,抖動的SSAA顯然是最好的,但前提是可以節省性能。

    法線貼圖依然可用,大多數法線貼圖在虛擬現實中效果都很好。無效的情況:跟蹤體積內大于幾厘米的特性細節不好,以及被跟蹤體積內的表面形狀不能在法線貼圖中。有效的情況:無法*距離查看的被跟蹤體積外的遠處物體,以及表面“紋理”和精細細節。法線貼圖映射錯誤:

    任何只生成*均法線的mip過濾器都會丟失重要的粗糙度信息:


    用Mips編碼的粗糙度:可以存儲一個各向同性值(可視為圓的半徑),是所有2D切線法線與促成該紋理的最高mip的標準偏差,還可以分別存儲X和Y方向標準偏差的二維各向異性值(可視化為橢圓的尺寸),該值可用于計算切線空間軸對齊的各向異性照明


    添加藝術家創作的粗糙度,創作了2D光澤=1.0–粗糙度,帶有簡單盒過濾器的Mip,將其與每個mip級別的法線貼圖粗糙度相加/求和,因為有各向異性光澤貼圖,所以存儲生成的法線貼圖粗糙度是免費的。

    左:各向同性光澤度;右:各向異性光澤度。

    切線空間軸對齊的各向異性照明:標準各向同性照明沿對角線表示,各向異性與任一相切空間軸對齊,只需要2個附加值與2D切線法線配對=適合RGBA紋理(DXT5>95%的時間)。

    粗糙度到指數的轉換:漫反射照明將Lambert提高到指數(\(N\cdot L^k\)),其中\(k\)在0.6-1.4范圍內嘗,試了各向異性漫反射照明,但不值得這么做,鏡面反射指數范圍為1-16384,是具有各向異性的修改的Blinn-Phong。

    void RoughnessEllipseToScaleAndExp(float2 vRoughness, out float o_flDiffuseExponentOut,out float2 o_vSpecularExponentOut,out float2 o_vSpecularScaleOut)
    {
        o_flDiffuseExponentOut=((1.0-(vRoughness.x+ vRoughness.y) * 0.5) *0.8)+0.6;// Outputs 0.6-1.4
        o_vSpecularExponentOut.xy=exp2(pow(1.0-vRoughness.xy,1.5)*14.0);// Outputs 1-16384
        o_vSpecularScaleOut.xy=1.0-saturate(vRoughness.xy*0.5);//This is a pseudo energy conserving scalar for the roughness exponent
    }
    

    各向異性的光照計算過程:

    幾何鏡面鋸齒:沒有法線貼圖的密集網格也會產生鋸齒,粗糙度mips也無濟于事!可以使用插值頂點法線的偏導數來生成*似曲率的幾何粗糙度項。

    float3 vNormalWsDdx = ddx(vGeometricNormalWs.xyz);
    float3 vNormalWsDdy = ddy(vGeometricNormalWs.xyz);
    float flGeometricRoughnessFactor = pow(saturate(max(dot(vNormalWsDdx.xyz, vNormalWsDdx.xyz), dot(vNormalWsDdy.xyz, vNormalWsDdy.xyz))), 0.333);
    vRoughness.xy=max(vRoughness.xy, flGeometricRoughnessFactor.xx); // Ensure we don’t double-count roughness if normal map encodes geometric roughness
    

    flGeometricRoughnessFactor的可視化。

    MSAA中心與質心插值并不完美,因為過度插值頂點法線,法線插值可能會在輪廓處導致鏡面反射閃爍。下面是文中使用的一個技巧:

    // 插值法線兩次:一次帶質心,一次不帶質心
    float3 vNormalWs:TEXCOORD0;
    centroid float3 vCentroidNormalWs:TEXCOORD1;
    
    // 在像素著色器中,如果法線長度*方大于1.01,請選擇質心法線
    if(dot(i.vNormalWs.xyz, i.vNormalWs.xyz) >= 1.01)
    {
        i.vNormalWs.xyz = i.vCentroidNormalWs.xyz;
    }
    

    法線貼圖編碼:將切線法線投影到Z*面上僅使用2D紋理范圍的約78.5%,而半八面體編碼使用2D紋理的全部范圍:

    縮放渲染目標分辨率:事實證明,1.4x只是HTC Vive的一個建議(每個HMD設計都有一個基于光學和面板的不同建議標量),在較慢的GPU上,縮小建議的渲染目標標量,在速度更快的GPU上,放大建議的渲染目標標量,盡量利用GPU的周期。

    各向異性紋理濾波:提高了顯示器的分辨率(別忘了,VR的每度只有更少的像素),對于顏色和法線貼圖,強制啟用此選項,默認使用8x。禁用其它所有功能,僅三線性,但需要測量性能。如果在其它地方遇到瓶頸,各向異性過濾可能是“免費的”。

    噪點是你的朋友,在虛擬現實中,過渡很可怕,帶狀(banding)比液晶電視更明顯,當像素著色器中有浮點精度時,可在幀緩沖區中添加噪點。

    float3 ScreenSpaceDither(float2vScreenPos)
    {
        // Iestyn's RGB dither(7 asm instructions) from Portal 2X360, slightly modified for VR
        float3 vDither = dot(float2(171.0, 231.0), vScreenPos.xy + g_flTime).xxx;
        vDither.rgb = frac(vDither.rgb / float3(103.0, 71.0, 97.0)) - float3(0.5, 0.5, 0.5);
        return (vDither.rgb / 255.0) * 0.375;
    }
    

    環境圖:無窮遠處的標準實現 = 僅適用于天空,需要為環境圖使用某種類型的距離重新映射:球體很便宜,立方體更貴,兩者在不同的情況下都很有用。

    模板網格(隱藏區域網格):用模板屏蔽掉實際上無法透過鏡頭看到的像素,GPU在提前模板拒絕時速度很快。或者,可以渲染到接*z的深度緩沖區,以便所有像素都可啟用提前z測試,透鏡會產生徑向對稱變形,意味著可以有效地看到投影在面板上的圓形區域。

    模板網格圖例。從上到下從左到右依次是:扭曲視圖、理想扭曲視圖、浪費的空間、無扭曲視圖、無扭曲視圖(屏蔽無效像素)、最終無扭曲視圖。

    模板網格(隱藏區域網格):SteamVR/OpenVRAPI提供此網格,填充率可以降低17%!無模板網格:VR 1512x1680x2@90Hz:4.57億像素/秒,每只眼睛254萬像素(總計508萬像素),帶模板網格:VR 1512x1680x2@90Hz:3.78億像素/秒,每只眼睛約210萬像素(總計420萬像素)。

    扭曲網格,依次是:鏡頭畸變網格、暴力、剔除0-1之外的UV、剔除模板網格、收縮扭曲。

    需要性能查詢!總是保持垂直同步,禁用VSync查看幀率會讓玩家頭暈,需要使用性能查詢來報告GPU工作負載,最簡單的實現是測量從第一個到最后一個draw調用。理想情況下,測量以下各項:從Present()到第一次繪圖調用的空閑時間、從第一次繪圖調用到最后一次繪圖調用、從上次繪圖調用到現在的Present()的空閑時間。

    總結:立體渲染、預測、“運行開始”(每幀節省1.5-2.0毫秒)、各向異性照明和Mipping法線貼圖、幾何鏡面抗鋸齒、模板網格(節省17%的渲染像素)、優化的扭曲網格(降低15%的成本)。


    14.4.3.4 并行技術

    UFO Invasion: DX11 and Multicore to the Rescue講解了DirectX 11下的多線程特性。文中對比了多線程和單線程的任務執行模式,多線程下不再串行地執行任務,而是劃分成若干各子任務,并在多幀直接重疊:

    在多線程模式下,當有的工作線程處于饑餓(空閑)狀態時,應該可以從其它滿負載的線程偷取任務執行:

    文中提及了Entity的概念和性質,Entity包含兩種數據:State和Mind。State是公開給游戲的其它系統的數據,Mind是Entity的私有數據。Entity更新時,可以并行地更新State和Mind,但需遵循的規則如下:

    • 永遠沒有其它實例讀取Mind。
    • 更新Mind時不要改變State。
    • 更新Mind時不要關注其它實例。

    Entity更新允許依賴,有時上一幀的信息不夠,但知道想知道的,Entity可以聲明自己依賴于另一個(或其它)Entity。

    Render是完全并行的,因為每個線程都在自己的隊列中收集數據,排序稍后會將所有內容放在一起。每個條目的權重為128位(64位密鑰、32位實體ID、32位參數),對于4096個條目,需要排序的64K數據,勉強足以證明并行排序的合理性……許多昂貴的渲染部分同時發生在此,最顯著的是剔除。

    渲染本質上是在一個線程上連續發生的,查看當前密鑰與前一個密鑰之間的差異,引擎可以非常有效地更新管線的狀態。從一個越界的“Last Key”開始,它使引擎選擇正確的渲染目標、視口和各種狀態。Entity被回調以繪制它需要繪制的任何內容,當前鍵成為最后一個鍵,重復直到完成。實例化實際上只是累積具有相同實例化id的鍵并使用結果列表回調Entity的問題。

    DirectX 11的多線程非常簡單:

    • 多線程渲染到延遲上下文。
    • 延遲上下文生成命令列表。
    • 主線程將它們提交給立即上下文。

    適應DirectX 11多線程模型的渲染流程如下:

    最巧妙的是,這種方法還處理命令列表不依賴于當前管線狀態的要求:總是從一個超出范圍的“最后一個鍵”開始,即每個命令列表都以完整的管線狀態開始。

    Shears - Squeeze the Juice Out of the CPUs: Post Mortem of a Data-Driven Scheduler介紹了一種通過解決多線程環境中的數據爭用來安排引擎循環的創新方法。這種數據驅動的調度最大限度地減少了數據競爭,并最大限度地增加了包括Cell的SPU在內的硬件占用。使用無鎖算法實現,與更傳統的調度程序相比,實現了更好的性能。

    當前引擎在循環上主流的做法是采用重疊,利用多線程并行:

    執行分布在多個線程上,然后收集有關同步點的數據,即使它可以很好地在微觀和宏觀上擴展,但有數據訪問問題:處理的只讀權限或寫訪問但需要鎖同步原語,需逐項目調整。

    下圖的架構即使它的伸縮性比同步點好,但和上一個問題一樣,仍然需要同步點和/或鎖,逐項目的調整:

    可以做些什么來避免這些問題?使用剪刀(Shear):將大任務切割成更小的塊。結合下圖看一下前面的任務序列,推送一堆數據,任務C鎖定直到A&B完全完成,所以,識別2個數據流D0&D1。

    改變視角,從數據流看。數據流表示自上而下,任務仍然存在,添加了重要元素:任務訪問,任務訪問定義調度。現在放入數據流,任務A&B先運行然后C,結果和以前一樣。

    現在將數據倒入數據流中,結果 => 任務C可以更早開始,不等待任務A&B,調度程序的輸入意味著數據訪問聲明而不是任務序列聲明。

    以游戲循環引擎為例:數據從頂部 => 流向底部,一切都與數據流有關,沒有數據 => 沒有進程 => 只會執行必要的代碼。

    文中還給出了詳細的動畫說明Lock-free的執行過程(下圖是靜態的,無法展示動畫):

    下表是在Intel Core 2 Quad Processor Q6600測試臨界區和無鎖原子的性能對比,單位是操作數/毫秒,2個線程:不是20%或50%,快36倍 - 超過3000%,57%的空閑時間調用鎖函數,即使鎖成功。

    隨著硬件開發人員遠離創建更快的處理器來代替多核架構,游戲開發人員必須利用多線程技術來利用這些新設備。 對于多核移動設備,對基于網絡的多線程游戲引擎的需求已成為現實。Building a Multithreaded Web Based Game Engine Using HTML5, CSS3 and JavaScript討論各種多線程Web引擎架構的設計,這些架構允許使用線程控制器在線程中動態處理請求。利用WebWorkers、WebSockets和WebGL等HTML5和JavaScript API可以在基于瀏覽器的3D游戲中建立一個新標準,這些游戲功能齊全,真正跨*臺支持移動和桌面設備,無需插件。


    Web端的應用架構和OS內核之間的關系。

    嵌套和共享類型的工作。

    Web端的多線程架構和運行模型。

    應用層、OS、第三方擴展的層次結構。

    我們知道CPU擅長任務并行,GPU擅長數據并行。而GPU Task-Parallelism: Primitives and Applications偏偏劍走偏鋒,不按套路出牌,嘗試在GPU上引入任務并行,闡述其概念、重要性、實踐等技術。

    什么是任務并行?

    • 任務:在單個上下文中執行的一組邏輯相關的指令。
    • 任務并行:任務并行處理。調度組件確定如何將任務分配給可用的計算資源。
    • 示例:Cilk、英特爾TBB、OpenMP。

    GPU是數據并行的,圍繞數據并行處理構建的GPU硬件,CUDA是一種數據并行抽象,基于任務的工作負載被忽略(到當時為止)。

    GPU任務并行性:擴展了GPU編程的范圍,許多任務并行問題仍然表現出大量的并行性,將GPU編程為任務并行設備,分為兩部分:原語(primitive)和應用程序。

    原語的目標是構建一個任務并行系統,它可以處理不同的工作流程,處理不規則的*行度,遵從任務之間的依賴關系,對所有這些進行負載*衡。

    原語并行:

    • 任務粒度。處理任務的正確并行粒度是多少?每個線程一個任務是好的做法,每個warp一個任務更好。重視SIMD,將warp視為具有 32寬矢量通道的MIMD線程。

    • 任務管理器(啟動、退出)。如何繼續處理任務直到沒有任務為止?持久線程編程模型:

      while(stillWorkToDo)
      {
          // 運行任務 
      }
      

      將啟動范圍與工作量分離,謹防死鎖!

    • 任務通信。如何在SM之間*均分配任務?具有工作捐贈程序的分布式隊列。也可用單個塊隊列,因為原子現在足夠快,也很簡單。

    • 任務依賴。如果任務有依賴關系怎么辦?如何增強當前的系統以尊重依賴關系?

      依賴決策:放入隊列的所有任務都在沒有依賴關系的情況下執行,依賴關系影響哪些任務可以放入工作隊列,維護一個任務依賴映射,每個warp必須在排隊其它工作之前檢查該映射。

      while(Q is not empty)
      {
          task t = Q.pop()
          Process (t)
              
          Neighbors tnset = dependencyMap(t)
          For each tn in tnset
              tn.dependencyCount--;
          if(tn.dependencyCount == 0) 
              Q.push(tn);
      }
      

    對于應用并行,有多種場景需要任務并行:Reyes渲染、延遲照明、視頻編碼,只使用必要的原語。

    對于Reyes渲染,需要任務并行的原因是不規則并行、動態通信。需要的原語是持久線程、動態任務隊列。

    Reyes的任務并行。

    對于延遲光照,不同的燈光影響屏幕的不同部分,所以我們用太多的燈光細分tile。需要任務并行的原因是不規則并行、動態通信。需要的原語是持久線程、動態任務隊列。

    總之,任務并行性很重要,包含多種應用場景。幾種基本原語:調度任務粒度、持久線程、動態排隊、依賴解析。

    Killzone Shadow Fall: Threading the Entity Update on PS4分享了PS4游戲Killzone Shadow Fall使用的線程化更新Entity的技術。

    實體是大多數游戲對象的基類,例如玩家、敵人、武器、門,不用于靜態世界,具有組件,例如模型、移動器、破壞性,以固定頻率更新(15、30、60Hz),幾乎所有頻率均為15Hz,玩家更新頻率更高,以避免延遲。實體和組件具有代表性,控制渲染、音頻和VFX,在實體未更新的幀中插入狀態,插入比更新更便宜,但會引入延遲,始終與上次更新保持一致。

    在PS3中,一個Entity = 1個纖程,大部分時間花在PPU上,沒有明確的并發模型,讀取部分更新狀態,實體相互等待。

    在PS4上,一個Entity = 一個作業,無纖程,實體整體更新,如何解決競爭條件?

    有依賴的實體不能并發更新,但沒有依賴的可以。無(間接)依賴=無訪問權限,工作方式有兩種:武器也可以接*士兵,創建依賴項有1幀延遲,全局系統需要鎖。

    但是,有少量實體會導致大量的瓶頸:

    非排它依賴項,進入“子彈系統”的通道必須有鎖保護。

    還可以使用弱引用,兩個坦克相互開火(下圖),循環依賴發生時,更新順序顛倒,不經常使用(每幀<10)。

    非更新實體,實體可以跳過更新(LOD),實體可以在其它幀中更新,正常調度!下圖是各種依賴的總結:

    調度算法:具有獨占依賴關系的實體合并到一個作業中,依賴關系決定排序,非排它性依賴成為作業依賴,先開啟開銷大的作業!

    邊界情況:非周期性依賴變成周期性作業依賴,作業1和作業2需要合并。

    跨幀*衡實體,防止所有15Hz實體在同一幀中更新,實體可以移動到其它幀,1次更新的增量時間更短,將父子關聯的實體保持在一起,如士兵的武器、騎槍士兵、鎖定*距離戰斗。

    性能問題:內存分配互斥,消除了許多動態分配,使用堆棧分配器,鎖定物理世界,主模擬世界的R/W互斥,第二次“子彈碰撞”寬相位+鎖定,大量依賴實體,玩家更新非常大開銷等。

    可以采用切分場景(Cut Scene)的策略。切分場景實體需要依賴關系,切分場景中的10多個角色創造了巨大的作業!

    以上問題的解決方案是為非交互實體創建子切分場景,主切分場景決定時間和流程,在時間線中向前掃描1幀以創建依賴關系。

    使用對象,不可能依賴可用對象(太多),獲取可用對象的列表,受鎖保護的全局系統,“使用”圖標出現在屏幕上,玩家選擇,建立依賴關系,啟動“使用”動畫,1幀后開始交互(依賴關系有效),隱藏1幀延遲!

    以上各類更新方式的性能對比如下:


    整幀的時間線和實體更新的時間線如下兩圖:


    總之,易于在現有引擎中實現,游戲程序員可以像單線程一樣編程,幾乎沒有多線程問題。

    Multithreading for Gamedev Students講述了多線程編碼的相關技術,如硬件支持、常見游戲引擎線程模型、競爭條件、同步原語、原子與無鎖、危險。

    多處理器(Multiple processors):成本高、功耗高、芯片間延遲、緩存一致性問題,通常僅限于高端臺式機和超大型計算機。

    多核(Multiple cores):多核可更有效地利用可用硬件資源,內核可能共享二級/三級緩存、內存接口,臺式機和游戲機最常見的設置。

    多個硬件線程(Multiple hardware threads):同步多線程(SMT),英特爾領地上的“超線程”,線程共享core的資源,如執行單元、一級緩存等。更有效地利用核心資源,暫停的線程不會浪費資源,與單硬件線程相比,通常快10%-20%,但變化很大。

    多個軟件線程(Multiple software threads):操作系統可以創建多個進程,游戲通常作為一個進程運行。一個進程可以產生多個線程,共享內存地址空間。線程可以在多個線程之間遷移,或固定到具有線程關聯的特定線程。

    硬件樣例:

    • Intel Xeon E5-1650:

      • 6 hyper-threaded cores
        • 12 ‘logical processors’
      • L1 & L2 per-core
      • Shared L3
    • Xbox 360:

      • IBM Xenon CPU
        • PowerPC
      • 3-core SMT
        • 6 hardware threads
      • L1 per core, shared L2

    ?

    • PlayStation 4 / Xbox One
      • AMD Jaguar architecture
      • 2 quad-core 'modules'
        • 8 hardware threads
        • L1 cache per core
        • L2 cache shared by all cores in module

    ?

    • AMD GCN GPUs
      • Used by both PS4 & Xbox One
        • 18 & 12 compute units respectively
      • Rendering is inherently parallel
        • Hardware exploits this to achieve high speed & throughput
      • Extensible to non-graphics workloads
        • Compute & async compute

    ?

    游戲內的多線程:30/60fps目標,必須使用所有可用資源,許多相互作用的系統,有限的共享數據集,一些常見的線程模型。游戲內常見的幾種并行方式:



    競爭條件:系統的輸出取決于時序,時間受到很多因素的影響,未定義的行為-所有賭注均已取消,隨機=不可預測=錯誤結果,調試噩夢。下圖是典型的競爭條件案例:

    同步:

    • 自旋鎖(Spinlock)

      • 緊密旋轉,試圖獲得鎖定,通常通過原子變量。
      • 可能會造成問題,CPU和內存帶寬使用。
      • 正確使用時是輕量級。
    • 互斥(mutex)

      • 鎖定/解鎖配對。
      • 保護代碼的關鍵部分,提供單線程訪問(互斥)。
    • 信號(Semaphore)

      • 維護內部計數器。
      • 等待(遞減)和信號(遞增)操作。<= 0個線程睡眠,大于0個等待線程繼續。
      • 信號可以喚醒線程。
      • 用于線程之間的信令,或者控制可以執行任務的線程數。
    • 條件變量(Condition variable)

      • 線程等待條件滿足。
      • 監視器:互斥+條件變量。
      • 游戲中使用的*臺特定事件。
    • GPU圍欄(Fence)

      • 用于CPU和GPU交互。知道共享數據何時產生或使用。
      • 特定于*臺的API,DX12和OpenGL自3.2以來的核心。

    編寫多線程代碼時,還需要注意內存順序(Memory ordering),以下面代碼為例:

    // global
    int data = 0;
    int readyFlag = 0;
    
    // thread A
    data = 32;
    readFlag = 1;
    
    // thread B
    if(readFlag == 1)
    {
        Output(data); // 這里的data并非唯一的值,可能是0或32!!
    }
    

    以上錯誤的出現正是多線程之間的內存順序問題。編譯器可以重新排序指令,CPU可以重新排列指令,CPU可以重新安排內存訪問順序。內存模型確定哪些讀寫操作可以相對于其它操作重新排序,硬件和軟件:處理器只有一個內存模型,語言可能還有另一個原因。

    順序一致性(Sequential consistency):內存訪問所見即所得,沒有明顯的重新排序,對可能的優化的后續限制,除非性能另有要求,否則請使用。

    內存屏障:用于在編譯器和CPU上強制執行內存排序,隱含在某些函數中,如std::atomic<>操作不是memory_order_relaxed的操作。明確獲取和釋放柵欄。

    無鎖編程:實現無鎖、無阻塞的多線程算法,沒有線程可以通過被中斷來阻止全局進程。使用無鎖的原因是免于鎖爭用、可擴展性、性能(無約束鎖的性能非常好)。但無鎖的缺點是復雜!

    多線程的危險(Hazard)包含:

    • 死鎖。獲得了兩把鎖,但順序不同,一個線程鎖定A并等待B,另一個鎖B并等待A。
    • 活鎖(Livelock)。多線程在局部處理,每個線程的活動都會導致其它線程多次無法取得全局處理。經典類比:走廊里有兩個人。
    • 優先級反轉(Priority inversion)。低優先級線程獲取高優先級線程所需的鎖,然后由于其優先級而進入睡眠狀態,系統性能最終由低優先級線程而不是高優先級線程決定。
    • 虛假分享(False sharing)。多個線程在同一緩存線中修改內存,導致持續緩存失效和不必要的內存流量,會顯著影響性能。
    • ABA問題

    ?

    多線程的復雜性:只知道一點知識是危險的。錯誤容易犯,但很難調試。沒有不可能的事情, “百萬分之一”,每幀50次,每秒30幀…~11分鐘,如果你運氣好,壞事就會發生。即使是簡單的事情也會引起問題:

    enum{EValueA, EValueB};
    // ...
    Assert(foo==EValueA || foo==EValueB);
    

    上面代碼乍一看,不會有太多問題,但它開始偶爾會觸發斷言。可能本能會想到是內存損壞或其它一些內存問題,比如對齊(GPU通過柵欄設置foo的值),因為該值只能是這兩個值中的一個。更讓人困惑的是,無論何時報告斷言,foo實際上等于EValueA。實際上,在斷言邏輯的中間,foo被從EValueB改為EValueA——在第一次測試之后,在第二次測試之前!這表明,當你放松警惕時,事情很容易出錯!!

    調試性:考慮到所有這些復雜性,提前計劃,始終嘗試保持單線程路徑處于活動狀態,所以你知道問題是邏輯還是線程,運行時可切換(如果可能),有時,僅僅思考比調試更好。

    [Concurrent Interactions in The Sims 4](https://www.gdcvault.com/play/1020190/Concurrent-Interactions-in-The-Sims#:~:text=For The Sims 4%2C we,in to perform the interaction)講述了The Sims 4(模擬人生4)架構的并發技術,包含互動、約束、交互隊列、轉換、社交等。

    The Sims的世界是用游戲對象建立起來的,游戲對象提供交互,模擬人生也是對象!模擬人生運行交互,互動是行為的基本單位。多任務是很自然的事情,人們同時做多種事情,頻繁請求的功能,系統方法是有價值的,臨時實現需要大量工作,結果不一致。

    不是真正地并發執行會很棘手,可能導致諸如死鎖、競爭條件等,多任務涉及上下文切換和協作等。


    角色多任務的串行和并行圖例。

    多任務中使用了子動作(Sub Action)的概念:

    規則:我能執行一個動作嗎?狀況→ 行動。如何執行動作?行動→ 條件。避免重復邏輯。

    約束:數據驅動的規則,運行互動的先決條件。回答問題:我可以進行互動嗎?如何運行互動?

    約束創作:數據驅動。動畫:位置、姿勢、攜帶,XML調優:幾何、方向、表面,腳本:評分功能、視線。

    約束組合:多任務組合約束,支持操作:交集、并集。

    交互隊列:每個模擬都有一系列激活的互動和等待交互的有序隊列。互動具有優先級:高(用戶導向)、低(自動)、空閑(已完成但仍在運行)。


    隊列處理和交互處理。

    生成行為:約束定義了執行交互的先決條件,可以生成性地使用,需要能夠找到到約束的轉換。

    轉換圖:每個對象上的約束都存儲在一個抽象圖中,邊是狀態變化,搜索圖形以生成轉換序列。

    使用轉換圖:

    圖搜索:多個節點可以滿足需求,邊緣按成本加權,按*似距離加權的路線,搜索決定最佳路徑。

    搜索優化:雙向搜索,使用攜帶(carry)、插槽(slot)簡化,節點查詢索引。

    Parallelizing the Naughty Dog Engine Using Fibers講述了Naughty Dog引擎利用纖程來實現并行化的技術。

    新作業系統的設計目標是允許將無法移動到SPU的作業化代碼,作業可以在執行過程中讓渡給其它作業,例如玩家使用kick更新并等待光線投射,游戲編程人員易于使用的API,用戶沒有內存管理,同步/鏈接作業的一種簡單方法,性能僅次于API的易用性。

    纖程(Fiber)就像一個局部的線程,用戶提供堆棧空間,包含纖程狀態的小型上下文和節省寄存器。由線程執行,協作多線程(無搶占),纖程之間的切換是明確的(PS4上的sceFiberSwitch),其它操作系統也有類似的功能。最小化開銷,在纖程之間切換時沒有線程上下文切換,只有注冊保存/恢復。(程序計數、指針堆棧、gprs…)

    Naughty Dog引擎的作業系統有6個工作線程,每個都鎖定在一個CPU內核上,線程是執行單元,纖程是上下文,作業始終在纖程的上下文中執行,用于同步的原子計數器。有160個纖程(128 x 64k堆棧,32 x 512k堆棧),3個作業隊列(低/正常/高優先級),沒有作業竊取。

    一切都是作業:游戲對象更新、動畫更新和骨骼混合、射線投射、命令緩沖區生成,除了I/O線程(套接字、文件I/O、系統調用…),這些是系統線程,像中斷處理程序一樣實現(讀取數據、發布新作業),總是等待,從不進行昂貴的數據處理。

    新作業系統的優點:極易更新現有游戲玩法,深度調用堆棧沒有問題,讓一個作業等待另一個作業是直截了當的:WaitForCounter(...)。超輕量,可更換纖程,系統支持的操作,如PS4上的sceFiberSwitch(),保存/恢復程序計數器和堆棧指針和其它所有的寄存器。缺點是無法再使用系統同步原語:互斥、信號量、條件變量…鎖定到特定的線程,纖程在線程之間遷移。同步必須在硬件層面上完成,原子自旋鎖幾乎無處不在,特殊作業互斥鎖用于持有時間較長的鎖,如果需要,將當前作業置于睡眠狀態,而不是旋轉鎖定。

    對纖程的支持:可以在調試器中查看纖程及其調用堆棧,可以像檢查螺紋一樣檢查纖程,纖程可以命名/重命名,指明當前作業,異常處理,纖程調用堆棧與線程一樣保存在核心轉儲中。纖程安全線程本地存儲(TLS)優化,問題是TLS地址允許在測試期間緩存,默認情況下,該函數將運行,在功能中間切換纖程用錯誤的TLS指針醒來。目前不受Clang支持,解決方法:對TLS訪問使用單獨的CPP文件。在作業系統中使用自適應互斥體,可以從普通線程添加作業,旋轉鎖->死鎖,在進行系統調用之前,旋轉并嘗試抓住鎖,解決優先級反轉死鎖,由于初始旋轉,可以避免大多數系統調用。

    引擎的管線如下,游戲邏輯向渲染邏輯向GPU依次發送任務:

    以幀為中心的設計,每個階段都是完全獨立的,不需要同步,一個階段可以立即處理下一幀,簡化了引擎設計的復雜性,由于并行性,幾乎不需要鎖,鎖僅用于在大量作業的階段更新中進行同步。下面是新舊設計的對比圖:


    Naughty Dog引擎對幀的定義:“經過處理并最終顯示在屏幕上的一段數據”,要點是“一段數據”,時間不長,幀由數據成為顯示圖像所經過的階段定義。

    幀參數(FrameParams)是每個顯示幀的數據,最終顯示的每個新幀的一個實例,通過引擎的各個階段發送,包含每幀狀態:幀序號、增量時間、蒙皮矩陣,每個階段訪問所需數據的入口點。無競爭資源,由于每個階段都在一個獨特的實例上工作,因此不需要鎖,狀態變量會在每一幀復制到此結構中,例如增量時間、攝像機位置、蒙皮矩陣、要渲染的網格列表,存儲每個階段的開始/結束時間戳:游戲、渲染、GPU和翻轉。如果幀已完成特定階段,則易于測試:HasFrameCompleted(frameNumber),現在可以很容易地跟蹤生命周期,如果在第X幀中生成GPU要使用的數據,則等待HasFrameCompleted(X)為真,有16個幀參數,可以在它們之間旋轉,但只能跟蹤最后15幀的狀態。

    內存生命周期:單游戲邏輯階段(臨時內存)、雙游戲邏輯階段(低優先級光線投射)、渲染邏輯階段的游戲(對象實例數組)、游戲到GPU階段(蒙皮矩陣)、渲染到GPU階段(命令緩沖區)及同時用于CPU和GPU的內存!

    內存不足:許多不同的線性分配器,許多不同的生命周期,所有尺寸都適合最壞的情況,從未同時遇到所有分配器的最壞情況,100-200 MiB的浪費內存。

    標記堆(Tagged Heap)是基于塊的分配器,2M的塊大小,2MiB是PS4上的“大頁面”–>1 TLB條目,每塊都有一個標記(uint64_t),沒有“Free(ptr)”接口,只能釋放與特定標記關聯的所有塊(下圖)。

    所有分配器都使用標記堆,從共享標記堆中分配2M塊,并在分配器中局部存儲,99%的分配都小于2MB,大于2M的分配會從標記的堆中連續分配2M的塊,在此局部塊中進行分配,直到為空,像這樣共享一個公共塊池允許動態調整分配器的大小。

    分配器給多個工作線程分配標記堆的示意圖。如果單個2M的塊被多個線程同時使用,則需要加鎖保護,防止競爭。

    優化:在分配器中為每個工作線程存儲一個2M塊,使用工作線程索引來選擇要使用的塊,該線程上的所有分配都將進入該線程的塊,從而避免競爭,99.9%的內存分配不需要鎖,實現高容量、高性能分配器。

    逐線程塊的分配器示意圖。

    總結:纖程很棒,以幀為中心的設計簡化了引擎,使用FrameParams之類的方法可以大大簡化數據生命周期和內存管理,在處理多幀引擎設計時,基于標記的塊分配器非常有用。


    14.4.3.5 特殊技術

    Advanced Screenspace Antialiasing描述了抗鋸齒的各類技術,包含形態抗鋸齒、基于屏幕空間的抗鋸齒及實現細節。該文總結了當時的主流抗鋸齒技術:

    文中關于刪除瑕疵的描述如下:

    • 已經丟失的信息無法被恢復。例如下采樣瑕疵無法被刪除。

    • 半透明瑕疵比較棘手。深度剝離、模板路徑的K-Buffer可能可以解決此問題,避免透明幾何圖形(如窗口和框架)的交叉,在透明發生的地方添加不透明邊緣。

    • Alpha Test幾何體和幾何邊緣可以被處理。

    形態抗鋸齒(Morphological Antialiasing)使用顏色不連續檢測器,可能會錯過重要的邊(其中顏色差異很小),可能過于敏感(檢測紋理細節),會檢測透明,不需要額外的緩沖區(如法線、紋理坐標和深度)。常用的邊緣檢測卷積核如下:

    檢測各種類型的線段,包含S形和由2個L形組成的U形:

    所需的實際長度和像素位置覆蓋率可以使用截距定理(intercept thorem)計算得到:

    對于水*計數,2個額外的緩沖區用于邊緣檢測(僅顯示一個):


    4個額外緩沖區的計數技術(上、下、左、右):

    總是拒絕所有不在垂直不連續緩沖區中的像素:

    形態抗鋸齒總是以明確的方式處理U形,Biri.v的實現占用了大量的內存:2個ARGB計數緩沖區、1個argb混合緩沖、1個RG間斷緩沖器、1512×512像素的浮點覆蓋率計算查找表。通過巧妙的封裝和緩沖區共享,可以顯著減少總體內存消耗(同時增加計算成本)。該算法在像素著色器中使用了大量的條件分支,使用了大量的通道,沒有使用Early-Stencil。因此,它不適合當時的主機。

    文中提出了改進版本,稱作Advanced Screenspace Antialiasing(ASAA)。ASAA實現沒有使用顏色來進行不連續檢測,因為深度會更精確,深度顯示并不是所有的邊緣和細粒度細節丟失。添加法線和紋理坐標作為額外的邊緣檢測提示,這兩個額外的緩沖區可以安全地與場景顏色共享,因為它們正在被使用。使用拉普拉斯卷積核進行不連續檢測→檢測邊緣兩側的不連續:

    通過改進邊緣檢測、不連續檢測、計數、覆蓋率計算等過程,ASAA獲得了以下優點:

    • 內存占用率適中。1個RGB緩沖區用于技術和不連續(比2xMSAA低),2個G16R16用于法線和紋理坐標,可共享,建議壓縮這些值,因為邊緣檢測著色器是紋理綁定的。
    • 沒有條件分支。
    • 適合當時的主機。
    • 其它特點:對U形的模糊處理,在Xbox360和PS3上,計數可以轉移到CPU上。

    ASAA的實現步驟和流程如下:

    其中混合可以優化,XBox360上可以使用線性紋理,可以在GPU上計數。ASAA的效果對比如下:

    Destruction Masking in Frostbite 2 using Volume Distance Fields分享了Frostbite 2引擎使用體積距離場來渲染破壞物體的效果。

    有符號體積距離場的使用是將球體放置在幾何體上,標記破壞面罩的位置,從球體組計算距離場,結果存儲在體積紋理中,使用低分辨率:約2米/像素,每個遮蔽的幾何體一個紋理。

    點采樣、三線性過濾、放大+細節等技術可以獲得更高細節的效果:

    // 高細節的距離場計算代碼
    float opacityFromDistanceField(float distanceField, float detail)
    {
        distanceField += detail * g_detailInfluence;
        return saturate(distanceField * g_distMultiplier + g_distOffset);
    }
    

    體積紋理注意事項,Xbox 360體積紋理要求32×32×4的尺寸倍數,許多小紋理會浪費內存,使用紋理圖集,每個維度一個圖集簡化了打包,需要填充以防止邊界泄漏。

    繪制破壞遮罩時,與幾何圖形一起繪制,在像素著色器中使用 [branch] 進行優化,RSX上的動態分支效率不高,6個循環命中分支,粗分段大小(800-1600 像素),將遮罩繪制為延遲貼花。延期貼花的繪制步驟:

    • 繪制場景幾何。
    • 在貼花區域周圍繪制凸體積。
    • 獲取深度緩沖區并轉換為體積內的局部位置。
    • 使用局部位置來查找不透明度,例如體積紋理。
    • 在幾何圖形之上混合。

    對于投影細節/法線紋理,需要切線向量,G-Buffer法線不適合,包含來自法線貼圖的數據。需要的切向量數量有限,使用主要幾何將索引寫入G-Buffer,使用索引對包含切線向量查找表的紋理進行采樣。

    對于Alpha混合,固定功能混合在混合到G-Buffer時會導致問題,用于混合和目標alpha的輸出alpha。受限于貼花的輸出alpha,也受限于G-Buffer布局中,可編程混合是個不錯的用例。

    對于紋理mipmap的選擇,由于四邊形mipmap選擇會導致邊界周圍的偽影,可在著色器中計算mip級別,使用tex2Dlod進行采樣。

    \[\text{lod} = \log_2\cfrac{\text{pixelPerMeter} \ \times\ \tan(\text{fov}) \ \times\ \text{distToNearPlane}}{\text{screenRes} \ \times\ \bold v \cdot \bold n} \]

    在四邊形內的紋理坐標中存在不連續性時,該四邊形的mipmap選擇將是錯誤的。在右側放大的圖片中,四邊形上部的紋理坐標來自磚墻紋理,底部來自地板紋理,這些紋理坐標位于一個紋理圖集中的不同位置,GPU將為這組像素選擇最低的mipmap。解決這個問題的方法是手動計算每個單獨像素的正確 mip 級別,并使用tex2Dlod顯式采樣。可以通過使用輸入v.n和distToNearPlane創建2D查找表紋理來優化計算。

    處理距離場三角剔除時,使用逐三角形的分支,針對距離場測試每個三角形,輸出兩個索引緩沖區,發出兩個繪圖調用。緩存剔除結果,距離場變化時更新。

    基于體積距離場渲染的破壞效果。

    Advanced Material Rendering介紹了幾種渲染高級材料的技術,例如皮膚、水晶、玻璃、海水、沼澤水和泥水。提出的算法針對當前一代的游戲機,同時考慮到有限的內存和計算能力。涵蓋了幾個重要的材質特性,例如:次表面散射、半透明、透明度、水散射、動態表面等。此外,還討論了延遲渲染器中的功能、性能、美學和實現問題,為上述材質渲染提供了久經考驗的解決方案。

    文中提到抖動技巧,抖動是以某種模式進行采樣以掩蓋更合理噪聲中的欠采樣,通常使用樣本偏移的“旋轉盤”完成分布,包含均勻和泊松。

    使用旋轉盤抖動,預先計算好的偏移分布表,使用磁盤分布的標準化空間中的N個點。對于每個陰影像素,獲得隨機法線向量N,對于每個樣本,將圓盤分布中的點旋轉N,使用該點作為縮放偏移進行采樣。由于非離散采樣點,線性采樣很重要。

    抖動的使用案例:陰影。雙拋物面軟陰影,僅4個Tap,最小的額外開銷,似是而非的噪音,更大的柔軟度需要更多的圖案。


    未使用(上)和使用(下)抖動的陰影效果對比。

    對于透明度,延遲架構的透明度很棘手,常用的情形有:簡單透明度(照亮)、全透明材料、半透明材質(照亮)、半透明材質(始終照亮)。

    對于簡單透明度,可以使用紗窗(screen door)效果,計算/查找抖動模式,使用它們“殺死”像素,根據透明度值在圖案之間交替。4級透明度在帶寬受限時易于計算,記得檢查編譯器是否在剔除像素——應該盡快做。代碼如下:

    float jitteredTransparency(float alpha, float2 vP)
    {
        const float jitterTable[4] =
        {
            float( 0.0 ),
            float( 0.26 ),
            float( 0.51 ),
            float( 0.76 ),
        };
        
        float jitNo = 0.0;
        int2 vPI = 0;
        vPI.x = vP.x % 2;
        vPI.y = vP.y % 2;
        int jitterIndex = vPI.x + 2 * vPI.y;
        
        jitNo = jitterTable[jitterIndex];
        if (jitNo > alpha)
            return -1;
        
        return 1;
    }
    

    抖動的透明度在720p中看起來很糟糕,想要模糊那些討厭的抖動像素,但負擔不起另一個會檢測到它們并模糊的通道。已在邊緣AA的pass中執行。

    自定義邊緣AA是延遲渲染器中的常用技術,是全屏通道,根據深度/法線數據查找邊緣,然后模糊它們,只需提示邊緣AA過濾器即可找到“介于”被剔除像素之間的邊緣,可以免費獲得漂亮的混合,可以通過改變邊緣檢測的來源(將不連續性深入)來使用標志或更hacky的方式來完成。

    左:沒有自定義邊緣AA;右:有自定義邊緣AA。

    完全透明物體不需要照明,只是反射/折射光線,適用于玻璃、水、扭曲粒子,視為后效,需要后緩沖作為紋理,便于在Alpha通道中獲取深度信息。

    半透明材質,需要照明以保證正確,與整個場景一致,帶陰影。因此希望它處于延遲模式,最好具有單一的照明和著色成本,在樣本重建中使用抖動模式。可以使用2通道渲染:

    • 第1個通道:使用抖動模式將半透明材料寫入G-Buffer。

      圖案覆蓋基本渲染四邊形(即 2x2),圖案選擇取決于被覆蓋的透明材料層的數量,一個2x2四邊形可以覆蓋,每增加一層都會導致照明質量下降。

    • 第2個通道:材質在光累積后完全渲染,使用樣本重建來獲得正確的照明值,需要排序和alpha混合。

      重疊的半透明材質從后到前排序(半透明首先被渲染),對于每種重疊材質,以正確的模式對光緩沖進行采樣以獲得原始光照值,使用全分辨率紋理和重建照明渲染材質,透明度是通過與后緩沖區進行alpha混合來處理的。

      光照重建時,只采集一個樣本會導致嚴重的鋸齒,必須采集多個樣本進行重建。檢查被著色的像素是否是原始像素,如果為假,則對鄰域進行采樣以獲取有效樣本,對它們進行加權并*均以進行樣本重建,如果為真,請保持不變。在運動過程中減少鋸齒并提高穩定性,對超過2種材質使用2x2四邊形=大量紋理緩存垃圾和鋸齒。

    • 類似的方法是推斷渲染(Inferred Rendering)。

    還存在具有單一透明度的延遲渲染器:

    • 半透明幾何圖形被渲染到具有棋盤格圖案的g-buffer。
      • 反照率設為1。
        • Pass 1是特性權重 – 僅法線和鏡面反射。
      • 延遲著色后。
        • 累積緩沖區包含半透明幾何照明信息和底層陰影幾何的交替像素。
        • Pass 2重建兩者:照明數據,著色背景。
      • 以全質量渲染材質。
      • Alpha混合手動完成。

    除了半透明,該文還涉及了皮膚、頭發、水體等特殊材質的渲染。其中水體的光學原理包含表面法線、反射、折射、光散射、消光、焦散、固體表面貼花、鏡面反射等(下圖)。

    針對水面的以上光學原理,文中給出了對應的渲染解決方法。

    [Adaptive Volumetric Shadow Maps](http://advances.realtimerendering.com/s2010/Salvi-AVSM(SIGGRAPH 2010 Advanced RealTime Rendering Course).pdf)是Intel的圖形研究人員針對煙、霧、云、頭發等體積性的材質能夠獲得可信的陰影而研發。

    AVSM可以流式簡化算法,使用較小的固定內存占用量生成自適應體積光衰減函數,具有固定數量的節點,可變和無限的誤差,易于使用的方法,不對光線遮擋體的類型和/或其空間分布做出任何假設。

    一個單獨的AVSM紋素編碼N個節點,每個節點由一個深度和一個透射率值表示。節點始終按排序(從前到后)的順序存儲。通過將紋素中的所有節點初始化為相同的值來清除AVSM,將深度設置為遠*面,將透射率設置為 1(無遮擋)。傳入的光線遮擋體由光視圖矢量對齊的段表示,一段由兩個點(入口點和出口點)和出口點的透射率(入口點的透射率隱式設置為1)定義。假設入口點和出口點之間的空間由均勻致密的介質填充,通常會生成形狀為分段指數曲線的透射率曲線,可以使用線條來簡化問題(在大多數情況下沒有太大的視覺差異)。

    第一個和最后一個節點永遠不會被壓縮/刪除,因為它們提供了非常重要的視覺提示。最后一個節點非常重要,因為它對投射在位于體積塊后面的任何接收器上的陰影信息進行編碼。 例如,一些香煙煙霧投射在桌子上的陰影總是正確的(沒有壓縮偽影)。刪除節點后,就不會更新剩余節點的位置以更好地擬合原始曲線(deep shadow maps)。事實上,當節點在壓縮*面上執行隨機游走時,在十幾次插入壓縮迭代中更新節點位置可能會產生一些不可預測的結果。

    基于DirectX 11的實現時,為流式簡化而設計的算法,但映射到同一像素的運行的片元會導致數據競爭,對像素著色器當前不可用的結構的原子RMW操作。有兩個實現版本:

    • 基于計算著色器,速度較慢但內存固定,粒子的軟件管線原型幾乎沒有優化工作,比可變內存實現慢約2倍。
    • 基于像素著色器,速度更快但內存可變。

    AVSM的性能方面表現在:競爭性能,更高的圖像質量,陰影查找占主導地位,通常<30%的AVSM相關渲染時間花費在插入代碼中,DSM比AVSM慢20-40倍。

    總之,AVSM的優點是通過自適應采樣提高圖像質量,避免基于定期采樣或可見性函數系列擴展的方法的常見缺陷,固定且易于使用,不需要任何關于遮光劑類型和空間分布的先驗知識,易于權衡圖像質量以換取速度和存儲。缺點是快速的固定內存實現需要圖形硬件在幀緩沖區上添加對讀-修改-寫操作的支持。

    Per-Pixel Linked Lists with Direct3D 11介紹了基于DX11實現的逐像素鏈表的特點、實現、優化和應用等內容。

    鏈表對編程有用的數據結構,使用以前的實時圖形API很難有效實現,DX11允許高效地創建和解析鏈表,逐像素鏈表,枚舉屬于同一屏幕位置的所有像素的鏈表集合。鏈表涉及兩步處理:

    • 鏈表創建。將傳入的片段存儲到鏈表中。

    “片元和鏈接”緩沖區包含所有片元的數據和鏈接以存儲,必須足夠大以存儲所有片元,使用計數器支持創建,UAV視圖中使用D3D11_BUFFER_UAV_FLAG_COUNTER標志。聲明如下:

    struct FragmentAndLinkBuffer_STRUCT
    {
        FragmentData_STRUCT FragmentData; // Fragment data
        uint                uNext;        // Link to next fragment
    };
    RWStructuredBuffer <FragmentAndLinkBuffer_STRUCT> FLBuffer;
    

    “起始偏移”緩沖區包含在每個像素位置寫入的最后一個片元的偏移,屏幕尺寸:寬 * 高 * sizeof(UINT32) ,初始化為魔法值(例如 -1),魔法值表示不再存儲片元(即列表末尾)。聲明如下:

    RWByteAddressBuffer StartOffsetBuffer;
    

    鏈表創建時,沒有顏色渲染目標綁定, 也還沒有渲染,只是存儲在LL。如果需要,綁定深度緩沖區,OIT將在后面需要它,綁定UAV作為輸入/輸出:StartOffsetBuffer (R/W) 、FragmentAndLinkBuffer (W)。

    Per-Pixel Linked List創建示意圖。圖中的黃色三角形占了Start Offset Bufer的兩個像素,每個像素指向了Fragment and Link Buffer的一個位置。注意計數器是一直累積的,黃色之前已經有綠色和橙色的像素被處理和存儲。

    鏈接創建代碼如下:

    float PS_StoreFragments(PS_INPUT input) : SV_Target
    {
        // 計算片段數據(顏色、深度等)。
        FragmentData_STRUCT FragmentData = ComputeFragment();
        
        // 檢索當前像素數并增加計數器。
        uint uPixelCount = FLBuffer.IncrementCounter();
        
        // 在StartOffsetBuffer中交換偏移量。
        uint vPos = uint(input.vPos);
        uint uStartOffsetAddress= 4 * ( (SCREEN_WIDTH*vPos.y) + vPos.x );
        uint uOldStartOffset;
        StartOffsetBuffer.InterlockedExchange(uStartOffsetAddress, uPixelCount, uOldStartOffset);
        
        // 在片段和鏈接緩沖區中添加新的片段條目。
        FragmentAndLinkBuffer_STRUCT Element;
        Element.FragmentData = FragmentData;
        Element.uNext = uOldStartOffset;
        FLBuffer[uPixelCount] = Element;
    }
    
    • 從鏈表渲染。鏈表遍歷和存儲片段的處理。渲染像素的步驟:

    1、將“起始偏移”緩沖區和“片段和鏈接”緩沖區綁定為SRV:

    Buffer<uint> StartOffsetBufferSRV;
    StructuredBuffer<FragmentAndLinkBuffer_STRUCT>
    FLBufferSRV;
    

    2、渲染全屏四邊形。

    3、對于每個像素,解析鏈表并檢索此屏幕位置的片段。

    上圖在第2行第個位置檢索到了有效數據,根據Start Offset Buffer去獲取Fragment and Link Buffer的數據。

    上圖在Fragment and Link Buffer獲取黃色像素的數據時,發現該像素還存在其它數據(橙色),于是根據索引去讀取下一個像素數據。

    4、根據需要處理片元列表。取決于算法,例如排序、查找最大值等。

    讀取像素鏈表的所有數據之后,就根據需要處理片元列表,圖中是*均像素的顏色。

    float4 PS_RenderFragments(PS_INPUT input) : SV_Target
    {
        // 計算UINT對齊的起始偏移緩沖區地址
        uint vPos = uint(input.vPos);
        uint uStartOffsetAddress = SCREEN_WIDTH*vPos.y + vPos.x;
        // 獲取當前像素的第一個片段的偏移量
        uint uOffset = StartOffsetBufferSRV.Load(uStartOffsetAddress);
        // 解析該位置所有片段的鏈表
        float4 FinalColor=float4(0,0,0,0);
        while (uOffset!=0xFFFFFFFF) // 0xFFFFFFFF是魔法數字
        {
            // 在當前偏移處檢索像素
            Element=FLBufferSRV[uOffset];
            // 根據需要處理像素
            ProcessPixel(Element, FinalColor);
            // 檢索下一個偏移量
            uOffset = Element.uNext;
        }
        
        return (FinalColor);
    }
    

    通過逐像素鏈表可以實現OIT(與順序無關的透明度),將透明片元存儲到PPLL,渲染階段按從后到前的順序對像素進行排序,并在像素著色器中手動混合它們,混合模式可以是每個像素唯一的!MSAA支持的特殊情況。

    鏈表結構通過減少向UAV寫入/讀取的數據量來優化性能(例如uint而不是float4的顏色),OIT的示例數據結構:

    struct FragmentAndLinkBuffer_STRUCT
    {
        uint uPixelColor; // 打包的像素顏色
        uint uDepth;      // 像素深度
        uint uNext;       // 下一個鏈接地址
    };
    

    也可以將顏色和深度打包到同一個uint中(如果相同的Alpha),使用16位顏色 (565) + 16位深度,性能/內存/質量的權衡。

    使用僅可見片元,在Linked List創建像素著色器前使用 [earlydepthstencil],可確保僅存儲通過深度測試的透明片元(即可見片元),可以節省性能和渲染正確性!

    [earlydepthstencil]
    float PS_StoreFragments(PS_INPUT input) : SV_Target
    {
        (...)
    }
    

    對像素進行排序,就地排序需要對鏈表的R/W訪問權限,稀疏的內存訪問 = 慢!更好的方法是將所有像素復制到臨時寄存器數組中,然后進行排序,臨時數組聲明意味著對每個屏幕坐標的像素數的硬性限制,性能所需的權衡。排序的具體過程見下面一組圖:





    // 存儲像素以進行排序
    (...)
    static uint2 SortedPixels[MAX_SORTED_PIXELS];
    // Parse linked list for all pixels at this position
    // and store them into temp array for later sorting
    int nNumPixels=0;
    while (uOffset!=0xFFFFFFFF)
    {
        // Retrieve pixel at current offset
        Element=FLBufferSRV[uOffset];
        // Copy pixel data into temp array
        SortedPixels[nNumPixels++]=
        uint2(Element.uPixelColor, Element.uDepth);
        // Retrieve next offset
        [flatten]uOffset = (nNumPixels>=MAX_SORTED_PIXELS) ?
        0xFFFFFFFF : Element.uNext;
    }
    // Sort pixels in-place
    SortPixelsInPlace(SortedPixels, nNumPixels);
    (...)
    
    
    // PS中的像素混合
    (...)
    // Retrieve current color from background texture
    float4 vCurrentColor=BackgroundTexture.Load(int3(vPos.xy, 0));
    // Rendering pixels using SRCALPHA-INVSRCALPHA blending
    for (int k=0; k<nNumPixels; k++)
    {
        // Retrieve next unblended furthermost pixel
        float4 vPixColor= UnpackFromUint(SortedPixels[k].x);
        // Manual blending between current fragment and previous one
        vCurrentColor.xyz= lerp(vCurrentColor.xyz, vPixColor.xyz,
        vPixColor.w);
    }
    // Return manually-blended color
    return vCurrentColor;
    

    通過支持MSAA的逐像素鏈表實現OIT時,若將單個樣本存儲到鏈接列表中需要大量內存,性能會受到影響!解決方案是像以前一樣將透明像素存儲到PPLL中,但也包括樣本覆蓋數據!需要與MSAA模式一樣多的位,在PS結構中聲明SV_COVERAGE:

    struct PS_INPUT
    {
        float3 vNormal : NORMAL;
        float2 vTex    : TEXCOORD;
        float4 vPos    : SV_POSITION;
        // 像素覆蓋數據
        uint uCoverage : SV_COVERAGE;
    }
    

    鏈表結構與之前幾乎沒有變化,深度現在被打包成24位,8位用于存儲覆蓋范圍:

    struct FragmentAndLinkBuffer_STRUCT
    {
        uint uPixelColor; // Packed pixel color
        uint uDepthAndCoverage; // Depth + coverage
        uint uNext; // Address of next link
    };
    

    樣本覆蓋率示例如下圖,第三個樣本被覆蓋,此時uCoverage = 0x04(二進制的 0100):

    打包深度和覆蓋數據,然后存儲:

    Element.uDepthAndCoverage = ( In.vPos.z*(2^24-1) << 8 ) | In.uCoverage;
    

    渲染階段需要能夠寫入單個樣本,因此PS以采樣頻率運行,可以通過在輸入結構中聲明SV_SAMPLEINDEX來完成,解析鏈表并將像素存儲到臨時數組中以供以后排序,類似于非MSAA案例,區別在于僅在覆蓋數據與被光柵化的樣本索引匹配時才存儲樣本。渲染代碼如下:

    static uint2 SortedPixels[MAX_SORTED_PIXELS];
    // Parse linked list for all pixels at this position
    // and store them into temp array for later sorting
    int nNumPixels=0;
    while (uOffset != 0xFFFFFFFF)
    {
        // Retrieve pixel at current offset
        Element=FLBufferSRV[uOffset];
        // Retrieve pixel coverage from linked list element
        uint uCoverage=UnpackCoverage(Element.uDepthAndCoverage);
        if ( uCoverage & (1<<In.uSampleIndex) )
        {
            // Coverage matches current sample so copy pixel
            SortedPixels[nNumPixels++]=Element;
        }
        // Retrieve next offset
        [flatten]uOffset = (nNumPixels>=MAX_SORTED_PIXELS) ?
        0xFFFFFFFF : Element.uNext;
    }
    

    Texture Compression in Real-Time Using the GPU介紹如何在當前控制臺和DirectX 10.1視頻卡上使用GPU來執行DXT紋理壓縮,提出了一種不依賴GPU計算API或存在按位數學運算的方法,涉及每個*臺的實現細節和優化。

    使用GPU壓縮的理由是游戲使用更多運行時生成的內容(混合地圖、動態立方體貼圖、用戶生成內容),CPU壓縮速度較慢,并且需要額外的同步和延遲。下圖的性能對比來自實時DXT壓縮論文的CPU性能數據:

    DXT1/BC1是代表4x4紋素的64位塊,其中有4個顏色值、2個存儲值、2個插值:

    顏色索引的偽代碼如下:

    Index00 = color_0;
    Index01 = color_1;
    Index10 = 2/3 * color_0 + 1/3 * color_1;
    Index11 = 1/3 * color_0 + 2/3 * color_1;
    
    if (color_1 > color_0)
    {
        Index 10 = 1/2 * color_0 + 1/2 * color_1;
        Index 11 = “Transparent”;
    }
    

    DXT壓縮的基本步驟:

    • 獲得一個 4x4 的紋素網格。代碼如下:
    float2 texel_size = (1.0f / texture_size);
    texcoord -= texel_size * 2;
    float4 colors[16];
    
    for (int i = 0; i < 4; i++) 
    {
        for (int j = 0; j < 4; j++) 
        {
            float2 uv = texcoord + float2(j, i) * texel_size;
            colors[i*4+j] = uv;
        }
    }
    
    • 找到想用作存儲顏色的顏色。此操作可能非常昂貴,但是后面會提到一些方法,查找端點顏色或非常便宜!建立端點值,需要小心Alpha。

    • 將每個4x4紋素匹配到最合適的顏色。查找紋素指數的代碼如下:

    float3 color_line = endpoints[1] - endpoints[0];
    float color_line_len = length(color_line);
    color_line = normalize(color_line);
    int2 indices = 0;
    for(int i=0; i<8; i++) 
    {
        int index = 0;
        float i_val = dot(samples[i] - endpoints[0], color_line) / color_line_len;
        float3 select = i_val.xxx > float3(1.0/6.0, 1.0/2.0, 5.0/6.0);
        index = dot(select, float3(2, 1, -2));
        indices.x += index * pow(2, i*2);
    }
    

    ? 重復接下來的8個像素。

    • 創建塊的二進制表示。
    dxt_block.r = max(color_0_565, color_1_565);
    dxt_block.g = min(color_0_565, color_1_565);
    dxt_block.b = indices.x;
    dxt_block.a = indices.y;
    return dxt_block;
    
    • 將結果放入紋理中。方法因*臺而異,渲染目標應該是源的1/4尺寸,1024x1024的源 = 256x256的目標,使用16:16:16:16的unsigned short格式。

    漫反射貼圖的運行時壓縮和離線壓縮的對比及它們的顏色差異如下:


    可以采取一些措施調整性能,著色器編譯器很聰明,但并不完美,確保測試比較過展開(unrolling)與循環(looping)的性能,為目標格式創建變體著色器,如果只使用2個組件,法線貼圖會更便宜。法線貼圖的運行時壓縮和離線壓縮的對比及它們的顏色差異如下:


    [An Optimized Diffusion Depth Of Field Solver (DDOF)](http://www.klayge.org/material/4_2/DoF/An Optimized Diffusion Depth of Field Solver.pdf)分享了CoC景深的幾種深度優化技術。DOF的解算涉及三對角系統(下圖),其中橙色是源自CoC的每個像素的輸入行/列,綠色是生成模糊的行/列,紅色是輸入圖像的行/列:

    文中提及的之前的解算器(Solver)有混合的GDC2010解算器、圓形縮減 (CR) 解算器。以下是Vanilla CR解算器的處理過程:

    但是上圖的Stop at size 1階段會阻礙并行,引起性能下降。可以以合理尺寸停止,隨后以足夠大的并行工作負載解算Y:

    在內存優化上,可以將rgba32f改成rgba16f(無明顯瑕疵)。此外,上圖的abc紋理再一次保存大量的內存,因為它是求解器使用的最大表面,可以跳過abc構造過程,并在第一個reduce通道期間動態計算abc。(下圖)

    第一個reduce之后的紋理又會顯著保存大量內存,可以reduce的4個通道減少成成1個特殊的reduce通道,在一個特殊的替換過程中替換1到4。

    在DX11中,可以將abc和X打包進一張rgba_uint紋理,可以使用SM5的數據打包:

    DX11內存優化還可以對解算器進行水*和垂直通道,低分辨率RT鏈需要出現兩次,水*減少/替代鏈,垂直減少/替代鏈。UAV允許將水*鏈的數據重用于垂直鏈,概念驗證實現表明這樣做很有效,但會顯著影響運行時性能(幀率降低約 40%)。保留RT,因為內存已經很低了,僅在真正關心內存時使用。

    經過以上方式優化之后,4-to-1 Reduction + SM5 Packing的方法獲得了最好的性能,且占用內存最低:

    Five Rendering Ideas from Battlefield 3 & Need For Speed介紹了來自Frostbite 2引擎的5種渲染技術:可分離的Bokeh DOF、Hi-Z//Z-Cull、色度亞采樣圖像處理、基于*鋪的延遲著色、時間穩定的屏幕空間環境遮擋。

    可分離的Bokeh DOF的模糊過程有以下幾種方法:

    • 高斯模糊。常見于DX9游戲中。
    • 2D區域采樣。紋理tap爆炸限制了內核大小。
    • GS擴大的點精靈。繁重的填充率,CryEngine3和UE3采納的方法。

    圖像空間中的任意模糊是\(O(N^2)\),高斯模糊可以是可分離的\(O(N)\),可以被分離的2D模糊有:高斯、方框、傾斜的方框。

    六角模糊(Hexagonal Blur)可以把一個六邊形分解成三個菱形,每個菱形都可以通過分離的模糊計算,總共7個pass:3個形狀 × 2個模糊+ 1個組合。

    使用可分離濾鏡的六邊形模糊,但7個通道和6次模糊并不具有競爭力,需要減少通道。

    下圖顯示了總共只需要2個通道,但通道1必須輸出兩個MRT,并且通道2必須讀取2個MRT。現在總共有5個模糊,也減少了1個,最后的組合通道是通道2的一部分。

    下圖展示的方法在通道1中,像往常一樣將向上模糊輸出到MRT0,但在輸出到MRT1之前,還將它添加到左下模糊的結果中。在通道2 中,徹底的模糊將同時在菱形2和3上運行。只需要1次模糊!!

    模糊效果如下:

    高斯和六邊形的對比:高斯總共有2個模糊;六邊形2個通道(3個解析),總共4個模糊,但每個模糊只需要一半的tap數,因此雖然相同的tap數,但每個tap貢獻一樣(高斯的不一樣),所以需要更少的tap,以滿足一個給定的美學過濾器的內核寬度。

    因為有相等權重的模糊,可以對模糊使用迭代優化,多個通道填充欠采樣,雙重迭代模糊需要總共5個通道、8次半模糊。

    偽分散過濾器(Pseudo Scatter filter),適當的散景應該有它的模糊分散到它的鄰居。然而,像素著色器是用來收集結果的,它們不能分散結果。典型的模糊默認過濾器內核為像素的CoC,而默認用大的CoC,并根據采樣紋素的CoC拒絕,額外的方法可以避免溢色瑕疵,并可以銳化*滑的梯度。

    文中提到了專用于X360的Hi-Z/Z的反向重新加載技巧。在現有深度緩沖區上添加渲染目標的重疊,初始重疊的RT成D3DHIZFUNC GREATER EQUAL,繪制全屏矩形(PS設為NULL、Zfun==Never),此時Hi-Z顛倒了:

    反向Hi-Z用于CSM渲染:定向光的每個級聯都以世界空間中的一個長方體為界,只有長方體內部的世界空間像素會投射到陰影圖上,通過繪制長方體背面,只有這些像素將通過反向Z測試。非常適合CSM,CSM的設計使它們包圍的體積相交并將相機包圍在*面附*,雖然后面的級聯不是這樣,但它們會包含前面的級聯。

    作為單獨的通行證進行評估,輸入是深度緩沖區,創建一個L8掩碼紋理輸入到定向光通道。可以做一個之前的全屏通過標簽背面像素寫到在模板的光源,啟發式的太陽與相機的角度。潛在的1/4分辨率雙邊上采樣,模板被更新以表示已經處理過的像素。

    基于反向Hi-Z的CSM。從上到下依次是級聯0到級聯3。

    后續還可以用Min/Max深度來實現PCF軟陰影(下圖),具體過程可參閱原文。

    色度亞采樣(Chroma Sub-Sampling)不是一個新想法,已用于電視廣播。Jpeg / Mpeg壓縮將圖像分解為亮度和色度,只用全分辨率存儲亮度(因為人眼對亮度更敏感),用低分辨率存儲色度

    最頂部是正常的圖片,下面三幅分別是分解出來的Y、U、V分量圖,其中Y是亮度,U和V是色度。

    后處理需要大量的帶寬,如果用普通的格式存儲數據,對GPU產生巨大的壓力和瓶頸。相反,如果使用Luma方法,可以將所需的帶寬減少到原始的1/4,但需要對顏色進行額外處理,可以是1/4分辨率的2個通道(原始大小的1/8)。

    采用色調亞采樣之后,著色器可以有4倍的速度提升嗎?答案是否定的,因為受限于ALU:紋理單元和ALU是為4組件分量(如float4)的SIMD設計的,只使用了一個組件,需要打包4個亮度值一起處理。

    只需要1次紋理讀取就可以獲得4個亮度值,所以1280x720亮度緩沖區是320x720的ARGB緩沖區,用壓縮緩沖區執行雙線性過濾是不正確的,必須手動使用DOTP水*過濾

    在打包數據時使用了蝴蝶打包(Butterfly Packing)的技巧,覆蓋每個象限到ARGB,鏡面周圍的圖像中心點,現在雙線性除了跨越邊界外都是有效的,以額外的混合和重組重新繪制一個strip(條帶),水*方向為R<-->g、B<-->A,垂直方向為R<-->B和G<-->A,徑向模糊有效。蝴蝶解包的過程如下圖:

    文中提到的TBDR技術除了傳統的渲染過程,還使用了GPGPU裁剪,過程如下:

    • 屏幕被劃分為920個32x32像素的塊(tile)。
    • 下采樣并將場景從720p劃分到40x23(1像素 = 1 tile)。
      • 找到每個tile的最小/最大深度。
      • 找到每個tile的材質排列。
      • 下采樣是通過多通道和MRT完成的。

    下圖是材質分類的示例,假設場景有3個材質:默認材質(紅色)、皮膚著色(綠色)、金屬(藍色)。

    當下采樣(通過手動雙線性tap)時,將材質組合成最終輸出顏色。可以在下圖右邊看到頭部周圍的下采樣是如何產生黃色的,這是紅色和綠色的組合,紅色和藍色類似,呈現洋紅色。

    跳過幾個步驟后得到下圖,下圖是40x23的紋理,將準確地提供每個tile中的材質組合。在并行MRT中,還下采樣并存儲了最小/最大深度。有了這些信息,現在可以使用GPU來剔除光源,因為已經知道相機視錐體中的所有燈光和每個tile(和天空)的最小/最大深度和排列組合(迷你的視錐體)。

    • 為每個tile構建一個迷你的視錐體。
    • 在著色器中剔除忽略天空的tile的光源。
    • 將裁剪結果存儲在紋理中(Column == Light ID、Row == Tile ID)。
    • 實際上,可以一次處理4個光源(A-R-G-B)。
    • 在CPU上回讀貢獻結果,準備計算光照。

    計算光照的偽代碼如下:

    // 分析紋理上的裁剪結果, 在CPU上執行.
    ParseCullingResultsTexture();
    
    For each light type:
        For each tile:
            For each material permutation:
                // 重新組合并設置PS常量的光照參數.
                RegroupAndSetLightParametersForPSConstants(...); 
                // 設置著色器循環計數器.
                SetupShaderLoopCounter(...);
                // 使用單個繪制調用累加地渲染燈光(到最終的HDR照明緩沖區).
                RenderLights(...);
    

    時間穩定的SSAO使用了線段采樣,線段采樣基本上是占用函數的解析積分乘以圓盤上每個采樣點的球體深度范圍。由于所有2D樣本都將投影到z緩沖區中的不同點,因此線采樣也更加有效和穩定,而在3D空間中隨機點采樣的常用方法,兩個點樣本可能會投影到同一位置,導致樣本數量下降。

    SSAO渲染過程還涉及了快速灰度模糊,其工作原理如下:

    Pre-Integrated Skin Shading闡述了皮膚渲染涉及的相關技術,包含SSS、各種*似方法,提出了運行時效率極高的預積分皮膚渲染,在工業界產生了深遠的影響。文中先總結了之前研究出的幾種*似方法:

    基于紋理空間擴散(TSD)的幾種皮膚渲染方法。

    快速次表面散射方法。

    屏幕空間的次表面散射。

    但是,以上方法都或多或少存在一些問題或限制。文中提出了Pre-Integrated Skin Shading,它的目標是只用一個簡單的像素著色器,所有輸入本地存儲在紋理/凹凸貼圖中(無模糊通道)。關鍵觀察:散射并非隨處可見,發生在入射光照變化附*(光照梯度)。策略是查找光照梯度和預積分散射。

    紋理空間擴散的幾種情況,如上圖數字標識。數字1的區域表示入射光是常量,不需要做任何擴散;數字2處是小表面凹凸,表面曲率產生了強烈的漫反射衰減;數字3處通過皺紋和凹凸等特征擴散;數字4處光照散射在陰影內,形成獨特的皮膚外觀。

    以上4處可以總結成3個待處理的問題:

    • 表面曲率(Surface Curvature)。預積分基于曲率的BRDF。

    對于普通的漫反射、環繞光照和距離衰減函數,都無法適用于表面曲率光照計算。但是,可以在環繞光照(wrap lighting)基礎上做改進,用預模糊漫射BRDF與皮膚輪廓來解決不是基于真實的皮膚擴散剖面的問題,使用曲率參數化 BRDF來解決不考慮曲面曲率的問題。

    經過上述分析之后,就可以進行常規的漫反射光計算,并準確計算特定尺寸(或特定曲率)球體上的散射情況,通過收集來自球體(或環,更容易)上所有點的所有漫射光來做到這一點。可以使用任何昂貴的技術,因為是離線計算,只會記錄結果。對于漫反射衰減的每個點(下圖右顯示法線和光線之間的一個角度),整合從整個球體散射而來的所有光,這一步非常昂貴,所以記錄這個值,以便以后可以使用它。

    下面分別是不同參數的漫反射預計算結果:

    現在已經使用準確的皮膚輪廓來捕捉各種表面曲率上的散射外觀,可以將所有這些數據存儲在由\(N\cdot L\)和曲率參數化的簡單2D紋理中:

    事實證明,可以通過使用法線向量的變化率和在世界空間中位置的變化率之間的簡單相似三角形關系來獲得曲率的一階估計,可以使用導數指令得到這些無窮小的變化(下圖左)。下圖右是將其應用于頭部模型的結果。注意:此處為了可視化曲率,但應該使用擴散輪廓中的正確單位,然后校正查找紋理中的曲率范圍。

    • 表面凹凸。預積分彎曲法線。

    BRDF適用于光滑的曲面,法線貼圖中的小凹凸呢?光照應該散射通過幾個表面凸起,使用曲率在小尺寸時會失效,現在只關注法線。

    使用法線貼圖,可以對鄰域進行采樣(可以使用許多樣本,光源、權重和累積)。或者,可以預先過濾法線貼圖嗎?預過濾點\(N\cdot L\)是正確的,預過濾max(0, dot(N, L))并不嚴格正確(但很接*),LEAN/CLEAN映射對鏡面反射執行此操作。

    切線可以獲取法線貼圖,一種應用光照梯度度來捕捉法線的技術,不同的光照顏色產生不同的捕捉法線。下圖使用了4個法線貼圖,其中每個法線貼圖分別用于計算紅、綠、藍漫射光和鏡面光。

    不需要捕獲的法線來使用這種技術,從“真正的”法線貼圖(高光)開始,使用R/G/B皮膚輪廓預過濾新的法線貼圖,最佳情況下,需要4個法線:R/G/B 和高光。看起來很棒,但占用很多內存!

    優化彎曲法線的方法有:

    • 使用幾何和高光法線(混合其余部分)。僅適用于某些藝術,法線貼圖必須只包含細節(皺紋/毛孔)。
    • 使用兩個正常樣本(混合其余樣本)。適用于所有藝術,一個法線貼圖,兩個采樣器,將一個采樣器截取在模糊的mip閾值處。

    有很好的解決方案:彎曲但光滑的表面(預集成的BRDF),*坦但凹凸不*的表面(預集成的法線貼圖)。它們相得益彰,從主要曲率(幾何)中選擇的BRDF,來自主表面的廣泛散射,彎曲法線填補了空白,提供局部細節的細粒度散射。

    • 陰影。預積分陰影半影。

    事實證明可以應用一個非常相似的技術,就是來自盒子過濾器的陰影。給定陰影值,反轉半影模糊功能,找到陰影內的位置。(下圖)

    如果衰減函數中有位置/距離,基本上可以指定一個新的衰減函數,一個為散射留下一堆額外空間的,然后就可以使用皮膚擴散配置文件將散射效果預先積分到陰影中。

    下圖是預先積分到陰影得到的結果。類似于漫反射計算,此處有一個2D查找。第二個維度代表了在世界空間中的半影大小,由于表面的坡度或陰影的模糊程度,可能會有不同的尺寸。這個寬度可以通過多種方式計算,例如使用光的表面斜率,甚至可能使用陰影本身的導數。注意,如果有一些瘋狂的東西(比如抖動陰影貼圖),就不會有硬邊,而且可能會得到不正確的散射。此外,在2D中,距離不會完全正確,但外觀才是最重要的。

    下倆圖是紋理空間擴散和預積分的對比:


    陰影還可以結合PCF、VSM進行效果改進。下圖是通過2的冪來改變尺寸和強度的效果矩陣,其中橫坐標是尺寸,縱坐標是強度:

    Practical Occlusion Culling on PS3分享了用于PS3的遮擋剔除技術,包含SPU運行時、創建遮擋體、調試工具、性能優化等。PS3游戲殺戮地帶的渲染管線見下圖:

    SPU、PPU和內存之間的協作和交互如下圖:

    在遮擋剔除方面,計劃如下:

    • 離線創建幾何遮擋。
    • SPU每一幀渲染遮擋到720p深度緩沖。
    • 將緩沖區分割成16像素高的塊用于光柵化。
    • 下采樣緩沖到80x45(16x16最大濾波器)。
    • 在場景遍歷期間測試這個邊界框。
      • 準確:光柵化+深度測試。
      • 粗糙:某種恒定時間點測試。

    添加遮擋剔除階段的管線如下:


    遮擋體查詢作業:

    • 在(截斷的)視錐體中查找遮擋體。
    • 遮擋體是正常的渲染圖元,使用標記位標識的可繪制對象的其余部分。

    遮擋體設置作業:

    • 解碼RSX風格的頂點和索引數組。
    • 輸出剪切+投影三角形到暫存區域。
    • 隱藏DMA延遲的內部管道。

    光柵化作業:

    • 啟動一個光柵每條工作。
    • 使用列表DMA從暫存區加載三角形。
    • 在LS中繪制三角形到一個640x16深度的浮點緩沖區。
    • 壓縮深度緩沖到uint16并存儲。

    過濾作業:

    • 光柵化完成后運行。
    • 生成粗糙的剔除數據。在查詢期間用于剔除小對象。
    • 回寫拒絕緩沖區(相鄰且閉塞的緩沖區)。

    遮擋查詢作業:

    • 測試工作在兩級層次結構中。對象存在于kd樹中,并包含多個部件。
    • 測試對象以避免提取部件。
    • 測試部件,避免繪制它們。
    • 最終的查詢結果寫入主存,并用于建立顯示列表。

    提高裁剪率:

    • 盡量使用球體測試,以得到足夠的RSX剔除。
    • 要確保對所有物體都進行全面測試。這使得優化變得更加重要。

    文中還涉及了裁剪各個階段的實現細節和優化建議,可點擊原文查看。

    Analytic Anti-Aliasing of Linear Functions on Polytopes闡述了多面體上基于線性函數的特殊解析抗鋸齒。

    采樣(Sampling)是計算機圖形學中非常常見的核心技術,應用廣泛,最主要的用例之一是將數據采樣到常規網格。

    普通輸入網格經過某種采樣方式之后,利用有限的樣本值重建網格。本文只涉及從原始網格采樣的領域。

    已知有一張具有空間高頻的圖片:

    利用不同的濾波將上圖下采樣到半分辨率:

    由此可知,Box、Hat濾波都存在一些問題,而高斯濾波效果最佳。為了更好地采樣,通常還需要加入隨機抖動:

    解析采樣的主體流程和步驟如下:

    其卷積公式和圖例如下:


    獲得的結果是復雜場景的無鋸齒采樣:


    總之,本文提出了多邊形和多面體的分析抗鋸齒,允許網格上的線性函數(例如顏色、密度……)、高階徑向濾波器函數、常規和非常規采樣網格。

    Water Technology of Uncharted分享了神秘海域的水體模擬、實現和優化。水有多種形式,從小到大的水體,與水的相互作用,可弄濕衣服,快速移動,緩慢而難以導航。水體的著色模型如下:

    水流模擬圖如下:

    基于流量的位移,每個頂點以不同的相位φ在圓形圖案上移動:

    海洋是渲染的挑戰,開闊的海洋,大浪(100 米以上),海浪驅動船只和駁船,動畫循環被考慮但未使用,可以游泳。

    波系統,包含程序化、參數、確定性、LOD等。有兩種波浪:Gerstner波,簡單但高頻細節不夠,在高開銷之前只能使用幾個。FFT波比較真實,更多細節,頻譜讓藝術家難以控制,在低分辨率網格上*鋪視覺失真。

    還有波粒子(Wave Particle),來自點源的波,神秘海域不使用點源,相反,在環形域中隨機分布,粒子來*似開放水域的混沌運動,一定速度范圍內的隨機位置和速度,產生一個可*鋪的矢量位移場。波粒子的特點是藝術家可以直觀地控制,沒有*鋪失真,速度快, 適合SPU向量化。時間確定性,無需移動粒子,新位置來源于初始位置、速度和時間。

    波場是4個Gerstner波+波粒子(使用4次):

    流網格是在網格中編碼流、泡沫、幅度乘數:

    添加更簡單的波之后:

    對于細節層次,有多種方法創建水體網格,屏幕投影網格 → 鋸齒失真,準投影網格 → 處理大位移的問題。

    不規則幾何剪貼圖基于Geometry Clipmaps: Terrain Rendering Using Nested Regular Grids,修改成水渲染。不同的分裂來固定環水*的T形接頭,關卡之間的動態混合,小塊(patch)可提高SPU利用率。



    水體裁剪圖運行機制。(只選取了部分步驟)

    不同LOD級別的水體網格邊界會出現裂痕,需要修復:


    需要對水體網格Patch進行剔除,使用Frustum-bbox測試剔除截錐體之外的Patch。

    用天空光照亮場景時,用視錐體-包圍盒測試剔除視錐體之外的patch,用*面和包圍盒測試剔除天窗外的patch:

    計算天空光光照時,對于渲染使用著色器丟棄操作來進行*面裁剪(下圖左),用于評估大廳包圍盒內的鉗位點(下圖右)。

    對于漂浮對象,采樣點和最適合定位的*面,在相交區域,將幅度相乘。附加對象,采樣點和最適合定位的*面,在相交區域,將幅度相乘。

    網格計算時,對于每個環,運行一個SPU作業來處理patch (i % 3):

    J1: (0,3,6,9,12,15) 
    J2: (1,4,7,10,13,[16]) 
    J3: (2,5,8,11,14)
    

    最小化環級計算,雙緩沖網格輸出。

    由于每個作業都會創建一個看起來完美的網格,因此無需縫合網格。海洋的最終網格,由多個網格組成。(下圖)

    需要注意時間,剪輯圖需要特定時間的波粒子,只要一個波粒子工作,此作業生成位移網格。為了同步作業,設置了一個屏障來等待波粒子作業完成生成其網格。(下圖)

    渲染效果截圖:

    Graphics Gems for Games - Findings from Avalanche Studios描述了游戲中的一些特殊渲染技術,如粒子修剪、合并實例、電話線抗鋸齒、第二深度抗鋸齒。

    文中提到GPU越來越強大,ALU漲麻了!TEX相當不錯的增長,BW(帶寬)有點遲鈍,ROP(渲染輸出單元)冰川般的速度:

    如果ROP受限,想方設法繪制更少的像素。對于粒子修剪,典型的ROP密集案例包含粒子、云、廣告牌、圖形用戶界面元素。解決方案:渲染到低分辨率渲染目標、濫用MSAA。本文的解決方案:修剪粒子多邊形以減少浪費。粒子使用的網格常可見大量alpha=0的區域,浪費填充率,調整粒子的網格以減少浪費,可使用自動化工具

    裁剪之后可以節省大量填充率,更多頂點?更大的節省,但收益率遞減(下圖),Just Cause 2使用4個頂點用于云、8個頂點用于粒子效果。

    粒子裁剪有兩種方式:

    • 手動修剪。乏味,但證明了這個概念,可用于云圖集。2倍性能,數十個圖集粒子紋理。

    • 自動工具。輸入紋理、Alpha閾值、頂點數,輸出優化的封閉多邊形。

    粒子裁剪算法:

    • 閾值化Alpha。
    • 將所有實心像素添加到凸邊形。通過潛在角測試進行優化。
    • 減少Hull頂點數。替換最不重要的邊,重復直到最大船體頂點數。
    • 蠻力遍歷所有有效邊緣的排列,選擇面積最小的多邊形。

    粒子裁剪算法過程。1:原始紋理;2:閾值化紋理;3:將所有實心像素添加到凸邊;4:減少凸邊形的面積;5:最終4個頂點的多邊形(60.16%);6:最終6個頂點的多邊形 (53.94%);7:最終8個頂點多邊形 (51.90%)。

    粒子修剪的問題:

    • 多邊形延伸到原始四邊形之外。常規紋理沒問題, 使用CLAMP。可能會切入相鄰的圖集tile,首先計算所有外殼,拒絕與另一個外殼相交的解決方案,如果沒有有效的解決方案,則恢復為對齊的矩形。
    • 性能。蠻力,保持凸包頂點數合理地低。
    • 過濾。添加像素的所有四個角(更快),或插入子像素alpha值(準確)。
    • 處理“奇怪”的紋理,例如在紋理邊緣alpha != 0。

    接下來是合并實例化(Merge-Instancing)

    實例化是一個網格多個實例,有多個繪制調用;合并是多個網格,每個網格一個實例,有頂點數據的重復;合并實例化是一次繪制調用,沒有頂點重復。下面是實例化和合并實例化的對比代碼:

    // 實例化
    for (int instance = 0; instance < instance_count; instance++)
      for (int index = 0; index < index_count; index++)
        VertexShader( VertexBuffer[IndexBuffer[index]], InstanceBuffer[instance] );
    
    // 合并實例化
    for (int vertex = 0; vertex < vertex_count; vertex++)
    {
        int instance = vertex / freq;
        int instance_subindex = vertex % freq;
    
        int indexbuffer_offset = InstanceBuffer[instance].IndexOffset;
        int index = IndexBuffer[indexbuffer_offset + instance_subindex];
    
        VertexShader( VertexBuffer[index], InstanceBuffer[instance] );
    }
    

    合并奇數大小的網格,選擇常用頻率,根據需要復制實例數據,根據需要使用退化三角形填充。例子——Mesh0:39個頂點,Mesh1:90個頂點,選擇頻率 = 45,用2個退化三角形(6個頂點)填充Mesh0。

    Instances[] = {
        ( Mesh0, InstanceData[0] ),
        ( Mesh1, InstanceData[1] ),
        ( Mesh1 + 45, InstanceData[1] ) }
    

    接下來聊電話線抗鋸齒。鋸齒的來源:

    • 幾何邊緣。主要由MSAA解決,后處理AA通常也有效,用細幾何體分解。
    • 著色。通過mipmapping解決的排序,缺乏研究/理解,一些實用的游戲技巧,如LEAN映射

    電話線是常見的游戲內容,通常是亞像素大小,MSAA有幫助但不多,在子樣本大小處中斷。其實可以不要亞像素大小!

    電話線是長圓柱形狀,由中心點、法線和半徑定義。避免進入亞像素,將半徑鉗制為半像素大小,以半徑減小比淡化。

    // Compute view-space w
    float w = dot(ViewProj[3], float4(In.Position.xyz, 1.0f));
    
    // Compute what radius a pixel wide wire would have
    float pixel_radius = w * PixelScale;
    
    // Clamp radius to pixel size. Fade with reduction in radius vs original.
    float radius = max(actual_radius, pixel_radius);
    float fade = actual_radius / radius;
    
    // Compute final position
    float3 position = In.Position + radius * normalize(In.Normal);
    

    接下來是第二深度抗鋸齒。過濾AA方法包含:

    • 處理AA:MLAA、SMAA、FXAA、DLAA。
    • 分析方法:GPAA、GBAA、DEAA、SDAA。

    深度緩沖區和第二深度緩沖區,深度在屏幕空間中是線性的,簡化邊緣檢測,可以預測原始幾何。兩種類型的邊緣:折痕、剪影,剪影需要第二深度緩沖,使用正面剔除進行pre-z通道,或者輸出深度以渲染背面幾何圖形的目標。

    先嘗試折痕,看深度坡度,計算交點,距離<1像素時有效,如果距離 < 半像素則使用,如果無效,嘗試剪影。

    嘗試作為剪影,鄰居深度沒用,看第二深度,計算交點,如果距離 < 半像素則使用。

    第二深度AA效果對比(左無右有):

    Using GPUView to Understand your DirectX 11利用GPU分析工具GPUView闡述了GPU的工作機制、原理及調試過程。圖形和WDDM(Windows Display Driver Model)的層級結構關系如下圖所示:

    發送任務給GPU的過程:

    標準的DMA代表了圖形系統狀態對象、繪制命令、對資源分配的引用(紋理、頂點和索引緩沖區、渲染目標、常量緩沖區)。GPUView可以查看DMA的詳情,還可以對上下文、隊列進行監控:

    CPU軟件上下文隊列是代表提交給GPU上下文的工作,隊列在時間上表示為一個堆棧,堆棧在UMD提交工作時增長,當GPU完成工作時,堆棧收縮。


    GPU硬件上下文隊列在時間上表示為一個堆棧,通過KMD提交工作時堆棧增長,對象被GPU完成工作時堆棧收縮,間隙表示CPU端瓶頸。還可以選擇特定的隊列,顯示其延遲:

    分頁緩沖包:作為結果提交分頁操作(可能是一個大的紋理),原因通常是準備一個DMA緩沖器,查看分頁操作之后的DMA數據包。對于硬件線程,顏色代表空閑,間隙代表工作:

    對于線程執行,淺藍色代表內核模式,暗藍色代表dxgkrnl(DX內核),紅色代表KMD(內核模式驅動):

    還可以查看垂直同步情況:

    獲得正確的遮擋查詢,延遲獲取結果為N幀,其中N = GPU數,可能需要人為膨脹遮擋體積以避免跳變。避免分頁控制顯存的使用,特別是在MSAA模式下降低低端硬件的紋理分辨率,避免使用過多的動態數據紋理和頂點緩沖。總之,確保GPU保持工作狀態,保持跟蹤CPU/GPU交互,保持跟蹤線程,監控multi-GPU交互,添加GPUView到工具箱。

    Sand Rendering in Journey分享了游戲Journey的沙粒及沙漠的渲染。

    沙子的渲染采用了銳化mip、各向異性遮罩、閃光鏡面、海洋鏡面、漫反射對比度、細節高度圖等:

    從左到右依次添加了:細節高度圖、漫反射對比度、海洋鏡面、閃光鏡面、各向異性遮罩、銳化mip、最終成像。

    沙粒的效果隨著相機的距離改變而改變,文中對漫反射也做了修改,而不是使用Lambert。修改后的漫反射著色與Lambert相比,對比度要高得多:

    左:修改的漫反射;右:Lambert漫反射。

    左:高度圖;右:高度細節圖,右上兩幅是微觀細節,右下兩幅是較宏觀的細節。

    Scalable High-Quality Motion Blur and Ambient Occlusion介紹了可擴展的高質量運動模糊和環境光遮蔽。

    需要可擴展的原因是能夠跨具有不同性能限制的*臺共享效果,分享適合不同藝術風格的游戲。好處是視覺一致性、節省開發時間、一致的內容要求、“免費”質量收益。

    潛在的可擴展元素有質量旋鈕:最大半徑、樣本數、夾緊輸入;分辨率獨立性,允許標準化的屏幕單位而不是像素;模塊化功能,準確性與速度;附加處理,附加通道,迭代算法。

    文中給出的方案是:低端質量設計,向高端延伸,迭代質量和性能,迭代之間有充足的時間。

    運動模糊的目標是不依賴資產,與所有幾何類型一致,獨立于場景復雜性,最小G-buffer編碼。這些限制消除了大多數現有技術。*距離觀察運動模糊:

    核心問題:自然散射效果,需要表示為聚集,對象在其范圍之外模糊,產生類似透明的效果。文中的方法:使用tile擴張速度,允許在對象邊界之外進行模糊處理,沿擴張速度采樣,分散 -> 聚集,根據速度和深度混合樣本,背景估計。具體步驟:

    • 渲染速度。計算屏幕空間中的速度,鉗制到最大模糊,重新縮放到[0, 1]。

    • 分塊最大化速度。NxN下采樣,N=最大模糊半徑,按幅度記錄最大值。

    • 鄰域最大化速度。輸入分塊最大結果,應用3x3盒式過濾器,通過中心和周圍分塊的大小找到最大值。

    • 重建。中心深度\(Z_C\),顏色\(Color_C\),中心速度\(\overrightarrow{v}_C\),鄰域最大值\(\overrightarrow{v}_N\)

    ?

    • 采樣。沿 \(\overrightarrow{v}_N\)兩個方向計算樣本,采樣\(Z_S\)\(Color_S\)\(\overrightarrow{v}_S\),確定前景或背景,與中心比較。

      • 確定背景還是前景。

    • 對比細節。有趣的案例:背景樣本\(Z_C < Z_S\),潛在背景估計,前景樣本\(Z_C > Z_S\),可能移過中心,兩個樣本都在移動(\(||\overrightarrow{v}_S|| \ne 0 , \ ||\overrightarrow{v}_C|| \ne 0\)),也就是想要一些模糊的東西。

    • 最終細節:收集樣本,比較權重總和,總和顏色貢獻,結果歸一化。

    運動模糊總體流程。

    接下來聊聊可擴展的AO。

    AO極大地有利于陰影中的照明,接觸和折痕陰影。項目的約束是快速、表面感知、減少鋸齒。

    核心問題是不想要兩種不同的算法,想要的視覺一致性。解決方案是更少但更高質量的樣本,基于兩件事的貢獻:點到中心的距離、樣本距離到法線的投影長度,試驗衰減函數,直到滿意的結果。該AO的步驟如下:

    • 在中心采樣深度和法線,重建位置。

    • 采樣屬性。選擇采樣位置,采樣深度,重建位置。

      ng)

    • 樣本貢獻。計算?? ?,計算‖?? ? ‖,計算?? ???? ?,根據半徑r應用衰減。



      12.png)

    • 總的遮擋。匯總貢獻并歸一化:

      其中:?????????? = 藝術家調整的貢獻量表,?? = 樣本數。

    • 采樣不同的法線對AO也有明顯的影響,下圖分別是采用GBuffer法線和派生法線:

    • 模糊結果。深度感知雙邊模糊,軟化貢獻,寬的濾鏡可隱藏圖案,結合陰影來攤銷成本。最終AO:

    可擴展和實現:

    • 設計擴展性。增加半徑,更改衰減函數,強調接觸陰影,更廣泛的影響,變更申請,調制一切,調制環境。
    • 質量擴展性。增加半徑,增加樣本數,更好的采樣模式,更廣泛的模糊。

    當前的實現:全分辨率4-tap,圍繞螺旋采樣,圍繞隨機向量旋轉,使用鏡像tap,將半徑限制為約20像素,轉置數學以一次計算,以Blue/Alpha編碼深度以進行模糊。

    DX11的實現:9 個樣本,程序螺旋采樣模式,引入一些噪點,無半徑夾緊,未夾緊半徑的Mip深度,以Blue/Alpha編碼深度以進行模糊。

    Separable Subsurface Scattering是時任職于暴雪的Jorge Jimenez等人呈現的皮膚渲染和眼睛渲染,詳細闡述了皮膚建模、SSS、SSSS及眼球的渲染技術。對于皮膚,擴散曲線如下圖:

    使用若干項高斯函數擬合皮膚的次表面散射:

    任何信號都可以通過若干個有理項來擬合并重建,例如傅里葉、快速傅里葉(FFT)、球諧函數(SH)及此處的擴散曲線等。

    可分離參數化配置文件和優化后的公式如下:

    當光線在物體的薄部分內部傳播時,就會發生半透明,光線行進的距離越遠,衰減就越多,意味著下圖中第一點的半透明性將低于第二點:

    除了距離,另一個因素是到達物體背面的光,但不幸的是,此信息不可用(下圖紅圈),因為使用的是屏幕空間方法。

    觀察:背面信息未知,人體皮膚的半透明隱藏了高頻細節,在人體皮膚中,反照率不會發生顯著變化。假設:可以使用反轉的正面法線作為背面的法線,可以使用前面的反照率值,作為背面的反照率值(下圖)。

    這些假設允許將所有數學簡化為下圖紅圈所示,可以預先計算成一個簡單的紋理。

    預計算紋理效果如下:

        float scale = 2e4 * (1.0 - translucency) / sssWidth;
        float4 shrinkedPos = float4(worldPosition - 0.005 * worldNormal, 1.0);
    
        // 通過使用陰影貼圖,計算在物體內部行進的距離.
        float4 shadowPosition = mul(shrinkedPos, lightViewProjection);
        float d1 = shadowMap.Sample(LinearSampler, // 'd1' has a range of 0..1 shadowPosition.xy / shadowPosition.w);
        float d2 = shadowPosition.z; // 'd2' has a range of 0..'lightFarPlane'
        d1 *= lightFarPlane; // So we scale 'd1' accordingly:
        float d = scale * abs(d1 - d2);
    
        // 使用前面顯示的預積分方程計算這個距離對應的顏色.
        float dd = -d * d;
        float3 profile = float3(0.233, 0.455, 0.649) * exp(dd / 0.0064) +
                         float3(0.1,   0.336, 0.344) * exp(dd / 0.0484) +
                         float3(0.118, 0.198, 0.0)   * exp(dd / 0.187)  +
                         float3(0.113, 0.007, 0.007) * exp(dd / 0.567)  +
                         float3(0.358, 0.004, 0.0)   * exp(dd / 1.99)   +
                         float3(0.078, 0.0,   0.0)   * exp(dd / 7.41);
        
        // 最后,使用標準環繞照明(wrap lighting), 開始半透明漸變, 比普通的點積要早一點.
        return profile * saturate((0.3 + dot(light, -worldNormal)) / 1.3);
    

    光子映射和上面透射*似的對比。

    這種皮膚半透明技術適用于直接光,但不幸的是,無法模擬環境光的半透明度。下圖的耳朵里怎么沒有光,哦,去到了在鼻子那里......

    另外,還會導致下圖陰影區域漏光的問題:

    在直射光的情況下,我們知道多遠,光已經穿過物體,通過使用常規陰影映射。但是在環境光的情況下,我們不知道這些信息,因為環境光不是來自特定方向。

    有效的解決方案是:反轉法線,向半球的每個方向投射一條光線并取*均值:

    \[\text{thickness} \times (1.0 \ – \ \text{ambientOcclusion}) \]

    其中,\(\text{thickness}\)是直到命中的光線距離,\(\text{ambientOcclusion}\)是被光線命中的表面上的環境遮擋。但是,耳朵上的環境遮擋太強,并且會過多地衰減透射光,解決方案是渲染環境光遮擋的多次反彈。

    左:沒有透射;右:使用了透射。

    還有陰影映射的瑕疵,可以使用用戶引導的透射率。

    上:藝術家選擇影響范圍和方向,在耳朵、鼻子或眼瞼等區域。下:用于預先計算每個球體沿特定方向的厚度,特別是計算十度圓錐的*均厚度。

    文中還詳細闡述了眼球的渲染,涉及諸多細節,可以點擊原文或筆者的另外一個系列的文章:

    Terrain in Battlefield 3: A Modern, Complete and Scalable System闡述了Frostbite的地形系統,包含可擴展性、工作流程、CPU和GPU性能、程序化虛擬紋理、數據流、穩健性、程序網格生成等。


    地形使用了多個柵格資源:高度場、著色器噴濺蒙版、顏色圖(用作著色器飛濺頂部的疊加層)、物理材質、破壞深度蒙版、反射光的反照率貼圖、額外的遮罩通道。

    Frostbite對可擴展性的定義:任意視距(0.06m至30000m),任意細節級別(0.0001m 及以下),任意速度(超級跑車和噴氣式飛機)。主要觀察:一切都與等級制度有關!層次結構的一致使用提供了“免費”的可擴展性,層次結構對地形渲染并不陌生,Frostbite方法類似于飛行模擬器,用于所有空間表示的四叉樹層次結構!

    四叉樹節點有效載荷比較復雜,可以全部負載或部分負載,也可以LOD負載,抑或是視圖相關的負載,

    GPU優化:程序化虛擬紋理,使用著色器濺射,藝術家可以創造美麗的地形,渲染非常緩慢(10-20 毫秒)。著色器濺射在視野距離上不可擴展,負擔不起多次通道,通過濺射到紋理中,利用幀到幀的一致性(性能),可以多次渲染(可擴展性),使用貼圖渲染全屏耗時 2.5-3ms (PS3)。

    虛擬紋理鍵值:每米32個樣本,集成兩個像素邊框的256x256的tile,以圖集存儲,默認大小為4k x 2k,兩個DXT5紋理:

    非常大,可以輕松達到1M x 1M (= 1Tpixel)!典型的虛擬紋理為64k x 64k。

    間接紋理格式:RGBA8,虛擬紋理tile圖集的索引,低分辨率區域的比例因子,其中一個tile覆蓋多個間接樣本,CLOD漸變因子:用于*滑淡入新合成的tile(淡入tile),以前的LOD(淡入淡出tile)已經在圖集中并使用間接mips獲取,CLOD因子更新每一幀。

    Teratexture的間接紋理可以輕松達到4k x 4k,太大了!使用Clipmap的間接紋理,Clipmap是早期的虛擬紋理實現,用6個64x64的clipmap層替換4k間接紋理。

    clipmap間接紋理:每個繪圖調用在CPU上解析剪輯圖級別,避免額外的像素著色器邏輯,要求每個64x64的圖都有自己的mip鏈,紋理空間必須與世界空間大致組織起來(不是地形問題),使用多個傳統的虛擬紋理可能會更好地使用更通用的用例。

    Tile合成:Tile在GPU上合成并在GPU或SPU上壓縮,好處(與從光盤流式傳輸相比):磁盤占用空間小 – 數據放大,源柵格數據放大約 1000倍,低延遲:tile已準備好使用下一幀,動態更新:破壞、實時編輯,高效的工作流程:藝術家不必畫出數百*方公里的每一塊鵝卵石。

    地形還啟用了數據流,先了解一下流基礎。流媒體單元:光柵tile(又名節點有效負載),典型的tile尺寸,包含高度場:133x133x2 字節、掩碼:66x66x1字節 x 每個有效負載0-50個tile、顏色:264x264x0.5 字節。固定大小的tile池(圖集),典型的圖集大小,包含高度場:2048x2048、蒙版:2048x1024、顏色:2048x2048。

    流模式:

    • 逐塊(又名免費)流式傳輸,用于較慢的游戲。
    • Tile bundle(又名基于推送)流式傳輸,用于更快的游戲,與布局關聯的tile被捆綁,基于地形分辨率的布局。
    • 混合流(最常見),在選定的生成點和過渡處使用的捆綁包,免費流填充其余部分。

    關卡上所有數據的布局,在生成點加載的數據子集,地形分辨率布局定義子集,用戶通過關卡時加載(和卸載)的子集。

    總之,Frostbite 2擁有強大且稱職的地形系統:高度場、陰影、貼花、水、地形裝飾,大多數方面都可以很好地擴展:視距、數據分辨率、裝飾密度和距離,流暢的工作流程,游戲內編輯,良好的工具范圍,良好的性能(CPU、GPU、內存),并行化、流式處理、程序化虛擬紋理。

    Loading Based on Imperfect Data介紹Insomniac Games的新引擎中的數據加載基礎架構,該引擎為游戲Fuse提供動力,演講將詳細介紹Insomniac Games如何改進其源代碼管理,并構建資產文件以在不犧牲工具迭代速度的情況下實現光學媒體的快速加載時間,還介紹一種新穎的磁盤布局方法,用于文件復制,使用蠻力GPU計算能力來計算最終布局。

    數據構建在后臺自動構建,資產大多以1:1的來源、產出比,沒有全局的依賴關系視圖,目標是一項資產變更只有 一項文件重建。運行時數據鏈接,簡單構建系統的陰暗面,“先買后付!”,松散的文件加載,I/O和依賴檢測同步運行。松散加載流圖例如下:

    松散加載的好處是隨時加載任何資產,非常適合原型制作,資產只在RAM中存儲一次,引用計數,易于在運行時重新加載資產。構建加載列表時,后臺優化任務,僅在構建系統空閑時運行,運行游戲不需要數據,通過完全依賴掃描產生,需要爬取大量數據。

    加載列表結果:完整關卡在DVD上的加載列表:1 分鐘,開發時HTTP加載加速:約50%!需要另外2-3倍的DVD加速才能發行,良好:I/O 管道中的停頓消失了,不好:還是搜索受限!

    文中還提及了資源去重的技術:


    Practical Implementation of Light Scattering Effects Using Epipolar Sampling and 1D Min/Max Binary Trees描述了光散射效應在參與介質中的實際實現,該技術將極線采樣與一維最小/最大二叉樹相結合,并利用一種新的、簡單且有效的半解析解決方案來解決由點光源引起的散射積分。該技術具有許多參數,允許以質量換取性能,這使其適用于各種硬件。內散射積分推導如下:



    Rayleigh散射和Mie散射及組合效果如下:

    體積陰影計算過程:


    徑向采樣(Epipolar Sampling):


    實現概覽:

    • 從深度緩沖區重建相機空間z坐標,必需的操作,因為深度是非線性的,而z坐標可以安全地插值。
    • 計算一維紋理,其中包含每個核切片的進入點和退出點。
    • 該紋理以及相機空間z然后用于在極坐標中渲染坐標紋理和相機空間z,在這個階段,還設置了一個深度模板緩沖區,用于標記有效樣本。
    • 檢測深度中斷并計算插值源紋理。
    • 渲染另一個1D紋理,其中包含按上述計算的切片原點和方向。

    • 原始陰影貼圖、方向和原點紋理用于為每個極線切片構建1D最小/最大二叉樹。
    • 在模板中標記光線行進樣本,并對每個樣本執行具有1D最小/最大優化的光線行進算法。

    • 使用插值源紋理對初始內散射進行插值。
    • 內散射從極坐標轉換為直角坐標,使用對極和矩形相機空間z紋理來計算雙邊權重。在此階段,無法從極坐標插值的這些像素在模板中標記。

    • 最后,對模板中標記的像素執行內散射修復過程。在這個階段沒有使用一維最小/最大優化。

    光束效果對比。左上:蠻力的參考圖;右上:高質量;左下:均衡;右下:高性能。

    Graphics Gems from CryENGINE 3闡述了2013年的CryEngine 3的一些特殊渲染技術,主要是抗鋸齒和后處理。

    抗鋸齒\延遲MSAA的回顧:問題是Multisampled RT中的多次通道 + 讀/寫,DX10.1引入了SV_SampleIndex / SV_Coverage系統值語義,允許通過多通道解決像素/采樣頻率通道。SV_SampleIndex強制執行每個子樣本的像素著色器并提供當前執行的子樣本的索引,索引可用于從Multisampled RT中獲取子樣本, 例如FooMS.Load(UnnormScreenCoord, nSampleIndex)。SV_Coverage指示像素著色器在光柵階段覆蓋了哪些子樣本,還可以修改自定義覆蓋掩碼的子樣本覆蓋率。基于DX 11.0 Compute Tiled的延遲著色/光照MSAA更簡單,循環遍歷帶有MSAA標記的子樣本。

    延遲MSAA的注意事項:簡單的理論,麻煩的實踐,至少對于復雜的延遲渲染器,非MSAA友好的代碼積累很快。打破常規,因為在沒有考慮MSAA的情況下添加了新技術,即使仍然有效。很多時候,需要查明并修復非MSAA友好的技術,因為這些技術會引入視覺失真,例如白色/深色輪廓,或根本沒有AA。改造渲染器以支持延遲MSAA是具備相當的工作量,而且非常挑剔。

    延遲MSAA的自定義解析和每個樣本掩碼:后處理G-Buffer,執行自定義MSAA解析,預解析樣本0,用于像素頻率通道,例如照明/其它MSAA相關通道。在同一通道中創建子樣本掩碼(比較樣本相似性,如果不匹配則標記),避免使用默認的SV_COVERAGE,因為它會導致對不需要MSAA的區域進行冗余處理。

    延遲MSAA的模板批處理:使用常規模板緩沖區對每個樣本模板掩碼進行批處理,從模板緩沖區中保留1位,使用子樣本掩碼更新,標記整個四像素而不是單個像素 -> 提高模板剔除效率,使用模板讀/寫位掩碼來避免每個樣本位覆蓋,StencilWriteMask = 0x7F,每當模板清除發生時恢復。由于過度使用模板而無法實現?可以使用剪輯/丟棄,額外的開銷也來自為每個樣本模板讀取的額外紋理。

    延遲MSAA的像素和采樣頻率通道:像素頻率通道將模板讀取掩碼設置為每個像素區域的保留位 (0x80),綁定預解析(非多重采樣)目標SRV,按常規執行渲染通道。

    采樣頻率通道將模板讀取掩碼設置為每個樣本區域的保留位 (0x80),綁定多重采樣目標SRV,通過SV_SAMPLEINDEX索引當前子樣本,按常規執行渲染通道。

    延遲MSAA的Alpha測試SSAA:Alpha測試需要臨時解決方案,默認SV_Coverage僅適用于三角形邊緣,創建自己的子樣本覆蓋掩碼,例如檢查當前子樣本是否使用Alpha測試并設置位。

    static const float2 vMSAAOffsets[2] = {float2(0.25, 0.25),float2(-0.25,-0.25)};
    const float2 vDDX = ddx(vTexCoord.xy);
    const float2 vDDY = ddy(vTexCoord.xy);
    [unroll] for(int s = 0; s < nSampleCount; ++s)
    {
        float2 vTexOffset = vMSAAOffsets[s].x * vDDX + (vMSAAOffsets[s].y * vDDY);
        float fAlpha = tex2D(DiffuseSmp, vTexCoord + vTexOffset).w;
        uCoverageMask |= ((fAlpha-fAlphaRef) >= 0)? (uint(0x1)<<i) : 0;
    }
    

    延遲MSAA的性能優化:延遲級聯太陽陰影貼圖,像往常一樣以像素著色器渲染陰影,延遲著色組合過程中使用雙邊上采樣。訪問深度的非不透明技術(例如軟粒子),在現實世界場景中,逐樣本處理的方法相當慢,在大多數情況下使用Max Depth就可以,而且速度快N倍。許多游戲也在用的方法:跳過Alpha測試超級采樣,改用alpha覆蓋,甚至不使用alpha測試AA(讓形態AA解決這個問題),使用MSAA僅渲染不透明,然后在沒有MSAA的情況下渲染透明,假設HDR渲染:注意色調映射是在解析后隱式完成的,結果是高對比度區域的細節丟失。

    延遲MSAA的MSAA友好性:注意下圖,沒有明顯的MSAA生效或明顯的亮/暗輪廓。

    修復后的效果如下:

    延遲MSAA的回顧:訪問和/或渲染多重采樣RT?然后需要關心訪問和輸出正確的子樣本。一般來說,總是應該努力最小化帶寬,避免香草(vanilla)延遲照明,優先完全延遲、混合或者完全跳過延遲。如果延遲,優先選用輕量的GBuffer,GBuffer上的每個額外目標都會導致導出數據的開銷 ,NV/AMD (GCN):導出成本 = 成本(RT0)+成本(RT1)...,AMD(舊硬件):導出成本 = (RT數) * (最慢的RT),高精度格式是GCN上雙線性過濾模式的半速率采樣成本,對于照明/某些hdr后期處理:大多數情況下32位R11G11B10F格式就足夠了。

    抗鋸齒 + 4K分辨率下我們是否需要MSAA?下圖是高度壓縮的4K和原始4K的對比:

    抗鋸齒/追求更好(和更快)的AA:2011年:替代AA模式(和命名組合)的繁榮年,如FXAA、MLAA、SMAA、SRAA、DEAA、GBAA、DLAA、ETC AA,以及“實時抗鋸齒的過濾方法”。著色抗鋸齒:Mip映射法線貼圖”、LEAN、CLEAN等。

    時間SSAA、SMAA 2TX、4X回顧:形態AA + MSAA + 時間SSAA組合,*衡成本/質量權衡,技術相輔相成,時間分量使用2個子像素緩沖區,每幀為2x SSAA添加一個子像素抖動,重新投影前一幀并在當前幀和當前幀之間混合。之前的幀,通過速度長度加權,保持圖像清晰度+合理的時間穩定性。


    時間AA/常見的魯棒性缺陷:依賴不透明的幾何信息,無法處理信號(顏色)變化或透明度。為了得到正確的結果,所有不透明的幾何體都必須輸出速度。負面案例:Alpha混合表面(例如粒子)、光照/陰影/反射/uv動畫/等,在AA解決之前的任何分散和類似的后期處理。可能導致分散的錯誤,例如透明度、照明、陰影等方面的重影,可能會出現剪影,來自分散和類似的后期處理(例如Bloom)。多GPU最簡單的解決方案:強制資源同步,NVIDIA通過NVAPI公開驅動程序提示以強制同步資源,是NVIDIA的TXAA使用的解決方案。

    SMAA 1TX/一個更穩健的時間AA:概念:只跟蹤信號變化,不依賴幾何信息,為了更高的時間穩定性:在累積緩沖區中累積多個幀,例如TXAA,重新投影累積緩沖區,權重:映射累積緩沖,將顏色緩沖到 當前 的范圍內幀鄰域顏色范圍, 高/低頻區域的不同權重(用于保持清晰度)。


    float3 cM   = tex2D(tex0, tc.xy); 
    float3 cAcc = tex2D(tex0, reproj_tc.xy); 
    
    float3 cTL = tex2D(tex0, tc0.xy); 
    float3 cTR = tex2D(tex0, tc0.zw);
    float3 cBL = tex2D(tex0, tc1.xy); 
    float3 cBR = tex2D(tex0, tc1.zw);
    
    float3 cMax = max(cTL, max(cTR, max(cBL, cBR)));
    float3 cMin = min(cTL, min(cTR, min(cBL, cBR)));
    
    float3 wk = abs((cTL+cTR+cBL+cBR)*0.25-cM);
    
    return lerp(cM, clamp(cAcc, cMin, cMax), saturate(rcp(lerp(kl, kh, wk)));
    

    文中還涉及了部分后處理,如Bokeh DOF、運動模糊。

    DOF不同權重的效果。

    運動模糊的重建濾波器。

    總之,文中闡述了實用MSAA詳細信息,該做什么和不該做什么。SMAA 1TX是更強大的TAA,只需4個額外的紋理操作和一對ALU。一個合理且性能良好的DOF重建濾波器,可分離的柔性濾鏡,任何散景內核形狀都可行。第一次通道:0.426 毫秒,第二次通道:0.094 毫秒,總共消耗0.52ms的重構濾波器。一種改進的合理運動模糊重建濾波器,可分離,第一次通道:0.236 毫秒,第二次通道:0.236 毫秒,總共消耗0.472ms的重構濾波器。

    Oceans on a Shoestring: Shape Representation, Meshing and Shading介紹海洋渲染的新技術,對現有表示的許多簡單改進,允許有效高度查詢的新程序表示,以及一種新的可顯著減少鋸齒的“固定網格”的網格劃分技術。當然,還有在表面著色方面的經驗。

    海洋著色包含形狀、網格及著色三大方面的技術。

    在形狀方面,采用傅立葉合成:幾種已知的海浪頻率成分模型,可以使用傅里葉變換來合成具有所需光譜的表面,將FFT離線預計算為一組置換貼圖,64幀循環動畫,64x64空間分辨率,8bit定點值,1.5mb用于位置和法線,對一組波求和。

    還有采用波粒子,以模擬在水面上移動的“水滴”,為著色提供表面高度和泡沫值,可以自由移動或拴在浮動物體上,使用smoothstep作為粒子核,可以偏移參數得到波紋:

    用于查詢的世界軸對齊統一網格,更新后將活動粒子寫入網格,將波紋柵格化到網格上的簡單循環。

    形狀還采用了LOD,樣品數量有限,尤其是在CPU上網格化時,避免視圖依賴的不明顯的形狀。程序化 – 省略較小的波長,傅立葉合成——斜降(ramp down),波粒子——斜降。

    總之,描述形狀的多種方式,本文發現的方法是富有表現力且相對快速且易于實現。

    網格方面,對于剪輯圖(clipmap),將不同分辨率的網格縫合在一起,在預生產期間排除,未來的也行可行。

    投影網格簡單又高效,視圖自適應,頂點隨相機移動,插值錯誤變得生動而明顯。在屏幕空間和世界空間對比如下:

    如果可以保持頂點靜止,我們可以凍結插值錯誤,可以采取極坐標網格劃分(Polar Meshing)

    普通投影網格和極坐標網格對比圖。

    極坐標疊加波浪及各類變換之后的網格形狀。

    它可以顯著減少明顯的失真,低開銷,相對容易實現。

    在著色方面,添加了泡沫、閃光、次表面散射、深/淺顏色,引入拋物線波,將波粒子擴展為“波紋粒子”,呈現在短時間內滿足視覺和設計目標的形狀描述組合。

    Pixel Synchronization: Solving Old Graphics Problems with New Data Structures像素同步的背景、技術、優化等內容。

    可編程著色器具有(并將繼續具有)巨大的影響,推動了無數新渲染技術的開發,管線后端仍然無法編程,只能從固定菜單訂購顏色、z和模板操作,但非常快速且省電。添加新的可編程后端?讓它與固定功能硬件并存,發揮各自的優勢。可編程后端使得DX11/OGL 4.2 可從像素著色器啟用任意讀寫內存操作,但映射到同一像素的片元可能會導致數據爭用。

    片元可以亂序著色,不支持順序依賴算法:

    Haswell可以檢測片元之間的依賴關系,并且避免數據競爭,保證R/M/W內存操作的原始提交順序:

    像素同步:像素/片段著色器的簡單擴展,為R/W內存訪問啟用排序(即與Alpha混合的順序相同),只是著色器中的一個函數調用:IntelExt_BeginPixelOrdering()。非常好的性能,在大多數情況下幾乎沒有性能影響,R/W內存訪問由完整的SoC緩存層次結構支持。比從像素著色器讀回幀緩沖區更強大,構建和訪問任意大小/類型/維度的數據結構(包括體素),與MSAA解耦,可以使用逐像素和/或逐樣本數據結構。


    一些可編程的混合應用程序:新的混合運算符、非線性色彩空間、奇異編碼等,例如RGBE、LogLuv等,延遲著色器的混合,例如通過混合法線和其它材質屬性來應用貼花。

    K緩沖區:Z-緩沖區的泛化,一次渲染N層圖像。無數應用:深度剝離建設性的立體幾何、景深和運動模糊、體積繪制...

    性能提示:不要清除大緩沖區,清除一個小緩沖區并將其用作透明蒙版。

    更小的數據結構可以提高性能,使用更多指令來打包/解包數據,*衡數據結構大小和打包/解包代碼的數量,將一維結構化緩沖區尋址為*鋪以更好地利用數據的局部性,例如1x2、2x2(2D 紋理)、2x2x2(體素)等,優先在著色器的后半部分插入同步點,增加映射到同一像素的同時著色片段的可能性。推論:盡可能使用硬件z測試以獲得更好的性能(Hi-Z很快!)。

    總之,可編程著色徹底改變了實時渲染,但不涉及管線的尾部。像素同步是一種新方法,可為3D管線注入新的活力,選擇能夠更好地解決渲染問題的逐像素數據結構,繪制幾何圖形以流式方式構建數據,使用數據并享受結果,DX11+擴展現在可用,OpenGL擴展在開發。

    REDengine 3 Character Pipeline概述了《巫師2》制作中使用的角色管道,詳細說明了使用的問題和解決方案,探討使用的著色器、為藝術家設定的預算、角色照明的特殊性,尤其是過場動畫中的特殊性,最后是使這些角色真正可信的動畫和模仿。該文還展示了新系統,該系統利用DX11和Forward+渲染來獲得更好的結果,例如頭發模擬和渲染、皮膚著色以及完全修改的模擬動畫系統,也分享了有關動畫如何從動作捕捉工作室進入游戲以及如何為怪物和動物制作動畫的詳細信息。此外,還討論了創建具有如此多專為角色創建的功能的一致照明和環境系統的困難。

    Advanced Linux Game Programming闡述了Linux系統下的游戲編程技術,包含構建系統改進、信號處理、內存調試及OpenGL調試技巧。Unix信號是異步通知,來源可以是:進程本身、另一個過程、使用者、內核。很像中斷,在第一次非原子操作時跳轉到處理程序。

    系統安裝默認處理程序,通常終止和/或轉儲核心,核心≈Windows術語中的minidump,但會轉儲整個映射地址范圍(截斷為RLIMIT_核心字節)。可以指定自定義處理程序,通過sigaction獲取/設置處理程序void handler(int, siginfo_t *, void *);,在sigaction()調用中,需要SAU_SIGINFO標志。信號可以嵌套,但不能和主程序共享:

    無法調用異步不安全/不可重入函數。

    Valgrind是Linux系統下的一款內存調試工具,動態運行時分析框架,動態重新編譯,機器碼→ IR → 工具→ 機器碼,性能通常為未修改代碼的25-20%。其內部有用于“遠程”調試的gdbserver,每個錯誤上的SIGTRAP(斷點),無限內存檢測點!

    對于OpenGL調試,古老的方法是每次調用OpenGL后調用glGetError(),獲得八分之一的錯誤代碼,需在手冊里查一下調用,看看這個特定的錯誤在這個特定的上下文中意味著什么…然后檢查實際情況,GLTEXAGE*()中GL無效值的6個可能原因!通常要附加調試器、重播場景…太糟糕了!

    調試回調使得再也不用調用glGetError()了!它可以提供更詳細的信息,包括驅動提供的性能提示,看看不同的驅動說了什么,如果沒有調試OpenGL上下文(GLX_context_debug_BIT_ARB),可能無法生效。由以下任一方提供(ABI兼容):GL_KHR_debug[OPENGL02]、GL_ARB_debug_輸出[OPENGL03]。不同的GPU供應商支持表如下:


    可以控制冗長(過濾)(glDebugMessageControl_ARB),向11(GL_DONT_CARE)查詢有價值的性能信息,如緩沖區的內存類型、未使用的mip級別…

    API調用跟蹤可以記錄應用程序運行的跟蹤,重播并查看跟蹤,在特定調用中查找OpenGL狀態,檢查狀態變量、資源和對象:紋理、著色器、緩沖區......使用apitrace或VOGL。

    另外,gcc multilib是32/64位交叉編譯的先決條件,在Clang和gcc之間來回切換很容易也很有用,使用gold可以大大提高鏈接時間。緩存gdb索引可以改善調試體驗,崩潰處理很容易,但很難正確處理。Valgrind對內存調試有極大的幫助,即使在使用自定義分配器時,通過使用一些擴展,OpenGL調試體驗可以大大提高。

    2014年,Efficient Usage of Compute Shaders on Xbox One and PS4講述了主機*臺上的計算著色器的特性、應用及優化,如基于GPU的布料模擬、著色器、優化技巧等。嘗試將布料從CPU到GPU的主要原因之一是當時的主機的GPU分峰值性能是CPU的15~23倍:

    首次方案選用了以下的方式:

    但是太多Dispatch,瓶頸還是在CPU。合并多個布料項目以獲得更好的性能,所有布料必須具有相同的屬性。新方法是一個巨大的計算著色器來模擬整個布料,著色器內的同步點,一次“調度”而不是50次+,使用單個“分派”模擬多個布料條目(最多32個)。其并行化處理如下:

    確保連續讀取以獲得良好性能,合并=1次讀取而不是16次讀取,即使用陣列結構(SoA)而不是結構陣列(AoS)。

    著色器優化方面,普通的規則是瓶頸 = 內存帶寬,可以采用數據壓縮:

    使用局部數據存儲(也稱為局部共享內存):

    在局部數據存儲器中存儲頂點:

    使用更大的線程組,用256或512個線程,以隱藏最多的延時:


    The Easy Route To Low Latency Cloud Gaming Solutions闡述了AMD的基于GPU的云渲染框架RapidFire的特性、架構和技術。RapidFire的特點是低延時、高清圖像質量、多流、虛擬化支持、高分辨率、可協作、虛擬桌面、自適應網絡環境,提供了服務端、網絡、客戶端、UI等組件。其渲染架構如下:

    數據流概覽:

    客戶端數據流:

    客戶端組件的初始化和循環:

    LEAP DIRECT游戲*臺——使用RAPIDFIRE的云游戲之彈性編碼:

    Volumetric Fog: Unified compute shader based solution to atmospheric scattering分享了大氣散射導論、現有游戲解決方案、算法概述、實現細節等內容。大氣散射包含天空顏色、霧、云、耶穌光、光束、體積陰影等視覺效果,每種效果又對應了不少渲染技術。大氣散射涉及了復雜的各類散射,它們的模型和公式說明如下:




    游戲中的*似方法:

    • 解析解(簡單介質密度函數)。
    • 基于廣告牌/粒子。
    • 基于后處理。
    • 射線行進。

    文中并沒有采用2D射線行進,原因是:通常不是基于物理的,循環使用GPU并行性很差,樣本按順序計算,而不是并行計算,像極線取樣這樣的解決方案是有限的,無變化的介質密度,沒有多個光源,與前向著色不兼容,單層效果,信息存儲一個深度,小量邊緣失真。

    受Light Propagation Volumes啟發,采用了體素化的3D網格方案。算法概覽:

    • 作為中間存儲的體積紋理。
    • 使用計算著色器和UAV高效地進行光線追蹤和寫入。
    • 解耦典型散射步驟。參與介質密度估計,散射光計算,射線行進,應用效果。

    算法細節:

    3D紋理布局:

    渲染效果:

    SCRIPTING PARTICLES闡述了腳本化粒子的特性、實現及優化。腳本化粒子的好處在于更靈活、快速迭代、藝術家可控。

    靈活性與性能:腳本編寫很棒,但對于高性能任務來說速度太慢,即使使用了JIT。一些高性能領域將受益于更高的靈活性,如粒子模擬、風模擬(和其他矢量場效果)、聲音處理等等,如何讓腳本為它們工作?為什么腳本轉譯速度慢?下圖有些代碼是相同的:相同的機器指令,有些是使用字節碼而不是本機代碼的開銷:

    數據范圍虛擬機(DATA WIDE VIRTUAL MACHINE):對多個數據項執行每個指令,解碼和分支成本攤銷,字節碼是否與本機代碼一樣快?無法將數據保存在寄存器中,更多加載和存儲,觸摸更多的緩存。其循環順序如下:

    處理過程:

    • 構建在Vector4指令抽象之上。
    • 輸入/輸出數據為通道(SIMD向量陣列)。
    • 字節碼包含在通道上操作的指令:pos = ADD pos move
    • 解碼指令后,解釋器一次將其應用于n個對象。

    Vector4 *a = (decode channel ref);
    const Vector4 *b = (decode channel ref);
    const Vector4 *c = (decode channel ref);
    Vector4 *ae = a + n;
    // 循環可以被展開成n。
    while (a < ae) 
    {
        *a = *b + *c;
        ++a; ++b; ++c;
    }
    

    此外,在字節碼中對常量和臨時變量進行特殊處理和優化。概覽如下:

    • 離線階段。數據編譯器解析代碼:pos = pos + vel * delta_time,生成字節碼,必要時引入臨時變量:

      r0 = MUL vel (0.0 0.0 0.0 0.0)delta_time
      pos = ADD pos r0
      

      字節碼已優化(臨時變量消除)。

    • 運行時。打包字節碼中的常量,執行指令。

    總之,“數據范圍解釋器”模型是實現高性能的可行解決方案,腳本編寫,完全可配置的行為,全動態:可以快速重新加載,無需重新編譯引擎,與傳統修改器堆棧解決方案相比,開銷為18%(與原生相比為34%),支持AVX的腳本化解決方案比本機解決方案更快。未來每個組件一個通道(位置x、位置y、位置z),更多后端:JIT編譯器、GPU計算、SPU…

    無獨有偶,Compute-Based GPU Particle Systems也涉及了GPU粒子,陳述了利用計算著色器對粒子加速的技術,包含碰撞、排序、分塊渲染等內容。使用GPU的原因是高度并行的工作負載,釋放CPU來做游戲代碼,杠桿化計算。粒子的數據結構包含粒子屬性(位置、速度、年齡、顏色等)、排序列表(序號、距離)、銷毀列表(序號)。發射和模擬計算著色的數據流如下:


    碰撞用到了圖元、高度場、體素數據、深度緩沖區等。深度緩沖區碰撞時,將粒子投射到屏幕空間,從深度緩沖區讀取Z,比較視圖空間粒子位置與Z緩沖區值的視圖空間位置,使用厚度值。

    深度緩沖碰撞響應,使用G緩沖區中的法線,或者多次點擊深度緩沖區,注意深度不連續性。為正確的Alpha混合排序,附加混合只是使效果飽和,雙調排序在GPU上可以很好地并行。

    for( subArraySize=2; subArraySize<ArraySize; subArraySize*=2) // subArraySize == 4
    {
        for( compareDist=subArraySize/2; compareDist>0; compareDist/=2) // compareDist == 1
        {
            // Begin: GPU part of the sort
            for each element n
                n = selectBitonic(n, n^compareDist);
            // End: GPU part of the sort
        }
    }
    

    光柵化:DrawIndexedIndirectInstanced()或DrawIndirectInstanced(),VertexId=粒子索引(或VertexId/4表示VS公告牌),1個實例。對大粒子的過繪制限制了游戲設計,在紋理周圍使用多邊形公告板。渲染到一半大小的緩沖區,排序問題,存在失真。

    逐tile的雙調排序:因為每個線程都會添加一個可見的粒子,粒子以任意順序添加到LDS,需要分類。僅在tile中排序粒子,而不是全局列表。分塊渲染(1個線程=1個像素):

    • 將累加顏色設為float4(0,0,0,0)。
    • 對于tile中的每個粒子(從后到前):
      • 評估粒子貢獻。
        • 半徑檢查。
        • 紋理查找。
        • 可選的法線生產和照明。
      • 手動混合。
        • color = ( srcA x srcCol ) + ( invSrcA x destCol )。
        • alpha = srcA + ( invSrcA x destA )。
    • 寫入屏幕大小的UAV。

    分塊渲染(改進版):

    • 將累加顏色設為float4(0,0,0,0)。

    • 對于tile中的每個粒子(從前到后):

      • 評估粒子貢獻。

      • 手動混合。

        • color = ( srcA x srcCol ) + ( invSrcA x destCol )
        • alpha = srcA + ( invSrcA x destA )
      • **if ( accum alpha > threshold ) **

        ? accum alpha = 1 and bail

    • 寫入屏幕大小的UAV。

    粗糙剔除:

    • 將粒子放入8x8中。
    • UAV0用于索引,使用偏移將陣列拆分為多個部分。
    • UAV1用于存儲每個bin的粒子數量。每個bin1個元素,使用InterlockedAdd()累加計數器。
    • 對于每個活著的粒子:
      • 對于每個bin:
        • 根據bin的截錐*面測試粒子。
        • UAV1中的累加計數器,用于獲取要寫入的插槽。
        • 將粒子索引添加到UAV0。

    性能對比:

    結論:利用計算進行粒子模擬,深度緩沖區碰撞,正確混合的雙調排序。分塊渲染比光柵化更快,非常適合解決嚴重的過繪制,更可預測的行為。未來的工作是體積跟蹤,為OIT添加任意幾何體。

    Landscape creation and rendering in REDengine 3講述了巫師系列所用的引擎REDengine的地形系統的創建管線和渲染流程。

    REDengine引擎的目標是支持超過16k分辨率的地圖,頂點間距小于0.5米,各種景觀特征,用于放置洞穴網格的地形孔洞,由一個相對較小的團隊進行繪制和填充,地形形狀由世界機器生產并導入。在紋理方面有很大的期望,混合材質,基于斜坡的、逐像素,地形必須投射陰影,廣泛的復制粘貼功能勝過一般的“景觀”。

    地形/流:內存中的Clipmap,具有流式區域,使用texture數組。Novigrad的工作設置:46x46的tile,每個512x512(23萬+),窗口分辨率=1024x1024,5個clipmap級別,內部頂點間距=~0.37厘米,約74*方公里。

    地形/Clipmap:3個流式clipmaps:高度圖(16位unorm)、控制圖(16位uint)、顏色(32位,分辨率降低,如果是163842高度數據集,則為40962)。3個運行時生成的Clipmap:垂直誤差(64x64常見情況)、法線(可選)、地形陰影。

    地形/曲面細分:靈感來自Gpu Pro 3的文章,類似的技術應用于clipmap,三角形數量仍然非常好,最大曲面細分因子為8或16時,效果最佳,尤其適用于控制臺GPU。

    垂直誤差圖生成:對于每個細分塊[x,y](1個控制點=1個曲面細分塊):

    軟件曲面細分:對誤差圖進行下采樣,以便在依賴硬件細分之前在四叉樹級別進行簡化,避免使用最小細分塊的密集網格渲染大區域。

    紋理目標:幾乎不費吹灰之力就擁有令人信服的前景,從那里開始真正的工作,在特寫鏡頭中融入精美的材質,離線可手動調整紋理大小,只使用UV刷。易于實現:沒有材質流,只有一個紋理數組(兩個都包含法線貼圖)。

    地形使用了三*面映射,疊加紋理沒有三*面貼圖,對于具有三*面貼圖的背景紋理樣本,選擇哪些*面起作用(與紋理獲取相比,更喜歡分支),盡可能縮緊混合區域,但不要出現瑕疵。混合區域縮緊如下:


    地形陰影clipmap存儲陰影中的最大高度,在流式處理clipmap或更改一天中的時間時更新,必須與clipmap計算緊密結合,以避免陰影閃爍,允許幾個巨大的網格投射地形陰影。

    地形陰影算法:

    • 鋪設地形深度-太陽透視圖。
    • 渲染到每個紋理的陰影片段clipmap:
      • 對每個紋素:
        • 在對應的高度紋理,計算全世界空間位置(wsPos)
          • For i=0 to n: // n=13有不錯的效果
            • 將wsPos轉換為太陽空間位置。
            • 比較太陽空間位置的z值和從pt.1紋理獲取的z值。
            • 如果位置被遮擋,wsPos.z += step,step減半。
            • 如果位置未被遮擋,則wsPos.z -= step。
          • 重新繪制地形陰影貼圖中的最后一個z分量值。

    此外,植被還采用了復雜的生成算法和工具鏈。效果圖如下:

    Next-Gen Characters From Facial Scans to Facial Animation闡述了高質量的虛擬角色的制作流程,涉及的工具鏈和技術。文中提出的工作流如下:

    1、掃描演員。

    掃描階段使用了攝影矩陣:

    2、將原始掃描處理成對齊的混合形狀。BlendShape掃描流程概述:

    • 在點處放置定位器。
    • 包裹網格進行掃描。
      • 將關節移動到點。
      • 匹配掃描上最*的點。
      • 放松。
      • 重復。
    • 導出頭部。
    • 投影紋理。
      • 迭代應用光流,曲率和高頻漫反射。
      • 將光流結果重新應用于網格。
    • 完成。

    在以上過程中,需要處理很多額外的細節,例如紋理投影、表情、基礎網格等等。

    3、壓縮blendshape紋理以實現實時回放。

    許多漫反射紋理(70),Tiger Woods有數千人。

    PCA指南:實際上是偏移量,下圖從左到右依次是微笑、中性、微笑偏移(微笑-中性):

    重建時,Final = w0*img0 + w1*img1 + w2*img2 + … + w11*img11,只需更改權重(著色器常數)即可設置動畫:

    計算主成分分析:可以使用SVD(奇異值分解),SVD將解決所有210列,然后砍掉除前12個以外的所有項:

    PCA算法見:http://en.wikipedia.org/wiki/Principal_component_analysis。

    4、使用mocap驅動混合形狀。

    查找與關節動畫匹配的形狀的權重:

    5、使用皮膚著色渲染。

    渲染使用的技術有Skin SSS、AO with spherical harmonics(SHAO)、Adaptive Tessellation、Eyes、Teeth等。

    從左到右:沒有陰影、SHAO、陰影、SHAO+陰影。

    Hybrid Reconstruction Anti Aliasing講述了Far Cry 4的混合重建抗鋸齒技術HRAA。HRAA的目標是時間穩定性,高質量邊緣消除混疊,與4倍RGSS相當的超級采樣,1個樣本/像素的著色成本,在分辨率為1080p的PS4/X1上的性能約為1ms。HRAA的概覽:穩定邊緣抗鋸齒、時間超采樣、時間抗鋸齒。穩定邊緣抗鋸齒包含:

    • 形態:SMAA[Jimenez 11]、FXAA[Lottes 09]

      • 優勢:最高的感知質量是靜態場景、捕獲所有行為、易于集成、使用光柵化數據。
      • 劣勢:1080p時1.0-1.5毫秒(PS4/X1),時間不穩定,在運動中搖擺。部分解決:更昂貴的SMAAx4。
    • 分析邊緣AA:GBAA[Persson 11]、DEAA[Malan 10]

      • 優勢:最高的邊緣質量接*基準真相,時間穩定,擴展到Alpha測試(使用SDF獲得最佳結果),1080p(PS4/X1)下快速的0.3毫秒。
      • 劣勢:復雜集成,每個G緩沖區著色器輸出到邊緣的距離,幾何體著色器/直接頂點訪問[Drobot 14],有光柵化問題,光柵化順序依賴,內容相關,過度曲面細分會明顯地關閉AA,不存在相交三角形。

      頂部:到邊緣距離的可視化,顏色編碼1比特方向(X,Y),符號值編碼4位距離。中間:1x質心光柵化的結果。底部:分析解析的結果。請注意,右側的邊緣完全消除了鋸齒。由于光柵化錯誤和亞像素三角形(多個三角形相交處),中間部分顯示的邊緣不正確。

    • MSAA。

      • 優勢:隨著樣本量的增加,收斂到基本真理,解決亞像素問題。
      • 劣勢:內存占用與采樣量成線性關系,網格渲染時間隨采樣量而變化,延遲渲染的復雜集成。

      2.png)

    • EQAA/CSAA。GPU可以將覆蓋率樣本與顏色/深度片元分離,由廉價覆蓋率樣本輔助的MSAA=EQAA。

    • 基于覆蓋率:覆蓋率重建AA(Coverage Reconstruction AA,CRAA)。它將顏色片元與其它覆蓋率示例一起使用,最低成本,從覆蓋范圍重建最終圖像,需要能夠直接訪問樣本的硬件,后面是基于AMD GCN架構的演示,其它IHV也支持覆蓋率抽樣。

    FMASK:與顏色緩沖區關聯的片段壓縮緩沖區,存儲樣本和顏色片段之間的關聯表,對于每個像素存儲:對每一個樣本,關聯片段的位索引。每像素( [1, 2, 4, 8, 16樣本] [1, 2, 4位用于顏色索引] + 1位用于UNKNOWN標記):

    4-sample/2-fragment = 4 * 2 = 8 bit
    8-sample/1-fragment = 8 * 1 = 8 bit
    16-sample/8-frag = 16 * 4 = 64 bit
    

    結合下圖舉個具體的例子:

    上圖頂部:樣本0和1被錨定——有自己的深度片元用于深度測試;上圖中部:藍色三角形命中錨定樣本0–藍色添加到Color Fragments的序號0;上圖底部:藍色三角形覆蓋的FMask樣本將被關聯到Color Fragments的序號0。

    CRAA設置:MRT設置:顏色/深度1F xS,管線:Gbuffer渲染、光照、CRAA解析。8x CRAA的樣本解析:

    • 對于每一個未知的樣本:
      • 獲取采樣位置。
      • 將樣本位置視為向量。
      • 累加在一起。
    • 總和定義了半*面分割像素的*似公式:
      • 計算半*面方向:垂直/水*。
      • 計算半*面坡度。
      • 從方向和坡度推斷未知片元:上/下、左/右。
    • 解析像素 = Color Fragment * Coverage + (1-Coverage) * Inferred Fragment。

    除了包含多個三角形交點的像素外,8xCRAA結果與8xMSAA相當。在這種復雜的情況下,單個邊緣估計無法正確解析邊緣。可見的失真與分析方法類似。

    8xCRAA LUT:亞像素偽影呢?能消除它們嗎?能擺脫ALU而只受限于帶寬嗎?解決方案:預計算LUT以存儲相鄰像素權重,使用完整的鄰居,穿過像素的多條邊/三角形。

    8xCRAA LUT效果對比:

    時間超采樣:基于Killzone: Shadow Fall [Valient14],對數據使用當前幀和上一幀(2個樣本),使用N-2框架進行顏色流向測試,N-1樣本僅在以下情況下有效:第N幀和第N-1幀之間的運動流是相干的,第N幀和第N-2幀之間的顏色流是一致的(注意N-2和N具有相同的亞像素抖動)。

    測試使用3x3鄰域,絕對差之和,出于性能原因=>較小的窗口=>更保守。GCN提供硬件加速:SAD、QSAD、MQSAD、打包的插值。

    如果N-1個樣本不符合幾何度量,從N開始插值,如果N-1個樣本不符合顏色度量,通過N個顏色邊界框限制N-1個樣本,提高穩定性,帶來了一些新信息。不同采樣模式的效果對比如下:

    ng)

    從左到右:1x、FLIPQUAD、4xRG。

    FLIPQUAD采樣模式:[AMD 13] AMD_framebuffer_sample_positions,2xMSAA–易于設置,在同等成本下,其質量明顯高于梅花形[Laine 06],下圖是不同采樣模式和誤差表:

    時間FLIPQUAD模式圖如下:

    把圖案一分為二,幀A(藍色)部分地渲染,幀B(紅色)在隨后渲染。需要在quad內逐像素地解析,根據幀在X軸或Y軸上進行方便地混合,Pixel0 = avg(BLUE(0,1), RED(0,2))

    TAA:歷史指數緩沖器,攤銷突然的視覺變化(閃爍),盡可能多地積累新的“重要”數據,使用基于頻率的驗收指標。操作新數據的鄰居(3x3窗口),接**均值的歷史樣本不會帶來新信息,更遠的歷史樣本帶來更多信息,歷史樣本太遠可能是一種波動。使用局部最小值/最大值作為軟邊界。

    HRAA的最終實現:

    • 時間穩定的邊緣抗鋸齒。
      • SMAA(法線+深度+Luma預測閾值)
      • CRAA
      • AEAA(GBAA)
    • 結合TAA的時間FLIPQUAD重建。
      • TFQ+TAA

    HRAA在Far Cry 4的最終實現:

    • 時間穩定的邊緣抗鋸齒。非顯而易見的選擇。
    • Alpha測試中的SMAA+AEAA。最可靠、最合理的性能。
    • Alpha測試中的CRAA+AEAA。最佳性能,一些內容問題。

    它們的效果及性能對比如下:




    Reflection System in Thief闡述了游戲《神偷》的引擎的反射系統,包含基本算法、反射系統概覽、SSR、IBR、光澤反射、反射管線、局部立方體圖反射、藝術管線等內容。

    神偷的反射系統的基本算法:*面反射、立方體貼圖反射、基于圖像的反射、屏幕空間反射,以及局部立方體映射、*面上的圖像代理、凹凸*面反射、Hi-Z屏幕空間反射等。

    反射系統規范:在下一代*臺(PC/PS4/X1)上小于5毫秒,多層反射面,人體高動態對象,準水*表面,應該抓住主要的地標。

    解決方案:多層反射系統。每一層負責捕獲特定的功能,立方體圖(局部+全局)、基于圖像的反射(IBR)、屏幕空間反射(SSR)。屏幕空間反射(SSR)的步驟如下圖的紅色箭頭所示:

    SSR優化:法線*似為(0,0,1),凹凸是后處理,更好的內存聚合——每次DRAM爆發都有更多有用的數據,使用Early Out,反射低時退出。

    // 以下代碼有什么【問題】?
    ...
    float4 res = 0;
    //Early out
    if (reflectionFactor < epsilon)
    {
        return res;
    }
    ...
    res = tex2D(...);
    ...
    
        
    // 修復問題版本
    ...
    float4 res = 0;
    // Early out
    [branch] // X4121:基于梯度的操作必須移動到流控制之外,以防止分支(divergence)
    if (reflectionFactor < epsilon)
    {
        return res;
    }
    ...
    res = tex2Dlod(...); // 使用非梯度指令和強制分支!筆者注:tex2D是梯度指令。
    ...
    

    在第一次深度緩沖區相交處退出:

    如果視線和反射方向的點積 > 0,則當前像素的菲涅爾系數相當低,可以提前退出。根據距離\(d\)而減少樣本數量:

    \[N_\text{linearSamples} = \max (1,\ k_1 e^{-ik_2 d}) \]

    以半分辨率渲染,顯著加速,在后處理過程中重建高頻數據,最壞情況計時(全屏)約1.0到1.5毫秒,但在真正的地圖上真的很難實現!

    對于基于圖像反射(IBR),UE3做得很好,但神偷每幀需要50個IBR代理,全高清8-10毫秒。依舊好用的半分辨率,IBR房間:將IBR定位到特定級別的位置,向下看時限制IBR反射。分塊渲染,使用準水*反射器假設,經驗主義,沒有提供證據。

    對于IBR分塊渲染,將屏幕分成垂直的tile,計算代理AABB投影,將垂直邊延伸到與屏幕的交點,將代理添加到受影響的tile。

    對于光澤反射,在光滑的表面上反射光線發散,取決于反射器和反射對象之間的距離。輸出反射距離,以4毫米的精度打包成2個字節,也可用于排序(因此精度高),擴大距離,與DOF方法類似的問題,存在模糊(2通道高斯)。

    SSR、IBR混合:SSR的Alpha取決于幾個因素:高度、跟蹤精度(深度增量)、表面水*度、射線離開屏幕、射線回到攝像機前、“就因為我不想要SSR”,IBR優先合并,然后是立方體貼圖,混合在sRGB中完成,但在PS4上使用RGBA16F。通常SSR比較接*,有些物體(火)是例外,使用距離在IBR著色器中排序。

    IBR排序,左圖未做排序,右圖做了排序,故而有正確的火苗反射。

    反射凹凸(Reflection Bump):在跟蹤過程中,法線是固定的,由于半分辨率,高頻信息丟失,類似于折射渲染的算法,所有輔助步驟計時約2毫秒。

    藝術管線方面,局部化立方體圖管線如下:

    所有SKU上使用的默認立方體貼圖,體積構造,設置捕獲對象的屬性,構建立體圖的構造過程,立方體貼圖被保存和組裝,立方體貼圖被指定給網格,獲得最終結果。

    IBR創建管線:確定IBR反射的候選區域(即水坑),為主要地標創建*面,被照亮的場景被烘焙到*面上,*面位置和照明調整(如果需要,增加*面),隱藏并移動到另一個區域。

    總之,實時反射并不是一個已解決的問題,SSR需要后備,正在/將要使用多層解決方案,混合分辨率渲染以節省帶寬。

    Tessellation in Call of Duty: Ghosts詳盡地描述了COD的曲面細分技術。曲面細分的實現可以分為3個步驟:離線全局曲面細分、運行時全局曲面細分、特性自適應曲面細分。

    細分的過程會產生不規則面,從而導致網格破裂(crack),需要在不規則邊緣上插入過渡點:

    屏幕空間自適應的算法過程和符號意義如下:

    邊緣外推和角外推如下:


    計算著色器可以當作Hull著色器:

    直接使用匯編來代替HLSL,可以獲得巨大的性能提升:

    此外,還針對Wave使用率(CP/clock、clock/wave)和延遲做了詳細的統計:

    High Quality Temporal Supersampling由Unreal Engine的Brian Karis演講,闡述了UE的時間抗鋸齒(TAA)的技術、實現、問題及優化。

    Brian Karis在文中對比了MSAA、空間過濾(MLAA、FXAA、SMAA等)、鏡面波瓣過濾(Toksvig, LEAN, vMF等),發現它們都或多或少存在問題,或者缺失信息,最終選用了時間抗鋸齒TAA。

    TAA將樣本分布到多個幀上,過去Brian Karis在這方面取得了巨大成功,如SSAO、SSR,替換空間濾波,高質量且更便宜。超級采樣也是這樣嗎?

    TAA涉及抖動、采樣模式、移動*均數等技術點,對于*均的時間點,可以在色調映射之前或之后。在色調映射之前,物理上正確的位置,明亮的值占主導地位,鋸齒嚴重程度受限于樣本數量;色調映射之后,所有后處理過濾器都會閃爍,鋸齒輸入→ 鋸齒輸出。

    直截了當的色調映射解決方案:

    • 混合色調映射之前和之后:在所有后處理之前應用,色調映射輸入,累計樣本,反轉色調映射輸出。
    • 與色調映射后的AA質量相同。
    • 向后處理鏈提供抗鋸齒的輸入,不再閃爍的泛光。

    更好的色調映射解決方案:

    • 色調映射會降低明亮像素的飽和度。

    • 取而代之的是基于亮度的權重采樣,保持色度,在知覺上更接*基準真相。

    • 不需要儲存權重,重新推導出權重,節省GPR。

      \[\begin{eqnarray} \text{weight} &=& \cfrac{1}{1+\text{luma}} \\ T(\text{color}) &=& \cfrac{\text{color}}{1+\text{luma}} \\ T^{-1}\text{(color)} &=& \cfrac{\text{color}}{1-\text{luma}} \end{eqnarray} \]

    重建過濾器有Box過濾(在運動時不穩定)、PRMan抗鋸齒指南,以及高斯擬合Blackman Harris 3.3(支持大約2像素寬)。

    重投影:當前像素的歷史記錄可能在屏幕上的其他位置,可能根本不存在,使用與運動模糊相同的速度緩沖區計算,記住移除抖動。

    速度精度:一切都需要速度(運動矢量),沒有正確速度的運動會模糊。準確度非常重要,微小的不精確會在靜態圖像上留下條紋,16:16 RG速度緩沖器。很棘手的是程序動畫、滾動紋理、幾乎不透明的半透明對象。

    邊緣上的運動:移動輪廓邊緣會丟失AA,*滑抗鋸齒的邊不會隨對象移動,實際上是速度緩沖區中的鋸齒遮罩,擴大速度,取前最高速。

    TAA的其中一個大問題是鬼影(下圖),解決方案用深度比較?并非所有樣本都具有相同的深度。用速度加權?著色變化和半透明讓它失效。

    可以采用鄰域截取(Neighborhood clamping),將歷史限制在當前幀的局部鄰域范圍內,假設結果是鄰居的混合,最小/最大為3x3的鄰域截取。

    然而,鄰域截取存在瑕疵:

    基本的鄰域截取與紅色歷史的對比。有很多瑕疵,最明顯的是每個邊緣都有紅色的鬼影,更微妙的是,圖像看起來像是低分辨率的。

    可以改成使用YCoCg顏色空間的盒子。可以將min和max的基數視作RGB空間的AABB,且可以將Box的朝向定位到亮度方向上(因為亮度有很高的局部對比度,而色度通常不會):

    用裁減(clip)而不是截取(clamp):限制為歷史和鄰域*均的混合,將線段剪裁到長方體,顏色不會像截取一樣聚集在盒子的角落里。


    基礎版和YCoCg版的對比。

    對于半透明,半透明不適合時間性的(單一的歷史和速度),理想情況下,渲染半透明單獨和合成,無法取消深度緩沖區的比較,可能的解決方案:4xMSAA深度預通道,選擇要著色的樣本。

    UE的半透明解決方案:“響應AA”材質標志,在渲染半透明時設置模板,時間AA通過測試模板,并使用最少的反饋,不幸的是,需要>0的反饋來防止可見的抖動。僅適用于火花等小顆粒,其余的由鄰居來處理。


    TAA就像是一道防火墻,隔離了可見性樣本和空間過濾,使得深度無法穿透TAA給空間過濾階段的DOF使用。

    另外在半透明階段之后特殊路徑,給DOF或光束設置數據,接著單獨執行TAA,再執行空間過濾階段的邏輯。

    閃爍:相機是靜態的,但有些像素會閃爍,丟失的亞像素特征的歷史被截取,由于相關抖動,通常是垂直或水*線。截取是瞬間的脈沖,導致鋸齒波閃爍。

    UE在多次嘗試之后,最終的解決方案是:當歷史接*截取時,減少混合系數,在截取事件之后發生,特定于事件的內存,不需要額外的存儲空間。但還沒有完全解決,非常困難!無法解決多個相反的clamp。

    模糊的過濾內核:Mipmap偏移所有紋理,超采樣的導數不正確。如果對比度低,則減小過濾器內核大小,從技術上講是鋸齒,但看起來不錯。可以添加額外的后銳化過濾器,Mitchell 4.0濾波器的負波瓣距離>1像素。

    模糊的重投影擴散:可以使用來回誤差補償,沒有取得好的結果,可以以更高的分辨率存儲歷史,但很大開銷。當重新投影外部像素時,減少濾波器尺寸和反饋。

    噪點過濾:不是它最初的目的,副作用很好,用于SSR和SSAO,隨機采樣效果很好,不需要額外的成本,幾乎完美的鏡面反射,僅需16個光線步進。

    更多潛在的應用:隨機透明度、單樣本各向異性鏡面反射IBL、軟陰影、光線投射的簡化步驟、視差遮擋映射、體積光照、路徑追蹤?虛擬現實?

    未來的方向:時空結合、單獨半透明、可見性和著色示例、每像素不同的抖動、定制MSAA樣本放置、更完整的運動矢量、半透明、運動估計。總之,時間超級采樣已準備好生產,高質量、高性能,需要大量的感知調整。更多可參閱:7.4.5 TAA

    Realistic Cloud Rendering Using Pixel Sync分享了用像素同步技術來高效地失效云體模擬。模擬云體的常用技術有3種:公告板、射線步進、直接體積渲染,它們各有特點和優缺點。可以嘗試將基于粒子的方法的控制與光線行進和切片技術結合起來。關鍵想法:使用代表實際3D形狀的體積粒子,使用基于物理的照明,預先計算照明和其它量,以避免在運行時進行昂貴的計算,執行體積感知混合,而不是alpha混合。算法步驟:

    • 初始步驟。使用球形粒子建模云。

    • 添加預先計算的云密度和透明度。

    • 添加預先計算的光散射。

    • 增加光源遮擋。

    • 添加體積感知混合(通過像素同步啟用)。

    • 增加光照散射。

    預計算照明:主要的想法是為簡單形狀預計算基于物理的照明,從這些簡單的形狀構造云,粒子一詞現在指的是這些基本形狀(不是單個的微小液滴)。接著預計算光學深度、散射,然后是組合云體,計算光照遮擋,添加體積感知的混合。

    左:傳統的Alpha混合;右:體積感知的混合。

    體積感知的混合算法細節如下:


    DirectX不會對像素著色器的執行施加任何順序,排序發生在輸出合并階段的晚些時候,如果兩個線程讀取并修改同一內存,結果是不可預測的。像素著色器排序可以確保:讀修改寫操作受到保護,即在其他線程完成對內存的寫入之前,任何線程都不能讀取內存;所有內存訪問操作都按照提交原語進行渲染的相同順序進行。

    像素同步前(上)后(下)對比。

    // Enabling pixel shader ordering
    #include "IntelExtensions.hlsl"
    ...
    void YourPixelShader(...)
    {
        IntelExt_Init();
        ...
        // 像素同步函數
        IntelExt_BeginPixelShaderOrdering();
        // Access UAV
    }
    

    為了提高性能,粒子被渲染到低分辨率緩沖區,然后執行雙邊濾波,以提高原始分辨率并保留邊緣。粒子生成時使用了網格,以攝像機為中心的同心環,下一個環中的粒子大小是內環的兩倍,每個細胞包含幾層粒子,每個單元中粒子的密度和大小由噪點紋理決定。

    粒子渲染:

    • 粒子排序。粒子必須按從后到前的順序渲染,在GPU上進行排序非常昂貴,可以在CPU上對細胞進行排序,并不是所有的細胞都包含真實的粒子。解決方案:僅為有效單元格輸出粒子,使用“流出”來保持順序,通過一個GS線程處理32個粒子。

    • 粒子處理。DispatchIndirect()用于執行CS計算每個有效粒子的光不透明度,DispatchIndirect()用于執行CS計算每個有效粒子的可見性。

    與光照散射技術的集成:云密度紋理是根據光線渲染的,在射線行進的每一步,都要確定一個點是在云層之上還是之下(假設云層具有恒定的高度),如果點位于云下,則對云密度紋理進行采樣,以獲得云的遮擋,在屏幕空間中,云的透明度和與云的距離用于衰減沿視線的散射。

    Adaptive Clothing System in Kingdom Come: Deliverance涉及了自適應的衣服系統。衣服模擬可能的方法:所有項目類型的形狀/網格相同,網格之間的大(安全)距離,所有組合的手動調諧,以及混合方法。文中的方法高度現實主義,每件新衣服沒有額外的藝術家投入。為了實現衣服的多層材質,使用了光線投射,過程如下:

    • 對每個三角形網格對:
      • 對每個三角形:
        • 挑選N個隨機樣本(質心坐標)。(下圖a)
        • 追蹤N條光線。(下圖b)
        • 變換交點到頂點權重。(下圖c)

    效果如下:

    Sound Propagation in Hitman分享了Hitman的利用聲波建模來實現聲音傳播系統。

    該聲音系統涉及傳播幾何體、遮擋、傳播路徑、阻擋等方面的內容和模擬。

    從左到右:傳播幾何體、傳播計算、傳播路徑。

    Unified Telemetry, Building an Infrastructure for Big Data in Games Development談到了遙測技術用于游戲的方方面面的性能檢測和分析統計,比如性能、尖峰檢測、加載時間、啟動時間、編譯時、日志、內存追蹤、緩沖區/池大小跟蹤、已用資產/本地化跟蹤、網絡復制調試、帶寬/延遲指標等等。

    文中定義的遙測(Telemetry)是一種高度自動化的通信過程,通過該過程,在遠程或不可訪問的點進行測量和收集其它數據,并傳輸到接收設備進行監控。應用案例:統計數據收集、事件、狀態快照、現場調試。非統一遙測數據和統一遙測數據流程分別如下兩圖:


    好處是更簡單的工具,跨域分析,非統計數據的團隊范圍分析,更容易協作。

    Quick and Dirty: 2 Lightweight AI Architectures講述了一種輕量級的AI架構。AI設計的目標是不需要太多人工智能,需要一種觸發事件的方式:關卡成功/失敗、聲音事件、UI操作、特殊效應,快速開發和迭代至關重要!短時間擴展,實驗性游戲設計。

    觸發器包括:單布爾觸發條件,可以是多個子句,并且可以一起使用,行動清單;當布爾子句變為真時,觸發器觸發,當觸發器觸發時,執行每個動作(按順序)。還有基于事件(與大多數架構不同),沒有輪詢或更新周期(大部分)。

    # Play a hint after 20 seconds, but only once
    playHint_1_8_moveCamera:
        triggerCondition:
        - and:
            - delay:
                - 20
            - doOnce:
        actions:
        - playSound:
            - ALVO36_Rover
    

    模塊化設計,子句是事件處理程序,事件持久性,定時閾值(默認值:667毫秒),如果觸發或場景重置(成功或失敗),則重置。觸發組,小心地設計觸發條件,給每個人一個(固定的)優先級,取最高的;全局觸發器(例如碰撞失敗、程序完成失敗),級別初始化=>只是一系列操作。下圖是更復雜的觸發案例:

    Physically-based & Unified Volumetric Rendering in Frostbite講述了Frostbite引擎的體積渲染技術,以提高視覺質量,讓藝術指導更自由,更加物理的體繪制(有意義的材料參數、將材質與照明分離、連貫的結果),統一體積相互作用,結合了照明、常規陰影和體積陰影,可以與不透明、透明和粒子的相互作用。

    在渲染體積時,Frostbite限制為單次散射,當光表面與表面相互作用時,可以通過評估例如BRDF來評估反射到相機的光的量,但在參介質面前,事情變得更加復雜,必須考慮透射率。然后,需要通過采集多個樣本,沿視圖光線對散射光進行積分。還需要考慮每一點到視點的透射率以及在每個位置對散射光進行積分,并考慮相位函數、常規陰影貼圖(不透明對象)和體積陰影貼圖(參與介質和其它體積實體)。

    Frostbite采用了裁減空間的體積:視錐體對齊的3D紋理[Wronski14],世界空間中的視錐體體素=>Froxel。Frostbite是一種基于分塊的延遲照明,帶有剔除的光源列表的16x16塊。在光源分塊上對齊體積分塊,重復使用每個分塊剔除的燈光列表,體積塊可以更小(8x8、4x4等)。仔細校正分辨率整數除法,默認值:8x8個體積塊,64個深度切片。

    數據流如下圖,使用剪輯空間體積來存儲管線不同階段的數據,材質屬性首先從參與介質的實體中進行體素化。然后使用場景的光源和該材質屬性體積,可以生成每個像素的散射光數據,這些數據可以臨時上采樣以提高質量。最后是集成步驟,為渲染準備數據。

    這種體渲染可以結合若干個*行光和點光源,最終渲染出來的效果如下:

    還支持粒子體積陰影和不同的采樣方式:

    The Real-Time Volumetric Cloudscapes of Horizon Zero Dawn闡述了游戲Horizon Zero Dawn中的影視級的體積渲染和云體景觀。地*線的世界是開放的,玩家可以穿越很遠的距離,支持晝夜循環、動態天氣,擁有壯麗的風景山脈、森林、湖泊,天空是風景的一部分。

    文中對體積云景觀從建模、光照、渲染、優化等方面進行了詳細的闡述。建模時考量了各式各樣的云體形態和不同的高度:

    更詳細地,考量了真實世界的諸多變化因素:密度在較低溫度下增加,溫度隨高度降低,高密度沉淀為雨或雪,風向隨高度變化,它們隨著來自地球的熱量而上升,密集區域上升時形成圓形,光線區域像霧一樣擴散,大氣湍流進一步扭曲了云層。建模的技術要點有分形布朗運動、分層柏林頻率、高度位移、程序化云、漂亮的威士忌形狀等。嘗試了各種各樣的噪點生成技術,使用2個3D紋理和1個2D紋理生成動態噪點結果。使用射線步進/采樣器、2級細節(低頻基云形狀、高頻細節和擾動),Perlin、Worley和Curl noise制造的定制噪點,由高度信號和覆蓋信號調制的密度,由天氣模擬/用戶輸入驅動,帶動畫。

    光照模型考慮了外散射、吸收、內散射等。

    使用的光照模型和曲線如下圖:


    渲染過程中,環境色貢獻增加高度,直接照明顏色的貢獻來自太陽,大氣層擋住了云層深度。采樣器做廉價的工作,除非它可能在云中,每次步進用64-128個步進樣本、6個光源樣本,光源樣本在某個深度從完全到低開銷。

    Sampling Methods for Real-time Volume Rendering也談及了體積渲染,但關注點在于采樣方式,通過控制樣本的布局和運動,大幅減少方差,還描述了如何自適應采樣深度,以便在保持接*視圖的質量的同時渲染大范圍的效果。改用下圖的采樣方式,可以解決旋轉或掃射問題,可以動態調整,但更改曲率會移動樣本,固定在兩種配置之間可能會起作用

    區分了水*對流:

    采樣了自適應采樣:

    最終的效果可以很好地匹配攝像機的任何運動而不產生跳變等瑕疵:

    Stochastic Screen-Space Reflections闡述了Frostbite引擎的隨機光柵化的反射效果。Frostbite對反射效果的要求是清晰而模糊的反射、接觸硬化、鏡面伸長、逐像素的粗糙度和法線:

    Frostbite的方法是從表面的重要方向采樣開始,但只發射很少的光線,甚至低至每像素一條。

    主要區別在于,沒有立即返回顏色,而是幾乎不存儲交點。然后有一個解析過程,在相鄰像素上重用這些交點。

    在重用過程中,使用*似的錐體擬合,并考慮反射距離,計算每個樣本所需的模糊級別。仔細權衡每個像素的BRDF的貢獻,這樣就避免了法線的涂抹、過度模糊,并保留了清晰的接觸點。

    SSR的算法流程如下:

    射線追蹤時使用了層次深度追蹤,即最小Z金字塔的無堆棧射線步進:

    mip = 0;
    while (level > -1)
        step through current cell;
        if (above Z plane) ++level;
        if (below Z plane) --level;
    

    光線重用:相鄰像素發射有用的光線,可見性可能會有所不同,可以假設是一樣的,以便重用交點結果。

    使用局部BRDF作為權重,除以原始的PDF,由射線追蹤和生命點返回,鄰居可以擁有截然不同的屬性,BRDF/PDF比率峰值,比不重復使用更糟糕的結果。

    左:1個像素1條光線和1個解析樣本(沒有重用);右:1個像素1條光線和4個解析樣本(1條光線重用到4個像素)。

    此外,為了減少方差,對蒙特卡洛進行了改進:

    R9.png)

    其中:

    • 分子是BRDF加權的圖像貢獻。
    • 分母是BRDF權重的標準化。
    • FG是預積分的BRDF。

    偽代碼:

    result    = 0.0
    weightSum = 0.0
    for pixel in neighborhood:
        weight = localBrdf(pixel.hit) / pixel.hitPdf
        result += color(pixel.hit) * weight
        weightSum += weight
    result /= weightSum
    

    不同的參數效果如下:

    稀疏光線追蹤(Sparse raytracing):降低分辨率的光線跟蹤,以全分辨率重復使用多條光線,每個像素都有獨特的光線混合,自動來源于BRDF,保留每像素法線和粗糙度,權重標準化彌補了差距。

    時間重投影:沿G緩沖區深度“涂抹”重新投影,添加反射深度,局部射線*均值,適當反射視差。


    14.4.4 渲染技術

    14.4.4.1 Inferred Lighting

    推斷光照(Inferred Lighting,IL)是延遲光照的一個變種,也叫光照預通道渲染(Light Pre-Pass Rendering)。將照明與場景復雜性隔離開來,對于處理破壞至關重要,推斷光照 = Light Pre-Pass++,可調照明分辨率、支持MSAA、Alpha照明。推斷光照有3個通道:

    1、幾何通道。準備光照所需的GBuffer。

    Red Faction的GBuffer布局如下:

    由此可見,GBuffer包含法線(23bit)、鏡面反射指數(7bit)、光照類型(2bit)。

    2、光照通道。計算光照。

    遍歷每個可見光源并計算該光源對場景的貢獻,累加所有光源的貢獻,并將結果存儲在稱為L-Buffer(光照緩沖區)的紋理中。

    計算光照所需的所有信息都來自光源和G-Buffer。

    光照信息存儲在L-Buffer(64位HDR紋理,見下圖)中,漫反射分量存儲在RGB通道中,鏡面反射分量不存儲為全色,而是存儲為亮度值,亮度可以像顏色一樣累加。

    當需要完整的RGB鏡面光照顏色時,采用漫反射光照顏色并將其縮放到為規范存儲的亮度,為真正的鏡面光照分量提供了可接受的*似值。

    3、材質通道。使用光照。

    遍歷可見對象并再次繪制它們,使用材質通道著色器,這些著色器將L-Buffer中的光照信息與為每個對象定義的其余材質(紋理、對象顏色、反射、自發光材質、距離霧、色調映射等)合成在一起,以產生最終的輸出顏色,對于每個像素,進入最終場景輸出。

    以上是推斷光照的常規流程,下面開始展示Red Faction的差異化實現。首先從如何在這些材質通道著色器中合成L-Buffer中的光照開始。

    合成照明時,簡單的普通的light pre-pass系統的做法是像素在G/L/M通道之間完全匹配:

    推斷光照允許幾何體/光照通道使用比材質通道更低的分辨率,允許向上或向下調整照明分辨率以權衡視覺質量與性能。不過,在這種情況下,直接嘗試使用L-Buffer值會非常失敗。對較低分辨率的L-Buffer進行采樣將導致在任何邊緣產生混合值,導致光滲色和偽影。

    為了理解如何處理這個問題,可以看看一個特定像素到底發生了什么。不妨把每個像素視為一個數學點,而不是一個正方形的顏色區域——這樣更容易理解數學。

    采樣低分辨率光照。

    結合下圖,被著色的像素屬于頂部對象(下圖左邊黃色),混合這四個光照樣本(下圖中),四個樣本中的三個來自底部對象,幾乎是最終顏色的80%(下圖右)!如果我們可以檢測并糾正這個邊緣會怎樣?

    可以使用不連續敏感過濾(Discontinuity-Sensitive Filtering,DSF),在保留邊緣的同時調整L-Buffer的大小,使用第三個G-Buffer:DSF數據,它是一個16位的值(8 位存儲對象 ID,由游戲分配;8 位存儲普通組ID,保存在網格數據中)。

    G-Buffer:DSF數據。

    DSF的過程如下:

    • 在材質通道著色器內部,可以知道當前正在繪制@這個像素的哪個對象。(下圖左)
    • 比較4個DSF ID中的每一個,DSF與L-Buffer1:1匹配。(下圖中)
    • 如果DSF ID不匹配:將混合權重設置為0.0,調整DO匹配的樣本加起來為1.0。(下圖右)

    下圖的上排沒有DSF,下排有DSF。請注意,第一列中對角線邊緣和通風口輪廓周圍的“階梯”偽影減少,第二列和第三列中引擎蓋和管道邊緣周圍的塊狀明亮像素被消除。

    上述的版本運行良好,但可能會很慢,8個紋理查找(4個DSF,4個光照),計算著色器中的加權和。優化:改為調整樣本UV,只有一個照明樣本,硬件進行混合。

    DSF的優化過程。圖中頂部的兩個樣本具有匹配的DSF ID,而底部的兩個樣本沒有,在這種情況下,可以簡單地垂直調整UV,從混合中消除底部的兩個樣本。根據匹配樣本的模式,可以以這種方式垂直、水*、兩者或都不調整UV。

    DSF可以更好地控制照明的質量與速度,還有硬件抗鋸齒 (MSAA)。每個像素多個子樣本,由硬件管理。LPP(Light Pre-Pass)不擅長處理MSAA,每個像素一個照明樣本與多個子樣本!使用DSF,IL可以免費處理MSAA!

    推斷光照可以支持透明度,但存在與MSAA類似的問題:每個像素只有一個L-Buffer樣本。具有不同DSF ID的像素可以“隱藏”,在計算光照時將被忽略,可以利用這一事實來發揮優勢!

    將屏幕分成2x2像素塊,每個塊中的一個像素(alpha),Alpha像素獲得不同的DSF ID,不透明材質會自動剔除這些光照樣本。

    Alpha使用修改后的DSF,將L-Buffer中的每個2x2塊視為一個單元,硬件過濾優化在這里不起作用。

    通過使用不同的塊像素,最多可能有四層。

    IL透明渲染效果如下:

    不連續性的總結,支持MSAA、半透明,不支持低分辨率照明。基本思路:檢查DSF ID是否與相應像素匹配。如果是,就完成了。如果不匹配,請檢查鄰居,直到找到匹配的鄰居,通常最多檢查四個鄰居。

    IL的限制和問題是:

    • Alpha對象上的低分辨率照明。1/4不透明光照的整體分辨率,盡可能避免高頻法線。
    • 頭發是IL不擅長的事情,應該避免用于頭發!
    • Alpha分層。附加層會降低不透明照明的質量,分配圖層以避免碰撞是很困難的!本質上是一個圖形著色問題,RF:A 僅使用一層alpha光照。
    • 粒子。粒子不適用于有限的Alpha光照層,可在粒子上偽造光照。
    • DSF增加了標準光預渲染通道的成本。降低照明分辨率有助于*衡這一點,不連續性修補可能是一種成本較低的選擇,RF:A使用不同分辨率的補丁:Xbox/PS3:960x540、PC:達到硬件限制,SR3使用完整的 DSF:1280x720場景、800x450照明,還使用了所有4層半透明照明。

    1年后,Lighting & Simplifying Saints Row: The Third分享了推斷照明的最新迭代,新增了幾項優化和功能。

    原先的推斷照明支持許多完全動態的光源、集成Alpha照明(無前向渲染)、硬件MSAA支持(即使在 DX9上)。而本文在此基礎上新增了雨滴照明(需要IL)、更好的樹葉支持(僅適用于IL)、屏幕空間貼花(由IL增強)、徑向環境光遮蔽 (RAO)(由IL優化)。

    雨滴照明:將每個雨滴的單個像素渲染到GBuffer中,照明通道免費地照亮雨滴,DSF自動忽略雨滴采樣,雨滴查找它們的照明樣本。

    植被:推斷照明假設場景深度復雜度較低,以保持DSF成本有界,植被打破了這一假設。更快的植被DSF:

    在PS3場景完整的DSF,GPU時間:35.7ms,2個樣本是33.7ms(節省2ms)。

    動態貼花:屏幕空間貼花是應用于屏幕空間的體積貼花,使用DSF ID將貼花限制為特定對象。現有的DSF ID用作貼花鑒別器,以防止貼錯對象:

    屏幕空間貼花。左邊是錯誤的貼花,右邊使用現有的DSF ID修復。

    徑向環境遮擋(Radial Ambient Occlusion,RAO):大致基于 [Shanmugam & Arikan 2007],遮擋因子基于法線和到盒子或橢圓體的距離,非常像普通光源,用于調制照明的遮擋因子,對于車輛,藝術家放置接*車身的盒子,對于人類,橢圓體自動放置在腳下。

    RAO效果對比。左邊有,右邊無。

    14.4.4.2 GPU-Driven Rendering Pipelines

    GPU-Driven Rendering Pipelines由Ubisoft Montreal的Ulrich Haar等人呈現,講述了基于GPU驅動的全新渲染管線,并應用在了刺客信條中,使得刺客信條的場景復雜度成指數增長。

    GPU驅動的渲染管線需要用到分簇(cluster),即將網格線拆分成一個個cluster。一個cluster具有固定的拓撲(64個頂點的strip),拆分并重新排列所有網格以適應固定拓撲(插入退化三角形),從共享緩沖區手動獲取VS中的頂點,使用實例化非直接繪制接口DrawInstancedIndirect,GPU剔除輸出cluster列表和繪制參數。

    單個drawcall中任意數量的網格,按cluster邊界的GPU剔除,更快的頂點提取,cluster深度排序。

    GPU驅動的渲染管線的概覽如下:

    在CPU方面,仍然執行非常粗糙的截錐體剔除,然后按材質將所有未剔除的對象打包在一起。在GPU上,從逐實例截錐體和遮擋剔除開始,然后,在根據截錐體和遮擋深度剔除cluster之前,執行了一個cluster擴展的步驟。接著再壓縮索引緩沖區,壓縮過程中剔除一些背面三角形,壓縮結果提供給multi-drawcall使用。所有變形的對象都會繞過管線中特定于cluster的部分。

    CPU四叉樹剔除,每個實例的數據包含變換、LOD因子等,在GPU環形緩沖區中更新,靜態實例的持久性。基于非實例數據的Drawcall哈希構建,如材質、渲染狀態等,基于哈希的Drawcalls合并。

    實例流包含每個實例在GPU緩沖區的偏移量列表,允許GPU訪問變換、邊界、網格等信息。

    然后,GPU使用這些信息的截錐體并剔除實例流。對于通過剔除的所有實例,將發出簇塊(cluster chunk)列表。

    使用簇塊擴展的中間步驟(而不是直接簇擴展),因為每個網格的簇數量變化很大(1-1000),即時簇導出通常在波前內的GPU線程之間非常不*衡,每個簇塊最多導出64個簇。

    然后,簇剔除步驟使用實例變換和每個簇的邊界,根據截錐體和遮擋剔除簇。對于每個簇,還獲取一個與視圖相關的三角形遮罩,用于預烘焙三角形背面剔除。通過剔除的簇,導出由三角形掩碼和索引讀寫偏移組成的索引壓縮作業,這些偏移量是通過對相關實例drawcall圖元數量執行原子操作來確定的。

    接著,所有未填充簇的索引壓縮都會發生在動態索引緩沖區中。壓縮索引緩沖區中的空間是在CPU上分配的,因此必須為每個網格實例分配完整(未填充)的索引量。由于壓縮索引緩沖區非常小(8mb),意味著一個渲染過程無法完全放入緩沖區,大的處理被分割,以便索引緩沖區壓縮和多繪制渲染可以交錯進行。

    此時,索引壓縮將刪除被逐簇剔除和三角形背面剔除剔除的三角形。在每個索引壓縮計算作業中,一個wavefront處理一個簇,每個線程處理一個三角形,完全獨立于其它線程和wavefront。根據簇剔除步驟發出的三角形掩碼和輸入/輸出偏移,每個線程獨立地計算輸出寫入位置到動態索引緩沖區,并復制3個三角形索引。這一步需要5%-10%的幾何體渲染。

    然后,逐批(batch)使用一個MultiDrawIndexInstancedIndirect調用來渲染在簇剔除期間使用原子操作生成的drawcalls組。

    靜態三角形背面剔除:以簇為中心的立方體貼圖像素截錐體的烘焙三角形可見性,基于攝像頭的立方體圖查找,獲取64位以查看簇中的所有三角形。每個立方體貼圖面只有一個像素(每個三角形6位),像素截錐體被遠距離切割以提高剔除效率(斜角可能出現誤報),10-30%的三角形被剔除。

    遮擋深度生成:使用最佳遮擋體進行深度預通道,以全分辨率渲染High-Z和Early-Z,降采樣至512x256,結合最后一幀深度的重投影,用于GPU剔除的深度層次結構。

    陰影遮擋深度生成:對于每個級聯,攝像機深度重投影(約70us),這陰影深度重投影(10us),用于GPU剔除的深度層次結構(30us)。

    相機深度重投影的過程如下圖:

    左上:黃色箭頭表示太陽的方向,紅線顯示一個陰影貼圖級聯的范圍,紅色物體是陰影投射者,不能擁有有效的接收者,因為所有可能的接收者都被地面和藍色物體隱藏。

    右上:亮黃色區域標記前景對象創建的地*線,陰影接收者在其下方不可見。每一個比這個地*線更遠的陰影投射者都可以被剔除。需要計算黃色區域頂部紅線在光照空間中的深度(也就是黃色箭頭方向),以用于遮擋剔除。

    左下:如果為相機深度中的每個像素渲染一個立方體,從**面拉伸到相機深度的值,可以得到圖中所示的黃色立方體。

    右下:現在可以看到綠線,即光空間中立方體的最大深度渲染,與前面提到的紅線相同。顯然,要為相機深度圖像的每個像素渲染一個立方體,需要太多立方體。取而代之的是,為相機深度的每個16x16的tile渲染一個立方體,并使用每個tile的最大深度。然后綠線變成了紅線的保守*似值。在光源空間中渲染立方體時,重要的是將其填充到一個像素的最小和大小,并考慮最大陰影過濾器大小。請注意,當沒有任何透明陰影接收器時,可以不從*面附*的攝影機擠出立方體,而是從每個tile的最小深度擠出立方體。在這些條件下,在最大下采樣期間,遠*面也可能被拒絕,從而產生更緊密的立方體渲染。渲染立方體時,重要的是直接在像素著色器中輸出深度,并使用紋理坐標插值器導出像素深度,而不是固定功能管線提供的深度,因為對于幾乎與光源方向*行的三角形來說太不精確。

    重投影建筑物的陰影。

    相機深度重投影:類似于[Silvennoine12],但由于有霧,遮罩無效:無法使用最小深度,不能排除遠*面。64x64像素重投影,可以預處理深度,以消除過繪制。

    獲得的結果是:在CPU上減少1-2個數量級的繪制調用,約上一代刺客信條的75%,約10倍的物體。在GPU上剔除20-40%的三角形(背面+簇邊界),整體增益很小:<幾何體渲染的10%,剔除30-80%的陰影三角形。正在進行的工作:更多GPU驅動的靜態對象,更批量友好的數據。未來的工作:無綁定紋理、基于DX12和Vulkan。

    該文還分享了GPU驅動渲染中的虛擬紋理、虛擬延遲紋理、MSAA技巧、兩階段的遮擋剔除、虛擬陰影映射等技術。

    虛擬紋理關鍵思想:只在內存中保留可見的紋理數據,虛擬256k x 256k紋素圖集,128 x 128紋素頁,8k x 8k紋理頁面緩存,5層紋理數組:反照率、鏡面反射、粗糙度、法線等,采用DXT壓縮(BC5/BC3)。

    視口=單次繪制調用(x2),不同頂點動畫類型的動態分支,現代GPU速度快(成本增加2%),簇深度排序提供與深度預處理類似的增益,反向排序的廉價OIT。

    額外的VT優勢:復雜材質混合和貼花渲染結果存儲到VT頁面緩存中,數據重用將成本分攤到數百幀上,恒定的內存占用,與紋理分辨率和資源數量無關。

    虛擬延遲紋理(Virtual Deferred Texturing):舊想法:將UV存儲到G緩沖區,而不是紋素[Auf.07],主要功能:VT頁面緩存圖集包含所有當前可見的紋理數據,8k x 8k紋理圖集的16+16位UV提供了8 x 8亞像素過濾精度。

    梯度和切線坐標系:計算屏幕空間中的像素梯度,用于檢測鄰居的UV距離,沒有找到鄰居則使用雙線性,存儲為32位四元數的切線坐標系[Frykholm09],隱式mip和材質id來自VT.Page = UV.xy / 128

    回顧和優勢:64位,完全填充率,沒有MRT。過繪制開銷非常低,紋理延遲到光照CS。Quad效率不那么重要,虛擬紋理頁面ID通道不再需要。梯度重建的數據相當接*基準真相:

    MSAA技巧:關鍵觀察發現UV和切線可以插值,可以使用有序網格4xMSAA模式,以2x2更低分辨率(540p)渲染場景,使用Texture2DMS.Load()在光照計算著色器中分別讀取每個樣本。

    1080P重建:將1080p重建為LDS,邊緣像素被完美重建,MSAA為兩邊運行像素著色器。插值內部像素的UV和切線,質量很好,差異很難發現。

    兩階段遮擋剔除:低多邊形代理幾何體沒有額外的遮擋過程,精確的WYSIWYG(所見即所得)遮擋,基于深度緩沖區數據,從HTILE最小/最大緩沖區生成的深度金字塔,O(1)的遮擋測試(gather4)。

    第一階段:使用最后一幀的深度金字塔剔除對象和簇,渲染可見對象。

    第二階段:刷新深度金字塔,測試剔除的對象和簇,渲染誤報(false negative)的對象。

    如果沒有GPU驅動的渲染,這種剔除方法是不可能的,因為它需要對GPU在上一步生成的數據進行低延遲訪問。如果CPU在幀中間來回運行,GPU將嚴重停頓(stall)。下圖是Xbox One在1080p的性能數據:

    虛擬陰影圖(Virtual Shadow Mapping):128k x 128k虛擬陰影貼圖,256 x 256紋素頁,從Z緩沖區中識別所需的陰影頁,使用GPU驅動的管線剔除陰影頁面,一次渲染所有頁面。

    VSM質量和性能:在所有區域接*1:1的陰影到屏幕(shadow-to-screen)的分辨率,測量:在復雜的“稀疏”場景中,速度比SDSM快3.5倍,在簡單場景中,VSM略慢于SDSM和CSM。

    此外,GPU驅動可以和DX12等新生代圖像API更完美地結合:

    14.4.4.3 Adaptive Virtual Texture

    Adaptive Virtual Texture Rendering in Far Cry 4分享了2015年的Far Cry 4所使用的自適應虛擬紋理的技術,包含虛擬紋理概述、地形、自適應虛擬紋理、渲染挑戰等。

    虛擬紋理的原理類似于虛擬內存,但是在GPU上實現的。結合下圖,虛擬紋理可以表示非常大尺寸的紋理,當需要用到紋理數據時,需要提供虛擬紋理的坐標去查詢非直接紋理的信息,從而獲取真正的物理頁緩存數據。

    游戲中的虛擬紋理可以是巨型紋理(Mega Texture)或程序化虛擬紋理(Procedural Virtual Texture)。Mega Texture由Rage的id軟件開發(Waveren 2013),紋理數據存儲在磁盤上,并根據需要傳輸到內存中,運行時確定所需的tile(頁面)并從磁盤請求它們,tile被加載到tile緩存(物理紋理緩存),頁面表(間接紋理)被更新。程序化虛擬紋理用于DICE的Frostbite引擎(Widmark 2012),在運行時將地形渲染分解為虛擬紋理,磁盤中沒有高度壓縮的虛擬紋理,直接渲染到缺少頁面的虛擬紋理中,利用幀到幀的一致性降低地形渲染成本,用于地形渲染的強大GPU優化。

    Far Cry 4的程序化虛擬紋理參數見下圖,512k x 512k的巨型虛擬紋理、2k x 2k的間接紋理、9k x 9k的物理紋理,其中虛擬紋理和間接紋理有11級mip:

    間接紋理的格式如下圖,入口(Entry)坐標(x,y):每個入口代表一個虛擬頁面,入口坐標 = 虛擬頁面坐標 / 虛擬頁面大小。入口內容格式是32位整數,其中:PageOffsetX = 物理頁面U坐標 / 物理頁面大小,PageOffsetY=物理頁面V坐標 / 物理頁面大小,Mip:此頁面的Mip映射級別,調試:僅用于調試(如保存幀數)。

    如果用傳統的虛擬紋理,512K x 512K虛擬紋理在10 x 10公里的世界上,需要1000萬x 1000萬虛擬紋理!急需另一種新的技術。

    這種新的技術就是自適應虛擬紋理,它基于程序虛擬紋理,10x10KM的世界分為64x64米的區域(sector),*地形地段:在虛擬紋理中分配虛擬圖像,更*的區域:更大的虛擬圖像,如64K x 64K(64K/64米=10 texel/cm),更遠的區域:更小的虛擬圖像,如32K x 32K、16K x 16K、…、1K x 1K。

    在虛擬紋理中為所有閉合區域分配虛擬紋理,其中下圖紅色區域是可視化虛擬紋理中緊密區域的虛擬紋理分配,每個彩色方塊代表附*每個區域的一個虛擬紋理:

    下圖的2個紅色方塊表示離攝像機最*的分區的虛擬紋理,達到64k x64k;黃色離相機稍遠,32k x32k;依此類推,直到附*的所有分區都已分配完成。

    相機靠*時的提升虛擬紋理大小,下圖由32k x 32k提升到64k x 64k:

    若相機遠離,則反向操作:

    在放大過程中,會在虛擬紋理中分配一個較大的虛擬圖像,并移除舊的圖像。地形材質與附加貼花混合,已經緩存在物理紋理緩存中,偏移并重新使用它們!對于從mip 1到mip 10的所有頁面,將間接紋理入口從舊圖像復制到新圖像,同時向上偏移1個mip。

    然后為新虛擬圖像更新間接紋理中的所有mip 1入口:

    需要特殊處理mip 0頁面,因為它們沒有在舊圖像中渲染。

    4個mip 0頁有1個對應的mip 1頁,臨時映射到低一級的mip頁面,在這一幀中,圖像顯得模糊,正確更新后會變得更清晰。在新的虛擬圖像中對mip 0中的所有頁面執行此操作:

    還需要更新間接紋理的mip 0頁面,復制間接紋理入口內容:

    縮小虛擬圖像時,在虛擬紋理中分配一個較小的虛擬圖像,并移除舊圖像,反向執行放大虛擬圖像的步驟。

    面臨的挑戰有:需要減少虛擬頁面id緩沖區的內存占用,限制每幀的渲染消耗,生成海量貼花,各向異性過濾,支持三線性過濾。具體的接*方案見原文,此處忽略。最終的性能和效果如下:


    14.4.4.4 Signed Distance Field

    Dynamic Occlusion With Signed Distance Fields闡述了Unreal Engine使用有向距離場高效高質量地生成環境光遮蔽的效果。

    對于動態制作游戲場景中的通用遮擋,沒有很好的解決方案,需要柔和、準確且不連貫的可見性查詢,用于區域陰影、天空遮擋、反射陰影。解決方案:光線步進的有符號距離場,受Fast Approximations for Global Illumination on Dynamic Scenes啟發。早在2011年的Samaritan演示(虛幻引擎3)中使用SDF(有向距離場)光線行進進行反射陰影,單體紋理與靜態場景,需要高端硬件。

    早期動態天空遮擋質量原型,每像素圓錐體在每個相交的物體上行進,使用27個圓錐體,在一個小場景中的運行速度僅有2FPS!GDC 2015的風箏技術演示中,使用這些方法進行巨大世界的全動態照明,需要980 GTX。

    關閉(左)和開啟(右)SDF陰影的對比。

    當前,Fortnite正在開發中,支持一天中的動態時間,使用這些照明方法,通過大量玩家建筑程序生成關卡,所有都支持PlayStation 4級硬件。SDF存儲每個點到最*表面的距離,內部區域存儲負距離(有符號),d=0的等值面(level set)是表面。有符號距離在表面上線性插值,精確地表示任何表面方向,盡管是網格。光線相交會根據到表面的距離跳過空白空間,如果光線相交,則光線被遮擋。免費*似圓錐交點,從而獲得區域陰影。

    SDF與體素的比較:+相等地表示任意方向的表面,+線性插值,+更高的有效分辨率,+減少偏差以避免自相交,+在跟蹤中跳過空白,+無預濾波的*似圓錐交點。體素僅表示表面位置和法線,只能從跟蹤中獲取可見性。從根本上說,SDF是一種表面表示,而不是體積,只能用于入射光照和可見性可以解耦的地方,在任何方向提供柔軟、準確的可見性追蹤!

    場景表達:蠻力執行三角形光線跟蹤,離線生成網格SDF,存儲在fp16體積紋理中,\(50^3\)足以容納*均網格(240KB),GPU方法可用于動態更新。SDF需要在任何地方定義,在邊界周圍添加的邊界保證位于曲面之外,有效區域外的樣本被夾緊,合成距離給出了*似值。

    可以跟蹤相機光線并可視化步數:

    內部/外部由背面光線命中確定,不需要閉合網格。

    半支持部分遮擋體,被視為雙面曲面,雙面曲面不會導致完全相交,對樹葉很有用。然而,射線步進開銷大。

    實例支持:相同的SDF數據,不同的變換,當前實現中只有均勻的縮放,基于資產縮放的默認分辨率分配。

    角根據分辨率進行四舍五入,薄表面只能用內部的負紋理表示,尋找根的必要條件。

    在光線行進時,距離場紋理被用于全局場景訪問,\(512^3\)的關卡占用約300Mb,場景對象完全在GPU上管理,CPU發送增量更新,移動一個對象只需要更新一個矩陣。剔除對象比CPU快100倍,Kite demo中的200萬個樹實例 -> 屏幕上的50k@PS4上的.1ms,因為對管道中的對象的所有操作都在GPU上。

    地形高度場,根據高度場計算的*似圓錐交點,重用高分辨率高度圖,單獨通道組合結果。當前表示處理許多游戲場景,非均勻縮放網格除外,以及蒙皮/動態變形網格,和大型有機網格/體積地形,可能的替代圖元:解析距離函數、稀疏體積SDF?

    足夠的分辨率用于直接陰影!具有球形光源形狀的放射狀燈光(區域陰影),剔除到燈光邊界的相交對象,然后是屏幕tile,從包含tile和對象邊界的燈光的錐體。球形光源形狀的放射狀光源的偽代碼:

    Foreach intersecting object
        Foreach sample step
            DistanceToSurface = Sample SDF
            Track min visibility (DistanceToSurface / ConeRadius)
            Step along ray toward light by abs(DistanceToSurface)
            Terminate if inside or exceeded max step count
    Shadow = 1 - MinVisibility
    

    直接陰影:錐遮擋啟發式算法,距離表面/圓錐半徑=1d交點,發現更智能的啟發式算法在視覺上給出了與\(\Big( \cfrac {\text{DistanceToOccluder}}{\text{SphereRadius}}\Big)^X\)相同的結果。

    定向光:剔除為最大視圖距離的相交對象,然后進入光源空間網格,使用相同的跟蹤內核。頂點動畫無法表示,要在明顯的位置使用級聯陰影貼圖。

    左:CSM;右:CSM+SDF。

    三角形LOD(公告牌)與SDF不匹配時,使用保守的深度寫入:

    使用SDF用作直接陰影的原因:有尖銳接觸的區域陰影,可控自陰影失真-世界空間偏差,沒有陰影貼圖鋸齒,與三角形計數無關的性能,取決于對象密度和分辨率(num道),可以相當容易地減少采樣,支持大視場范圍–無CPU成本,比傳統陰影貼圖(cubemap/CSM)快30-50%,取決于三角形LOD的效率。

    天空遮擋的問題:SSAO僅適用于小規模的屏幕空間的瑕疵,想要中等距離遮擋的通用解決方案(10米),建筑有薄薄的墻。單個SDF錐體追蹤可以是軟的:

    多個錐體(例如9個)覆蓋半球(朝向法線的半球),使用min()處理多個對象的遮擋,遮擋距離被限制(默認為10米)。

    產生最小遮擋的圓錐體(彎曲法線),其它方向遮擋表示也是可能的,應用于天光的SH漫反射照明,也適用于使用*似圓錐體/圓錐體相交的天空鏡面反射。

    天空遮擋優化:對象影響剔除到屏幕tile的邊界,每個tile有兩個深度邊界和兩個剔除列表,利用對象SDF進一步與tile邊界剔除。

    在GCN上,使用光柵化器的分塊剔除比分塊計算著色器更快,當成千上萬的物品達到10個時,最大的收益。可能適用于許多其它用例(光源、貼花、反射探針),GCN上的時間是0.4ms->0.2ms。

    分塊剔除過程:構建繪制參數緩沖區–截錐體剔除的輸出,繪制邊界幾何圖形–DrawIndexedInstancedIndirect,像素著色器在UAV中構建分塊相交列表。在每個尺寸的1/8處計算圓錐跟蹤,嚴重鋸齒,無需進一步過濾,以半分辨率執行過濾,幾何感知的雙邊上采樣。

    4倍時間超采樣(4個抖動位置),重新利用時間消除鋸齒的精確像素速度進行重投影,深度拒絕,將拒絕的像素標記為不穩定,時間不穩定區域的空間填充。

    采樣對象距離場很昂貴,下圖的白色=200個對象影響,解決方案是:合成到全局距離場(global distance field)中。

    全局距離場:存儲在以攝像頭為中心的clipmap中,使用了4個clipmap,每個clipmap的尺寸是\(128^3\),Clipmap會隨著相機移動而滾動,根據需要從對象SDF合成的新切片,遠距離的clipmap更新的頻率較低。

    全局距離場在地面附*不準確,在圓錐體起點附*采樣對象SDF,其余為全局SDF。

    全局距離場可以大幅減少對象影響重疊!

    14.4.4.5 Destiny's Multi-threaded Renderer Architecture

    Destiny's Multi-threaded Renderer Architecture闡述了2015年的Destiny引擎的多線程渲染架構,包含Destiny核心渲染器架構、游戲模擬到GPU數據流、作業和負載*衡考量、延遲減少技術(減少輸入和渲染延遲、保持GPU完全飽和)、復雜性封裝等內容。更進一步地,涉及粗粒度并行、Destiny渲染器目標、解耦模擬和渲染、核心工作負載作業化、數據驅動的渲染提交及高級優化等技術。

    早期的Halo引擎使用了線程上的系統(System on a thread,SOT)的渲染管線:


    幀間的時序圖如下:

    靜態每線程負載*衡意味著工作負載分配不理想,所以傾向于在模擬和渲染循環(最大的工作負載)的線程上看到大量的利用率,但其它線程看到了大量的空閑時間。

    SOT的缺點是難以跨代/跨*臺采用,不適用于異構*臺,同步需要游戲狀態的完全雙緩沖區,序列化的前端高可視性成本,潛在的GPU空閑氣泡。它的優點在于方便的數據訪問、可擴展、容易、簡單線程模型、模擬和渲染的流水線并行執行。

    到了Destiny引擎時代,渲染器目標變成:推出一款具有出色視覺效果和響應速度的游戲,復雜、生動、美麗的世界,環境多樣的大型目的地形,高質量照明、動態時間、實時陰影、天氣因素、高分辨率渲染,許多圖形功能。推出一款視覺效果極佳、反應靈敏的游戲,具有可玩性,保持它的可伸縮性,跨代跨*臺,在所有*臺上都有堅如磐石的性能。保持高效,最大CPU和GPU占用率,讓它們有充分的時間,避免GPU空閑,動態負載*衡和智能作業批處理,保持低延遲。保持簡單,讓人們停止擔憂和熱愛多線程,在短時間內入門多線程渲染,新功能對現有工作幾乎沒有改變或沒有改變。將模擬與渲染解耦,完全數據驅動的渲染管線。

    首先是從渲染解耦出游戲狀態遍歷。解耦可見性和游戲對象,獨立于游戲對象的可見性遍歷,根據可見性結果驅動渲染工作負載,將渲染和游戲對象解耦。把那些靜態數據收起來,渲染數據不等于游戲對象數據,在對象的生命周期內,渲染數據的大部分是靜態的,在渲染器中緩存不可變的渲染數據,注冊后只讀。提取每幀瞬態渲染特定數據,靜態數據已經被緩存,每幀僅提取動態數據,可見性結果定義了要提取的游戲狀態,僅提取可見元素的數據。下圖是修改前后的對比:

    接下來是解耦物體和渲染。在每個系統中單獨表示:對象系統(游戲端)、渲染(渲染對象)、可見性(可見性對象),提供跨層通信的接口(使用嚴格的訪問規則,只有在特定的游戲階段,參加下圖詳解)。

    )

    1:游戲對象的每個組件將映射到渲染器側的特定渲染對象,當一個新的游戲對象被添加到世界中時,它會在渲染器中注冊自己,在渲染器中緩存該游戲對象的靜態渲染數據,渲染對象可以緩存游戲對象句柄(用于動態對象)以供后面使用。

    2:然后,渲染器將渲染對象的句柄返回給游戲對象,游戲對象將其緩存在相應的渲染組件中。

    3:使用此渲染對象句柄,游戲對象向可見性系統注冊,以創建一個可見性對象,該對象將渲染對象句柄緩存在其中。此渲染對象控制柄指向原始渲染對象。

    4:然后將可見性對象句柄返回給游戲對象。現在如果要隱藏游戲對象,只需要注銷這個對象的可見性,它就會停止渲染。

    5:請注意,渲染對象對可見性一無所知;只有可見性知道渲染對象。因此,將游戲對象系統、可見性和渲染層解耦,現在可以根據可見性結果驅動渲染系統,而無需訪問游戲對象。那么動態數據提取呢?。

    幀包(frame packet):動態逐幀渲染數據存儲,所有特性的渲染器動態數據都存儲在那里,完全無狀態,每幀用完就扔掉。總占用小,PS3 Xbox360上每幀1MB,占總游戲狀態的9%。

    一個視圖等于視錐體和攝像機的組合,例如玩家視圖、陰影視圖、開銷視圖等。

    此外,視圖還等于渲染作業鏈單元,如可見性、提取、準備、...、提交等。每個幀環形緩沖區可以包含多個幀包(一般是兩個,例如UE),每個幀包可以包含多個視圖包。視圖包(view packet)包含了視圖相關的所有數據,每個視圖的作業鏈見下圖右側:

    左:幀環形緩沖、幀包、視圖包的結構關系,右:每個視圖的作業鏈和步驟。

    視圖作業鏈(View Job Chain)是核心系統作業,總是獨立于幀內容運行,數據驅動的視圖作業鏈,動態創建,基于在給定的幀中的內容。

    確定視圖階段:保留幀和視圖數據包,沒有重量級的分配、堆操作等。

    提取階段:為每個視圖計算可見渲染列表,存儲在可視性環形緩沖區(臨時)。它涉及的階段如下圖:

    視圖鏈作業在渲染節點上操作,渲染節點效率很高,全能壓縮(uber compact),緩存一致性表達,填充渲染節一致性數組。按渲染對象類型對可見列表進行排序,以便后面一致性地執行,在視圖包中分配渲染節點。渲染視圖節點,存儲視圖依賴數據,可以分配自定義幀包數據(基于渲染對象類型)。與幀節點共享視圖中的數據,幀包內的數據共享,避免冗余數據:

    回到前面的圖表,在可以解鎖下一幀的游戲tick之前,需要完成提取階段,因此帶來了游戲模擬延遲(game simulation latency)。

    提取窗口和延遲:從程序中提取塊,游戲狀態被鎖定以供讀取,使提取成為最具延遲影響的工作負載。需要優化提取窗口,提取窗口由
    所有視圖的可見性計算,游戲狀態數據提取,可見性計算是CPU工作,能不能把可見性移到提取之外?玩家視圖的靜態環境可見性是最繁重的工作。

    將可見性移出提取后,上一代游戲延遲減少了約4毫秒,而當前一代游戲機延遲減少了約2.5毫秒。

    提取:以最少的工作量復制出動態數據,不要運行任何復雜的數據變換,完成并解鎖下一個游戲tick的游戲狀態/模擬。

    準備:僅對可見元素運行準備作業,盡可能基于LOD跳過計算,不要計算無法感知的內容。

    準備和模擬的幀序列如下:

    現在,當模擬并行運行時,我們將執行準備階段。與extract類似,prepare也通過迭代視圖和幀節點以及緩存的渲染對象數據來操作,為不同的渲染對象執行prepare入口點。Prepare還將每個Prepare作業的結果寫入幀數據包。但與extract有一個重要區別:渲染作業不再可以訪問游戲對象。在準備過程中,將為下圖角色(突襲者)破爛的斗篷運行布料模擬工作,如果突襲者離玩家足夠*,還將為他的手指和腳趾骨骼計算非確定性動畫(如果他離玩家不夠*,則跳過該工作)。還將根據他的垂直計數將他放入一個連續的LOD桶中,以保持戰斗幀的一致LOD足跡。

    可以對其它視圖重復此作業流程(例如如果在一個幀中總共有三個視圖),每個視圖都有以下作業鏈:

    值得一提的是,UE的場景渲染器的流程和上述的視圖作業鏈高度一致,說明UE也是借鑒了這種并行化渲染架構。

    保持簡單:簡化圖形功能開發,渲染工作負載的透明作業,保證緩存一致性和核心工作的潛在同步。解決方案:功能抽象和封裝。

    渲染特性:渲染特性是圖形渲染的一個單元,定義為:緩存一致性數據表示,用于數據管理和呈現的代碼路徑。可以映射到特性渲染器的實例。Destiny特性渲染器定義圖形特性如何映射到:使用緩存數據表示呈現對象,幀包渲染節點表示,每個階段的作業入口點。(下圖)

    特性渲染器作業:每個特性渲染器為每個階段公開一組入口點,約束數據訪問:僅讀取相應的節點狀態和特性渲染對象數據,僅輸出到幀數據包,所有輸出都自動進行雙緩沖。核心渲染器使用這些入口點為每個階段進行作業,在不影響葉子特性的情況下,當核心架構重構作業依賴關系或加載*衡規則時葉子特性不受影響。

    下面是界面提供的功能渲染器的所有入口點集,你可能看不懂它們的名字,但別擔心,它們只是想讓你了解系統是如何工作的。

    性能和內存優化:

    • 按工作頻率組織數據和工作負載:跨視圖、跨渲染對象,共享數據:保存幀包內存,節省性能:逐頻率執行一次代價高昂的計算。
    • 核心架構同步訪問和執行,多線程訪問共享元素。暴力鎖定內部循環會降低性能。為快速共享渲染節點操作開發了自定義無鎖原語。
    • 提取或準備每幀操作時,只要對象在任何視圖中都可見,每幀一次,每幀節點索引上的無鎖同步原語,對于在渲染器中注冊的所有對象都是唯一的。
    • 提取或準備每個游戲對象的操作時,多個渲染對象的共享工作負載,蒙皮提取和準備/動態AO計算/前向光探測計算/非確定性動畫解算,基于對象骨骼哈希的無鎖同步原語。

    數據驅動的提交管線之幀提交:高級提交代碼執行通道,并行提交,基于游戲狀態。建立不頻繁但成本高昂的全局狀態:渲染目標操作(綁定、清除、解析、解壓縮)、全局狀態(幀/視圖寄存器)。每個通道調用提交視圖指令,簡單、跨*臺的APl,為該視圖生成GPU命令。每個提交視圖指令都設置提交視圖作業,每個*臺作業規則各不相同。管線GPU的工作如下:

    最終,要做的是從高級提交管線中找出如何啟動提交視圖作業和功能提交入口點,需要做的是區分哪些可見元素需要提交到貼花階段,哪些需要提交到透明階段。

    高級提交控制:跨*臺代碼路徑,分階段組織:1、所需的通道,如著色通道、色調映射/解析等等;2、提交階段提交指令。

    // 高級提交的偽代碼
    generate_gbuffer_pass()
        set_render_targets(depth_stencil, gbuffer_surfaces)
        setup_viewport_parameters()
        ...
        clear_viewport()
        submit_render_stage_for_view(first_person_view, _render_stage_gbuffer_opaque);
    

    渲染階段機制:過濾運行時提交過程,提供運行時著色器技術管理,在正確的時間選擇正確的技術,允許篩選可見列表,選擇正確的網格集,以使用正確的技術提交到正確的過程,專為緩存一致性和快速迭代而構建。

    渲染階段(Render Stage):每個渲染對象可以隨時訂閱渲染階段,通過向核心渲染器注冊階段,在注冊時或運行時的任何時刻。每個視圖都可以在運行時訂閱渲染階段,可見列表將僅過濾到訂閱的渲染階段。

    這里的渲染階段其實是類似于UE的FMeshPassProcessor及其子類,更多可參閱:剖析虛幻渲染體系(03)- 渲染機制

    按渲染階段過濾視圖:僅當兩個條件都滿足時渲染對象才會被提交:該視圖支持給定的階段和該視圖包含了訂閱該渲染階段的可見節點。

    逐階段填充提交節點:按渲染階段篩選可見列表。

    逐階段排序提交節點:某些過程需要在渲染之前進行排序,如透明材質等。在每個階段的準備過程中進行排序,每個渲染階段使用可配置回調,每個渲染節點在運行時計算每個階段的自定義啟發式。每個渲染對象可以有一個或多個提交節點,基于小排序鍵進行內聯快速排序。

    緩存一致性:所有核心渲染器作業都在渲染節點上操作,輕量化數據結構。所有核心工作負載的快速、緩存一致性排序、分配和遍歷,較小的數據重復,在緊湊的迭代循環中實現更快的緩存訪問。

    逐階段作業提交視圖:分成不同的提交葉作業,如逐階段、視圖、特性、階段集等,動態負載*衡拆分。對于每個特性提交入口點,僅訪問本地和渲染對象數據,如渲染節點、特性渲染對象緩存。

    特性渲染器提交內核:每個特性的一致代碼路徑,可以按特性類型進行排序,只對允許的通道。提交視圖作業是經過優化的內核處理器。

    渲染抖動和減少延遲:多線程提交,CPU/GPU負載*衡,減少GPU氣泡,高效的命令緩沖區刷新,異步交換,通過延遲自動恢復減少抖動。

    GPU占用率因素:最終目標是保持GPU完全飽和,永不空閑。重要因素有刷新GPU命令到GPU的時間、繪制調用、執行命令列表指令、渲染目標狀態、上一幀在GPU上的工作完成并翻轉。

    跟蹤GPU空閑:計數器不規則,但在某些*臺上可用,引擎是用來幫助追蹤的,用于所有性能和活動測試,GPU空閑==缺少GPU,有助于精確定位由于GPU不足而導致的延遲更高的位置。

    上圖顯示了很多GPU空閑氣泡,表示GPU饑餓狀態,會增加整體渲染延遲。

    保持GPU的占用:多線程提交,高效生成命令緩沖區刷新,提交作業粒度負載*衡,Vblank同步。

    同步到VBlank:基于vblank偏移計算,以便在準備翻轉時完成GPU工作,而不會錯過一個間隔。一些控制臺允許原始vblank事件跟蹤,對于完全控制非常有用。其它控制臺/PC不需要手動操作,只需旋轉一個單獨的線程,等待Vblank事件,發布調整大小/全屏事件時,請注意呈現死鎖。

    多線程提交:復雜的主題,當我們將命令緩沖區刷新到GPU時,無法涵蓋所有重要方面。高級提交腳本為每個視圖、每個階段生成命令緩沖區提交。GPU必須按順序執行命令,主要在將命令緩沖區刷新到GPU時,而不是在生成它們的時候。

    高級渲染提交:提交高級別、高成本渲染狀態:目標綁定/清除/解決。提交高級提交指令,為渲染階段渲染視圖(可選:進一步的特性過濾),在此層作業化,為該指令生成命令緩沖區。

    生成提交作業:作業N一起提交節點,按特性或渲染狀態分組。為了提交作業創建而拆分命令緩沖區,來自高級提交腳本作業,用于GPU順序執行。每個逐特性、逐階段、逐視圖提交作業 == 1個命令緩沖區。

    提交作業和GPU占用率:需要CPU和GPU負載的最佳*衡,輕量CPU快速刷新到GPU并啟動,接著是攜帶GPU大量工作的中等CPU等等。按渲染特性和渲染階段成本對提交作業進行排序,按*臺調整,核心渲染器提供了這種機制。

    下面涉及動態負載*衡。

    負載*衡渲染作業:粗略的方法可能會使初始實現變得過于簡單:每個階段每個特性一個作業(提取/準備/提交),工作量變化很大,例如環境/地形無需準備,角色裝備、蒙皮、布料是繁重的工作,開銷太大。

    動態負載*衡:每個特性都提供了每個階段的成本函數,需要獨立工作嗎?基于實體和每個階段{提取、準備、提交}的特性成本的核心系統批處理,基于幀中的數據自動加載*衡。

    異構作業執行:每個特性都指定了它支持的執行單元,核心渲染器會根據可用性自動安排時間,例如:SPU/PPU調度。大多數用于提取,其它具有繁重工作負載的階段工作于PPU。未來可以擴展到其它系統。

    結果:跨多個*臺提供低延遲、高效、高度可擴展的執行,如PlayStation 4、PlayStation 3、Xbox One和Xbox 360,特性代碼是跨*臺的,不受體系架構調整的影響。渲染器的多線程處理是交付Destiny的關鍵,可擴展性和動態負載*衡,緩存一致性數據訪問,異構支持,實現了低延遲,盡管工作量很大。數據驅動的流水線體系結構為創建圖形功能提供了極大的靈活性,扎實的封裝允許在整個開發過程中進行大量優化。實現了最初設立的目標,成功地將游戲狀態遍歷與輸出解耦,實現了更好的CPU和GPU利用率,通過數據驅動架構將渲染算法分解為任務和數據并行工作負載。

    面臨的挑戰是每個*臺有自定義需求:提交作業粒度、命令緩沖區生成,在整個項目中持續優化作業開銷成本。

    Lessons from the Core Engine Architecture of Destiny講了Destiny引擎的渲染架構以及如何達成目標。當時的Destiny引擎的模塊眾多:

    引擎支持的特性有:作業圖多線程,跨*臺,分層代碼庫實現引擎共享和快速重建,與博弈邏輯解耦的博弈狀態和資源生命周期,組件化對象系統,支持高級功能開發,自定義C#編輯器套件,可為游戲性迭代編寫深度腳本,對所有內容進行快速迭代,提供了大約60%的這些功能。下圖是不同時代和層次的引擎架構演變圖例:







    14.4.5 開花期總結

    總結起來,在2010~2015年間,渲染引擎的總體特點是:

    • 以DirectX 11和OpenGL 4.x的圖形API帶來了許多新的特性,為游戲引擎和應用程序提供了廣闊的發揮空間。
    • GPU硬件結構和驅動層也在適應或推動圖形API的發展,計算能力、并行度呈指數級發展,但IO的帶寬瓶頸依舊,趕不上ALU的提升。GPU驅動渲染管線的出現為高度復雜的場景渲染提供了堅實的基礎。
    • 多線程得到充分利用,如多線程渲染、異步加載、工作線程、線程池、DX11的多線程上下文等等都是其中的具體體現。適應多線程和提升并行的技術和數據結構也得到空前的應用,如SOA、線性數組、DirectCompute、數據驅動等。
    • 渲染技術也得到迅猛的發展,如PBR、延遲光照、推斷光照、分塊及分簇光照,還有諸多直接光影、間接光影、體積光影等技術。大量的抗鋸齒技術被研發出來,典型的代表是FXAA、TAA、SMAA及它們的變種。后處理技術也不甘其后,大量更真實更高效的算法也接踵而至,典型代表是DOF,眾多文獻都見諸身影。
    • 特殊材質也日新月異,以皮膚、頭發、眼睛、布料及自然景觀(地形、天空、大氣、光束、天氣、植被、湖泊、海水、霧氣)等為案例的文獻遍地開花。
    • 隨著移動終端的智能化進程,移動端的渲染也日趨成熟,專用于移動端的GPU、圖形API、渲染引擎不斷完善,形成完整的生態鏈。
    • 光線追蹤、VR、Web端等新興技術在悄然發育,為后續的發展奠定了良好的開端和基礎。

     

     

    • 本篇未完待續。

     

     

    特別說明

    • 感謝所有參考文獻的作者,部分圖片來自參考文獻和網絡,侵刪。
    • 本系列文章為筆者原創,只發表在博客園上,歡迎分享本文鏈接,但未經同意,不允許轉載
    • 系列文章,未完待續,完整目錄請戳內容綱目
    • 系列文章,未完待續,完整目錄請戳內容綱目
    • 系列文章,未完待續,完整目錄請戳內容綱目

     

    參考文獻

    posted @ 2022-05-02 20:24  0向往0  閱讀(1048)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看