All Unreal Engine 4

Fornite Imposter 原理




  • 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:




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.

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





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.



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.



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.





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).



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).




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.



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.




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.



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.



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):





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



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.




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 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.



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方法对于非常薄的对象存在问题,并且当整行都是黑色时,它无法进行最佳切割。 您可以按转换为手动切口按钮,也可以手动调整点!】