Origin
mp.weixin.qq.com
Tags
开放世界
程序化
性能优化
经验分享
项目
地平线:黎明时分
收藏夹
创建时间
收藏类型
Cubox 深度链接
更新时间
原链接
描述
地平线:黎明时分开放大世界地图生成,GPU 程序化生成环境的秘密
先来看看开场背景介绍,下方视频的内容是程序化生成的场景和原来的裸地形场景:
notion image
技术分享
  • 程序化地图生成是近几年主机平台 3A 大作的核心技术点,能够熟练操作这种技术的游戏厂商在世界范围内也是屈指可数
  • 微元素将在这个技术领域尽可能分享,翻译更多,更有技术含量的文章
  • 中英文对照,满足学习专业术语人群,地图编辑,技术美术,艺术指导等多个工种的需求
  • 本文的分享来自 Guerrilla 公司的技术总监
  • 为大家展示了一个丰富度,精度极高的开发大世界的环境创建思路
  • 游戏【地平线】中的渲染技术,地图编辑技术有超过 10 余年的沉淀
  • 前几年 PS3 游戏【杀戮地带】和小岛修复最新力作【死亡搁浅】都是出自这个引擎技术团队
  • 游戏【地平线】是自研引擎,所以用这个引擎开发的游戏极少,相关资料在整个网络也是不可能存在
  • 但是他们的地图制作思路,技术手段是可以影响整个游戏行业
notion image
精彩技术学习点
  • 物种分配清单与资源占比,在性能、丰富度、精度、密度找到平衡点
  • 地形数据生成,houdini 生成资源
  • 离散算法,规则化配比生成,随机生成,放置到区域
  • 基于节点生成的地编数据,多通道混合计算植物生态分布
  • 道路通道编辑,非线性流程影响植被分布
  • 类似 unity 的 map magic 工作流
  • GPU 笔刷绘制区域植被,快速种植片区
  • 优化与数据结构,移动中分区块加载
notion image
PPT 正文:
notion image
notion image
Before we made Horizon, Guerrilla was known fortheir Killzone Franchise.
In our Killzone games, each square meter of alevel was manually polished. Guerrilla Games has always had high qualitystandards when it comes to our environment art.
Environment artists are experts in dressing upenvironments to look interesting and believable, using light, composition andcolor.
The open world of Horizon Zero Dawn led us toinvestigate how to create and dress a large open game world using proceduralsystems, while trying to stay true to that quality standard.
Historically, procedural systems have oftenlooked monotonous, bland and robotic, but they do allow for QUICK ITERATIONSand at the end of the day, a reduced time investment per square kilometer makeslarger world scales feasible.
Our goal was to create a system, in which anartist can describe a LARGE VARIETY of interesting and BELIEVABLE environments,which can be applied anywhere in the world.
But we had some restrictions. Both the systemand the resulting content should be highly ART DIRECTABLE and seamlesslyintegrate with manually placed art.
On top of this, our art director wanted to beable to freely move mountains, rivers and gameplay without the need to continuallyredress the world.
That means that the system should be fully datadriven, deterministic and locally stable.
在我们制作《地平线》之前,Guerrilla 公司以杀戮地带系列而闻名。在我们的杀戮地带游戏中,每一关的每平方米都是手工打磨的。游击战游戏在我们的环境艺术中一直有着高质量的标准。环境艺术家擅长运用光线、构图和色彩来装扮环境,使其看起来有趣而可信。《零点黎明》的开放世界让我们研究如何使用程序系统创造并装扮一个大型的开放游戏世界,同时努力保持游戏的质量标准。从历史上看,程序系统常常看起来单调、乏味和机器人化,但它们确实允许快速迭代,而且在一天结束时,每平方公里的时间投资减少使更大的世界规模成为可能。我们的目标是创建一个系统,在这个系统中,艺术家可以描述大量有趣和可信的环境,这些环境可以应用于世界上的任何地方。但是我们有一些限制。系统和生成的内容都应该是高度艺术可直接访问的,并与手工放置的艺术无缝集成。最重要的是,我们的美术总监希望能够自由移动山脉、河流和游戏玩法,而不需要不断纠正世界。这意味着系统应该完全由数据驱动,具有确定性和局部稳定性。
notion image
We started off with the more traditionalprocedural workflow of using procedural definitions and off-line bakes placeour hand-authored assets from our procedural definitions.
We had already experimented a bit with thisidea during Shadowfall, but the bake times were a big problem and iteration wasslow.
Looking for a solution, we tried moving ourprocedural placement OVER TO GPU, in an effort to reduce bake times.
When we were looking at the placement speed ofour first GPU prototype, we quickly realized that this was the way to go.
In fact, the results were looking so good, thatwe decided to try to make the system fully REALTIME, as this would not onlyremove bakes altogether, but it would also help reduce the amount of data wewould have to store, and stream from disk.
This would mean, that we would be activelygenerating the environment from procedural logic, and update the world whilethe player moves through it.
In order to accomplish this on the GPU whilestill being deterministic and locally stable, we have chosen to go with DENSITYBASED SYSTEM, which means that our procedural logic does not directly placeobjects. Instead the procedural system generates 2D density maps, which arethen discretized into a point cloud of objects.
我们从使用过程定义的更传统的过程工作流开始,离线烘焙将手工创建的资产从过程定义中放置。我们已经在 Shadowfall 中尝试了这个想法,但是烘焙时间是一个大问题,迭代很慢。为了寻找解决方案,我们尝试将过程性布局转移到 GPU,以减少烘焙时间。当我们观察第一个 GPU 原型的放置速度时,我们很快意识到这是我们要做的事情。事实上,结果看起来很好,我们决定让系统完全实时,因为这不仅可以完全消除烘焙,而且还可以帮助减少我们必须存储的数据量和磁盘流。这意味着,我们将从过程逻辑中积极地生成环境,并在玩家移动时更新世界。为了在 GPU 上实现这一点,同时保持确定性和局部稳定性,我们选择了基于密度的系统,这意味着我们的过程逻辑不会直接放置对象。相反,程序系统生成 2D 密度图,然后将其离散为一个点云对象。
notion image
Our prototype started small, but we keptgetting requests about expanding the system from our sound, effects and evengameplay teams.
During the development of Horizon, it grew intoa rather versatile system.
You see the game running behind me, and you canreally see the difference the placement system makes to the game, especiallywhen you turn it off.
As you can see, almost the entirety of thevegetation of Horizon zero dawn is procedurally placed.
我们的原型开始很小,但是我们不断收到来自声音、效果甚至游戏团队关于扩展系统的请求。在地平发育的过程中,它发展成为一个相当多用途的系统。你看到游戏在我身后运行,你真的可以看到放置系统对游戏的影响,特别是当你关闭它的时候。如你所见,几乎所有地平线零黎明的植被都是按程序放置的。
notion image
We ended up with the procedural placementmanaging around 500 types of objects at any given time. During normal gameplay,the dedicated rendering back-end is managing around 100.000 placement meshesaround the player when the player explores the world.
This is a lot more than what we had originallyscoped for the system; we ended up placing not only vegetation and rock meshes,but also effects, gameplay elements (like pickups) and wildlife.
To support all of this, we were continuallytinkering with our GPU placement pipeline and its dedicated rendering pipelineto keep things in budget, which averages around 250 per frame when the playeris moving through the world。
最后,我们在任何给定的时间对大约 500 种对象进行了程序化的放置管理。在正常的游戏中,当玩家探索世界时,专用的渲染后端在玩家周围管理大约 100000 个放置网格。这比我们最初为这个系统确定的范围要大得多;我们最终不仅放置了植被和岩石网格,还放置了效果、游戏元素(如拾音器)和野生动物。为了支持所有这些,我们不断地修补我们的 GPU 放置管道及其专用的渲染管道,以保持预算,当玩家在世界各地移动时,平均每帧 250 个。
notion image
As I said earlier, one of our goals was to havea large amount of variety within the world of horizon.
To accomplish this, we broke the world downinto different unique environment types that we could then design and buildindependently. In the real world; classification of natural environments isdone through the concept of ECOTOPES.
We decided to adopt this concept, and westarted by defining our version of an ecotope.
An ecotope defines the biodiversity andgeographical characteristics of a particular area.
In practice this includes what type of ASSETSneed to be placed and HOW. In drives the COLORIZATION of rocks and vegetation,determines WEATHER patterns, EFFECTS, SOUND and WILDLIFE.
It follows that each ecotope will need to haveits own procedural design, and so this is where we start our proceduralauthoring.
就像我之前说的,我们的目标之一是在视野范围内拥有大量的多样性。为了实现这一点,我们将世界分解为不同的独特环境类型,然后我们可以独立地设计和构建它们。在现实世界中; 通过生态位的概念对自然环境进行分类。我们决定采用这个概念,从定义我们的生态小区开始。生态小区定义了特定区域的生物多样性和地理特征。在实践中,这包括需要放置什么类型的资产以及如何放置。在驱动岩石和植被的颜色,决定了天气模式,影响,声音和野生动物。因此,每个生态区都需要有自己的过程设计,这就是我们开始程序创作的地方。
notion image
Our goal was to populate the world in anatural, Believable and interesting manner as a good environment artist wouldmanually accomplish.
To get as close to this goal as possible, weneeded to create a system that captures our artists logic, expertise and skill.
Therefore, the system was designed in such away that our artists could have full control on not only the INPUT DATA butalso the system’s PROCEDURAL LOGIC. And of course we will still be using handauthored individual ASSETS.
我们的目标是用一种自然、可信和有趣的方式来填充这个世界,就像一个优秀的环境艺术家手工完成的那样。为了尽可能接近这个目标,我们需要创建一个系统来捕获我们的设计师的逻辑、专业知识和技能。因此,系统的设计使我们的设计师不仅可以完全控制输入数据,而且可以控制系统的过程逻辑。当然,我们仍将使用手工创建的单个资源。
notion image
So what data do we need to create convincing,natural looking ecotopes.
Well, we didn’t know either […]so we just started building, adding data as we needed and keeping a tightmemory budget. In the end we had a large amount of data that described theworld to not only the placement system, but all other game systems as well.
We call this our WorldData, which is aCOLLECTION OF 2D MAPS that we can access across our gameplay systems, and whichare also used as the base inputs of our procedural system. These maps arecontinuously STREAMED IN SECTIONS around the player and fed into the ecotope’s placement logic.
Most maps are initially seeded using variousBAKE PROCESSES such as WorldMachine and tools like Houdini. We have additionalpaintable layers on top of these base maps, so artists can EDIT THE MAPS withour in-game editor through brushes or other tools.
The Procedural Placement System USES ABOUT4MB/km2 worth of this maps exclusively. This comes down to a data footprint ofabout 32 bits per square meter.
Lets give you an idea about what kind of datawe ended up with in our game
那么,我们需要什么样的数据来创建令人信服的、看起来自然的生态系统呢? 嗯,我们也不知道 […],所以我们只是开始构建,根据需要添加数据,并保持严格的内存预算。最后我们获得了大量的数据,这些数据不仅向安置系统描述了这个世界,也向所有其他游戏系统描述了这个世界。我们将其称为 WorldData,这是一组 2D 地图,我们可以通过游戏系统访问它们,它们也被用作程序系统的基本输入。这些地图不断地在玩家周围流动,并被输入到生态墙面的布局逻辑中。大多数地图最初都是使用各种烘焙过程进行播种的,比如 WorldMachine 和 Houdini 这样的工具。我们在这些基础地图上添加了额外的可绘制图层,因此艺术家可以通过画笔或其他工具使用游戏内编辑器编辑地图。程序布局系统仅使用了大约 4MB/km2 的这种地图。这归结为每平方米大约 32 位的数据占用。让我们来给你一个关于我们在游戏中最终得到的数据类型的概念。
notion image
Here is a section of one of the most widelyused world data maps, called Placement_Trees. It was originally baked from worldmachine, but later extensively HAND PAINTED to fit art direction and gameplayrequirements.
Artists always want to pack as much data inthese kinds of maps as possible.
To that end, they designed some sharedPROCEDURAL LOGIC to encode additional information out of these maps.
This particular map, Placement_Trees , acts asour main tree placement map.
It can be used as a density map, but ecotopesoften encode additional meaning to it.
[picture, gradient, soft brush]
Within an ecotope they can then hook in Lush “Edge trees” on values near zero and high branchless Inner Trees on values nearone.
[picture, graph]
This is an example where artist control overboth the data and the placement logic really helps to create a more naturallook. Even better, it also solves a visibility requirement from design withoutthe need of programmer support.
这是一个最广泛使用的世界数据地图之一 Placement_Trees 的一部分。它最初是从世界机器上烘焙出来的,但后来广泛地手绘以适应设计指导和游戏要求。设计师总是希望在这类地图中包含尽可能多的数据。为此,他们设计了一些共享的过程逻辑来从这些映射中编码额外的信息。这个特殊的映射 Placement_Trees 用来充当我们的主树位置映射。它可以用作密度图,但是生态系统通常会为它编码额外的含义。[图片,渐变,软笔刷]。在一个生态系统中,它们可以在接近零的值上钩住茂盛的 “边缘树”,在接近 1 的值上钩住高的无枝内部树。[图片,图表]。在这个例子中,设计师对数据和布局逻辑的控制确实有助于创建更自然的外观。更好的是,它还解决了设计中的可视性需求,而不需要程序员的支持。
notion image
There are many other types of world data. Mostof them are BC7 compressed.
Resolution varies across different types,ranging from 1m to 4m resolution.
Lets quickly look at a few more maps before wemove on.
还有许多其他类型的世界数据。其中大部分是 BC7 压缩的。分辨率因不同类型而异,从 1m 到 4m 不等。在我们继续之前,让我们快速地看一下更多的地图。
notion image
Here we see the road data, which is painted bythe road tool, and the object data, which is generated from non-proceduralobjects within the game.
Because the procedural system is map based,world data maps like these are the main way it can read the the rest of thenon-procedural content.
You can imagine, that when an artist designsthe procedural logic for an ecotope, artists have to make sure that the ecotopereacts naturally to things like roads, rocks and rivers.
And big part of ecotope logic revolves aroundreading these kinds of maps and defining areas such as “side of the road”, “next to a rock”or as we just saw “edge of a forest”.
These structures can get rather complex,luckily these definitions can be made once and then shared between ecotopes.
在这里,我们看到了道路数据(由道路工具绘制)和对象数据(由游戏中的非程序对象生成)。因为程序系统是基于地图的,所以像这样的世界数据地图是它读取其他非程序内容的主要方式。你可以想象,当设计师为生态区设计程序逻辑时,设计师必须确保生态区对道路、岩石和河流等自然反应。生态系统逻辑的很大一部分是围绕着阅读这些地图和定义诸如 “路边”、“石头旁边” 或我们刚才看到的 “森林边缘” 等区域。这些结构可能会变得相当复杂,幸运的是,这些定义可以只定义一次,然后在生态园之间共享。
notion image
Our height maps are also part of the worlddata, and these maps are used in logic graphs and as a placement height for ourprocedural objects.
We have several layers of heightmaps, so we canplace things not only on the ground, but also on top of objects, and the watersurface.
我们的高度映射也是世界数据的一部分,这些映射用于逻辑图中,并作为程序对象的放置高度。我们有好几层高度图,所以我们不仅可以把东西放在地面上,还可以把东西放在物体和水面上。
notion image
Finally some generated maps; these come out ofworld machine and are almost never painted by artists. We use these kind ofnatural maps to create realistic variations and environmental reactions withinour ecotopes.
I think you all get the idea; lets move on。
最后一些生成的地图;这些来自世界机器,几乎从未被艺术家绘制过。我们使用这些自然地图来创造现实的变化和生态系统内的环境反应。我想你们都明白了,我们继续。
notion image
So I hope this gives you an idea of our inputdata, and how artists can bake and paint into them.
So we’ve seen how artists can setup andmanipulate world data; now lets take a look at the logic side of things.
In short, we use logic networks; similar tonuke, substance or any other shader builder tool out there.
We’ve already seen some hints at sharinglogic, and using tools such as curves, these are all aspects of the densitynetworks that artists can click together.
The purpose of a logic network is to generate asingle density map, which can be linked to assets, or sets of assets, usingworld data is its input. Those density maps are then discretized into a cloudto actually instance the linked assets in the world.
Lets see how we can combine world data mapsinto a density map.
所以我希望这能让你了解我们的输入数据,以及设计师如何在数据中烘焙和绘画。我们已经看到了设计师如何设置和操作世界数据;现在让我们看看事物的逻辑方面。简言之,我们使用逻辑网络;类似于核、物质或任何其他着色生成器工具。我们已经看到了一些关于共享逻辑的提示,使用曲线等工具,这些都是设计师可以点击在一起的密度网络的各个方面。逻辑网络的目的是生成一个单一的密度图,使用世界数据作为输入,该密度图可以链接到资产或资产集。然后将这些密度图离散为一个云,以实际来举例世界上的链接资产。让我们看看如何将世界数据地图合并成一个密度地图。
notion image
In this example, lets say we are designing thelogic for placing a tree within an ecotope.
As a starting point, we’ll bring up a world data nodethat links in the placement_trees map we saw earlier.
We can hook this up as the density of our treeasset directly, but then we’ll get trees placed in water, through rocks and roads.
So to remove the rocks, we multiply out theobject map, by multiplying the values together.
Then we do the same with the water map and theroad
The result looks like this, you end up with amap that defines the valid areas for trees.
This logic graph can then be referenced by notonly the trees, but also it can be linked into other logic that want to knowwhere trees might be placed.
NextSlide: our first ecotope。
在本例中,假设我们正在设计将树放置在生态区中的逻辑。首先,我们将打开一个 worlddata 节点,它链接到我们前面看到的 placement_trees 映射中。为了移走石头,我们用物体图乘以这些值。我们可以直接把它和树的密度联系起来,然后我们把树放在水中,穿过岩石和道路。你最终得到的一个定义了树的有效区域的地图。这个逻辑图不仅可以被树引用,还可以被链接到其他想知道树可能放在哪里的逻辑中。下一步:我们的第一个生态圈。
notion image
Here we have our forest ecotope assetdefinition. It consists of trees and plant assets grouped in a hierarchicalstructure. The leaf nodes on the right link to actual resources, called the(placement) targets, which will be instantiated across the ecotope.
Now for each asset, we define the Footprint,which defines the effective diameter of the object within the placement system.
The discretezation algorithm will use thefootprint to space out objects and perform collision avoidance. You can seethat the large meshes such as trees are placed six meters apart, while theundergrowth meshes are only one meter apart.
Since there is no logic linked in yet, all theassets will have full density. The density maps of the individual assets willbe fully white.
Ok, let’s load it into the game。
这里有我们的森林生态资产定义。它由树和植物资产组成,按层次结构分组。右边的叶节点链接到实际资源,称为(放置)目标,将在 Ecotope 中实例化。现在,对于每个资源,我们定义了足迹,它定义了放置系统中对象的有效直径。离散化算法将使用足迹来划分对象空间并执行碰撞避免。可以看到,像树这样的大网格相距 6 米,而灌木丛网格仅相距 1 米。由于还没有逻辑关联,所有资产都将具有完全密度。单个资产的密度图将完全为白色。好的,让我们把它加载到游戏中。
notion image
Before capturing this movie, I’ve had to lower the bushdensity, because the bushes were covering the ground completely, obscuring thegrass.
Other than that this is the direct proceduraloutput of the simple graph we just made, without any logic to speak of.
在拍摄这部电影之前,我不得不降低灌木的密度,因为灌木完全覆盖了地面,模糊了草地。除此之外,这是我们刚才制作的简单图形的直接过程输出,没有任何逻辑可言。
notion image
It’s a good starting point, but let’s layer a little bit of logic into it.
First we load up a world data map and apply itdirectly to the root forest node in our placement graph; CLICK
Now that the forest node has a density maplinked to it, it will pass its density on on to its child nodes.
Lets take it one step further; Let’s define a clearing in ourforest.
We link in a new WD map, we’ll call it clearing CLICK
Now we want to remove things from clearings,not add things, so we add an inverse node. CLICK
And now we link it into the trees and bushes.CLICK
So while the forest as a whole will stilllistens to the tree map, inside the forest, we can now paint a clearing thatwill reduce the density of all bushes, and all trees.
So lets run this in-game, there won’t be any world data mapsfilled in that area, so we’ll have to paint in the treemap and the clearing map ourselves.
这是一个很好的起点,但让我们在其中加入一点逻辑。首先,我们加载一个世界数据地图,并将其直接应用于我们的布局图中的根森林节点; 现在单击 forest 节点有一个链接到它的密度映射,它将把它的密度传递给它的子节点。让我们更进一步;我们定义一个森林中的空地。链接到一个新的 WD 地图,我们称之为清除点击,现在我们要从清除中删除内容,而不是添加内容,所以我们添加一个反向节点,点击。现在我们把它连接到树和灌木丛中,点击。因此,当森林作为一个整体仍在聆听树木地图时,在森林内部,我们现在可以绘制一个空地,它将降低所有灌木和所有树木的密度。所以让我们在游戏中运行这个,在那个区域不会有任何世界数据地图,所以我们必须自己在树地图和清除地图中绘制。
First we have to paint in the forest map, thisstarts black, giving it a starting density of zero.
Next, lets paint in the clearing map, creatinga clearing within the forest.
This is all very basic, but it shows how easilyan artist can setup a piece of procedural logic.
In production, the ecotopes ended up being verycomplex; artists could create deep systems of asset groups and logic networks.
首先我们要在森林地图上绘制,这开始是黑色的,给它一个初始密度为零。接下来,让我们在清除地图中绘制,在森林中创建一个清除。这都是非常基本的,但它显示了设计师可以多么容易地设置一段过程逻辑。在生产过程中,生态系统变得非常复杂; 设计师可以创建资产组和逻辑网络的深层系统。
notion image
That small network we just made looks like thisin our editor framework.
CLICK
For instance, this is one of our most complexlogic networks, containing a large amount of our shared area logic, which isused across ecotopes. You make them once, and then share them between ecotopes.
Now lets load in a full fledged ecotope, andsee how it reacts to the world around it
NextSlide: movie
我们刚刚创建的小型网络在编辑器框架中是这样的,点击,例如,这是我们最复杂的逻辑网络之一,其中包含大量跨生态系统使用的共享区域逻辑。你做了一次,然后在生态系统之间分享。现在让我们加载一个成熟的生态系统,看看它如何对周围的世界作出反应。下一步:电影
notion image
notion image
So here we have another part of the world,where a full fledged ecotope has already been applied. Let’s see how it reacts to world data.Here is me trying to use the editor
因此,我们在世界的另一个地方,一个成熟的生态系统已经被应用。让我们看看它对世界数据的反应。这是我试着使用编辑器。
notion image
notion image
notion image
Here we are looking at the inverse; instead ofchanging the world data with our painting tools, we’re applying multiple ecotopesto the same area in the world.
在这里,我们看到的是相反的情况;我们不是用我们的绘画工具改变世界数据,而是将多个生态位应用到世界的同一个区域。
notion image
Ok, let’s get into the technical details!
We have seen how to setup the logic, and how tolink in assets and assets groups. We’re done looking at the inputs of thesystem, now lets move on to our real-time algorithms.
Before we can do anything with our data, weneed to compile our authored content networks into a usable form. Our firststep is to flatten the graphs into more manageable units.
We iterate through all the assets within ourecotope logic, and convert them into a flat list of layers.
Each layer represents a run-time proceduralpayload, linked to a single asset. It contains all the information to calculatea density map from world data and populate an area within the world.
The logic network that is associated with it iscompiled into an INTERMEDIATE FORM. This representation can be compiled into acompute shader binary, or be fed direcly into a GPU based interpreter shaderthat we use for our debugging interfaces.
This intermediate form allows us to apply ofAUTHOR DRIVEN MERGING SEMANTICS; this merging step is necessary to reduce theamount of unique layers.
In practice it crunches down the amount oflayers around the player from several thousands, to several hundreds.
Now have our list of layers, lets see how theplacement of such a layer takes place on GPU. We start out with our originalplan of density map generation, followed by discretization.
好的,让我们来了解一下技术细节!我们已经看到了如何设置逻辑,以及如何在资产和资产组中链接。我们已经完成了对系统输入的研究,现在让我们继续研究我们的实时算法。在我们对数据做任何事情之前,我们需要将我们编写的内容网络编译成一种可用的形式。我们的第一步是将图展平为更易于管理的单元。我们遍历 Ecotope 逻辑中的所有资产,并将它们转换为一个简单的层列表。每一层表示一个运行时过程负载,链接到一个资产。它包含了根据世界数据计算密度图并填充世界中某个区域的所有信息。与之相关联的逻辑网络被编译成一个中间形式。这个表示可以编译成一个计算着色程序二进制文件,或者直接输入到一个基于 GPU 的解释器着色程序中,我们使用它来调试接口。这个中间形式允许我们应用作者驱动的合并语义;这个合并步骤对于减少唯一层的数量是必要的。实际上,它将玩家周围的层数从几千层压缩到几百层。现在有了我们的图层列表,让我们看看这样一个图层是如何在 GPU 上放置的。我们从最初的密度图生成计划开始,然后是离散化。
notion image
Our first part of the run-time pipelineevaluates the density graph for a SINGLE LAYER, within a given area in theworld. Under normal conditions, this is done by a precompiled compute shadercalled the densitymap shader, precompiled from the intermediate form stored inthe layer data.
Our entire placement pipeline scales up inGRANUARITY, depending on the footprint of the asset. Large objects such astrees are placed in big 128x128m blocks, while grasses are placed in 32x32blocks. Independent on the granularity, we have a fixed densitymap resolutionof 64x64 pixels per block.
我们的运行时管道的第一部分评估世界上给定区域内单层的密度图。在正常情况下,这是由预编译的计算着色器完成的,称为 densitymap 着色器,该着色器是从存储在层数据中的中间形式预编译的。根据资产的足迹,我们的整个布局管道在 Granuary 中不断扩大。大型物体如树木被放置在 128x128m 的大区域内,而草被放置在 32x32 的区域内。独立于粒度,我们有一个固定的密度图分辨率为每块 64x64 像素。
notion image
Here you can see the in-game debugginginterface, where artists can step through density calculations, and see theresulting discretization step. It’s also used to browse and inspect thehundreds of active layers.
This is the placement system running on a GPUintepreter, that could be easily halted, stepped through and debugged. The useof an intermediate representation and an interpreter turned out so versatile,we used it throughout the development of horizon.
You can see a density map being built, followedby the discretization step.
在这里,您可以看到游戏中的调试界面,在这里,艺术家可以逐步进行密度计算,并看到产生的离散化步骤。它还用于浏览和检查数百个活动层。这是运行在 GPU 集成程序上的布局系统,可以很容易地停止、单步执行和调试。中间表示法和解释器的使用非常广泛,我们在 horizon 的整个开发过程中都使用了它。你可以看到正在构建一个密度图,然后是离散化步骤。
notion image
After the densitmap step, we run the generatestep, which DISCRETIZES the density map into individual placements.
Our method is based on a technique calledordered dithering.
在密度图步骤之后,我们运行生成步骤,该步骤将密度图离散到各个位置。我们的方法基于一种称为有序抖动的技术。
notion image
So here we have our super-realistic lookingdensity map.
Now if we downscale this, and apply ditheredfiltering, And then apply an ordered dithering on it (in photoshop), we end upwith something like this. CLICK
Now if we imagine creating an object on everywhite pixel, we have a form of discretization. CLICK
There are no objects on zero density pixels,with density increasing as the values become higher, until white has fullcoverage.
The process behind this is extremely simple:Each pixel is evaluated independently against a small repeated pattern ofthreshold values, and the result colors the pixels. Perfect for GPUs: Nodependencies, and a small dataset.
The most commonly used pattern in these kindsof dithering is a Bayer Matrix. CLICK
The numbers define the order in which thethreshold increases for a single output pixel. The pattern below shows theresulting image using a slowly incrementing input.
But lets’s be honest, it will be a bit obvious if wewould place all our assets in a pattern like this.
Luckily, we’re not tied down to pixel boundaries.
Next Slide
这就是我们的超高真实感密度图。现在,如果我们缩小这个比例,应用抖动过滤,然后在上面应用有序抖动 (在 photoshop 中) 过滤,我们最终会得到类似这样的效果。点击。现在,如果我们想象在每个白色像素上创建一个对象,我们用一种离散化的形式。点击。零密度像素上没有对象,密度随着值的增加而增加,直到白色完全覆盖。这背后的过程非常简单:每个像素都是根据一个小的重复的阈值模式独立评估的,结果为像素着色。非常适合 GPU:没有依赖关系,数据集很小。在这种抖动中最常用的模式是 Bayer 矩阵。点击。这些数字定义了单个输出像素的阈值增加的顺序。下面的模式使用缓慢递增的输入显示结果图像。但是老实说,如果我们将所有资产都放在这样的模式中,那就有点明显了。幸运的是,我们不受像素边界的限制。
notion image
We’re on GPU, and we have linear interpolationand we can define our own uv’s thankyouverymuch.
So our pattern is not a regular pixel grid, buta nicely sorted set of explicit positions, each with its own implicit thresholdvalue.
This is an old screenshot from our patterngenerator, from years ago when we were setting it up. It’s a bit hard to see, but itis basically a disk packing with some configurable randomization, point count,boundary configurations etc.
It’s not a coincidence that everybody used abayer matrix when doing ordered dithering, it’s amathematically constructed matrix that has some nice properties, which we alsoapplied to our more freestyle version.
The thresholds themselves need to be EVENLYSPREAD between 0 and 1
the points are ordered to have MAXIMIZEDDISTANCE BTWEEN two consecutive threshold values.
By scaling the pattern so the 2D distance Wequals the footprint of the layer, we can apply the pattern directly in worldspace, with the resulting point-cloud having proper spacing as defined by itslayer settings.
在 GPU 上,我们有线性插值,我们可以定义自己的紫外线。因此,我们的模式不是常规的像素网格,而是一组经过良好排序的显式位置,每个位置都有自己的隐式阈值。这是我们的模式生成器的一个旧截图,是多年前我们设置它时的截图。这有点难看,但它基本上是一个磁盘包装,带有一些可配置的随机化、点计数、边界配置等。在进行有序抖动时,每个人都使用了 Bayer 矩阵,这不是巧合,它是一个数学构造的矩阵,具有一些很好的特性,我们也将其应用于更自由的版本。阈值本身需要在 0 和 1 之间均匀分布。这些点被排序为在两个连续阈值之间具有最大距离。通过缩放模式,使 2D 距离 W 等于该层的占用空间,我们可以将该模式直接应用到世界空间中,得到的点云具有其层设置所定义的适当间距。
notion image
Trees in a forest are a good example of a verylarge footprint, combined with full coverage density. This way, we have aneven, well defined minimum distance between trees, guarantees proper navigationfor the player, as well as the enemies.
But even with a proper bayer pattern, generatestill has the annoying side-effect of creating visual patterns when densitygets high.
You can see here how the trees are lining up inthe middle there.
It took quite long for someone to notice this,but once you see it you cannot un-see it, so we added a user defined noiseoffset. We had plans for multiple stencils and Wang tiling, but in the end itwasn’t reallyneeded.
森林中的树木就是一个很好的例子,它们的足迹非常之大,而且覆盖范围非常之广。通过这种方式,我们在树与树之间有一个均匀的、定义良好的最小距离,这保证了玩家和敌人都能正确导航。但是,即使使用适当的 Bayer 模式,generate 仍然有一个恼人的副作用,那就是当密度变大时创建视觉模式。你可以看到树是如何排列在中间的。有人花了很长时间才注意到这一点,但是一旦你看到它,你就不能取消它,所以我们添加了一个用户定义的噪声偏移量。我们原计划是制作多个模板,但最终并不是真的需要。
notion image
This is an overview of four threadgroups of theGENERATE shader, that discretizes a density map across a area. Here we see thetarget area in white, and four tiled patterns across the area. Each computethreadgroup runs over a single pattern, where each compute thread evaluates onesample point. This maps very nicely into the threadgroup shader structure.
Let’s step through a single compute thread.
First, we have to make sure we EARLY OUT on allthe points that lie outside of the tile;
We read the density and to do our THRESHOLDTEST.
When we pass the threshold test we have ourposition in 2D, but no height to go with it. Therefore, we sample the properHEIGHTMAP to generate the full position.
Since we’re in the texture cache neighbourhood here,we might as well construct the HEIGHT NORMAL along with it.
The threads that pass the tests appended theiroriented point to buffer in GROUP LOCAL MEMORY.
Finally, we append the data onto the outputbuffer at the end to reduce atomic contention on the output buffer.
So now we have a buffer with an oriented pointcloud, but no full world data matrices yet. We also haven’t applied any per-objectlogic yet, such as random tilting, rotation, elevation etc.
这是生成着色器的四个线程组的概述,这些线程组在一个区域内离散化密度贴图。在这里,我们看到目标区域是白色的,区域上有四个瓷砖图案。每个计算线程组运行在一个模式上,其中每个计算线程计算一个采样点。这可以很好地映射到线程组着色器结构中。让我们单步执行一个计算线程。首先,我们必须确保早点发现瓷砖外的所有点;我们读取密度并进行阈值测试。当我们通过门槛测试时,我们的位置是二维的,但没有高度可以与之匹配。因此,我们对适当的高度图进行采样,以生成完整的位置。既然我们在纹理缓存的附近,我们也可以用它来构造正常的高度。通过测试的线程将其定向点附加到组本地内存中的缓冲区。最后,我们将数据附加到输出缓冲区的末尾,以减少输出缓冲区上的原子争用。现在我们有了一个带有定向点云的缓冲区,但是还没有完整的数据矩阵。我们还没有应用任何对象逻辑,如随机倾斜、旋转、仰角等。
notion image
Here again we see some in-game screens to showthe GENERATE discretization in game.
Here we have a layer that is covering areaswith particle effects. The placement system is instructed to simply fill thedesignated areas with full coverage. The result is a tightly squished hexagonalgrid.
This shows a more natural placement, a douglasfir is placed with within a forest area.
这里我们再次看到一些游戏中的屏幕来显示生成的离散化。在这里我们有一个覆盖有粒子效应区域的层。放置系统被指示简单地用完全覆盖填充指定区域。结果是形成了一个紧密挤压的六边形网格。这表明一个更自然的位置,道格拉斯冷杉是放置在一个森林地区。
notion image
Finally, the PLACEMENT shader applies all ofthe behavior parameters and generate world matrix and bounding box from thegenerated point cloud. For each input point, there is one output matrix.
So far, all operations have been deterministic,except for the output order of the GENERATE shader.
We need to pass the stencil point and tile IDalong with the position and normal, so each placement has a fully deterministicID.
[We have now completed our journey from adensity map graph to a buffer full of world matrices.]
最后,放置着色器应用所有行为参数,并从生成的点云生成世界矩阵和边界框。对于每个输入点,都有一个输出矩阵。到目前为止,除了生成着色器的输出顺序之外,所有操作都是确定性的。我们需要传递模具点和图块 ID 以及位置和法线,因此每个位置都有一个完全确定的 ID。[我们现在已经完成了从密度图到充满世界矩阵的缓冲区的旅程]
notion image
This is the full overview of the the GPUcompute pipeline that is kicked of for a single layer of placement.
World data goes into the DENSITYMAP shader,which evaluates it into a 64x64 density texture. It is sampled by GENERATE intoa point cloud, expanded into world matrices by PLACEMENT and finally copied toCPU memory.
You can imagine how this all plays out acrossmultiple layers, and all these layers can be evaluated in parallel on GPUwithout much problems…oh wait
We haven’t solved collision. as it stands, you’ll just end up with lots of assets placed on top of each other sosome kind of collision avoidance has to be added.
Luckily, there are some interesting solutionsto this problem.
这是对 GPU 计算管道的完整概述,该管道是为单个放置层而启动的。世界数据进入 DensityMap 着色器,该着色器将其评估为 64x64 密度纹理。它被采样生成一个点云,通过放置扩展成世界矩阵,最后复制到 CPU 内存。你可以想象这一切是如何在多个层之间进行的,所有这些层都可以在 GPU 上并行地进行评估,没有什么问题…… 哦,等等。我们还没有解决碰撞问题。事实上,你最终会得到很多资产放在彼此之上,所以必须添加某种避免碰撞的方法。幸运的是,这个问题有一些有趣的解决方案。
notion image
The general solution for collision in thesecases is doing read-backs, this means that you would have to read back previousplacements, and either discard or re-iterate on collision.
This creates complex dependencies within yourGPU pipeline if you want to hold on to that local stability and deterministicbehavior. And GPU’s don’t really like those; no flushesplease.
However, when footprints between layers matchup, we can use a dithering technique called layered dithering, which isbasically free collision avoidance during discretization.
Readback was our backup path for colliding objectsacross different footprints. This lives somewhere in my saved changelists, itnever made it into the game proper. Instead we make extensive use of thelayered dithering technique to solve collision between placement layers withthe same footprint.
在这些情况下,冲突的一般解决方案是进行回读,这意味着您必须回读以前的位置,并在冲突时放弃或重新迭代。如果您希望保持本地稳定性和确定性行为,那么这将在您的 GPU 管道中创建复杂的依赖关系。GPU 不太喜欢这些,请不要冲洗。然而,当层之间的足迹匹配时,我们可以使用一种称为分层抖动的抖动技术,这基本上是在离散化过程中的自由碰撞避免。readback 是我们的备份路径,用于跨不同的封装外形碰撞对象。这存在于我保存的变更列表中的某个地方,它从来没有使它成为游戏的适当部分。相反,我们广泛使用分层抖动技术来解决具有相同足迹的放置层之间的冲突。
notion image
Before we look into the details, lets changegears and focus on a one dimensional slice of the densitymap to make it easierto visualize.
So we grab a density map
Take a slice out of it
在我们研究细节之前,让我们换个档位,把注意力集中在密度图的一个一维切片上,以便于可视化。所以我们拿一张密度图,从里面切一片。
notion image
So in normal dithering, you have only onedensity map, with layered dithering, we’re dithering multiple layers of densitiesat once. Our starting point is still the same, a single density layer.
We then run our GENERATE shader, which samplesthe density on different points, with different thresholds. We can visualizethis as these little pins. So each pinpoint that lies below the density mapgenerates a placed object
Now what would happen if we would run anotherlayer directly afterwards, with the same footprint.
It would have an identical dithering pattern,and it would place directly on top of them.
The solution to this problem, is to simplylayer the densities together.
Now each pin-point can only lie in one range,removing any possibility of collision.
The density maps are now suddenly doublelayered (density volumes), having a minimum and maximum bound.
[each layer floats on top of the other layers]
The layers along a given sample point can nowbe seen as a propability distribution of multiple assets, where the thresholdof the sample point is the evenly distributed stochastic value that selectsone.
This whole thing also holds in 2D dithering.
所以在正常抖动中,只有一个密度图,使用分层抖动,我们会同时抖动多个密度层。我们的出发点还是一样的,一个密度层。然后运行 GENERATE 着色器,它在不同的点上以不同的阈值采样密度。我们可以把它想象成这些小别针。因此位于密度图下方的每个点都会生成一个放置的对象,现在,如果我们在后面直接运行另一个具有相同足迹的层,会发生什么。它将有一个相同的抖动模式,并将直接放在上面。解决这个问题的办法,就是把密度简单地叠加在一起。现在每个针点只能在一个范围内,消除了任何碰撞的可能性。密度图现在突然变成了双层(密度体积),有一个最小和最大的界限。[每个层浮在其他层的顶部]。沿着一个给定采样点的层现在可以看作是多个资产的一个可比性分布,其中采样点的阈值是选择一个均匀分布的随机值。这整件事也适用于二维抖动。
notion image
So we solved collision, and we still end uphaving dependencies, but the dependencies only relate to the DENSITYMAP phase,and with some refactoring, you can still evaluate all layers independently onGPU using atomics, saving costly GPU flushes.
So here you can see that we run multipledensitymap shaders before we hit GENERATE.
This is actually the most common case inproduction ecotopes. We often have more than 20 layers with the same footprintthat stacked on top of each other like this and we are almost always interestedin placing only a very specific subset of layers.
Meaning that we run DENSITYMAP on layers thatwe don’t want toplace, but we need their DENSTIYMAP to reach higher up in the stack.
So our pipelines run N density maps for eachlayer that actually need to be placed. We want this number N to be as low aspossible,so we use all kind of heuristics to order the layers in such a waythat the most commonly placed layers are at the bottom of these stacks.
所以我们解决了冲突,我们仍然有依赖关系,但依赖关系只与密集映射阶段有关,通过一些重构,你仍然可以独立地评估 GPU 上的所有层使用原子,节省昂贵的 GPU 刷新。在这里,你可以看到我们在点击 Generate 之前运行了多个 DensityMap 着色器。这实际上是生产生态环境中最常见的情况。我们通常有 20 多个具有相同足迹的层,像这样彼此叠加在一起,我们几乎总是对只放置一个非常特定的层子集感兴趣。这意味着我们在不想放置的层上运行密度图,但我们需要它们的密度图在堆栈中更高。所以我们的管道为每一层运行 N 个密度图,实际上需要放置。我们希望这个数字 N 尽可能的低,所以我们使用各种启发式方法来对层进行排序,使最常见的层位于这些堆栈的底部。
notion image
As a final step, we look at our GPU scheduling.If we would only place one layer per frame, it would extremely slow, so inorder to schedule more work, we instantiate our entire pipeline multiple times.Note that we can not schedule dependent layers across different pipelines. Sowe mostly parrelize spatialy in this step; each pipeline picks an area topopulate.
In our first prototype, pipelines would emitits shaders one at a time, but again this means that each shader uses theoutput data of the shader before it, which is not ideal because they can’t overlap on GPU.
最后一步,看一下我们的 GPU 调度。如果我们每帧只放置一个层,那么速度会非常慢,因此为了安排更多的工作,我们将对整个管道进行多次实例化。请注意,我们不能跨不同的管道调度依赖层。因此,在这一步中,我们主要对空间进行 Parrelieze;每个管道都选择一个区域进行填充。在我们的第一个原型中,管道将一次发射一个着色器,但这再次意味着每个着色器使用其之前的着色器输出数据,这并不理想,因为它们不能在 GPU 上重叠。
notion image
So this is our final gpu load layout.
We first run DENSITYMAP across all pipelinesindependently
We keep doing this until we hit a layer thatneeds to be placed
Then we run the GENERATE shader, and a specialALLOCATION shader to dynamically allocate memory in the copy buffer
Then PLACEMENT converts the oriented points tothe copy buffer
The entire thing can be repeated until all workis done for all pipelines, but we shipped with a maximum of 4 emits to reducememory load and prevent GPU spikes.
Finally the copy buffer copies all data to CPUin one go. This saves us hundreds microseconds of syncing and copying overhead.
所以这是我们最后的 GPU 负载布局。我们首先在所有管道上独立运行密度图。我们一直这样做直到我们碰到一个需要放置的层,然后我们运行 generate shader 和一个特殊的分配 shader 来动态地分配复制缓冲区中的内存。然后 placement 将定向点转换为复制缓冲区。整个过程可以重复,直到所有管道的所有工作都完成为止,但是为了减少内存负载和防止 GPU 峰值,我们提供了最多 4 个发射器。最后,复制缓冲区将所有数据一次性复制到 CPU。这为我们节省了数百微秒的同步和复制开销。
notion image
notion image
notion image
notion image
notion image
notion image
notion image
notion image
notion image
notion image
具体沟通,咨询,讨论,升级通知,请进入元素 TAQQ 群  元素 TA QQ 群   318958005
元素地编 QQ 群 782480353
notion image
> 本文由简悦 SimpRead 转码