[演講筆記][UnrealEngine] HPG 2022 Keynote: The Journey to Nanite

posted in: UnrealEngine | 0

HPG 2022 Keynote: The Journey to Nanite – Brian Karis, Epic Games

transcription of the talk: https://www.highperformancegraphics.org/slides22/Journey_to_Nanite.pdf

整個演講跟下來,發現這場演講比較偏向文獻回顧,講者提到了很多創造nanite過程中試過的方法以及為何最後捨棄該方法的原因。

有很多名詞跟技術對我來說都是第一次聽到,講者也不會特別花時間講解那些名詞到底是什麼東西,也因此花了蠻多時間試圖搞懂作者到底想表達什麼東西;作者沒多解釋的部份,也有試著去找文獻理解並寫了一些註解,寫著寫著沒想到總字數居然來到了2萬7千個字。

大部份內容都是經過我自己消化過後再試著重新用文字表達,很多地方或多或少都會混入自己的看法在裡面,只是由於能力有限,可能理解有誤或偏離原義的部份還請見諒。

借由這次的分享,也希望自己能夠好好的將一些沒搞懂的觀念補齊,也希望能夠拋磚引玉,希望能引出專家出來幫忙指正或補足內容不足的地方。

另外我也有做pdf的版本,比起網頁應該會比較好閱讀,需要的可以從這邊取得

那麼,就讓我們開始這次的旅程吧。

本文開始:

Introduction

講者一開始先展示了使UE5中使用Nanite跟Lumen讀入Moana island dataset(*1),完整處理這組資料聽起來是這個領域中的惡夢,裡面的三角面數量非常的巨大,細到每細沙灘上的沙是都有三角面。

*1: Moana island dataset 查了一下資料來源,disney有放出很多這類的資料集供研究跟軟體開發使用,看他license重點是不能商用。This dataset has become notorious for being massive and unwieldy with 146 million unique triangles, 27 million instances resulting in 164 billion instance triangles。 https://www.disneyanimation.com/data-sets/?drawer=/resources/moana-island-scene/

由於unreal不支援Ptex(*2),所以沒辦法導入任何材質,整個場景只有vertex color,在這個前題下,整個場景fps穩定超過40FPS。(作者沒說他用什麼機器)

*2: Ptex 看起來是Disney的貼圖系統,網路介紹 https://ptex.us/

這場演講不談nanite怎麼運作的,而是談他的研發過程。

講者非常好奇那些開創性的發明都是怎麼被創造出來的,是水到渠成?還是單純的幸運?領域工作者常常被問什麼是你領域上最重要的問題,然後接著問為什麼你不去解決那些問題。

所有的發明都是為了使用者而服務,所以重點是要了解你的使用者。他們想要做的是什麼?但用工具造出後實際上是什麼?二者的差異在哪?

講者發現美術花很多時間在技術而非美術問題上,例如切UV、產Collision Geometry、同個模型但需要針對不使用使情境產出不同的版本,並做profile以確保符合場景預算。以下是幾個美術需要關注的效能問題:poly count、draw call、texture memory、mesh memory、light count、shadow casting light count、shader instruction count。

Early years

1999年當講者在讀高中的時候,他就有試著想解決這件事:期望美術只需要做一個有最高細節的版本就行了,其他部就靠某些自動化的機制,根據機器的效能自動切換成合適的版本。

講者當時的做法是想要靠ROAM+Bezier patch(*3),利用adaptive tessellation的概念來處理Terrain System,雖然事後覺得加上Bezier patch蠻蠢的,但對講者來說是個開始。

*3: ROAM 全名是Real-Time Optimally Adapting Meshes,最早是在1997年被提出來做Terrain visualization,目的是用來動態細切三角面的同時還能維持鄰近三角型的連續性,也就是說不要裂縫(crack)。這個切割的機制可以根據geometric error或者是在營幕上的大小決定要不要往下切細,這意即他是view dependent。

Bezier patch則是curved surface,是由一些控制點跟數學式來表達,代表他能夠無限細切成任意三角面。

這二個技術合起來,看起來這個方法的思路是想要丟入一堆bezier patch之後,自動連在一起,並產一大塊沒有crack的地形。

https://ieeexplore.ieee.org/document/663860

講者在工作之後有讀到一篇paper(*4),是在談怎麼利用數學的方法計算出某個特定的view需要的mipmap level,這個數學方法中,texture size會在算式中被cancel掉,這讓講者很興奮,因為原始的texture size對於mipmap不重要的這個事實,告訴了我們,texture size理論上可以無限大。於是講者馬上寫了一個mip-based texture streamer,然後跟他們的美術說,把所有的貼圖大小從256換成了2K…但馬上就發現錯了,雖然算式是對的,但不代表貼圖大小不重要,該機制無法在實務上直接應用(*5)。

*4: paper連結 https://web.cse.ohio-state.edu/~crawfis.3/cse781/Readings/MipMapLevels-Blog.html

*5: 該方法假設了整張貼圖都可以被看到,無視了視角跟occlusion,這代表有很多看不見的資料會被讀進記憶體中。想像有一堆2K的貼圖其實根本沒出現在畫面上但還是被讀進記憶體中,在2009年那個時代看起來是個災難。

id Tech and Mega Texture

virtual texture解決了mip-based texture streamer中很多問題,因為我們每次sample的對象是固定的size page,我們可以控制讀入貼圖的粒度(granularity)在一個小範圍內。在看了ID Tech的演講談論mega texture(*6)後,作者決定開始著手寫一套virtual texture system。

講者希望他的Virtual Texture System不要像mega texture那麼激進,做好一個texture streamer的角色,並讓系統能夠在可控的風險下。對講者來說,只保留一張大的mega texture是一個很大的賭注,因為他需要的是一個完全不同的pipeline,為了很好的編輯、製作這張巨大的mega texture,引擎需要製作工具才能處理。

*6: mega texture是一張非常大的virtual texture,這個名詞第一次出現在id Tech 4(2004)上,最大貼圖大小是32,768×32,768,過了3年進化id tech 5(2007),最大可以有約20 GB左右的texture data(128,000 × 128,000),其中用128×128的區塊來做讀取控制。

這看起來是id tech的招牌技術,沒有其他引擎採用像他這麼激進的做法,這相當於我們需要在引擎內部做一個類似於photoshop工具,並且能夠在這麼大的畫布上畫出所有的細節。不過另一個令我訝異的是在2018年發表的id Tech 7,把mega texture這個招牌pipeline整個移除了。

我是覺得雖然mega texture可以有無限豐富的細節,但是對於強調modular與reuse的現代化pipeline來說製作上太費力。另外由於mega texture上每個區塊都是獨立的內容,當camera不斷移動時,系統需要去把view內的maga texture區塊讀進來,而這些區塊在記憶體中通常不會是連續區段,可以想見對於現代的硬體設計不是非常的cache friendly。以上可能就是為何id Tech 7把mega texture拿掉的原因。

參考連結:

https://blog.csdn.net/yinfourever/article/details/89888501

https://zh.m.wikipedia.org/zh-tw/Id_Tech_4%E5%BC%95%E6%93%8E

https://en.wikipedia.org/wiki/Id_Tech_5

https://en.wikipedia.org/wiki/Id_Tech_7

https://www.zhihu.com/question/55903617

http://en.wikipedia.org/wiki/MegaTexture

https://blog.51cto.com/u_14267314/4798383

http://mrl.cs.vsb.cz/people/gaura/agu/05-JP_id_Tech_5_Challenges.pdf

Nanite也常常被問了跟mega texture一樣類似的問題,講者希望這場演講能回答疑問。

Nanite的主要目標是想要像Vritual Texture一樣,做一套Virtual Geometry系統。這代表我們想要直接使用影視級別的原始mesh,在不需要人工手動介入以及損失品質的前提下,移除以下幾個預算限制:Polycount、Draw Calls以及Memory。

Nanite製作的動機,是他在2007年John carmack說的一句話: the hope for their next iteration of their engine was to virtualize geometry like they had texture。

這個是典型的創作來自於生活中不經意的一個靈感,這個靈感可能單純來自於聽到某人說某件事是可行的,就算是聽到的當下技術上辦不到。為什麼會有這種現象?講者認為,因為有前人證明了可行性,所以後人才有有信心跳下去做;又因為知道有競爭者也在解決同樣的問題,因此也驅動著人們搶先去解決那些難題。

雖然講者在2007年的時候就決定想要解決這個問題的,但不代表能夠全職投入,當下他能做的是在有空時當興趣推進。

Virtual Geometry

講者第一個關注的解決方案雖然並不是非常開創性的手法,但讓他的思維從virtual texture推進到Virtual Geometry,最後昇華成Virtual Geometry Images。講者最後找到某篇paper(*7)最接近他目前的想法,該篇paper利用參數化的手法建構了一個quad mesh(*8),並能達成seamless texture atlas目標。背後的idea是每張被產生出來的texture chart可以被當成一個polygon mesh的局部,而每個polygon edge會跟他鄰居的texture chart連接。所有的polygon mesh都可以利用catmull-clark subdivision的流程轉成quad mesh。而最後quad的總量會跟生成的texture chart的數量成正比,這表示美術不用擔心技術細節,因為一定比他們自己做出來的quad mesh數量還要少而且不會有品質的損失。

*7: Multi-grained Level of Detail Using a Hierarchical Seamless Texture Atlas https://www.cs.jhu.edu/~cohen/Publications/HSTA.pdf

*8: 在建立複雜的模型的時候,用QuadMesh會比較容易做subdivide之類的操作,而且他會有比較少的artifact,這就是為什麼像是blender、zbrush之類的DCC工具比較常用Quadmesh來建模而不是triangle mesh。

只是由於目前主流GPU標準是基於Triangle的設計,所以實際上遊戲引擎通常只支援TriangleMesh的呈現方式。例如 DirectX PrimitiveType 就沒有定義Quad;另外雖然OpenGL有GL_QUADS,因為Quad沒辦法像triangle一樣可以保證在同一個平面上,所以實際上在GPU內部也是被當成2個Triangle處理,這個處理過程會產生額外的計算成本,也因此從 OpenGL3.0 開始就被標記成deprecated。

參考連結:

http://wedesignvirtual.com/quads-vs-tris-in-3d-modeling/

https://stackoverflow.com/questions/6644099/what-is-so-bad-about-gl-quads

https://hhoppe.com/proj/gim/

一年之後(2009/01),講者在他的blog(*9)上把這個idea寫了下來。virtual geometry image只是virtual texture的擴充,很多觀念都可以從virtual texture類比推論過來。

*9: http://graphicrants.blogspot.com/2009/01/virtual-geometry-images.html

在此同時,John carmack提出了一些初步的想法:他的這個idea就是raycasting sparse voxel octree,並在隔年的GDC,ID Tech展示了一個prototype。跟講者不同的是,他的手法是從2D往3D思考。voxel會儲存color跟normal data,作為geometry版本的mega texture被提出。只是這個方法無法用在animated geometry的應用,因此無法被講者所接受。另外這個方法雖然有考慮memory的使用量,但最大的問題是他不夠快,並沒有把時間留給lighting跟shadow。不過這個demo確實讓講者留下了深刻的印象,因此他決定以後再回頭來看voxel相關的手法。

Clipmap and HLOD

接下來由於新主機發售,講者覺得有機會開始這個技術的研究,但當時被要求新的技術必須要能夠跨世代,也就是PS3/PS4都要能跑,這個要求他覺得沒有信心可以達成。不過在思考的過程與找paper的過程中,他偶然發現可以使用progressive buffer的手法,也就是後來的HLOD(*10),來應用在二個跨世代的主機上,

*10: 關於HLOD(Hierarchical Level of Detail)的用法。

我們知道,當遠方的geometry比pixel還小的時候會產生quad overdraw,這時候用LOD的機制可以緩解這個問題。只是當我們的geometry真的很遠的時候,quad overdraw最終還是避不開的,這時候我們一個手法就是設cull distance不要去畫他。只是有的時候我們還是會想保留某些物件來營造氛圍,例如平原遠方一顆樹或房子都沒有在視覺上讓美術覺得不太對,利用HLOD的機制,引擎會自動幫我們把mesh object分成不同的ClusterActor(LODActor),並把群中的所有物件Merge起來,產生一個更簡略版本的proxy mesh來呈現。UE4中的實際操作可以看這個影片介紹:HLODs: Medieval Game Environment extended tutorial

HLOD簡單來說就是把多個TriangleMesh打包成一組組的Chunk,然後他Hierarchical中的每個Level再用傳統的mesh simplification algorithm做出一個減面過後的版本。而每個frame在做render的時候會根據view去裁剪出需要的東西出來顯示。

為了確保LOD Level之間的切換不會產生裂縫(cracks),progressive buffer會根據距離做geomorph。如果每個vertex知道他在下一個Simplified LOD Level的位置,把vertex事先移動到該位置就能避免裂縫(cracks)的產生。這邊有一個假設:該層級的LOD所有的東西必須要讀進記憶體中,只要有東西沒被讀入就會產生一些問題。

Progress Buffer他的行為跟clipmap(*11)有點像,基本上是一張sparse virtual texture(*12)。

*11: clipmap在最早提出來的別稱是virtual mipmap,主要適合應用在開放世界的terrain上,是基於mipmap概念所發展出來的變體應用,其概念直接影響了mega texture以及後續virtual texture的開發,後續各種開放世界的應用大多離不開這個技術。mipmap強調的是依據距離的遠近自動選擇合適合貼圖套用,近的選大張的、遠的選小張以節省記憶體;但是clipmap強調的則是是我們最大那張貼圖能夠覆蓋到多遠的距離,遠的選大張的、近的選小張的,以求遠至地平線的那一端也能有適當的細節呈現。

只是在Clipmap中由於最大最粗的那張貼圖通常會非常大,不可能一次讀入,因此運行時會根據需求,將貼圖的部份區塊裁切出來顯示,例如google map在zone in/zone out時顯示局部或整體的行為,這也就是為何這個技術稱作clipmap的原因。當然,由於他本質上還是mipmap,所以還是會根據距離跟視角來決定要選用哪張mipmap-level。這個技術原本主要是用在遊戲上,但實際上,在Never Lost Again的這本研發回憶集中有提到,Clipmap也是google map背後的主要技術。(作者Bill Kilday原本任職於Keyhole的 marketing Director,深度參與電子地圖的研發,隨後該公司被google收購變成了今天的googlemap,現在是Niantic,也就是Pokémon GO那家公司,的Vice President of Marketing。)

在定義上Clipmap每1 meter需要有一個texel來表示,因此在使用這個技術之前我們必須要先決定世界的範圍,例如原始paper目標是想要覆蓋全地球,地球的半徑是6371km,攤平到平面上的話,這張貼圖的長寬就是赤道周長(6371000 * 2 * 3.14 = 40009880 ),這代表我們需要準備27級的mipmap,也就是說最大那張需要至少有2^26*2^26的大小,接著再從這張大圖產出完整的mipmap出來。越小張的貼圖表示1個texel覆蓋的距離越少,換句話說也就是會更精細。

2^25=33,554,432 < 赤道周長(40009880)  < 2^26=67,108,864

他處理的手法跟sparse virtual texture有點像,都是只載入貼圖上需要的部份而已。clipmap是先裁切完後再來處理mipmap,而sparse virtual texture則是先選好mipmap-level後再來決定讀入的範圍。

從這裡可以看出來,clipmap(1998)的核心理念直接影響了後繼者mega texture(2004)。由於我們每個texel都必須要有資料,因此怎麼產出這些資料會是另一個議題。通常我們都會做工具或利用一些自動化的手法來自動產生或快速進行製作,例如直接從外部匯入真實世界的地型。在UE5中的Virtual Shadow Map,其Directional Light的Shadow相關處理就是使用clipmap。

Clipmap的簡略流程如下:首先我們會需要定義一個clipSize來決定我們要截出多大的貼圖,這個clip的動作會從最大張貼圖開始選定一個中心點,對整個mipmap Pyramid (想像所有mipmap由大往小疊在一起) 往下裁。由於裁切保有mipmap資訊,這讓我們有機會對二個不同層級的mipmap做blend,在特定視角下保持貼圖的細節。

順帶一提,2^26對於paper發表的那個年代(1998)幾乎是個天文數字,需要將近11PB的資料量,就算是2022年的現代也是一個可觀的數字。因此原始Paper中有有提出截出sub-clipmap的概念,利用offset調整mipmap層級以及scale、bias的方式來微調texture coordinate來讓精度維持在2^15以下,這招稱作virtual clipmap。

參考連結:

https://www.cse.ust.hk/~psander/docs/progbuffer.pdf

https://read01.com/zh-tw/nx8mByz.html#.Yx_dzqRByHs

*12:  sparse virtual texture跟MegaTexture很像,基本概念都是只讀某張大圖中需要的區塊(page)進來。這個技術有mipmap的概念,並且每個page都會給予一個編號。

參考連結:

https://silverspaceship.com/src/svt/

https://substance3d.adobe.com/documentation/spdoc/sparse-virtual-textures-172823866.html

http://holger.dammertz.org/stuff/notes_VirtualTexturing.html

不管是sparse virtual texture或Clipmap,只要是這類的截取部份內容的技術,為了優化,大都會想辦法把內容切成page後cache起來,這個page size通常是128×128,之後比較前個frame跟這個frame的差異並只載入需要的部份。但是可以想見,如果我們突然跳到一個完全不同的區域,例如瞬間移動(teleport)或者是相機快速移動,這些技術的弱點就會被放大出來。

參考連結:

SIGGRAPH ’98: Proceedings of the 25th annual conference on Computer graphics and interactive techniquesJuly 1998 Pages 151–158 https://doi.org/10.1145/280814.280855  https://dl.acm.org/doi/pdf/10.1145/280814.280855

https://gamedev.net/forums/topic/553790-clipmaps-vs-sparse-virtual-textures-aka-mega-texture/4563322/

HLOD, Cracks and Voxel

講者喜歡HLOD的Idea,但不喜歡他處理crack需要切level的時候事先移動vertex位置的手法。因此接下來開始思考:那為什麼voxel不會有cracks?Sparse voxel octree(*13)下面的subtree可以做獨立分散建置而不會互相影響。而當我們只讀某區的subtree也不會因為其他的subtree沒讀入而產生crack。如果能確切知道原因的話,就能夠模仿他的機制。結論是,voxel是有體積的,而他的thickness就等於該voxel的寬,當resolution改變的時候他會自動選擇適當的那個voxel顯示;而surface error必須要小於voxel才會產生,因此當voxel的thickness大於error的情況下,就永遠不會有視覺破綻。

*13 SVO(Sparse Voxel Octree),屬於空間分割的一種,我們先找到所有目標物件集合的中心點,這個物件可以是mesh-based或是triangle任何你定議的目標物件,然後由該中心點開始由左右上下8個方向做grid切割,由於在3D中每個層級有8個子節點,因此又稱為octree,在2D中就是quad tree,sparse是指這顆樹不會是「perfect octree tree」,若該子節點中沒有要做空間切割的物件我們就不繼續往下,若往下繼續展開的話,最細可以到達該grid只包含1個triangle,因此看起來就會有些地方特別密集,有些地方特別稀疏。就像是texture有mipmap,SVO則是3維空間上類似的概念,我們可以根據相機的位置決定要拿出哪一層的voxel出來處理。

https://www.nvidia.com/docs/IO/88972/nvr-2010-001.pdf

為了模彷這個機制,我們必須要給mesh一個thickness,這邊從terrain rendering中借了skirt(*14)的概念來使用。只是該方法必須假設我們的mesh必須要是manifold(*15)並且可以用Voxel操作,但實際遊戲應用上常常沒辦法滿足這二個假設。

*14 Skirt:我不知道怎麼翻譯這個詞,查了一下有人翻作地形底座,其目的是用來解決terrain邊緣連接處產生的接縫(crack),藉由替我們的terrain穿上裙子,在四個edge各別塞上垂直平面,就可以很好的把那些接縫藏起來。

https://www.researchgate.net/figure/A-terrain-chunk-a-without-skirts-and-b-with-skirts_fig2_235952216

*15 何謂topological Manifold?中文翻成流形,就我目前粗淺的理解,topology本質上是一堆點的集合,而該理論在探討的是怎麼在不破壞整體結構的前提下做deformation,而Manifold則是在進一步的描述符合某些前提的情況下,R^n+1 topology可以用多個R^n的topology來表示。若n為2,這代表我們可以在不破壞連續性的前提下,讓3D模型可以很好的用一堆2D貼圖拼裝還原出來。每個局部的貼圖對應到模型的homeomorphism function叫做chart。

想像我們有一本世界地圖的書包含世界各個角落,這本書裡面我們會依照地名截出該區域的地圖出來。理論來說我們能夠把世界地圖中所有的區域集合起來拼接回原本的地球。只是地球是3D圓球,區域地圖則只是書上的一張2D貼圖。為了2D跟3D轉換,我們需要設計一個Homeomorphics Function來幫助我們做轉換,而這個區域(area)搭上Function h的組合,我們會記作chart(area, h),所有地圖(chart)的集合稱之為地圖集(altas)。當然拼接的過程中會發現我們區域地圖某些地方,為了方便讀者能夠在跨區之後還能對上位置,某些區域會包含別張地圖已經有的區域。這時候我們需要靠一個叫transition map的function在chart之間做轉換,讓二個要比較的chart在同樣的座標軸上才能做比較,目的是找出overlap的區域,這而找的出overlap的那些chart稱為differentiable。這些overlap的區域最後用在讓地圖邊緣能夠平滑的拼接起來,我們才能完美的重現一顆3D的地球。所有的地圖拿出來都互相包含重覆區域的地圖集,稱之為Smooth Manifolds。

就定義來說Manifold需要符合以下特性:

  1. 所有的點必須要是second-countable,意即有限的點集合(open-set)能夠覆蓋某個局部(base),這個有限的點集合(open-set)稱之為first-countable,再進一步就是我們可以圈出有限數量的局部(base)來覆蓋整個topology,這個有限數量的局部稱之為second-countable。non-countable的例子是Cofinite Topology,該集合是無限的,所以不可數。例如Topology A{1, 2, 3}的集合,而Cofinite Topology B會是Topology A{1, 2, 3}以外的所有的集合,這代表該集合裡面有所有的自然數,而自然數是無限的,所以不可數,在概念上一個有限集合的補集會是無限集合。
  2. 所有的點必須要在Hausdorff space,也就是說任二個點要能找的到不重疊的鄰居,也就是說我們能夠有辦法圈出open set來分離任意二個點。反例non-Hausdorff space最有名的就是the line with 2 origin,這代表當我們任意二點的定義會產生overlap,有興趣可以看non-Hausdorff space的參考連結。
  3. 任意局部都能變換到Euclidean space中(locally homeomorphics to Euclidean space)

對這些特性的細節有興趣的可以去看下面附上的影片介紹。

參考連結:

https://en.wikipedia.org/wiki/Topology

https://en.wikipedia.org/wiki/Manifold

http://lavalle.pl/planning/node132.html

https://en.wikipedia.org/wiki/Non-Hausdorff_manifold

Who cares about topology?   (Inscribed rectangle problem)

What is a manifold?

Manifolds – Part 1 – Introduction and Topology

non-Hausdorff space:

 A classic counterexample — the line with 2 origins

 What is Hausdorff Topological Space?

Join EpicGames

接下來加入了EpicGames,所以這些想法講者沒有機會再繼續往下發展。

當一個新入剛加入一個大公司時,通常都會想問:該做什麼才好?

講者在他的第一次team meeting中有提出,他可以把當時UE中的Mip-based streamer換成Virtual Texture,但被否決了,因為當時團隊認為texture streaming並沒有什麼問題。他非常震驚甚至於沮喪:難道以前花那麼多心力做的東西其實只是個自我感覺良好的垃圾?當加入一個新的團隊的時候,是很容易陷進冒牌者症候群這種負面情緒中,但講者覺得他當時並沒有這個傾向,因此很快的就振作起來開始找別的題目來做,因為他覺得自己還是有很多其他東西可以貢獻的。

接下來的事讓講者非常驚訝的是,在早期UE4剛開始的時候,Epic其實是有在做Virtual Geometry相關研究的。該方法採用的是multi-resolution meshes的方案,跟virtualized geometry image的idea很像,但更具有野心,該方案想要能跑在比講者預期更多的硬體上。

MAPS vs Geometry Images

講者對他Build Data的手法蠻感興趣的,他使用的是MAPS based reduction,該手法的核心概念是從topology領域過來的。例如mug跟doughnut在topology上是一樣的,更嚴格來說,他們是homeomorphic,因此二個mesh之間可以互相轉換。該演算法的重點就是在對原始mesh減面的同時維持homeomorphic,這樣就可以把複雜的mesh換成一個簡略版本後再換回複雜的。

這個方法有一個假設:我們永遠可以從一個complex mesh上持續做reduction,在維持homeomorphic的前提下,最後變成一個最簡略版本的simple mesh。但是這個假設並不總是正確的,因為simple mesh必須要跟complex mesh有同樣數量的genus(*16)。例如鎖鏈會有非常多個genus,我們沒辦法在維持相同的genus數量並對類似的mesh做reduction。

*16:Genus是Topology 領域上的名詞,指的是surface上有幾個洞,例如有手把的mug跟doughnut都只有一個洞,是Genus-1,所以可以進行互換。

https://en.wikipedia.org/wiki/Genus_(mathematics)

https://hhoppe.com/proj/gim/

http://multires.caltech.edu/pubs/maps.pdf

另外講者比較了MAPS跟Geometry Images的手法,前者可以由低模到高模、再由高模到低模做二個方向的轉換,因為我們是從3D surface map到另一個3D surface;後者雖然沒有Genus數量的限制,因為他會延著模型上genus洞切出不同的manifold chart,只是他只能從低模到高模單個方向可以維持continuous(*17),反方向沒辦法,另外這個方法最大的問題在於我們怎麼切出3d surface並攤平存到2D Image中。

*17 Continuity可以分為C0、G1、C1、G2、C2以及幾種,主要是用來描述一個物體表面或曲線在交界處的Smoothness,C是Parametric Continuity的縮寫,G則是Geometric continuity的縮寫,上面的數字指的是微分的次數,該概念詳細的解釋可以看Cem Yuksel教授的公開課。這邊簡單總結:

C0,也稱為point continuity,表示二個線段交界處的點就只是連在一起,因此Surface的連接處會有明顯的瑕疵。

G1表示連接二個線段交界處二點的方向要相同,也就是說斜率的正負需要一致,因此連接處就不會有明顯的瑕疵。

C1表示除了他們的方向要一樣以符合G1的定義以外,交界處二點的速率(斜率)也需要一致。

G1跟C1也稱為continuity of tangency,要達成這個條件需要先滿足C0。

G2會看整個線段的斜率變化,也就是加速度,需要維持遞增或遞減。

C2更進一步的表示斜率變化遞增或遞減的速度必須一致,加速度是一致的,也就是說我們的曲線會很平滑不會彎的太快。

G2跟C2也稱為curvature continuity,要達成這個條件需要先滿足C1。

理論上數字越高物體的表面就會越平滑,在圖學中通常不會討論微分到3階以後的曲面,因為在視覺上看不出什麼差異。

https://communities.bentley.com/products/products_generativecomponents/w/generative_components_community_wiki/2023/geometric-continuity

https://en.wikipedia.org/wiki/Smoothness

Understanding G0, G1, G2 and G3 surface continuity using curvature comb

Genus只是第一個遇到的問題,講者也有另外測試displacement map,但這個方法也有一樣的問題,它並不能對genus數量做增減,我們沒辦法把sphere變成torus。另外當surface之間沒有connect的時候,這二個手法都遇到了瓶頸。continous domain沒辦法在打破continuous的前提下map到discontinuous domain。當鎖鏈的建模方式都像現實世界一樣是分離的獨立零件時,我們只能投降。

這邊學到的一課是:我們不能假設topology是simple connected以及low genus。

第二個使用Geometry Images遇到的問題是從美術的feedback過來的,美術發現實際遊戲中他們關注的某些細節遺失掉了。這個問題其實在原始的Geometry Images這篇paper中也有提到,根源是來自於resample時,在一些比較sharp的地方產生的aliasing。這邊可能會有人提出異論,說過份關注細節是見樹不見林,但美術製作需要的是他的每個更動會有明確的輸出。

由於Geometry Image這個方法會遇到Genus跟resample的問題,讓講者決定走別條路,認真去研究怎麼把HLOD應用在irregular mesh(*18)上。這表示講者不希望他研發出來的成果只能用在那些Genus為0的大石頭上,也不希望成像出來因為resampling的關係看起來整個畫面都是毛茸茸的。

*18:  要理解何謂irregular mesh,我們需要先理解何謂Tessellation。簡單來說,Tessellation指的是我們怎麼把一個表面填滿而不留空隙,而這個填滿的機制必須要有週期性的重覆pattern,也有人稱這個動作為tiling。在ComputerGraphics中,指的是我們怎麼在Triangle中細分出更多的Triangle,常見的方法是catmull-clark subdivision,該演算法善長對物體的表面做平滑化,例如可以對一個方塊的面不斷的切細,最終無限接近一顆圓(對,不是一顆完美的圓),常見的應用是使用在人臉上。用演算法做切細的運算,在本質上就會在舖平的表面上產生某種pattern,因此符合我們對Tessellation的定義。

回到irregular mesh,我們知道我們知道三角型的三個內角和是180度,根據角度的分配,我們可以把把mesh分為以下三種:

  1. regular triangle mesh:當pattern內所有的內角都是60度時稱之,另外這種三角型也稱作Equilateral 以及Equiangular triangle。
  2. irregular triangle mesh:當Pattern組成的三角型內角不為60度,意即不是Equilateral或Equiangular時稱之,我們在遊戲中見到的mesh大部份都是屬於這類,mesh上三角型大小跟形狀分配的不是很均勻。
  3. semi-regular triangle mesh:當pattern中的三角型組成有些是regular triangle有些是irregular triangle時稱之。

https://en.wikipedia.org/wiki/Equilateral_triangle

https://en.wikipedia.org/wiki/Equiangular_polygon

https://www.mathsisfun.com/geometry/tessellation.html

https://www.researchgate.net/figure/Meshes-Irregular-semi-regular-and-regular_fig2_225913168

接下來講者開始討論在根據距離遠近增減Triangle時,該怎麼樣把crack藏起來。這些討論都是根據Triangle Cluster這個思路在進行的,只是講者在這裡並沒有多做介紹,這邊稍微做個補充:原始提出triangle cluster這個思想是在Siggraph 2015關於刺客教條大革命的演講,他是用64個三角型作為一個cluster,在nanite中則是128個,弄個boundingBox group起來後,就可以幫助我們在做完Object Culling後更進一步的再對裡面的Triangle做Culling。Triangle Cluster這個思路,在模型幾何越來越複雜的近代Render pipeline幾乎是標配。

HLOD, Reduce Triangles, Cracks and triangle cluster edge boundary

在之前的研究中,作者知道關鍵問題是接縫(cracks),因此決定先把所有可能的方案都列出來看看各種可行性之間是否能產生什麼化學變化。

要處理減面後的cracks,第一個要思考的是,每個triangle cluster需要多少鄰居的資訊才能確保crack不會發生。我們可以限制自己和鄰居需要各別待在什麼相對位置上,這樣就可以減少需要處理的資訊量。如果我們的geometry是一個volume,另外一種可能的限制就是可以把處理crack的方法限定在垂直平面的位置選擇上,這就跟之前討論過的skirt的手法一樣。只是我們能把skirt的手法用在除了volume以外任意zigzag boundary的情況下嗎?不行,基本上這個虛擬垂直平面的目的是用來區分不同的Triangle Cluster,當我們讓每個zigzag edge每個都塞上同樣寬度的垂直平面時,並沒辦法真正的把crack藏起來。

若我們在build time完全無視空間限制,讓那些boundary vertices可以移動到任意位置,為了不產生crack,代表我們需要在runtime要花很多時間把他們黏起來,這個手法叫stitching,這意即我們需要在runtime保留那些資訊。

skirt需要在build time處理crack;stitching需要在runtime拼接。在不事先建出triangle cluster之間的matching boundirest的前提下,沒有其他可以處理crack的選擇了。(之後講者有補充基於Point-based Rendering的Screen space hole filling也是選項之一,之後會談。)

那如果我們讓boundary都維持原樣,然後開始對各別內部的三角型做reduce呢?某些case會有問題,例如當我們沒有non-boundary edge可以再做collapse的時候,會reduce過頭。

從另外一個方向思考,我們減boundary而不做reduce的話?這個手法我們都可以在geometry image quadtree跟progressive buffer看到,例如在progressive buffer上,我們的boundary vertices除了要存他們要移到下一個coarser level的位置資訊,還需要把他們的鄰居也一起存下來。在runtime的時候,每個node都需要知道他們鄰居是在哪個mipmap level,因此當我們在建立progressive buffer需要的Coarse buffer hierarchy tree來保存mesh資料的時候,需要限制每個node跟他的鄰居差異都不能超過一個mipmap level;另外我們也可以試著導入更多的限制來減少需要儲存的資訊,使用spatial subdivision的手法,例如octree,那麼有些資訊就可以runtime算出來:

  1. 每個octree node的鄰居可以直接從資料結構上得知。
  2. boundary的頂點可以從octree node的position中算出來。
  3. 頂點的鄰居可以從相鄰的face做推導。

另外可以導入的限制是,之前有提到過progressive buffer,他有點像是mesh版本的clipmaps,但我們不一定要套用全部的概念,其實也可以在上面加限制,讓各別triangle cluster在做LOD的geomorph時,二個鄰居的level是一樣的。

到目前為止我們假設每個node的hierarchy跟其他部份是獨立的,但mesh的boundary之間要能夠接合起來沒有crack,要達成獨立,代表他們要duplicate一份鄰居的vertice資料。比起重覆資料,我們需要找到一個方法,能夠很好的用index的方式來存取鄰居的頂點資訊。

這個需求最近接的方法是view dependent progressive mesh。

首先我們從最精細粒度的edge collapses/split開始,在考慮目前的view的同時慢慢的往下一個層級進行分割跟合併的操作,就跟progressive mesh類似的操作。整個過程就像是在offline時對mesh做reduction一樣,因此最後不會有crack產生。只是這些操作會根據每個vertex決定要做分割還是合併,有點太過頭了,就跟virtual texture中我們是根據texel來做streaming一樣,更不用說我們這些分割跟合併的操作還不能batch一起做。

到這裡我們掉進了一個陷阱:我們不可以將讀進記憶體中的資料跟renderable畫上等號。在技術上來說,為了解決crack我們只需把那些LOD改變的部份串流進記憶體中,但這不代表跟準備好做render是同一件事,因為同一個場景上,可能會有很多同一個model的instance,並不是每個instance都需要讀進記憶體中那份有最豐富細節的model,因此progressive mesh這個方法在記憶體使用量上是不可行的。(*19)

*19 這一段的重點在說在處理減面問題時,不管鎖不鎖boundary在處理crack上都有問題,前者會reduce過頭,後者會需要讓相鄰的triangle cluster也保留對方的LOD資訊,但這沒意義,最後要畫出場景上所有遠的近的instance還是要將所有的LOD層級都Load進來,造成記憶體大爆炸。

HLOD, Increase Triangle, Cracks and BDAM based on Triangle Cluster

在面對增加三角型的狀況時,講者回頭去看ROME(Real-Time Optimally Adapting Meshes)這個方法,他就在思考,為什這方法對付crack這麼有效?結論是,這個方法並不是一次對所有三角型的邊做split,只會最長邊會做,最後建立一個right triangle hierarchies,而哪個是最長邊必須要考慮到鄰居,若鄰居不是最長邊,就會不斷的遞迴往下切他的鄰居,直至我們所有的split操作符合最長邊的定義,也因此在轉換LOD Level的時候,boundary會被當成內部操作一次處理掉,而所有的內部操作的修改都不會有產生crack的可能性。基於這個概念,我們可以把一群triangle內部化,也就是說區分出cluster來後再來操作。這樣做其實就是Batched Dynamic Adaptive Mesh(BDAM)(*20)所提出的方法。跟ROAM比起來,BDAM在處理irregular mesh的時候會有更少的error。

*20 BDAM – Batched Dynamic Adaptive Meshes for High Performance Terrain Visualization

http://vcg.isti.cnr.it/publications/papers/bdam.pdf

看起來有點像是Unreal目前使用的方法?

到目前為止,為了移除crack,我們需要某種溝通機制來確保二個Triangle Cluster是處於同樣的LOD層級上。只不過,這個溝通是可以利用某些技法移除的。還記得ROAM的機制是不斷切割最長邊嗎?基本上就是三個邊都往下切來展開一顆樹,最後藉由下面node回報的error數值,再來決定要往那個branch走,當我們覺得error足夠小了的話,就可以停止繼續往下。由於這個切割機制需要runtime進行,該層LOD最後要怎麼切,需要等到所有可能的切割都展開完之後才會知道哪條路是最優解,這個等待溝通的成本其實蠻大。

但在BDAM中,我們可以透過把error事先bake進整顆樹的節點上,之後在樹展開的時候,可以事先知道哪條branch的深處會有比較小的error,因此不需要全部展開,直接往就往那該條會有較小error的edge切下去就行了。

只是要Bake error的前提是,我們設計來評估error的function必須要是monotonic,也就是越深層node越近近原始mesh的錯誤要越小。(*21)

*21: 在nanite中這個error function使用的是QEM(Quadric error metric),目的是讓每下一級LOD都會是最小的變化,也就是說外觀的變化最小。樹的最底層是LOD 0是原始mesh,代表error值會是0。

http://advances.realtimerendering.com/s2021/index.html

在有了monotonic的error function,並把error bake進tree之後,我們node之間就不需要做溝通了,這代表我們LOD層級轉換的操作可以很簡單的做平行化處理,可以做平行化,代表著可以把多個不同Triangle Cluster選擇LOD的操作丟進GPU執行。

Spatial partition and remove Small Triangle Cluster

上面提到的機制其實都是用來處理terrain,我們知道ROME最後會建出一個right triangle hierarchies,那是基於2D平面不斷進行切割的結果。那麼有3D版本的right triangle hierarchies嗎?有的,當我們對tetrapuzzles上面的3D三角體不斷的做最長邊的切割,最後產生的結果為tetrahedron hierarchies(四面體)。

只是這時候問題來了,只要採用spatial partition的手法,不管是octree、用BDAM建出來的right triangle hierarchy或tetrapuzzles,上面每個cluster裡面的三角型數量跟節點的spatial size成正比。當我們的cluster非常小的時候,可能上面只有一個小三角型,這會造成它無法被有效率的做render。由於大部份遊戲中看到的都是irregular mesh,我們無法事前假設每個LOD Level上最小三角型的size。這點,事實上也是很多model visualization research需要關注的重點,因為很多研究會使用掃描進來的模型做測試,而這些模型大多會是regular或semi-regular mesh,其三角型的大小跟密度會非常的均勻,但在這個條件下做的研究跟實際應用會有所落差,因此必須特別注意。

為了解決spatial partition會造成切出過小cluster的問題,我們需要另外找其他的方法來處理。這時候講者關注了以下二個方法:

  1. Quick-VDR:Interactive View-Dependent Rendering of Massive Models https://gamma.cs.unc.edu/QVDR/
  2. Batched Multi Triangulation: http://publications.crs4.it/pubdocs/2005/CGGMPS05a/ieeeviz2005-gpumt.pdf

這二個方法都是藉由增加節點上存儲的資訊來減少限制,他們把每個cluster相關的空間資訊儲存到節點上,因此在樹的建構上會多了不少彈性。不過實際上由於我們已經用monotonic error function做bake的手法把節點之間需要的溝通移除了,因此重點需要放在我們這顆樹該怎麼被建構出來,主要的目標是上面的資訊要能夠讓我們對cluster上的三角型數量或需要被lock的boundary edge的數量做優化(越少越好)。(*22)

*22: paper我還沒空去看,但根據講者講的,讓我感覺這個技巧有點像是計算理論(Theory of computation)中提到的problem reduction:利用增加減少資訊量或限制的手段,把問題轉換到我們可以處理的範圍後再把演算法套進去解決。例如某個人想要到月球上,但他不可能自己造火箭,因此問題就可以化簡成去找NASA看看他們有沒有提供登月的服務。這個方案雖然移除了造火箭需要去研讀的知識量,但多出來的限制很可能就是他要出的起這筆錢。這邊可以特別注意的是,reduction過後的問題不一定會比較簡單,他只是把問題換成另外一種形式而已,例如對某人而言,努力讀書進到NASA當太空人可能比他賺到250億美元還要簡單。

既然提到了計算理論了,那我覺得可以快速的帶過這個概念。

計算理論主要是在談電腦能做到哪些事以及他的極限在哪。主要可以分為以下二個部份:

  1. 談怎麼做計算:我們可以把問題用一個有限狀態機建構出來,接著餵入一堆符合regular language定義的symbol序列作為輸入來觸發狀態的遷移,最後到達goal state就可視為問題被解決的狀態。對於這樣一個解決問題的計算模型,我們稱它為圖靈機(Turning Machine),當然這邊是很簡略的描述,而我們現在的電腦是屬於通用圖靈機(Universal Turing machine),意即他可以模擬任意圖靈機來解決任何計算問題。
  1. 談計算要多久:對於所有的問題,我們可分以以下幾類:P/NP/NP-Complete/NP-Hard。首先我們需要有三個function當作工具:1. 用來解決問題的function。2. 用來驗證答案是否正確的function。3. 用來reduce做問題轉換的function。

2.1 Polynomial Problem:指的是解決問題的function可以在P時間內處理完。

2.2 Non-deterministic Polynomial Porblem:指的是解決問題的function目前還沒找到能在P時間內處理完的演算法,所以是non-deterministic,並且驗證答案是否正確的function需要能在P時間內處理完。

2.3 NP-Complete:任何一個NP問題都能利用reduce function在P時間內轉換過去時稱之,也就是說他能夠把所有的NP問題歸納到這個問題上。因為還是NP問題,所以要符合NP問題的定義。NP-Complete是NP中的代表性問題,若能找到一個演算法能在P的時間內解決這類問題,代表我們證明了P=NP這個世紀大難題,這基本上可以得圖靈獎了。

一個代表性常被提出來討論的NP-Complete問題叫Boolean satisfiability problem,簡稱SAT。 這個問題很重要,它幾乎可以解決所有的計算問題。SAT說明了我們是否可以把問題拆成一堆回傳bool的function,然後在套用演算法後是否能找到一組參數,能夠在餵進所有被拆解出來的小bool function後全都回傳true,全部回傳true這代表問題被解決了,我們可以想像成它在一個有限狀態機上到達goal state。SAT問題是現代計算機的基底,因為所有高層的計算輸入,最後都是被拆解成一個一個只會回傳1跟0的邏輯電路。

另一個著名的問題是CSP(Constraint Satisfaction Problem),跟SAT問題有異曲同工之妙,他是說我們有一組輸入跟一組限制條件,我們能不能找到一組輸入滿足所有的限制條件,也就是說讓我們的有限狀態機到達goal state。UE5中有個叫Wave function Collapse(WFC)的experimental plugin就是用來實作解決CSP問題的演算法,這個演算法的概念是從量子力學借用過來,常見是用在random dungeon產生器的製作上。這個plugin被用在Matrix Awaken的city generator中,該演講有展示了黑貓產生器,結果就是我們可以把頭跟尾巴限制在頭尾,中間身體可以是任意長度,這樣就可以讓我們的遊戲中有不同長度的貓存在。

另外NP-Complete問題還有圖著色問題、背包問題、旅行推銷員問題……等等,基本上我覺得研究NP-Complete的問題就是在對計算本質的思考。由於所有的NP問題都可以在P時間內reduction到NP-Complete問題,要把手邊的問題reduction到哪個NP-Complete問題,就要看哪個最接近你的domain problem、reduction function容不容易設計、專案中是否已經有相關Library的實作,這樣才比較容易維護跟理解。

2.4 NP-Hard:當我們的問題沒辦法在P時間內處理完,而且驗證答案是否正確的function不一定能在P時間內處理完。NP-Hard跟NP-Complete有一些交集,意即有些是NP-Hard問題可以在P時間內驗證完答案,也就是說所有的NP-Complete問題也是NP-Hard;沒交集部份,一個常見的例子是Halting Problem,這個問題的計算永遠停不下來(undecidable),更不用說要去驗證他的答案。這個問題在講說若有二個機器,其中機器A能夠判斷任何程式是否halting,若halting回傳true否則回傳false,另一個機器B則輸出跟機器跟A完全相反的結果,若A停B就跑,A跑B就停。但當機器B把自己餵進去的時候,就會就會產生邏輯矛盾,讓程式永遠停不下來。 另一個例子是理髮師悖論,這邊是我改過的版本:我們先定義理髮師這個職業只會幫除了自己以外的人理頭,另外定義肥宅這個職業會因為懶得出門,所以只能在家幫自己理頭。 但這時候如果某個人身兼理髮師跟肥宅這二個職業的話,那他到底能理誰的頭?

回到nanite的演講上,作者為了解決Triangle Cluster裡面有太少triangle的問題,他先把問題reduce成可以套用Quick-VDR這個演算法的型式,然後再以此為基礎進一步的導入自己的heuristic做加速,例如把QEM bake進Quick-VDR樹上的節點,為的是讓Triangle Cluster選擇LOD的操作可以平行化丟進GPU執行。

因此在處理irregular-mesh的時候,Quick-VDR跟Batched Multi Triangulation會是二個比較好的選擇。因為LOD跟Occlusion的選擇可以丟到GPU上跑,其他比較複雜用來處理crack的計算則是放在build time。比較大的triangle batch我們會直接做render,而所有的triangle data只會被讀進來一次、並且所有的Triangle cluster都可以被獨立的被保存下來。各種mesh compression機制只要我們確保boundary match的上,就可以直接套用到cluster上。

 

Drawcall and Quad Overdraw

除了前面討論的那些處理high poly mesh的方法之外,drawcall也是個必須要面對的問題。只是drawcall並不像polycount是美術細節跟render成本之間的拉扯,更多是決定於場景的建置方式,例如我們場景中某個書架上的書需要合併成一個mesh嗎?但如果我們在圖書館中有多個書架,書架的款式要不一樣嗎?上面每本書的排序需要有變化嗎?有更小的控制粒度,代表我們的場景可以有更多的變化,但這也代表drawcall越高。

所以說我們把控制粒度變粗一點,對於效能而言會是好事囉?不儘然,因為粒度變粗代表我們把很多mesh都合併在一起了,這會造成我們在做visibility culling時沒辦法去掉那些看不到的mesh。

因此Virtualized geometry的另外一個目標是,我們的方法必須不能影響到製作人員的場景建構決策,而不管使用者做任何決策都不能犧牲效能跟品質。mesh要合併還是分開來放,通常是看當時的狀況,因此控制權要完全交給使用者。

因為view dependent、animating以及non UV based的material常常會把我們做出來的cache invalidate掉,因此講者決定把visibility從material中decouple出來,主要是他想讓每個triangle在每個frame只被rasterize一次,而不是需要多個pass才能移除overdraw(*23)。雖然有方法可以減掉overdraw,但由那些細長三角型所造成quad overdraw的render效率議題還是沒解決。講者在過去處理virtual texture的經驗上,最好的解決方案就是把material evaluation做一份cache弄進virtual texture中。這個概念跟在texture space shading(*24)很像,但不包含lighting。這個存進virtual texture中的cache基本上是一份compressed GBuffer。另外存一份compressed GBuffer的理由是,若果真的模彷texture space shading的機制,那這份資料會需要每個frame都去做更新,所以很難從cache中得益而且他並不能避開pixel quad overdraw的議題,除非我們保留一份成本很高的visibility buffer給texture space,但相對的,使用compressed GBuffer的話,就很容易做cache跟持久化,雖然會排擠到其他美術可用的material shader預算,但最後在rasterizing triangle的時候,每個pixel只需要physical texture的UV資訊就能產出output。這個idea跟SIGGRAPH 2015時,Sebastian Aaltonen所做的演講非常相似,但他們各別想出了這個方法。

*23: 這邊聽起來是想要去掉depth prepass,讓每個物件只被處理一次。

*24: TextureSpaceShading這個技術的idea是,我們不需要每個pixel在每個frame都做一次shading,我們可以拿前一個frame的值來利用,又或者是在VR的case我們左眼shading完後的結果可以用在右眼上。這個技術的原理是會把shading計算的結果記錄在一張texture上,之後pixel再用正規的texture sample操作把需要texel拿出來map回去,而所有的texel只有在收到shade request的時候才會重新計算,不然就會一直重覆利用。

Brief idea Summary for Virtualized Geometry

到這裡講者覺得已經可以下手,因此把一些idea總結起來往上提報:

  1. 使用HLOD搭上Quick-VDR的方案,整個演算法跟原始paper不同的地方在於我們會把所有的LOD的計算邏輯移進GPU,這代表著我們可以搭配HZB(*24)使用,並基於前一個frame的depth buffer來做occlusion culling。
  2. triangle cluster預計使用Multi Draw Indirect這個GPU新API來做繪制。(*25)
  3. GBuffer會被cache進texture space,並且只會分時分區做更新。

只是當時有其他優先權更高的任務,因此就先被抓去做其他東西了。

*24:  HZB(Hierarchical z-buffer)主要的概念就是在GPU中針對z-buffer做了一組mipmap,他可以用在很多基於ScreenSpace相關的應用,例如SSAO、SSR,不過最常見的是用在occlusion culling。這個方法做Culling會比較保守一點,因為他會先計算測試物件在營幕上的比例拿出最適合的那張mip-level來做測試,只要有一點點被看到的可能性才往上拿更精確的那一張,由於大部份的物件在第一次做測試的就候就會被cull掉了,因此可以省下不少texture fetche的時間。第0級會原留原始深度,所以不用擔心會有東西漏掉。

https://docs.unrealengine.com/4.27/en-US/RenderingAndGraphics/VisibilityCulling/

https://zhuanlan.zhihu.com/p/439858076

*25:  Multi Draw Indirect(MDI):可以想像成他是一個Drawcall Array(Command Buffer),裡面有很多Sub Drawcall,每個Subdraw可以指定不同的Command type做不同的事,等同於我們事先用一個batched multi-drawcall就把一系列的指令跟參數丟給GPU,讓我們可以在GPU上做scene-traversal跟culling 。

為什麼叫Indirect?暫時沒找到相關的描述,目前我的理解是,direct draw需要的是從CPU發起來drawcall需要帶入所有需要的資訊給GPU,而Indirect則是CPU發起的drawcall只是指令,叫GPU去把他本來就存在GPU Memory中的資料拿出來做事,真正準備各種繪製需要資料的工作是發生在GPU端。

我們知道mesh instancing是常見用來減少drawcall的手法,在DX11的舊方法使用DrawInstanceIndirect,每個 drawcall之間我們只能改變他的instance id,而ExecuteIndirect就是Dx12對於MDI概念的實現,它移除舊方法中的很多限制,可以讓我們做出更多的drawcall merge,例如他允許我們在每個Subdraw利用Command type來改變state跟binding。

在UnrealEngine中,它把這系列的操作用RHIDrawPrimitiveIndirect跟RHIDrawIndexedIndirect封裝了起來,想當然肯定是支援各平台的,例如OpenGL、Vulkan、Metal。

https://zhuanlan.zhihu.com/p/97671775

https://learn.microsoft.com/en-us/windows/win32/direct3d12/indirect-drawing

https://microsoft.github.io/DirectX-Specs/d3d/IndirectDrawing.html

https://www.cnblogs.com/dydx/p/10994457.html

https://zhuanlan.zhihu.com/p/452013032

Geometry 2.0

那時候Epic打算整個翻新他內部從UE1時代開始的上古工具 BSP geometry editing,因此開始了名為Geometry 2.0的專案。這個任務是講者真正意義上在epic裡的第一份工作。Geometry 2.0還有另外一個涵意,一個全新面向未來的model data format也是以此命名。(*26)

*26:  我沒有找到Geometry 2.0的定義,但從上下文的描述來看,猜測是他想要改成使用Curve跟Curved Surface取代Triangle來表達Geometry的資料結構。官方論壇從2015年開始就有Geometry 2.0相關的討論,可以看得出來Unreal希望能夠發展出新的Geometry資料結構與圍繞在該資料結構設計的新工具來取代原始的BSP建模流程。

使用Curve的好處有很多,在Real-time rendering的第17章有提到,他有以下4個好處:

  1. 比起triangle,他能夠更精簡的表達一個模型。
  2. 他能夠簡單的做scale,意即我們可以隨意的在Curve上產生任意數量的三角型後再餵給GPU處理,代表他天生就擁有做LOD的能力。
  3. Surface的表面比起三角型,能夠有更加平滑的表現。
  4. Animation跟Collision Detection的機制會更簡單,對於動畫我們只要移動Curve上的Control Point;對於Collision我們只要用數學方法去計算有沒有相交。整體效能表現會更好,不再需要去處理一大堆的Geometry Primitive Type(點、線、三角型)。

演講中一直出現future proof modeling這個詞,從以上的好處來看應該是八九不離十了,或者可以說是非常類似的東西。

只是Curve有這麼多好處,那為什麼現在建模方法還是流行使用PolygonMesh呢?University of Utah的Cem Yuksel教授在他的公開課中有提到,使用Curved Surface來建模的話,你需要先理解公式,但就算是你理解了公式你也很難做控制,常見使用Curved Surface建模方法有Bezier Patches、NURBS(Non-Uniform Rational B-Spline);相對而言,Polygon mesh簡單粗暴,雖然需要非常多的勞力活去微調頂點位置,但可以非常精確的控制物體表面怎麼呈現。

目前Curve比較常用在毛髮之類的應用上。

https://forums.unrealengine.com/t/csg-bsp-mapping-tools-progress-geometry-2-0/27038

https://forums.unrealengine.com/t/geometry-editor-2-0-suggestion-thread/18088/19

https://www.amazon.com/Real-Time-Rendering-Fourth-Tomas-Akenine-M%C3%B6ller/dp/1138627003 見第17章介紹Curve跟Curved Surface

https://www.youtube.com/watch?v=EM73mJwfwLw Cem Yuksel教授關於Curved Surface的介紹

那時候他們想解決的問題,是想要直接做一個有最高品質的mesh,該模型是future proof,也就是說可能在10年後重開某部作品的續集製作時,不需要因為圖型技術的演進而需要重新製作更高規的美術模型。──這跟講者剛開始的目標相同,應該說是領域中所有人的共通夢想──如果我們從data format、工具,整個從一張白紙開始設計,或許我們可以達成這個遠大的目標。

這邊不談這個專案最終想達到什麼目標的細節,但簡單來說,我們在解決問題的過程中非常綁手綁腳。一個例子是,如果我們想設計統一的方法來解決所有建模問題的話,代表我們首先需要把舊的BSP Modeling tool移除。只是舊工具有很多BasicShape是CSG(*27)一系列操作的核心元件,取代BSP,代表我們新的建模工具也要能支援CSG操作,不然會讓舊的版本沒辦法運作。然而當我們對2個做完Catmull-Clark Subdivision的surface做boolean交集操作後的mesh,出來的結果跟我們利用新的建模方法建出來mesh根本八竿子打不著,但是為了解決這個問題,若只針對他的control edge做boolean,出來的結果只會變得更亂七八糟。記得我們的遠大目標是直接建出一個擁有最高品質的mesh嗎?若是為了支援CSG,限定我們的boolean操作只能對surface subdivided到某個level才能做的話,就違背了我們的初衷,我們希望能夠他在surface subdivided之前就能夠支援boolean操作。

由於有太多類似的問題了,因此Geometry 2.0最終被暫停了。

*27: Constructive Solid Geometry(CSG)最主要的特性是支援使用Boolean Operation來對Geometry進行建模。(And, or, not, xor)

https://en.wikipedia.org/wiki/Constructive_solid_geometry

Constructive Solid Geometry – Friday Minis 142

不過雖然Geometry 2.0原本的遠大目標從2015年到2022年的現在還沒有看到盡頭的那天,但我猜過程中開發出很多東西應該都在UE5新版的Geometry tool中被繼承下來了,可以看以下影片介紹:

Exploring Geometry Tools in UE5 | Inside Unreal

作者這邊開始在思考計畫被暫停的原因,他覺得想要設計一個大一統方法非常困難,有時候甚至是罪惡,雖然統一的方法可以確保使用者體驗是一致的,但面對各種狀況時有會有他適合的工具。這個論點,當我們去看美術他們的工作流程時就會發現。之前當我們嚐試使用Geometry 2.0時,會希望美術利用新的建模方法建出一個最簡略的版本,之後再丟進Catmull-Clark subdivision來產生高精度的模型。本來是想簡化美術們製作模型時的勞力活,但是實際上他們還是會加一大堆的Control Point在裡面,在影視製作上這個數量又會往上一個量級,這代表我們餵進去的東西不會是最簡略的模型版本。為了把美術製作的版本簡化,我們的解決方案裡面會需要存在二套方法:一個是處理簡化模型、另一個是Sbudivision負責細分模型。因此我們最初打的美好主意終究無法實現,想要靠adaptive subdivision的機制讓我們的模型是future proof,需要先說服美術們改變習慣的製作流程。

Confidence

講者研究了非常多可能的方法,雖然他有自信自己在geometry這塊領域上沒有人踩的坑比他還深,但一直沒有機會去試,主要原因在於他知道他加入的團隊是由一群非常聰明的人組成的,而且團隊已經運行很久,他沒有足夠的自信去挑戰整個團隊的研究方向。看著成員們不斷重覆踩自己過去犯過的錯誤,他也在思考著怎麼克服被指正錯誤時害怕的感覺,把自己知道的東西傳達給團隊。他覺得這段歷程自己學了非常多的東西,不要假設自己的知識沒什麼,在跟別人交流的過程中常常會有非常多的啟發。尤其是團隊中有各種專家,肯定在他的領域上都有非常卓越的見解。只要你踏出第一步,單純分享自己所知,不要害怕自己說錯什麼,知識互相碰撞常常會有意想不到的收獲,暫時的錯誤只是你到達真理前的一個狀態而已。

我很喜歡演講中這段分享,這邊附上原句:Being wrong is not nearly as bad as you imagine. It’s the state you are before being right.

Virtual Texture Implementation

還記得講者剛進Epic的時候有提出想要把原本引擎內的mip-based texture streaming換成VirtualTexture的事嗎?當初整個團隊都認為不是問題,但那只是工程師們還沒感受到痛苦而已。美術們在進行製作時,通常會習慣性的管理他們產品的預算,因此工程師們從來不會知道他們的痛點在哪裡。一些比較大的創新通常不會是來自使用者的反饋,因為美術們不會提出一些看起來不可能的要求。因此理解你的使用者非常重要,看他們怎麼工作,常常能夠幫助你找出哪些東西是需要被解決的。

不管VirtualTexture在過去是不是個好主意,但由於SSD的普及,在未來肯定會扮演一個重要的角色。講者知道,Virtualized geometry肯定會是下一個撼動整個業界的大事,為了讓模型的真實度有飛越性的提升,我們必須要能在合適的streaming控制與資料壓縮的技術下對大量的資料進行virtualization。只是要達到這個目標之前,必須先把VirtualTexture實作出來才行。

New Work published

這時候市面上有一些作者感興趣的技術發表了,其中幾個是關於改變了整個landscape的製作方式,主要是在談怎麼利用triangle cluster以及GPU-Driven Rendering的技術來做culling,這部份可以直接取代作者之前思考的做法;另外則是MediaMolecule發表怎麼在他們遊戲中使用Voxel以及point based rendering的研發過程分享,帶給講者不少的啟發。(*28)

*28

第一篇是Ubisoft於2015年發表的GPU-Driven Rendering Pipelines,主要是在講怎麼讓GPU保留整份場景的狀態,把本來在CPU上做的計算儘可能的往GPU的Compute Shader丟。這其實就是GPGPU這個概念的一種實現方式,部份GPU的效能不是用來做render而是分擔CPU的工作,由於近來GPU Memory越來越大了,使得GPU保留複雜場景資料這件事變的越來越可行。

由於GPU上有所有我們事先在CPU上準備好的資料了,所以理論上只需要一次batched drawcall command就能夠畫出所有的opaque geometry。以前Dx11上是靠DrawInstancedIndirect這隻API,在DX12時代則是使用是ExecuteIndirect。使用multidraw indirect(MDI)來取代舊方法可以讓我們做出更多的drawcall merge,這隻API是可以說是近代引擎怎麼邁向Fully GPU-Driven pipeline的關鍵。Unreal從4.22開始就開始逐漸的把整個Drawing pipeline改成Retained mode以支援GPU-Driven pipeline的思想,這部份的細節可以看官方於GDC2019關於Refactoring the Mesh Drawing Pipeline的演講。

GPU可以幫忙做掉的事包含各種Culling以及LOD的選擇……等等。在UE中可以搜尋GPUCULL這個關鍵字,只是我們會看到一堆TODO。特別是GPU非常適合處理這種大量重覆資料的平行化,比起單純在CPU上使用ISPC做SIMD計算,不僅能大量減少CPU跟GPU之間的溝通,而且在效能上會有飛躍性的提升。由於這個方法非常強調Drawcall merge以及cache,而所有的cache都有怎麼做invalidation的問題,因此我們還是必須要從CPU使用某些手段把資料更新到GPU上那份Cache上去,這部份的細節一樣可以在MeshDrawingPipeline的介紹中看到。

在目前Unreal最新版本5.0.3中,這些Cache被封裝在FPersistentUniformBuffers這個Struct中,裡面包含了:

  1. ViewUniformBuffer
  2. NaniteUniformBuffer
  3. LumenCardCaptureViewUniformBuffer
  4. MobileDirectionalLightUniformBuffers
  5. MobileSkyReflectionUniformBuffer
https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf

https://vkguide.dev/docs/gpudriven/gpu_driven_engines/

https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-executeindirect

第二篇是EA在2016年發表的Optimizing the Graphics Pipeline with Compute

https://frostbite-wp-prd.s3.amazonaws.com/wp-content/uploads/2016/03/29204330/GDC_2016_Compute.pdf

第三篇是MediaMolecule發表研究怎麼在PS4中分試著使用Signed Distance Field(SDF)、Voxel跟PointCloud的技術來做非常手繪風的UGC的分享。Dreams比較像是一個PS4上的遊戲製作平台,很像pc上的roblox。該平台重點是UGC,玩家在上面做創作或是遊戲,而做出來的東西需要讓全世界的玩家下載,因此不僅做出來的檔案要很小而且要易於在PS4上用手把編輯。他們的研發過程在很多相關的研究都有提到,看起來啟發了不少人。

A Survey of Promising, Unconventional and Mostly Abandoned Renderers for ‘Dreams PS4’, a Geometrically Dense, Painterly UGC Game

Alex Evans at Umbra Ignite 2015: Learning From Failure

http://advances.realtimerendering.com/s2015/AlexEvans_SIGGRAPH-2015-sml.pdf

What is Dreams? (PS4)

Define the requirements

到目前為止講者已經湊齊幾個解決問題的要件,接下來需要的是把這些東西要解決的範圍跟要求正式完整的陳述出來。他沒辦法改變整個CG產業的workflow,因為大部份modelling跟texture的工作都發生在unreal之外。我們沒辦法控制使用者想要使用什麼軟體來製作,以及輸出的format是什麼,他們做出來的asset可能本來是要給電影用,因此有著他們自己的UV以及tiling detail maps。從使用者的觀點來看,我們新的virtualized geometry 技術必須要能直接放進static mesh中。

這邊講者提了一些例子說明在遊戲中該怎麼做記憶體優化。

第一點是做好貼圖的UV:學術界常常把製作貼圖跟在mesh上畫畫混在一起講,但2D貼圖更常被當成壁紙使用。我們可以籍由編輯UV來控制貼圖上每個像素在3D上的位置來節省需要的貼圖張數。關於這點,最好的DEMO是由Tor Frick在2012年所設計的SciFi lab場景,整個Level只使用了一張512×256的貼圖。雖然這個例子有點舊了,但他展現極緻的記憶體優化手法,幾乎可以在現在所有的遊戲中看到。(*29)

*29:  Amazing One – Texture Environment

https://www.unrealengine.com/en-US/blog/amazing-one-texture-enviroment

第二點是data reuse:Model instancing是常見的Geometry data的reuse方法;texture tiling是常見的texture reuse的方法。John carmack曾說過,texture tiling只是一種有限形式的compression。──這句話是對的,非常正確,因為在遊戲中我們常常會需要重覆利用上千上萬次同樣的資料,不管是Geometry或Texture,我們沒辦法在一些通用的image compression方法上看到這種壓縮率。

在未來,那個metaverse到來的世界中,或許stream的效率會有飛越性的提升,可以把所有看到的東西即時顯示出來。不做data reuse或許到時候也不會有什麼問題,但受限於目前的技術,在可見未來data resue還是扮演著非常重要的角色。

因此,nanite的下一個要求是要能夠支援instancing並且mesh要能夠做transform,而且上面存放的資料必須要是可以重覆利用。

最後一個需求是,必須要夠快,至少要有60FPS。但這句話不代表可以把完整16ms全部花在畫geometry上,我們還必要把時間留給遊戲中的其他系統,例如lighting跟shadow。講者預期畫geometry跟material必須要小於6ms,去掉material shader evaluation部份的話,代表geometry的部份可能最多只能有4ms。

Chart a path

要一次達成這些要求非常困難,因此講者分了幾個目標開始執行。有一些階段性的成功,不僅能夠增加你自己達成目標的信心,更重要的是高層開心的話你的專案被關掉的機率也會變少。理想上你可以試著把中間的結果給美術看看,或許會得到一些之前沒有思考過意見。

在geometry budget這個問題上,最優先需要被解決的是場景上的static mesh,其次是deformable geometry跟animated character,理想上要能基於相同系統的擴充,不需要馬上解決,但要能確保我們設計的方法有條路能夠通往那裡。第三件議題是terrain,雖然改善render的品質以及製作工具是一件值得做的事,但卻不是我們的目標;實際上就地形製作而言,它並沒有什麼真正的geometry budget問題,雖然可以使用一個共同的geometry架構,但這代表我們需要引入新的限制,這太困難了。

Use Visibility Buffer for Solving Cache Invalidation Issue

在經歷過Paragon專案後,講者得到了一些教訓。Surface caching這個idea是先假設一件事實,那就是我們每個frame需要產生的page數量是非常少的,因為的大部份都會從cache hits上取得需要的資料。但是在遊戲中,我們的material很多是以下三種:view dependent、animating以及non UV based,而這些material會invalidate我們做出來的cache,這代表我們每個frame會產生大量新的page。

為了解決這個問題,一個方法是導入Visibility buffer(*30)來做deferred materials,講者在他的筆記中有特別強調deferrd materials上面只存Object ID + Triangle ID,如果存了任何vertex attribute,例如UV跟normal,他認為應該稱作deferred texturing。

*30 Visibility Buffer第一次提出是在2013年,這技術原本提出來的目的是用來解決G-Buffer以下二個問題:

  1. 在讀寫時需要消耗太多GPU頻寬,以原始paper給的例子來看,在一個4百萬像素的營幕上跑60Hz,假設每個pixel的sample是4個24 byte的channel(RGBA),則我們每秒需要耗費46GB的頻寬。

4(百萬像素)*60(Hz)*4(channel)*24(byte)*2(read/write)=46080MB/1000 約等於46GB

  1. 在分離看不到的pixel顏色計算時還是有一些浪費:在render pipeline中通常會實作prepass搭配zbuffer來幫助我們去掉不同物件之間的overdraw問題,但同一個物件間的三角型互蓋的問題並沒有解決。Deferred Shading搭配GBuffer本來的目的就是想要避免這類的overdraw,讓lighting的計算只會針對那些看的到的pixel進行處理。只是,並不是所有的shading都被deferred,為了產出gbuffer,我們還是需要先做一次預處理對整個場景render過一次,稱為geometry pass,在這個階段我們會場景上各種物件的surface property做各種不同的計算,再把結果存到不同的GBuffer中。但是,這時候場景上互相擋住的三角型還是會重覆多次把他的surface property計算結果寫進GBuffer中,關於這點我們可以想像有一顆樹叢,我們沒辦法保証上面的葉子那個會被先畫,因此前面計算出來的結果就會在GBuffer上被後面畫的蓋掉。也就是說我們能避的只有pixel算lighting的部份,計算surface property這塊省不掉。

在Unreal中完整的GBuffer儲存的Surface Property如下:BaseColor、Separate Translucency RGB and Alpha、Scene Depth、Specular、Roughness、Subsurface Color、Metallic、World Normal and Tangent、Opacity、Shading Model,這些資訊會分成幾張貼圖存到不同貼圖的Channel中,而貼圖總數量會跟平台有關,例如Mobile Deferred Render的GBuffer為了支援大約16%左右只有4個Input Attachment的Android機器只能把GBuffer限制在3個(最後一個要留個SceneDepth),而PC則沒有數量的限制。

使用Visibility Buffer的話,不像GBuffer要把算出來那堆Surface Property寫進貼圖中的動作,我們預處理只需要針對visibility做計算,小場景的Sample只4 byte,大場景8byte,然後之後還要拿出來計算,比起gbuffer的96byte(4*24),可以大幅度的減少GPU的頻寬,這在mobile或integrated GPU上這類GPU水管比較小的機器非常有價值。

這個技術的重點是我們在前期做預處理的階段只計算visibility,並存下該pixel所對應的深度值、triangle ID以及cluster index,在Unreal中是使用uint2,二個4 byte的uint(見NaniteVertexFactory.ush中的UnpackVisPixel),前7個bit存TriIndex,接下來25bit是VisibleClusterIndex,剩下32 bit存depth。這樣我們就可以真正達成把surface property分離出來,之後要針對看到的部份做計算的時候,再用某些手把把幾何重建出來就行了。

只是在unreal中,nanite導入visibility buffer的目的並不是像原本paper一樣是為了取代GBuffer的流程,而是作為輔助流程來幫助我們更快速的產出GBuffer。

參考資料:

The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading, https://jcgt.org/published/0002/02/04/

http://filmicworlds.com/blog/visibility-buffer-rendering-with-material-graphs/

https://learnopengl.com/Advanced-Lighting/Deferred-Shading

UE5 Deferred Technology Pipeline for Mobile Games

 

Prepare Tech Direction for UE5

接下來新的Consone平台發表(PS5/XSX),UnrealEngine準備進行一次大升級,從UE4到UE5,需要的是一次飛躍越性的提升,只是單純的在既有系統下增加功能是不夠吸引人的,他們需要的是對「次世代」遊戲做一次定義。講者覺得這是他的一次機會,可以向公司展現他對於未來想像,可以在遊戲畫面上做一次根本性的提升。


團隊內討論的結果其實想要的方向都還蠻接近的,由於講者的壓箱寶是Virtualized Geometry,因此就順勢的在這時候提了出來。從這個時機點開始,virtualized geometry這件事,從講者本來只是有空的時間進行的研究,開始變成了他全職的工作。

為了展示講者對於成品的想像,他找了幾張art station上的concept art: https://www.artstation.com/artwork/oxVk 。畫面上所有的東西都佈滿了管線,這麼極端複雜的場景很有可能讓他接下來要做的任何東西炸掉。就像是之前提到,現存的研究很多都是用雷射掃描進來的東西來做測試,但那些mesh通常是regular mesh,會有光滑且平均的表面平均三角型大小都會比一個pixel還大;但是這張圖中展現的東西卻不同,它們的表面幾乎都不會是光滑跟連續的,並且很多三角型都會小於1個pixel。

講者沒有信心能夠處理大量小於1個pixel的三角型,當硬體遇到一大堆三角型小於1個pixel場景時會有什麼結果可想而知。他不想要他的研發只能用在像大塊岩石這種Genus為0的mesh上、而且也不要因為resample的關係讓整個畫面丟失一堆細節。他想要最後的成果能夠應用在hard surface跟幾械這類非有機物的表面上。這幾個期望,也是他放棄使用geometry images跟displacement的想法,改往irregular mesh研究的主要原因。

講者非常確定triangle cluster為底的rasterization可以處理非常大量的三角型,但對pixel level triangle還是沒輒,那麼該怎麼辦呢?雖然可以先循著這條思路先往前走再說,但UE5這塊招牌的重量讓他決定先研究看看有沒有其他方案。

Triangle Ray Tracing

比起rasterization的問題,這時他首先關注的是怎麼使用ray tracing來找出那些需要處理的triangle,由於這個方法通常使用Bounding Volume Hierarchy(BVH)來加速(*31),他是樹狀結構,因此時間複雜度是Log(N)。這表示這個演算法可以很好的做scale up,例如使用有4-way BVH(每個node下面每次切4個child),處理1百萬面triangle需要10次的讀取,10億面則需要15次。當時Oculus research發表的Hierarchical Ray caster,說他們每秒能夠有100億個ray,這個賣點確實吸引了講者的眼球。但做了一些實驗之後,講者發現對於VR應用可能會有很好的效果,但對於處理pixel size的triangle沒有任何幫助。

*31: Bounding Volume Hierarchy(BVH):為了加速我們做ray tracing的速度,通常我們都會想辦法減少需要判斷物件的數量而不是一個Array從0開始處理到N。這時候常見的手法是使用tree來幫忙我們將物件分群,這個資料結構一個非常好的特性是他的複雜度是O(Log(N)),因此可以處理非常大數量的物件。而分群的手段有幾種:

  1. 基於物件分割:例如BVH。
  2. 基於空間分割:例如Octree-Tree對世界做grid切割、或BSP-Tree及其只針對軸向做平行切割的特例KD-Tree。

BVH的核心想法是,我先想辦法對場上的物件分群,中間每個結果都儲存可以包含一個夠大的BoundingBox包含群組內所有的物件,葉節點存儲實際的物件,這個BoundingBox可以是AABB、OBB或者是Sphere…等等,之後我們只要遍歷這顆樹,ray有打中的再往下找到葉節點,之後就可以找到ray發射出去打中的那個實際物件。

然而只要關於樹,直覺就會想到最差的狀況:該怎麼樣確保我們這顆樹長的夠方正而不是歪一邊呢?最差的case會造成我們這顆樹會退化成O(N),這時候通常會導入一些Heuristic Function來幫助我們判斷這刀切下去的成本是多少,成本越低代表我們分的越好,之後在做Ray tracing的時候可以過濾掉越多需要判斷的物件。常見的方法是使用surface area heuristic (SAH),利用物件的大小來當成衡量的標準,當然還有一些其他相關的變體研究這邊就不細講。只是怎麼建構一個optimal BVH普遍被認為是一個NP-Hard的問題。

https://meistdan.github.io/publications/bvh_star/paper.pdf
https://medium.com/@bromanz/how-to-create-awesome-accelerators-the-surface-area-heuristic-e14b5dec6160

這時候講者意識到,如果我們能找出座落在Pixel Center的Triangle,我們可以模仿hierarchical ray caster的機制做hierarchical rasterizer。他不確定這個idea之前是不是有人做過,但基本想法是我們一樣打ray出去,但不是對場景上BVH,而是對整個frame 做BVH,希望最後找到那些pixel size triangle。只是整個方法會產生很多cache miss,雖然Trace的成本是O(LogN),但Cache miss帶來的成本卻是O(N)不是很划算(*32),而且BVH data的建構也不是很便宜。

*32: 在Computer Architecture: Aquantitative Approach這本書的Figure B.29有提到,一半的Cache Miss會增加總體執行時間約25%。我目前沒辦法想像怎麼對frame做BVH切割,只是照字面上的意思來看的話,講者的idea聽起來Cache Miss會將近到100%,也就是說會增加成1.5倍的執行時間。

Point-Based Rendering

既然Ray Tracing的方法沒辦法處理pixel size triangle,那麼如果改用Point來畫呢?由於Point-Based Rendering之前有人發表了相關的使用成果看起來還不錯,而且也能用在動畫上,因此非常值得一看。(*33)

作者曾經從他的美術同事上聽到,他們以前在用DCC建模的時候,在處理複雜場景的時候,會使用VRayProxy這個東西來避開記憶體的問題,這個功能會放一個暫代Point在場上,之後算圖的時候再把實際的模型讀取進去。這個看起來神奇的功能,其實只是用了一個簡單的surfel(surface element)暫代原本複雜的模型,並且這個點可以參數化,上面存儲有normal、position之類的資訊,之後render time時再傳給實際的模型。這個可以「代理」原本複雜模型,並且可以把參數從material上decouple出來的性質,讓講者覺得用point based rendering這個方法有些希望。surfel這東西,就 wiki 的定義,可以想像成是相對於 volume 上的 voxel ( volume element) 跟營幕上的pixel (picture element) 。

但是在做了一些survey之後,發現有幾個問題:

  1. 用在一些thin feature的效果很糟,例如薄牆。由於同樣mipmap層級的Point的大小都一樣,想像我們有一條細長的繩子,離遠一點的部份看起來都特別粗,然後當繩子上的某個point跳到下一層的mipmap才突然變細。這個問題在下面提到的voxel也會遇到。
  1. 由於我們需要用Point來表達一個物件的surface,因此必須想辦法把二點之間填滿,但是我們該怎麼定義哪些是該填滿的hole呢? 很多問題需要導入connectivity metadata,另外儲存點跟他鄰居的資訊才行,但這基本上就是在重新發明Triangle mesh跟index buffer。
  1. 原本能支援動畫是考慮Point-Based Render的一個主要因素,但支援動畫有著更麻煩的填洞問題,例如當你的動畫人物是non-uniform scale時該怎麼辦?

*33: Point-Based Rendering指的是我們使用一堆點而不是triangle mesh來做render,在LiDAR( Light Detection and Range)中,他每個點的定義可以是位置(xyz)或位置(xyz)+顏色(rgba)。用這個方法有一些好處,當我們想從現實世界掃描模型或把一段影片轉成3D時,通常是先產生一堆點的資訊,之後可以再利用一些演算法把這些點連在一起來產生Triangle Mesh。但是不讓這些點有關連,單純使用Point來繪制可以做到一些有趣的效果,例如讓畫面呈現非常手繪的風格。Non-Photorealistic Render(NPR)之類的風格化應用是大部份使用這個方法的原因之一,可以參考前面介紹的Dreams。關於怎麼把點跟點之間的gap補上,常見是搭配Gaussian filter使用。

LiDAR這個技術跟Radar有點像,但他並不是用聲音,而是用雷射來取得跟目標物件的距離,常常用在自動車駕駛上,搭配一個相機,我們就可以利用AI來分析這些點是什麼東西並產生需要的資訊來做相關的路況決策。由於自動駕駛需要即時判別環境狀況,我們沒辦法把時間花在把點轉成成triangle mesh上,因此直接用Point-Based Rendering就還蠻合理的。

LiDAR point cloud support | Feature Highlight | Unreal Engine

https://www.newport.com/n/lidar

https://medium.com/realities-io/point-cloud-rendering-7bd83c6220c8

https://d-nb.info/1214007201/34

https://en.m.wikipedia.org/wiki/Point_cloud

http://graphics.stanford.edu/papers/qsplat/

https://www.graphics.rwth-aachen.de/media/papers/point_rendering1.pdf

Voxel Rendering

在講手著手研究Voxel的同時,引擎中做物理部份的人就說,他希望能夠有統一的implicit surface geometric representation。會有這個需求的原因,在於現代的主流引擎做法會把render跟物理拆為二種不同的geometry的表達方式。render會使用explicit surface,而物理相關模擬計算卻會在另外另一個孿生世界中做implicit surface來表達。(*34)

*34:geometry representation的表達分為三種,例如:

  1. explicit representation:,通常用在可以用數學表達的mesh上。
  2. implicit representation:,我們知道x, y的位置,在繪出mesh時需要餵入該model space上所有可能的x, y值,在圖學上常見用來把implicit surface轉成explicit mesh後再畫出來的演算法(正式名稱為IsosurfaceExtraction)是Marching Cube。
  3. parametric representation:,若x跟y都是未知,這時候就可以使用Reparameterization的技巧把原始公式的參數重新解釋,並帶入我們已知的數值。例如這邊我們想要做lerp的話,t我們知道必須限制在0~1之間,這時候就可以把x, y做remap並用t代換掉。Curve或CurvedSurface就是一個典型的例子。

這部份的介紹可以看Cem Yuksel教授的Intro to Graphics 09 – Curves (Part 1)

http://groups.csail.mit.edu/graphics/classes/6.838/S98/meetings/m7/impexplicit.html

MarchingCube介紹:

Marching Cubes Animation | Algorithms Visualized

https://graphics.stanford.edu/~mdfisher/MarchingCubes.html

 

Voxel Storage Size

若我們能夠將表達方式統一起來,會打開很多可能性。這邊提供的想法是把SDF(*35)存進voxel,這樣會有一些好處,例如collision很便宜、LOD也很簡單,也能支援CSG bool operation。
 

*35  Signed distance field(SDF)在Unreal中是一張用來記錄Voxel跟surface距離的3D貼圖,是常見的implicit representation。

在Unreal中Mesh Distance Field的計算還蠻暴力的,首先會在每個StaticMesh一打開的時候呼叫CacheDerivedData,並建出FAsyncDistanceFieldTask來計算voxel的SDF值,最後的結果會存在FDistanceFieldVolumeData。

整個流程大約是先把Mesh的BoundingBox拿出來平均切成幾個Brick,要切多少個根據r.DistanceFields.DefaultVoxelDensity這個數值,之後每個Brick會切成8x8x8的voxel grid。

Brick(磚頭)從實作來看是Voxel怎麼在3D空間中被畫出來的實體,所以稱為磚塊。

我覺得這東西很像是Voxel版本的Triangle Cluster,每個Brick裡面有512個voxel(8x8x8). 之後在每個voxel裡面做ray tracing射出一堆sample ray(5.0.3是120個)來找到離最近的物體表面,若有超過1/4個Ray打中的地方是surface背後,則認為這個voxel在surface裡面並設為負數。最後的結果會被remap到0-255之間,並做成一張PF_G8的3D貼圖(可以看Engine中ResizeBrickAtlasIfNeeded這個方法的實作),這張貼圖稱為BrickVolumeTexture。

只是講者這邊使用SDF的目的是想要取代explicit surface,因此還是需要一套機制把voxel重建回triangle mesh,大致上的流程是把需要判斷grid跑過一次,每個voxel grid要畫出多少個三角型再透過Marching Cube或其他相關從implicit surface重建三角型演算法決定。只是,重建回來的東西,可以預期的是肯定會丟掉某些細節,這點很多相關的分享都有提到。(見dreams的分享)

https://www.cs.upc.edu/~virtual/SGI/docs/1.%20Theory/Unit%2010.%20Volume%20models.%20Marching%20Cubes/Marching%20Cubes.pdf

使用Voxel的話,第一個需要擔心的是資料量,它需要n^3的資料量,加入SDF的話,由於Surface二側都需要有值,因此資料量又會往上增加,要降低這部份的大小我們只能保存narrowest band data(我不確定這是什麼意思,但我猜是把精度降低,只用int8來存資料)。另外一個議題是,當我們的Voxel因為相機的位置被放到很大的時候該怎麼辦?若我們用Binary Voxel來表達(voxel只用bool來記錄是不是表面),直接麥塊化,這看起來不會是寫實風格想要的效果,同樣問題,若voxel被放大的話需要做texture filtering(*36)來處理artifact。

*36 Texture Filtering(Texture Sampling)

畫面上會產生artifact的原因,主要是來自於texture上的texel跟營幕上的pixel沒辦法達到1:1的比例,為了讓每個pixel能夠得到一個顏色,我們需要一個演算法來幫助我們每個pixel該怎麼從texture上把顏色取樣下來,這個動作我們稱為texture sampling,如果這個動作做的不好的話就會有各種不同的artifact產生。

就這個sampling的動作本質來看,有人又稱它為texture filtering,那麼這時候問題來了,它到底filter了什麼?它要濾掉的是貼圖上高頻的部份。那麼又有另外一個問題,對一張texture而言到底什麼是它的頻率?頻率指的是一段區間中能夠被量化的數值。當我們把我們的texture映射到要fragment的時候,這個區間指的是fragment(0, 0)到fragment(n, n),而frequency指的是fragment跟它鄰居的顏色差異。若裡面有很多高頻的部份,那麼最後在映射回營幕的pixel上時就會造成畫面上會有很多的雜點。例如下圖由左至右,第一張是濾掉高頻的部份,整張圖變得非常模糊;第二張圖是只留中間的頻率;第三張只留高頻,整張圖變的非常的sharp;第四張則是原圖:

https://www.researchgate.net/figure/In-a-texture-with-A-L-A-M-A-H-high-frequency-elements-are-clearly-visible_fig2_220245034

為了展示高頻的過濾能力,我們常常會看到antialasing相關的技術會拿黑白棋盤來當測試。當把顏色變化頻率繪制出來後,這張圖會長得像這樣:

https://forum.beyond3d.com/threads/high-frequency-textures.53496/

而Texture Filtering的主要目標就是要把高頻的部份降下來,讓整張圖的顏色變化不要那麼快速。

根據情況,整個流程需要分為magnification filtering來處理一個texel佔據了多個pixel狀況以及minification filtering來處理一個pixel佔據了多個texel的狀況。常見的演算法有Nearest-Point、Linear、Bilinear、Anisotropic幾種,常見是搭配mipmap使用,先讓pixel跟texture的比例盡可能的達到1:1之會能比較好的達到anti-aliasing的效果。

常見的aliasing有jagged edge(鋸齒)、ghosting moving(鬼影)、flicking(閃礫)以及blurry(模糊),目前還沒有一套antialiasing(反雜訊、反失真)演算法能夠在所有的硬體上解決所有的問題,這就是為什麼遊戲中會提供那麼多選項供玩家選擇。例如使用TAA因為引入了前一個frame來做計算,特別善長在自然景物植被很多這類不需要明確線條的場景中使用,但缺點是畫面會變的很模糊,而且如果有快速移動的物體的話就會造成ghosting的現象;MSAA由於手機硬體大多支援的關係,因此常常是手機遊戲的首選,另外它優透的線條處理能力,在VR中可以減少頭暈的現象,使得它幾乎是VR應用的標配,但由於MSAA只支援Froward Shading,在流行使用Deferred Shading的PC遊戲上就不常見。另外Unreal還支援FXAA,這最簡單、消耗最少的AA,只是粗糙的blend物體邊緣附近的pixel來做模糊化;還有在5.0變成預設的TSR(類似於AMD的FSR以及NVDIA的NIS)與第三方NVDIA提供的DLSS這類基於upsampling的技術。基本上各種方法都有適合應用的情境,

名詞全稱:

Temporal Super Resolution (TSR)

NVIDIA Image Scaling (NIS)

FidelityFX Super Resolution (FSR)

Deep Learning Super Sampling (DLSS)

https://www.imagejoy.com/article/690

https://en.wikipedia.org/wiki/Texture_filtering

https://learn.microsoft.com/en-us/windows/win32/direct3d9/texture-filtering

https://zhuanlan.zhihu.com/p/373379681

為了解決資料量的議題,講者想說試著降voxel上面資料的精度就能解決,但是實際做計算之後,才發現那個數據量大到難以想像。講者舉了一個原始有2M poly的mesh,雖然我們把SDF Voxel的數量加到了13M,但還原出來的結果看起來還是有採樣問題。之前也提過,美術不喜歡自己做的模型跟原本長的不一樣。根據採樣定理中的Nyquist rate,我們voxel size必須要比最短那個三角型的edge的一半以下才不會遺失任何細節。這代表模型上只要有一個小三角型存在數據量就會膨漲到很誇張的地步,這還是在只留SDF的數值而無視其他資料的前提下。

作者的結論是,我還需要保留原始的triangle mesh來當成mip0的數據去處理相機視角靠的夠近的情況,但這讓我們原本SDF Voxel可以得到的各種好處變得沒有什麼意義,而且這樣做的話會需要維護2條 render path。

另外一個問題在Point-Based Render時也有提過,就是該怎麼處理薄牆這類的thin feature,這類的物件在遊戲中到處都是。當我們的牆比voxel還簿的話,代表我們需要至少一個voxel防止牆面消失,但在這之前,AttributeData的資料量是另一個比較大的問題,因為想要節約的話就會有voxel leaking。由於SDF需要牆面的內外二個voxel的正負值來做內插,假設我們voxel上的AttributeData存有一份顏色的資料,當我們利用這份資料幫某面牆上顏色的時候,例如前面紅後面藍,這時候每個牆面都需要有2個voxel才能正確顯示,不然就會有被內插成紫色的,這種情況稱作voxel leaking。也就是說,我們共需要4個voxel來正確顯示一面牆,前二個voxel存紅色,後二個voxel存藍色。

因此,關於voxel資料量的另外一個惡耗是,我們需要1個voxel的大小來防止牆消失,2個voxel來防止voxel leaking。

談到了AttributeData,並不是所有的東西都可以做texture filter,例如UV,由於UV有接縫(seam)問題,若在邊界做filter會打破連續性,這代表我們要另外儲存哪些區域是可以做內插,哪些是不行的,嗯,又有更多東西要記錄了。雖然這份額外的資料可能可以幫我們解決需要至少二個voxel才不會有leaking的問題,但這份資料光想起來就很複雜。如果我們不想要UV的接縫處有鋸齒狀,這代表我們需要在UV Chart boundary存一份類似於SDF的資料來做blending。

Voxel Animation

雖然現在還不需要,但是否有利用voxel做animation的可能性?在不利用march cube之類的方法轉成triangle的前提下,講者有看到一篇paper:Non-linear sphere tracing for rendering deformed signed distance fields,他是對incoming ray做deform而不是geometry,只是不知道這個方法對於rigger的控制力如何。

那morph target呢?不行,這個方法只有對有頂點的explicit surface有意義。displacement maps?一樣需要頂點資訊。是有各種procedural geometry的方法可以幫助我們在SDF Space中建構triangle mesh,但那些沒辦法在所有的平台上很好的運行,要記得,做引擎需要考慮到平台的兼容性。

Voxel Prototype


雖然知道voxel方法會有資料量的問題,但講者還是花了一些時間在prototype上來做性能測試。他的方法是借鏡於Dreams的Brick Renderer(*37)跟OpenVDB。

*37 前面也有提到,Dreams的這篇演講很多在做Point/Volume Render的人都有提到,看起來是他們的研發過程啟發了不少人。Brick Engine的idea是將Volumetric Billboard跟其後續者GigaVoxels的概念做運用,它們都是image-based rendering。

Volumetric Billboard在Unreal中有類似的實作,叫 3D Imposter Sprites,目的是讓我們可以讓遠方的mesh變成用一張image顯示來避開pixel size triangle所產生的效能問題。由於這技術可以讓我們指定一個模型為基底,在editor time建出一張不同視角3D Texture,運行的效果可以看這個影片:  https://www.youtube.com/watch?v=Sd1nXiI_ros

參考資料:

https://phildec.users.sourceforge.net/Research/VolumetricBillboards.php

GigaVoxel的核心概念是在研究怎麼有效率的render出海量的voxel,例如10億筆,基本想法就是先建出sparse voxel octree(SVO)之類的資料結構,然後利用view-dependency的概念想辦法只處理看的到的voxel,通常是做ray tracing。 每個tree node上會存一份VoxelBrick,代表一張3D貼圖,這個Brick包含了某一區Voxel資料,例如Unreal的MeshSDF中的Brick是切成8x8x8個voxel,之後再利用Volumetric Billboard的概念把樹上被看到的所有Brick都畫出來。

可以看到一個增加效率的想法是把資料做一份LeastRecentlyUsed(LRU) Cache放在GPU上以減少跟CPU之間的溝通成本。

參考資料: https://www.researchgate.net/publication/47863824_Building_with_Bricks_CUDA-based_GigaVoxel_Rendering

http://gigavoxels.inrialpes.fr/

OpenVDB是由夢工廠動畫開發的一套用來處理Volume資料結構的開源軟體。

這邊講者有提到Scatter跟Gather這二個詞,並且使用了hybrid scatter gather這個方法,但我查了很久都沒有找到相關的定義,為了理解下面內容,因此我這邊先假設:

  1. Scatter是指產生Brick的那個階段,例如unreal中這是editor time在做的事,也就是把物件的BoundingBox拿出來切成Brick。
  2. Gather是指把Brick上儲存的3D貼圖畫出來的動作,這邊是作者實驗想要用implicit surface取代explicit surface。
  3. Hybird Scatter/Gather是指在Brick裡面對每個voxel做raycast,最後再把數值存到某張3D貼圖上的動作。

當然我這邊理解可能有誤,如果有人能提供相關文獻定義的話,我會非常感謝。

講者做的這個prototype時為為了節省資料量,讓我們可以只用一個uint64搭配bit mask就可以記錄brick中的voxel是不是有surface,因此他的brick裡面只切了64(4x4x4)個voxel。由於處理一筆brick的時候只需要讀一個uint64,所以這個操作會非常的輕量。在產生Brick的Scatter階段的時候,我們會在每個Brick中做一個短程的raycast來算出每個voxel上的SDF數值,這邊選擇把voxel的資料在8個角落各放一份而不是只在voxel的中間存一份,這樣我們interpolate出來的SDF數值才能避開voxel leaking的問題。raycast有打到surface的話就把uint64上對應的bit設成1,否則設0,之後在做Gather的時候就可以直接跳過不處理。

另外為了降低資料量我們對SDF的數值做了一些限制,因此在Gather階段我們沒辦法從相機位置打出sphere tracing並靠SDF來決定我們每次要前進多少,取而代之的是使用DDA Ray march(*39),每往前一步只處理一個voxel,如果該voxel的bit被設成1,代表該voxel有surface,可以讀取上面的SDF數值出來處理。

(*39)  Digital differential analyzer(DDA) 是rasterization時用來畫線條的演算法。由於營幕上的pixel點是離散值,我們沒辦法用數學算式直接畫出連續直線,因此這方法的基本概念是先算出斜率,然後利用斜率找出哪些pixel點是必須要被繪製的。這個演算法的概念可以直接引入Sparse Volume Octree(SVO)上,稱為DDA Ray March。

接著為了讓我們的brick能夠一次含蓋更多的voxel,讓我們scatter/gather能夠一次跳掉更多那些沒有包到surface的brick,但又只想用uint64來記錄brick,因此就學OpenVDB的方法,做了一套Hierarchy的架構,也就是說,這邊會再把brick包成4x4x4,每個brick的size沒變,只是上面多了一層,只是這樣做代表我們的DDA也要做Hierarchical。

其他還有做的優化有:

  1. 由於不是所有的brick裡面都有資料,因此可以把bounding box縮小。
  2. 由於每個voxel要讀8次資料出來做lerp,因此可以想辦法把同樣的資料pack在一起,例如很多brick他們的orientation都一樣,就可以把這些值pack進volume texture atlas中。
  3. 這時候發現,大部份的brick資料可以延著軸向用一個Height field來表示,這樣就不用存8份SDF資料,資料量是原本的1/3。

這時候他發現,為了節省資料量,會讓CSG操作跟物理完全無法使用。另外一個發現是,如果我們在mip0保留一份原始mesh來解決magnification的問題的話,那麼我們就不需要存SDF數值,只需要Binary voxel,這樣就可以把整個個資料量壓到很小。基於這個想法,他做了一個prototype來驗證,並分享了幾個發現:

  1. Hierarchical DAA很難做優化,沒辦法有效率的去culling掉那些不需要的brick
  2. Scatter比他想像中的更快,改用pointer-based rendering呢,也就是說每個voxel都是一個point,結果更是出乎意料,雖然沒有認真做優化,但他的測試場景上還是得到一個很漂亮的數字,在PS4上是9-10ms,加上Hierarchy架構的話,速度提升約5倍,變成2ms。他知道point-based rendering在理論上很快,但這是他第一次見證。
  3. 當然他把方法換回voxel,在computer shader上做software box rasterizer,發現這個速度比point-based的方法慢了約2倍半。他覺得是overdraw造成的,因為Box的處理不會在同一個平面上,就算是一個flat ground也會有overdraw產生。
  4. 如果把box換成triangle,他覺得可以避開overdraw的問題,因此他動手做了測試,結果讓他很驚訝,因為只比point-based的方法慢了70%左右,而且更令他驚訝的是,他的software triangle rasterizer比用硬體做還快了16倍,非常的反直覺,也就是說在處理那些小triangle時,用軟體做居然還比硬體快。

Prototype小結

根據以上實驗,他發現使用voxel搭SDF的方案有太多難解的問題了。他覺得這需要多年的學界跟業界的經驗才有辦法完全用implicit surface取代explicit surface,他個人無法在UE5的時程壓力下完成這件事。只是就算能取代,完全使用implicit surface是否有優勢還未可知。因此講者最後的結論還是回歸使用triangle。他覺得他在voxel上面浪費太多時間,只是想證明這個方案不可行,這邊他學到的一課是,不要浪費時間在你覺得不可能的方案上,不要做惡魔的證明,也就說不要試圖去證明某件事是不可能的,因為只要找到一個反例就能推翻你前面所有的工作。如果他一開始就往triangle cluster、HLOD加上Quick-VDR這個思路往下鑽,並用visibility buffer來解決texture space cache的話,UE5釋出時的版本會更加的成熟。

Voxel跟surfel都不是3D版本的pixel,對一個surface而言,triangle才是。

如何預測實作的結果

接下來講者在談他怎麼在做研究跟實作中取得平衡,由於時間的關係,他沒辦法每個演算法都去實作看看,因此他提了幾個技巧來幫助排除不可能的方案。

第一個技巧是採用fail fast的精神,先思考問題的edge case以及他怎麼做scale up,找出任何可能讓該演算法失敗的case。

第二個技巧則是利用費米估算(fermi estimation),藉由現有資訊推算出實際上可能的數值,雖然會跟實際上的結果有落差,但費米的例子告訴我們,熟練的使用估算的技巧可以把誤差降至10倍以下,也就是說少於一個數量級。

第三個技巧是只寫那些能驗證核心問題的code,這些code可能是用來輸出使用估算巧技時必須要的資料。

當你對於估算以及預測有足夠的信心時,就可以真的跳下去把細節刻出來。

那些實作可以先跳過

我們不需要一次把所有的細節都完成,有些議題如果你覺得非常多種可能的解決方案時,而且不會影響到整個技術方向時,那麼就代表該功能可以先放到後面,等之後有空的時候再回來處理。

如何面對沒有十成把握的未知細節

在動手實作nanite前,講者其實對於以下議題其實沒有十成把握:

  1. 如何處理big triangle?做tessellate,然後丟給hardware raster?
  2. 如何處理popping?TAA沒辦法很好的藏住。
  3. 要準備多少記憶體才夠?
  4. visibility buffer因為cache hit的關係是否只對那些大的三角型有效率?

但幸運的是,最後結果看起來還ok。

研發心得

講者這時候得到公司的許可,開始了將近一年的研發期,有許多人加入了他的專案。那時候他覺得,他把自己跟這項工作綁的太深了,投資了過多的時間在解決這個問題,如果失敗了怎麼辦?機會可能永遠再也不會到來。他非常害怕做到一半的時候有人走過來打他一巴掌,但有趣的是,他最害怕的那個關鍵人物,最終加入了nanite的研發團隊。

他們花了一年的時間把prototype做成了第一個public demo,接著再花了二年打磨,最終趕上了UE5的發佈時程。

Nanite的研發,其可行性是基於眾多的未知數,回頭看來,他不否認他有點幸運,因為公司給了他大量的研發時間。只是若沒有多年的準備與對這個問題的思考,當這個機會到來時他恐怕也不能掌握住。

在看完這段分享後,講者希望能夠讓nanite的研究過程不再那麼神秘,也希望能夠回答一開始的問題:nanite是否會跟mega texture一樣會造成pipeline根本上的改變?答案是不會的,因為並沒有改變美術任何既有的製程,他們還是可以選用任何他們習慣的DDC工具之後再匯進引擎中。

只是在研究的過程中,他還是針對很多細節做了調整,很多的決策有可能是基於錯誤的結論或者對workflow認知不夠完善,又或者是對未來的需求考慮不夠完善,因此他覺得,目前nanite雖然接近了他的夢想,但還不完美,因此他也還在努力中。他也鼓厲大家著手去處理他沒做好的部份,就像是他一開始說的,在這個領域中,最重要的事情是怎麼讓3D內容的製作變的更便宜、讓美術能夠更快實現他們的想法。

最後他請大家思考以下三個問題,這幾個問題原始是出現在圖靈獎得主Richard Hamming的 You and Your Research 這個演講中:

  1. 在你的領域中什麼問題是最重要的? What are the important problems of your field?
  2. 現在你正在做哪一項?  What important problems are you working on?
  3. 如果你覺得正在做的東西沒那麼重要,為什麼你還要做?If what you are doing is not important, and if you don’t think it is going to lead to something important, why are you working on it?

Q&A

這邊沒有全記錄下來,只有記我我覺得有比較明確回答的部份。

1.有人問是不是需要重新審視是否需要設計硬體加速機制給那些小的三角型,但講者說他很難回答,因為他不是硬體出身的,不知道要多少transistor才能做一個micro poly rasterizer,但他覺得可能要做出跟nanite一樣的限制,也就是說不能太有彈性,只能在特定條件下運作。

注:這邊很像nvdia在RTX 40系列新加的 NVIDIA Micro-Mesh   https://developer.nvidia.com/rtx/ray-tracing/micro-mesh

2. 有人問Occlusion,講者說雖然這場演講沒有提到太多相關的東西,但Occlusion是讓整個系統能夠運作的重要關鍵。理想上nanite的核心思想是要讓營幕上的pixel的數量等於成本,也就是說不管triangle數量再怎麼多,最多也就跟pixel一樣,不過目前occlusion做的還不夠完美,因此triangle會比pixel數量還多一些。

3. 有人問關於Transparent的支援,講者說理論上可以做到支援,但由於需要透明的mesh通常不需要這麼複雜的幾何,Epic內部沒有相關的使用案例,因此目前的優先權不高。

Leave a Reply

Your email address will not be published. Required fields are marked *