【GDC翻譯】“地平線零之曙光”中基於GPU的程序化實時放置系統

【GDC翻譯】“地平線零之曙光”中基於GPU的程序化實時放置系統

 原視頻:

PDF:

介紹

大家好,我是

我一直在研究《地平線零之曙光》裡自然環境的程序化放置、渲染、仿真。

今天,我將討論我們為《地平線》所搭建的程序化系統。它的結果,以及GPU管線。

所以演講基本上分為三個部分:

  • 第一部分,我將討論我們選擇實時放置系統的動機和原因。
  • 第二部分,美術工作流。
  • 第三部分,我將簡單討論我們在GPU管線中所用的算法和shader。

動機

在我們製作Horizo​​n 之前,Guerrilla 以《殺戮地帶》系列而聞名。在殺戮地帶中,關卡中的每一寸土地都是手工打磨的。 Guerrilla Games 在環境美術方面一直有很高的標準。

環境美術師是使用光線、構圖和顏色來裝飾場景以使其看起來有趣和可信的專家。而《地平線零之黎明》的開放世界需要我們研究,如何使用程序化系統創建和裝飾大型開放世界,同時努力保持質量標準。

從歷史上看,程序化系統通常看起來單調、乏味和機械化。但它確實讓

我們的目標是創造一個系統,讓美術師可以在其中描述但是我們有一些限制:系統以及生成的內容都應該具有高度的

最重要的是,我們的美術總監希望能夠自由移動山脈、河流和遊戲性內容,而無需不斷修正世界。這意味著系統應該是完全數據驅動的、確定的和本地穩定的。

開始,我們使用傳統的程序化工作流——根據程序化的參數來離線烘焙出資產應該放置的位置。我們已經在《殺戮地帶:暗影墜落》期間對這個想法進行了一些試驗,但是烘焙時間是一個大問題,而且迭代速度很慢。

在尋找解決方案時,我們嘗試將程序化放置

當我們做出第一個原型,看到它的放置速度時,我們很快意識到這就是我們想要的!事實上,結果看起來非常好,以至於我們決定嘗試讓系統完全

這意味著,我們將根據程序邏輯來主動地生成環境,並在玩家穿過世界時更新世界。

為了在GPU 上實現這一點,同時仍然具有“確定性”和“局部穩定性”,我們選擇了而是,程序化系統生成二維的密度圖,隨後將其離散化為物體的點雲。

最終,我們可以在任何給定時間管理大約500 種對象的程序化放置。在正常遊戲過程中,當玩家探索世界時,專用的渲染後端會管理玩家周圍大約100.000 個放置的模型。

另外,系統負責的範圍也比我們最初設定的要多得多:我們最終不僅放置了植被和岩石,還放置了特效、遊戲性元素(如拾取物)和野生動物。為了支持所有的這些,我們不斷修補我們的GPU 放置管線以及專用的渲染管線以保持預算。我們的目標預算是當玩家在世界中移動時平均每幀約250 微秒開銷。

美術工作流

現在我們來看看美術師們是如何製作這些環境的。

生態環境

正如我之前所說,我們的目標之一是在Horizon 世界中擁有大量的多樣性。為了實現這一點,我們將世界分成不同的獨特環境類型,然後為它們各自設計和構建。在現實世界中,自然環境的分類是通過我們決定沿用這個概念,並開始定義我們版本的生態環境。

一個生態環境定義了一個特定區域的生物多樣性和地貌特徵。事實上,這包括了需要放置

因此在程序化上,每個生態環境都需要有自己的設計,而這就是我們開始製作程序化的地方。

我們的目標是以自然、可信和有趣的方式填充世界,就像一個優秀的場景美術師手動完成的那樣。為了盡可能接近這個目標,我們需要創建一個系統來捕捉我們美術師的邏輯、專業知識和技能。因此,系統的設計方式是使我們的美術師不僅可以完全控制(當然,對於

WorldData

那麼,我們需要什麼數據來創建令人信服的、看起來自然的生態環境呢?

好吧,我們也不知道。 。 。所以我們就在內存預算允許的範圍內構建和添加需要的數據。最後,我們獲得了大量數據來描述這個遊戲世界,其中不僅包括放置系統,還包括遊戲性內容。

我們稱其為這些紋理在玩家周圍的部分

大多數地圖最初是使用各種我們在這些底圖之上有額外的可繪製圖層,因此美術師可以使用我們的遊戲內編輯器通過筆刷或其他工具來編輯地圖。

程序化放置系統僅使用約4MB/km2 的紋理。也就是說每平方米約32 位的數據。

下面看下我們在遊戲中最終得到了什麼樣的數據。

這一部分來自它最初是從WorldMachine 中烘焙出來的,但後來被大量的

美術師總是希望在這些類型的紋理中盡可能多地打包數據。為此,他們設計了一些共享的邏輯來編碼這些紋理中附加的訊息。這個特殊的紋理它可以用作密度圖,但生態環境通常為其編碼額外的含義。

在一個生態環境中,他們可以在接近0 的值上生成鬱鬱蔥蔥的“邊緣樹”,在接近1 的值上生成高大的無枝“內部樹”。

這是一個範例,其中美術師對數據和放置邏輯的控制確實有助於創建更自然的外觀。更好的是,它還解決了“可視性”的要求,而無需程序員的支持。

還有許多其他類型的其中大部分是BC7 壓縮的。分辨率因類型而異,從1m 到4m 的分辨率不等。在我們繼續之前,讓我們快速看一下更多的紋理。

下面是由道路工具繪製的“道路數據”,以及根據遊戲中非程序化生成的(即手動擺放的)物體生成的“物體數據”。由於程序化系統是基於紋理的,因此像這樣的

你可以想像,當美術師為生態環境設計程序化邏輯時,美術師必須確保生態環境對道路、岩石和河流等事物做出自然的反應。這些邏輯的很大一部分圍繞著讀這類紋理和定義的區域,例如“道路的邊緣”、“岩石的旁邊”或是我們剛看的“森林的邊緣”。

這些結構可能會變得相當複雜,幸運的是,這些定義可以只進行一次,隨後在生態環境之間共享。

我們的高度圖也是我們有多層高度圖,所以我們不僅可以把東西放在地上,還可以放在物體的頂部和水面上。

最後是一些生成的紋理。這些來自WorldMachine,幾乎從未由美術師手動繪製。我們使用這些紋理在我們的生態環境中創建逼真的變化和環境的反應。

邏輯網絡

所以我們已經看到了美術師如何設置和操作現在讓我們來看看邏輯端。

簡而言之,我們使用“邏輯網絡”。類似於nuke、substance 或任何其他shader製作工具。

邏輯網絡的目的是使用然後這些密度圖會被離散化成點雲,以便作為實際在世界中放置的資產的位置。

讓我們看看如何將

在這個例子中,假設我們正在設計一個生態環境中放置一棵樹的邏輯。作為起點,我們將調出一個雖然,我們可以直接將其作為我們樹木的密度,但那樣的話我們就會將樹木放在了水中、穿過了岩石、阻擋了道路。

因此,為了移除岩石上的樹,我們將值乘以物體紋理(隨後,我們對水圖和道路做同樣的處理。最終,你會得到一張紋理,該紋理定義了樹木真正的有效區域。

這個邏輯網絡不僅可以被樹引用,還可以被鏈接到其他的邏輯——如果它想要知道樹可能被放置在哪裡的話。

下面,是我們的“森林”生態環境的資產定義。它由樹木和植物資產組成,並按層次結構分組。右側的葉節點鏈接到實際的資產,稱為

對於每個資產,我們定義了離散化算法將使用可以看到喬木等大型模型相距六米,而灌木叢模型相距僅一米。

由於當前還沒有連接邏輯,因此所有資產都將具有完整的密度。每個資產的密度圖將是全白的。

下面讓我們將其加載到遊戲中:

這是一個很好的起點,但下面我們要在其中加入一些邏輯。

首先,我們加載一個現在,森林節點有一個鏈接到它的密度圖,它就會把它的密度傳遞給它的子節點。

讓我們更進一步,我們來在森林中定義一片空地。我們鏈接一個新的現在我們想從然後,我們將它連接到喬木和灌木中。

因此,雖然整個森林仍會看樹木圖,但在森林裡,我們現在可以繪製一塊空地,以減少所有灌木和所有喬木的密:

我們剛剛製作的那個小網絡在我們的編輯器框架中成品看起來像這樣:

這是我們最複雜的邏輯網絡之一,包含大量我們共享區域的邏輯,在多個生態環境中使用。但製作一次後,就可以在生態環境之間共享它們了。

GPU管線

下面讓我們進入技術細節的討論!

我們已經看到了美術師是如何建立邏輯的,我們知道了它們的輸出是什麼。但是讓我們看看,接下來在GPU上如何處理數據。

在我們對數據做任何事情之前,需要將我們創作的網絡編譯成

這個我們遍歷生態環境邏輯中的所有資產(或者說邏輯網絡的葉節點),把它們放在一起,變為層的列表。每層代表一個運行時的程序化負載,鏈接一個資產,它包含的訊息可以從

層所包含的訊息還包括與之關聯的邏輯網絡,它被編譯成一個這種表示可以編譯成計算著色器的二進製文件,或者直接輸入到我們用於調試接口的基於GPU 的

中間形式還能做的一件事是允許不同子圖的合併。一個資產有上百萬種不同的放置方式,可能有上百萬種不同的層都使用它,這很常見。由於有中間形式,所以你可以把它們都合併到一個層中,而美術師可以通過在實際中,它會將玩家周圍的層數從數千減少到數百。

現在有了我們的層列表,讓我們看看這樣一個層是如何在GPU 上放置的。我們從最初的密度圖生成開始,然後是離散化。

第1步:密度圖著色器

我們運行時管線的第一部分是計算世界上給定區域內在正常情況下,這是由稱為

我們整個放置管線在諸如樹木之類的大型物體放置在128x128m 的大塊中,而草則放置在32x32 的塊中。另外,無關於粒度,我們有每塊64x64 像素的固定密度圖分辨率。

在這裡你可以看到遊戲內調試界面,美術師可以逐步瀏覽密度計算,查看每一步離散化的結果。它還用於瀏覽和檢查數百個活動圖層。

這是在中間表示和解釋器的使用非常靈活,我們在Horizon 的整個開發過程中都使用了它。

你可以看到正在構建的密度圖,然後是離散化步驟。

第2步:生成著色器

在密度圖步驟之後,我們運行生成步驟,該步驟將密度圖離散化為單獨的位置。

我們的方法基於稱為有

下面是一個理想化的密度圖。現在,如果我們縮小它,並應用Dither 過濾,然後對其應用Ordered Dithering(在photoshop 中),我們最終會得到這樣的結果:

如果想像在每個白色像素上創建一個對象,我們就有了一種離散化形式。零密度像素上沒有對象,密度隨著值變高而增加,直到白色完全覆蓋。

這背後的過程極其簡單:每個像素的計算都是獨立的,它基於一個小的重複的闕值圖案,結果將作為像素的顏色。這種方式非常適合GPU:無依賴,數據量小。

這些類型的Dither 中最常用的圖案是這些數字定義了單個輸出像素閾值增加的順序。它下面的圖像顯示了當輸入緩慢遞增的結果。

但老實說,如果我們將所有資產放置在這樣的圖案中會有點明顯。不過幸運的是,我們不受像素邊界的束縛。

感謝我們在GPU上,我們有線性插值,我們可以定義自己的UV。所以我們的圖案並不是一個常規的像素網格,而是一組精心安排的顯式位置,每個位置都有自己的隱式閾值。

這是我們圖案生成器的一張舊截圖,是多年前我們設置時的截圖。也許不太能看出來,但它基本上是一個disk packing,帶有一些可配置的東西如隨機數、點數、邊界等。 ( 顏色編碼有點奇怪但基本上藍色是很低的,當它在閾值上升時就會變成紫色)

所有人在進行ordered dithering 時都使用拜耳矩陣,這並非巧合。因為它是一個數學構造的矩陣,具有一些不錯的屬性,而我們也將這些屬性應用到了我們更自由的版本。遵循的規則是:閾值本身需要在0 和1 之間

通過縮放圖案,我們可以使2D 距離W 等於層的之後就可以直接在世界空間中應用圖案,其生成的點雲會擁有適當的間距,正如層中的設置。

森林中的樹木是個很好的例子,它有一個非常大的這樣,我們就有了一個均勻的、明確定義的樹木之間的最小距離,保證了玩家和敵人的正確導航。

但即便使用適當的拜耳圖案,當密度變高時,生成仍然會產生令人討厭的副作用,即產生視覺上的圖案。你可以看到這個白色的三角形,後面的那些樹是完全對齊的。有人花了很長時間才注意到這一點,但是一旦你注意到了它你就再也無法忽視掉它了。所以,我們添加了一個用戶定義的噪聲偏移:

我們還有計劃做多個模板和“王浩瓷磚”,但最終發現並不真的需要。

這是在這裡,我們看到純白的目標區域,以及跨越該區域平舖的四個圖案。每個計算線程組運行在一個圖案上,其中每個計算線程計算一個採樣點。這很好地映射到線程組著色器結構中。

讓我們逐步觀察單個計算線程。

首先,我們必須確保我們

然後,我們讀取密度並進行

當通過閾值測試時,我們有2D位置,但沒有高度與之匹配。因此,我們要對正確的

由於我們在此處的紋理緩存鄰域中,我們不妨與其一起構建

通過測試的線程將它們的點添加到最終,我們將數據附加到輸出緩衝區的末尾,以減少輸出緩衝區上的atomic contention。

所以,現在我們有一個帶有方向的點的點雲緩衝區,但還沒有完整的世界矩陣。我們也沒有應用任何針對每個對象的邏輯,例如隨機傾斜、旋轉、高度等。

在這裡,我們再次看到遊戲中的屏幕來用來顯示遊戲中的一些下面是一個覆蓋具有粒子特效的區域的層。放置系統被指示以完全覆蓋簡單地填充指定區域。結果是一個緊密壓扁的六邊形網格:

這顯示了更自然的放置,花旗松被放置在森林區域內。

第3步:放置著色器

正如之前說的,我們有了點雲,但是還沒有完整的世界矩陣。因此我們製作了第三個著色器叫做放置著色器會抓取一個點雲,然後一個接一個地應用各種參數。比如,你想讓樹在地面上彎曲多少,或者你想讓石頭繞著它的角度隨機旋轉多少等等這些放置規則。

最後,它會應用所有行為參數,並從生成的點雲生成世界矩陣和包圍盒。對於每個輸入點,都有一個輸出矩陣。

到目前為止,所有操作都是確定性的,除了因此我們需要傳遞模板點和圖塊ID 以及位置和法線,這樣每個放置位置都有一個完全確定的ID。

現在,我們已經完成了從一個密度圖網絡到世界矩陣的完整過程!

這是GPU 計算管線的完整概述,每一層都運行該管線來實施放置。

WorldData然後由

你可以想像這一切是如何在多個層上進行的,所有這些層都可以在GPU 上並行計算,沒有太多問題。

解決碰撞問題:分層Dither

不過,我們還沒有解決碰撞問題。就目前情況而言,你會將大量資產堆疊在一起,因此必須添加某種避免碰撞的功能。幸運的是,這個問題有一些有趣的解決方案。

在這些情況下,碰撞的一般解決方案是進行回讀,這意味著你必須回讀以前的放置,並在碰撞時丟棄或重新迭代。如果想要保持局部穩定性和確定性,就會在GPU 管線中創建複雜的依賴關係。 GPU 並不喜歡這些。

但還有另一種非常有趣的特殊情況,如果你有兩個具有相同因此,第一種回讀的方式實際上從未在遊戲中使用過,我們只走捷徑。這意味著我們只能碰撞具有相同

But artists, they kind of liked it, they were starting to make all these cool things. but okay, we're gonna use these objects you want these to collide, so put them in the same footprint. it may like categories of footprints and all that kind of stuff, so that actually worked out very well for us.(不確定的翻譯:但是美術師們並不厭惡這個,它們仍舊製作很酷的東西,但是會保持一致的

下面,讓我們討論這個被稱為

在我們研究細節之前,讓我們換個角度並關注密度圖的一維切片,使其更容易被觀察。所以我們抓一張密度圖,從中做一個切片。

在普通的Dither中,你只有一張密度圖,而在分層Dither中,我們一次有多張密度圖。

不過,我們還是從單一的密度圖開始。我們運行我們可以將其形象化為這些小圓點,位於密度曲線下方的每個黃點都會生成一個放置的對象:

現在,如果我們直接運行另一個使用相同——它將具有完全相同的Dither 圖案。也就是說,它會將一些物體重合地放在之前的位置上。

這個問題的解決方案是簡單地將密度層疊在一起。這樣就不會有任何碰撞了,因為閾值上的點你只能放在其中一個區域,不能同時放在兩個區域。

沿著給定採樣點的層現在可以被視為多個資產的概率分佈,其中樣本點的閾值可以看做是均勻分佈的隨機值。整個方法也同樣適用於2D 的Dither 。

所以我們解決了碰撞。但還是有依賴關係,因為你會將層疊加在一起,所以如果你想放置第二層,首先要生成第一層。不過這個依賴關係只與通過一些重構,你仍然可以使用原子操作獨立地計算GPU 上的所有層,從而節省昂貴的GPU 刷新。

所以在這裡你可以看到我們在點擊

這實際上是成品的生態環境中最常見的情況。我們經常有20 多個具有相同

因此,我們的管線為實際需要放置的每一層運行N 個密度圖。我們希望這個數字N 盡可能小,因此我們使用各種啟發式方法對層進行排序,使最常放置的層位於這些堆棧的底部。

GPU調度

最後一步,我們看看我們的GPU 調度。

如果我們每幀只放置一層,它會非常慢。所以為了安排更多的工作,我們多次實例化我們的整個管線。請注意,我們無法跨不同管線調度依賴層。所以我們在這一步主要是按照空間進行並行化,讓每個管線選擇一個區域來填充。

但問題是,生成的密度圖仍然有依賴性,所以到處都是刷新。你得等它們完全完成,然後才能轉到下一個Shader。這並不理想,因為它們不能在GPU 上重疊。

所以這是我們最終的GPU 加載佈局:

我們首先在所有管線上獨立運行一直這麼做,直到碰到一個需要放置的層。然後我們運行然後

整個過程可以重複,直到所有管線的所有工作都完成,但我們最多提供4 個發射(譯註:可能意思是同一時間最多只有4個管線真正運行?),以減少內存負載並防止GPU 峰值。最後,複製緩衝區將所有數據一次性複製到CPU,這為我們節省了數百微秒的同步和復制開銷。

好吧,基本上就是這樣。下面是開啟了調試模式的遊戲:

你可以看到那些依賴的堆棧、那些層、那些堆疊的層,他們在運行時解算。我們必須降低可以並行運行的管線的數量,因為你可以看到它實際上可以製造很多管線。

總結

這就是整個系統,它非常成功。我們在遊戲中多處使用它,我們在模型、特效、玩法元素中使用它。

它的視覺質量非常好,非常適合我們的美術方向。我認為美術師真的可以按照他們的想法去做。甚至是未經修飾的區域,即美術師們從來沒有手動修改的區域,也具備了可發行的質量。

我們的開銷是250微秒的負載,這在預算之內。

這個工具用來製作開放世界非常強大。因為我們所有的自然資產都是由整個公司的三個人製作的。而關於生態環境的邏輯,都是一個人做的。

What do you think?

Written by marketer

前端:從零開發一款可視化搭建框架

只買不賣守幣137 天:利潤14467USDT,利潤率157%,可以準備用於定投比特幣的子彈了