All Unreal Engine 4

Fornite Imposter 原理

 
 

https://www.shaderbits.com/blog/octahedral-impostors

https://github.com/ictusbrucks/ImpostorBaker

 
 

Notes

 
 

  • Everything should be downloaded to the location: \MyProject\Plugins\ImpostorBaker\ .【一切都应下载到以下位置:\ MyProject \ Plugins \ ImpostorBaker \。】
  • The Plugin called “BlueprintMaterialTextureNodes” must be enabled (That one is my first c++ plugin in the engine. Makes it possible to the BP to save assets.)【必须启用名为” BlueprintMaterialTextureNodes”的插件(这是我在引擎中的第一个c ++插件。这使BP可以节省资产。】
  • Basic instructions are in the included map file Generate_Impostor_Map.umap.【基本说明在随附的地图文件Generate_Impostor_Map.umap中】

 
 

I will be talking about this in more detail at GDC next (this?) week. Here is more info on those talks:

http://schedule.gdconf.com/session/optimizing-span-classhighlightfortnitespan-battle-royale-part-2-presented-by-epic/857076

http://schedule.gdconf.com/session/realistic-foliage-imposter-and-forest-rendering-in-ue4/852286

 
 

Introduction

 
 

I have been experimenting with impostors for several years now. The idea is not exactly cutting edge. There is a version of an impostor generator built into UE4 that I added a long time ago, but at this point it is pretty dated and borderline unusable (it exists under the Render To Texture BP Library). While the biggest problem with the old method is the cumbersome usability, it also wasted lots of texture memory.【我多年来一直在尝试 impostors。这个想法并不完全是尖端的。我很久以前就添加了UE4中内置的 impostors 生成器的一个版本,但此时它已经过时且无法使用边界线(存在于渲染到纹理BP下)。尽管旧方法的最大问题是麻烦的可用性,但同时也浪费了大量纹理存储空间。】

 
 

This new version improves the workflow issues and makes more optimal use of the available texture space by using a layout based on octahedra.【通过使用基于八面体的布局,此新版本改善了工作流程问题并更有效地利用了可用的纹理空间。】

 
 

In order to make it easier to understand exactly what is going on behind the scenes for these new impostors, I think it is helpful to first think of Traditional Billboards since everybody understands them.

【为了更容易准确地了解这些新冒名顶替者在幕后发生的事情,我认为首先考虑传统广告牌会有所帮助,因为每个人都可以理解它们。】

 
 

Did I mention the Impostor Baker has a Traditional Billboard mode too?

【我是否提到过Impostor Baker也具有传统广告牌模式?】

 
 

 
 

The three modes are: Full Sphere, Upper Hemisphere and Traditional Billboards. In order to better understand the impostors, I will first show the Billboards and progressively enable expensive features the make them look better. Then the similarity/overlap should make sense.

【这三种模式是:全球面,上半球和传统广告牌。 为了更好地了解冒名顶替者,我将首先展示广告牌,并逐步启用昂贵的功能,使它们看起来更好。 那么相似性/重叠应该是有意义的。】

 
 

Billboards

 
 

 
 

The above video shows what the blueprint generates for “Traditional Billboards” mode.This is a 3×3 layout of frames with 8 side views and 1 top view (texture atlas shown briefly in video). Notice how the separate cards cause noticeable artifacts. Enabling “Dither” is like a masked Fresnel that uses DitherTemporalAA to smoothly hide the cards that are not facing the camera. It helps but TempAA is not an option for all platforms such as mobile, so this option is not reliable. Speedtree billboards use the vertex shader to simply hide the cards that are not facing the camera.

【上面的视频显示了蓝图为传统广告牌模式生成的内容。这是帧的3×3布局,具有8个侧视图和1个顶视图(视频中简要显示了纹理图集)。注意单独的卡如何引起明显的伪像,启用抖动就像蒙版的菲涅耳一样,它使用DitherTemporalAA平滑地隐藏了不面向相机的卡片。但TempAA并非适用于所有平台(例如移动平台),因此此选项并不可靠。 Speedtree广告牌使用顶点着色器简单地隐藏不面向相机的卡片。】

 
 

Next, PixelDepthOffset (PDO) is enabled in the video. This uses the captured depth to offset the pixels and cause more interesting intersections between the cards and the world. It has the same problem that its not supported or a good idea to enable for all platforms, plus it makes TempAA look smeary when parallax does not match for complicated reasons.

【接下来,在视频中启用PixelDepthOffsetPDO)。这使用捕获的深度来偏移像素,并在卡片和世界之间引起更多有趣的交集。它具有不支持所有平台或支持在所有平台上启用的好主意的相同问题,此外,由于复杂原因导致视差不匹配时,它会使TempAA看起来很脏。】

 
 

Finally, POM (parallax mapping) is enabled. Notice how this makes the transitions between frames much smoother. There are some strange artifacts at the edge of the frames (especially when approaching the top view). Those artifacts are very difficult to explain and fix, but I do not intend to spend much time fixing them, since enabling full POM on a billboard like this is quite expensive, and it would still require significantly more than 9 frames for POM to look smooth for all views.

【最后,启用POM(视差映射)。请注意,这如何使帧之间的过渡更加平滑。框架边缘(特别是在接近顶视图时)有一些奇怪的伪像。这些工件很难解释和修复,但是我不打算花很多时间来修复它们,因为在这样的广告牌上启用完整的POM相当昂贵,而且要使POM看起来很平滑,仍然需要9个以上的帧。对于所有视图。】

 
 

The point I wanted to make with the above example is that while decent results can be had with a few shader tricks, it does not scale to a smooth high quality version. To increase quality we need to add more billboard cards. This makes the mesh heavier in terms of both vertex and pixel shader, and doing POM is slow enough without it being in lots of overlapping masked triangles. The 9 card cross is already 72-81 verts, so if we add many more, whats the point? At some point a very low standard LOD would be better, but it would be harder to make.

【我想在上面的示例中说明的一点是,尽管可以通过一些着色器技巧获得不错的结果,但它并不能缩放到平滑的高质量版本。为了提高质量,我们需要添加更多的广告牌。这使得网格在顶点着色器和像素着色器方面都较重,并且执行POM足够慢,而不会出现大量重叠的蒙版三角形。 9卡牌的交叉点已经是72-81个点,因此,如果我们添加更多点,那么意义何在?在某个时候,非常低的标准LOD会更好,但是很难制造。】

 
 

What if there was a way to capture more view angles, but render the final material without needing geometry for each card? That is basically what impostors attempt to do.

【如果有一种方法可以捕获更多的视角,但渲染最终材质而无需为每张卡片添加几何形状怎么办?这基本上是冒名顶替者试图做的。】

 
 

Impostors

 
 

The idea behind impostors is to capture the object from a variety of view angles and store each view to a texture. An example of cameras arranged around an upper hemisphere:

【冒充者背后的想法是从各种视角捕获对象并将每个视图存储到纹理中。 围绕上半球布置的摄像机的示例:】

 
 

 
 

The method used here is very important. In my previous impostor generator, I defined the capture points with evenly spaced X columns split over Y rows based on pitch angle. If that made no sense to you, perhaps this image will help:

【这里使用的方法非常重要。 在我以前的冒名顶替者生成器中,我定义了捕获点,这些捕获点具有基于间距角均匀分布在Y行上的X列。 如果那对您没有意义,则此图像可能会有所帮助:】

 
 

 
 

You can see how each ‘ring’ of the sphere has the same number of verts. So the row near the top of the sphere has verts more densely packed than the equator. This is the layout the old impostor baker used. It wastes tons of resolution around the poles by capturing views that are next together. And yes, the very top Z frame also gets repeated instead of just being a single view. At the time the only reason I used the above layout was because it was cheap to compute in a shader, whereas the more ‘fancy’ methods with equal spacing that people were writing papers about required expensive trig and texture arrays because the grids did not map to 2D space uniformly (For example, BN11 in references).

【您可以看到球体的每个如何具有相同数量的顶点。因此,靠近球体顶部的行的顶点比赤道的顶点更密集。这是旧的冒名顶替面包师使用的布局。通过捕获相邻的视图,会浪费极点周围的大量分辨率。是的,最顶部的Z框架也会重复出现,而不仅仅是一个视图。当时,我使用上述布局的唯一原因是因为在着色器中进行计算很便宜,而等间距的花式方法使人们在编写有关所需的昂贵的TrigTexture数组的纸张时因为网格没有映射均匀地排列到二维空间(例如,参考文献中的BN11)。】

 
 

For this version, I am using Octahedra and Hemi Octahedra to handle the mapping. For those not familiar with octahedra, they are a convenient way to convert between 2D and 3D space, or vice versa. They have been used in graphics for a while to compress things like V3 normals to be V2 since the full direction is preserved (no Z sign loss as with derivenormalZ). UE4 makes use of them in this way internally a few times. Octahedra have very little distortion. Slightly more than a 6 sided cubemap but much much simpler to compute.

【对于此版本,我使用OctahedraHemi Octahedra来处理映射。对于不熟悉八面体的人来说,它们是在2D3D空间之间进行转换的便捷方法,反之亦然。由于保留了完整方向(在没有Z符号损失的情况下(如derivenormalZ那样)),它们在图形中已经使用了一段时间以将V3法线压缩为V2 UE4以这种方式在内部多次使用它们。八面体几乎没有变形。略多于6面的立方体贴图,但计算简单得多。】

 
 

To better understand how Octahedrons come into play, I made this animated gif. It shows the result of converting between 2D grid points and 3D points using Hemi-Octahedron (left) and full Octahedron(right).

【为了更好地了解八面体如何发挥作用,我制作了此动画gif。它显示了使用半八面体(左)和完整八面体(右)在2D网格点和3D点之间进行转换的结果。】

 
 

 
 

You may notice that the full octahedron is more evenly spaced. While this is true, the Hemi-Octahedron still gains significant resolution for cases where you never need to see below the horizon. It is also kind of convenient that it gives a bit of over-sampling to the horizon, which is where trees are seen from in most games. Most trees should use the Hemi-Octahedron layout for that reason. Both of these are significantly better than the ‘old style’ method I referenced at the start, notice there are no verts really close together. Note: I have flipped the edge direction around the poles above to make them symmetrical. The current shader is not actually doing that correction because it costs a few more instructions and added complexity. I did have it working at some point though and its a potential way to improve quality and ensure symmetrical flow.

【您可能会注意到,整个八面体的间距更均匀。尽管这是事实,但对于您永远不需要在地平线以下看到的情况,半八面体仍然会获得可观的分辨率。还可以方便地对地平线进行一些过度采样,这是大多数游戏中看到树木的地方。因此,大多数树木应使用半八面体布局。这两种方法都比我在开始时提到的旧式方法要好得多,请注意,没有任何顶点真正靠得很近。注意:我已将上方两极的边缘方向翻转为使其对称。当前的着色器实际上并没有进行该校正,因为它花费了更多的指令并增加了复杂性。我确实在某个时候使它起作用,并且它是提高质量和确保对称流动的潜在方法。】

 
 

To capture impostors, you specify how many XY frames you want. That determines how dense the ‘grid mesh’ is. Each vertex on the grid will become a rendered frame. Note that the grid itself is entirely virtual within the vertex shader, it is not actually rendered as a mesh at any point.

【要捕获冒名顶替者,您可以指定所需的XY帧数。这决定了网格的密度。网格上的每个顶点将成为一个渲染帧。请注意,网格本身在顶点着色器中完全是虚拟的,实际上在任何点都不会渲染为网格。】

 
 

If we show the virtual grid over the captured Impostor texture atlas, we can see that the vertices of the grid align with frame centers in the atlas.

【如果在捕获的Impostor纹理图集上显示虚拟网格,则可以看到网格的顶点与图集中的帧中心对齐。】

 
 

 
 

What happens if we simply find the nearest frame and draw it on a regular camera facing sprite? The result is not great:

【如果我们仅找到最近的帧并将其绘制在面向精灵的普通相机上会发生什么?结果不是很好:会跳变】

 
 

【视频见原文】

 
 

The impostor rotates wildly from the top view. This is because the view transform is changing quite a bit from those top views. If we think about the original billboard, each ‘view’ was always locked to its original worldspace. if we want to render with a sprite, we also need to lock the sprite to the original view projection it was captured at. That means it needs to change every time the view changes frames. That gets us looking like:

【冒名顶替者从顶视图疯狂旋转。这是因为视图转换与那些顶视图相比发生了很大变化。如果我们考虑原始的广告牌,则每个视图始终被锁定到其原始的世界空间。如果要使用精灵进行渲染,则还需要将精灵锁定到捕获它的原始视图投影。这意味着每次视图更改框架时都需要更改。这使我们看起来像:】

 
 

【视频见原文】

 
 

Now the sprite geometry is locked to the original view vectors, by deriving the original transform from the quantized vector and cross products. Notice it fixes the wild spinning but there is still popping. About halfway through the above video, I turn on single Parallax which distorts the frames to look like eachother. it helps the popping quite a bit (this is the version currently used for FNBR mobile). But we can still do better.

【现在,通过从量化矢量和叉积导出原始变换,可以将子图形的几何形状锁定到原始视图矢量。请注意,它修复了狂放的旋转,但是仍然弹出。在以上视频的一半左右,我打开了单个视差,使帧失真,看起来彼此像。它对弹出效果有很大帮助(这是FNBR手机当前使用的版本)。但是我们仍然可以做得更好。】

 
 

Looking back to the ‘virtual grid mesh’ above, we can see that for any triangle on the grid, it has 3 vertices. So if we want to blend smoothly across this grid, we need to be able to identify the 3 nearest frames. And remember how using a sprite caused messed up projection of just one frame? Well it turns out the same thing happens when you try to reuse the projection from one frame for another! This is a pain. So you actually have to render a virtual frame projection for the other 2 frames to simulate their geometry. While using the mesh UVs for one projection and ‘solving’ the other two does work, it falls apart for lower (~8×8) frame counts because the angular difference can be so great between cards that you see the card start to clip at grazing angles (not shown in any videos yet). As a compromise, the shader does not use ANY UVs right now. It solves all 3 frames using virtual frame projection in the vertex shader and then uses a traditional sprite vertex shader. The only downside is at close distances you occasionally see some minor clipping on the edge but it is much more acceptable this way.

【回顾上面的虚拟网格网格,我们可以看到对于网格上的任何三角形,它都有3个顶点。因此,如果要在整个网格上平滑融合,我们需要能够确定3个最近的帧。还记得使用Sprite如何导致混乱的一帧投影吗?好吧,当您尝试将一帧的投影重用于另一帧时,也会发生同样的事情!这很痛苦。因此,您实际上必须为其他两个框架渲染虚拟框架投影以模拟它们的几何形状。在使用网状UV进行一个投影并解决另外两个问题时,它分拆成较低的帧数(〜8×8),因为卡之间的角度差异可能非常大,以至于您看到卡开始以掠射角夹住(尚未在任何视频中显示)。作为一种折衷,着色器现在不使用任何UV。它使用顶点着色器中的虚拟帧投影来解决所有3帧,然后使用传统的精灵顶点着色器。唯一的缺点是在相距很近的地方,您偶尔会在边缘看到一些轻微的剪裁,但这种方式更容易接受。】

 
 

First, another quick video to show how this eliminates all popping. It does slightly blur the result but that is preferable to popping and is not noticeable at typical impostor distances (0.1 screen size or so):

【首先,另一个快速视频展示了如何消除所有爆裂。它确实使结果稍微模糊,但是比弹出效果更好,并且在典型的冒名顶替者距离(0.1屏幕大小左右)下并不明显:】

 
 

【视屏见原文】

 
 

Now we will look at how the 3 views are found and blended using a weight function.

【现在,我们将研究如何使用权重函数找到3个视图并将其融合。】

 
 

Since this part is done in 2D space, it is relatively simple. First we find which QUAD (not tri) we are in by doing a standard floor(View2DVec * FrameXY)/FramesXY type operation. Then we say the ‘current frame’ has an offset of 0, the ‘right frame’ has an offset of (1,0), the ‘lower frame’ has an offset of (0,1), and the ‘lower right’ frame has an offset of (1,1).

【由于这部分是在2D空间中完成的,因此相对简单。 首先,我们通过执行标准floorView2DVec * FrameXY/ FramesXY类型的操作来找到我们所处的QUAD(而非tri)。 然后我们说当前帧的偏移量为0右帧的偏移量为(1,0),下帧的偏移量为(0,1),右下角帧的偏移量为(1,1)。】

 
 

For each quad, the weights for each frame and the ‘triangle mask’ are used to identify which part of the triangle the view intersects.

【对于每个四边形,每个帧的权重和三角形蒙版用于标识视图相交的三角形的哪一部分。】

 
 

 
 

A (or red) represents the weight for the upper left or ‘current frame’. B (green) represents the weights for both the right frame and the lower frame. Notice it is symmetrical across the diagonal. That is because to reduce from 4 to 3 neighbors, we ‘flip’ between either the right or lower neighbor. C (blue) shows the weight for the lower right frame. D (alpha) is the triangle flip mask. Ie when the ray hits the white part, we use the right neighbor. When the ray hits the black part of D, we use the lower neighbor. So we end up flipping which neighbor is used when the view is on the diagonal. This works because B has a weight of 0 along the diagonal. When the camera is perfectly aligned with any vertex, that vertex will always have full weight of 1 and the others will be 0.

A(或红色)代表左上方或当前帧的权重。 B(绿色)代表右框和下框的权重。请注意,它在对角线上对称。那是因为要将邻居从4个减少到3个,我们在右邻居或下邻居之间翻转 C(蓝色)显示右下框的重量。 Dalpha)是三角形翻转蒙版。即,当光线照射到白色部分时,我们使用正确的邻居。当射线照射到D的黑色部分时,我们使用较低的邻居。因此,当视图在对角线上时,我们最终翻转了使用哪个邻居。之所以可行,是因为B沿对角线的权重为0。当摄像机与任何顶点完美对齐时,该顶点将始终具有1的全权重,而其他权重将始终为0。】

 
 

This gif shows the intersection being found on both grids, before and after unwrapping. This demonstrates how finding the triangle in 3D also finds it in 2D. Notice the small white dot which is the precise camera ray to the center of each sphere. The color of the triangle at that single point is where the weights are read from.

【此gif显示展开之前和之后在两个网格上都找到的相交。这说明了如何在3D中找到三角形也可以在2D中找到三角形。请注意小白点,这是精确照射到每个球体中心的白点。从该点读取权重的三角形的颜色。】

 
 

 
 

The below image shows what the single depth offset is like, very similar to bump offset. Note that this works really well for very dense trees where the depth mostly forms a cohesive shell. For very sparse and busy trees, this single offset will show lots of noisy artifacts.

【下图显示了单个深度偏移的样子,非常类似于凹凸偏移。 请注意,这对于非常密集的树木非常有效,在这些树木的深度主要形成一个有粘性的外壳。 对于非常稀疏和繁忙的树木,此单个偏移量将显示许多嘈杂的伪像。】

 
 

 
 

This comparison shows a 12×12 layout impostor for one of the main Fortnite pine trees. It holds up pretty well at a medium distance. The smallest frame count used in Fortnite is for the smaller farm trees which get by pretty well with only an 8×8 layout.

【此比较显示了Fortnite主要松树之一的12×12布局冒名顶替者。 它在中等距离下可以很好地支撑。 Fortnite中使用的最小帧数是用于较小的农场树木,只有8×8布局才能很好地实现。】

 
 

 
 

HLOD

 
 

HLOD stands for Hierarchical LOD. We use HLOD in UE4 to represent distant levels that are not actually loaded. We tend to refer to those as “Proxy HLODs”. Originally, FNBR just used standard simplification for all the trees. That was causing HLOD meshes to be a ton of tris/verts and ti also meant the island did not look great while skydiving and popping was quite noticeable.

HLOD代表层次LOD。我们在UE4中使用HLOD表示未实际加载的远距离级别。我们倾向于将它们称为代理HLOD”。最初,FNBR只是对所有树使用了标准简化。这导致HLOD网格变成了大量的tris / vertsti也意味着该岛在跳伞和弹出时非常引人注目,看起来并不好。】

 
 

With the help of engineer Jurre de Baare, we made Impostors a built in part of HLODs. We added an option called “Use MaxLOD as Impostor” that tells an object to skip simplification and simply composite its data directly into the HLOD. This does bloat up sections because each impostor type needs its own material section.

【在工程师Jurre de Baare的帮助下,我们使Impostors成为了HLOD的一部分。我们添加了一个名为使用MaxLOD作为伪装者的选项,该选项告诉对象跳过简化并将简单的数据直接组合到HLOD中。这确实会使部分膨胀,因为每种冒名顶替者都需要自己的物料部分。】

 
 

All told, switching to impostors saved ~270,000 verts from being stored in always loaded memory (proxys never unstream) but it cost around 600 draw calls when viewing the whole island. That draw call cost mostly goes away from the ground.

【总而言之,改用冒名顶替者节省了大约270,000个变体,因为它们一直存储在始终装载的内存中(代理永远不会流失),但是在查看整个孤岛时,大约要花600次抽奖。抽奖电话的费用大都离地面很远。】

 
 

This shows the impact on an individual POI (point of interest), the Ranger Station aka Lonely Lodge. Standard HLOD was costing almost 13k tris. Impostor HLODs dropped that in half and made it look much nicer.

【这显示了对单个POI(兴趣点),Ranger Station aka Lonely Lodge的影响。标准HLOD花费了将近13,000 Tris。冒名顶替者将其减半,并使其看起来更好。】

 
 

The quality difference is fairly noticeable while skydiving in:

【跳伞时,质量差异相当明显:】

 
 

 
 

Another big reason to do this is performance savings when on the ground. Standing in wailing woods, enabling impostors saved around 500k triangles from rendering. This can be a several milliseconds on some hardware.

【这样做的另一个重要原因是在地面上可以节省性能。 站在哭泣的树林中,使冒名顶替者从渲染中节省了约50万个三角形。 在某些硬件上,这可能是几毫秒。】

 
 

In terms of memory, they do cost significant texture memory. We saved the 270k verts from proxy HLODs, which is estimated to be around 10mb from memory. But each impostor in FNBR uses a 2048 basecolor/alpha and 1024 normal/depth. So each tree is 5.4 + 1.4 or 6.8mb of textures. There are currently 12 trees so thats ~81mb of textures. But the good news is that the texture data is streamable whereas the proxy mesh data is not.

【在内存方面,它们确实花费了大量的纹理内存。 我们从代理HLOD中保存了270k顶点,估计它距离内存约10mb。 但是FNBR中的每个冒名顶替者都使用2048基色/ alpha1024普通/深度。 因此,每棵树的纹理为5.4 + 1.46.8mb。 当前有12棵树,所以〜81mb的纹理。 但是好消息是纹理数据是可流式传输的,而代理网格数据不是。】

 
 

For mobile we limit impostors to 1024 textures across the board. In the ASTC format, the 1024s use 341kb each. So on mobile the total texture memory cost is 8.1mb. So when you look at the numbers that way, switching to impostors DID save memory for mobile and everybody else paid a few megs to look nicer.

【对于移动设备,我们将冒名顶替者全线限制为1024个纹理。 在ASTC格式中,1024个每个使用341kb。 因此,在移动设备上,总的纹理存储成本为8.1mb。 因此,当您以这种方式查看数字时,切换到冒名顶替者DID可以为移动设备节省内存,而其他所有人则花了几笔钱才能看起来更好。】

 
 

Final Notes

 
 

The blueprint automatically makes a mesh cutout based on the combined alpha of all impostor frames. The cutout method has issues with very thin objects and it fails to make the most optimal cuts when entire rows are black. You can press the ‘Convert to Manual Cutout’ button and adjust the points manually too!

【蓝图会根据所有冒名顶替者框架的组合alpha值自动进行网格划分。 cutout方法对于非常薄的对象存在问题,并且当整行都是黑色时,它无法进行最佳切割。 您可以按转换为手动切口按钮,也可以手动调整点!】