Getting The World Of Sprint Vector Over The Finish Line

[11.20.18]- Kevin Andersen

"Sprint Vector" was an adventure to make. As I originally drafted this, project champion and lead designer Andrew Abedian looked like he crawled out of a shallow grave to take a nap in an office chair, producer Chris Thompson was pouring Red Bull in his coffee, and Console QA lead Kris Gruchalla was half-awake doing butterfly-knife tricks to an audience of frightened interns.

The game itself was a hugely ambitious project with massive maps, eight simultaneous players all with unique character models, loads of effects and UI elements, an original setting and story, two fully-voiced commentators, and probably some other things-all delivered straight to your face in virtual reality, sporting a brand spanking new Fluid Locomotion System that tricks your brain into thinking you deserve to go this fast.

Indeed, bringing "Sprint Vector" from concept to reality was an immensely educational, utterly infuriating, and beautifully rewarding process for the whole team. The solutions we found in doing so should be well worth reading about, not just for other game devs, but also for enthusiasts, fans, and the next-of-kin to the few QA testers who caught fire.

So here-from the initial block-out phase of the level designs to the finalized lighting and effects being frantically tuned at the last minute-the "Sprint Vector" team presents a nearly-interesting account of development's many technical hurdles and how they were chainsawed. Some jargon may be foreign to the average reader, so if you're unsure of something, just assume it was very impressive.

Arting the Game and Establishing "the Look"

The team wanted to do something bright, with a fun, comic-book-y feel that wouldn't overwhelm the player with details as they zoomed through it all at great speeds. A simple, colorful, cartoon world was the antidote to Raw Data's dark, detailed, sci-fi aesthetic. Seriousness summarily defenestrated, we adopted a new standard of zany wackiness and wacky zaniness, with hints of madcap shenanigans and offset with notes of whimsy and rich sandalwood.

Keeping everything simple-and immediately readable at 50 mph-required environments to be made up entirely of big, obvious shapes and solid colors. To that end, the use of textures on environment assets was eschewed and the era of vertex color began. "Sprint Vector's" world assets rely almost exclusively on vertex colors for diffuse and roughness/metallic values on the majority of their materials. They also use sharp-edged geometric detail to denote surface features like sand and rocks. This ultra-simplified style had both pros and cons vs traditional game-art production.

Pros: Fewer textures means less VRAM used and faster loading times. Making things quickly and authoring their colors all in maya, unifying color and roughness schemes throughout the game, and only needing a single UV channel (for lightmaps), since no textures also means no worrying about tilling or seams.

Cons: Using geo, instead of normal maps, for every feature inflates the vertex count, which is already doubled in any VR game. Also, hard edges (split vertex normals) on everything then further triples the vertex count, sending the transform cost and mesh memory allocation into the bad zone of sadness.

Every triangle that makes up a given surface gets its normal (facing direction) from the information contained in its three vertices and any one vertex can only have one normal. If you want an edge between two faces to be sharp, you have to split the vertices at its points so each face is getting its normal information from its own separate set of verts that are now just sharing locations with the vertices from the surrounding faces. That vert-splitting is not free; you pay the GPU cost of transforming every vertex no matter where it is and the average asset's vertex count can triple with just that one change. As stated, a VR game's geometry must be transformed twice for a stereo camera, so now you're looking at 6x maya's stated vertex count, throttling the GPU and making tech-artists like me seethe with rage at the hubris of it all.

Another issue with the hard-edge approach was that LODs became really ugly really fast. The swap from hi-res to low-res can be hidden somewhat by averaged (smooth) face normals, but when a hard-edge disappears everyone sees it. For that reason, Unreal's auto-generation of LOD meshes uses normals to determine which edges to delete while simplifying a mesh, and it just can't make good decisions when all the edges are sharp. We needed some other way sharpening the normals on our level geometry, so I made a material that altered the way surface normals are read in the pixel shader.

The block on the left is the model as it was imported to the game, with all of its vertex normals averaged. This keeps the vertex count as low as possible but makes it look like a sad blob of nothing. The block on the right is the same asset with the edge hardness restored, bringing the details back so one can see the individual bricks. Its surface normals have actually been faked in the pixel shader with the material logic below.

The GPU renders scene pixels in 2x2 quads that allow it to compare a surface's UV coordinates at 4 points and use that distance to determine the proper mip level to use for the texture lookup. The bonus here is that this also allows other properties of pixels to be compared to each mid-render by using the DDX and DDY functions in the material graph. These instructions return the delta between a pixel and its neighbor along the screen's x or y axis for the given attribute.

In this case, the attribute we want is the pixel's position in the world. The deltas between these positions are therefore useful tangents and bitangentsfrom which a surface normal can be derived by normalizing their cross product. The result is then plugged into the material's Normal input (where a normal map texture would typically go). Another advantage of this technique is the ability to blend between hard and smooth normals with vertex alpha, or over distance to hide LOD popping. This method did cause some annoying noise on metallic objects when you get up close, but that was acceptable considering the savings.