Raw Data & Granite: Making VR Textures Feel like IRL
By Kevin Andersen, Raw Data Lead Technical Artist
The world of Raw Data–its environments, objects, and characters–is rife with hard surfaces peppered with subtle details. That seems simple enough to render in theory; however, it can be unforgiving when artifacts show up. This became incredibly apparent on assets that get close to the player (such as the robot enemies) and on large assets like the rotating Earth model seen in the lobby environment. But removing these blocky artifacts from Unreal Engine (UE)’s default texture compression would have been impossible without an unreasonable increase in resolution.
Rather than simply bump up the texture size to some absurd degree and have a bunch of 8K textures just sitting in VRAM, we needed a solution that enabled us to be as selective and specialized as possible with our textures. Additionally, we had to be able to efficiently atlas and compress them to keep their VRAM footprint from becoming unmanageable. And this was before we added Raw Data’s Teleshift ability, which–because it allows players to rapidly end up in close proximity to objects that were previously at a distance–only increased the need for effective handling of large textures!
Early in Raw Data’s development, we found that having highly-detailed characters with environments using baked light and shadow maps was going to be very demanding on VRAM and increase loading times. After doing research into other texture-management plugins, we settled on giving Granite a configuration test run, to see how well it handled large textures and multiple compression schemes. Granite’s flexibility helps to reduce waste and minimize the memory-footprint of maps laden with large textures. We expect to eventually have most of Raw Data’s more detailed in-game assets outfitted with high-res Granite textures, reducing the amount of art that needs to be pre-loaded into memory by about 75%. At the moment, we still haven’t done much custom tweaking to Granite, but we haven’t needed to because its existing out-of-the-box setup has performed so well.
Before (left) and after (right) shots of the Earth model from Raw Data’s lobby area.
When it comes to tile sets, we always specialize for a texture’s intended asset-type. For example, the tile set used for enemy robots has a top layer using BC7 compression on a R8G8B8A8_sRGB color map with an optional alpha that defines areas to be color-shifted within the material (if needed). Granite optimizes away any unused channels–allowing us much greater flexibility in texture design and quality–that mitigates their impact on final file size. The normal map is the usual BC5 (X8Y8_TANGENT), so nothing special there, apart from affording a much larger map than usual. The third map is a stacked-masks texture with Ambient Occlusion, Roughness, and Metallic in their respective R8G8B8_LINEAR channels. BC7 is used here as well, and it really shines in keeping artefacts to a minimum.
Just for sake of visual fidelity, BC7 is generally preferable to UE4’s default BC1/3 because BC7 has much less noticeable compression artefacts and the ability to have an alpha channel of arbitrary bit depth. In UE4 default, BC7 is available (on Windows only) but not default because using BC7 doubles a texture’s file size and the amount of needed memory. However, Granite’s efficient packing and streaming makes BC7’s increased file size a non-issue, so we’re now using BC7 for most of our textures.
A close-up of an Automo enemy robot model before (left) and after (right) Granite implementation.
Finally, if it’s absolutely necessary, we use a single BC4 (X8) grayscale texture for an emissive map. I normally try to avoid emissive maps–putting emissives in the asset’s vertex color–because, typically, areas of a texture channel that are unused are represented with a uniform color like black or white. Without Granite, these solid-color areas occupy disk space and memory but contribute nothing. With Granite, these uniform areas are detected and automatically compressed to just one 128×128 pixel tile. Granite also supports sparse layers and does not require every texture layer be filled to compile a Tile Set–another point in its favor.
Another benefit we’ve found to using Granite is its quick adaptability to UE’s frequent updates. Whenever Epic releases a new version of UE, Graphine updates and deploys their Granite integration code very quickly thereafter. From there, it’s about a half-day’s work implementing Granite into our custom version of the engine. We’ve done this for three UE releases now (4.11, 4.12, 4.13), and each time the process went mostly trouble-free. As a plus, Graphine’s code changes are always clearly commented, which is extremely helpful when we are simultaneously juggling changes from Epic, Graphine, and ourselves to make them get along.
Currently, you can see the Granite plug-in’s effects in Raw Data every time you check out the spinning Earth model in the intro lobby–or whenever an Eden Corp robot gets up close and personal. But we’re not stopping there. As we continue to update Raw Data for the best possible performance and stability, we’re phasing in more Granite texture assets as we go. By the final game release, we plan to outfit most in-game assets with high-res Granite textures, especially objects that will be very near the player. Granite is a big help in reaching our target memory specification while keeping the quality of our content. So be wary of when those bots start drawing near: you may be so entranced by their high-quality, fast-loading textures that it may be the last thing you see. (Well, before you respawn and take revenge, that is!)