Shawn Hargreaves Blog
The bikes and tracks in MotoGP were built using a type of curved surface called a Bezier patch.
We originally developed this technology for Playstation 2, and although I never shipped any games using it on that hardware, I still have fond (?) memories of pulling an all-nighter implementing realtime patch tessellation in vector unit assembly language!
On Xbox, we decided it would be more efficient to render our geometry as regular indexed triangle lists, so our game code didn't know anything about Bezier patches. Curved surfaces were tessellated into triangles at build time, using the MotoGP equivalent of a custom content processor.
So why bother with curved surfaces at all, if we're just going to preconvert them into triangles? (this wasn't a cheap technology to develop: neither Max or Maya had good enough support for Bezier artwork, so we ended up writing our own content creation tools).
The great thing about curved surfaces is that artists can specify idealized shapes without tying themselves to any specific number of triangles. A simple tessellator might convert each patch to a fixed grid (say 8x8), but since our tessellator was running at build time, it didn't have to be especially fast, and therefore had room to be smarter.
For starters, let's look at how curvy each edge of the patch is. No point adding lots of triangles along an edge that is basically straight, right?
Also, let's consider how big each patch is. Makes sense that large things should have more detail than small ones.
What about applying game-specific knowledge such as the collision attributes for each patch, and the position of the racing line? The road surface obviously needs to be accurate, as this is where the gameplay happens. Surrounding grass areas, not so much. Distant fences, who cares if they're a little jaggy in places? But the boundary between track and grass, now that needs to be super smooth to look good (even more so than the interior of the road), especially where there are rumble strips.
Check out these screenshots, which I created by using telnet tweakables to move the chase camera way above the target bike, setting run/go=false when I reached an interesting place, then switching to wireframe mode via another tweakable:
See how the tessellation varies depending on surface type and curvature? That was entirely automatic. Sure, given enough time it would be possible to build something like this by hand, but it would be a serious PITA, and a nightmare to texture compared to the handful of evenly spaced patches used by our artists.
Once you have automatic tessellation, multiple levels of detail come pretty much for free. We tessellated each bike and track segment three times, creating low, medium, and high detail versions of the geometry for different view distances. This hardly took any extra memory, because we made sure the lower detail tessellations would reuse a subset of the vertices from the high detail version, so all three could share a single vertex buffer with only the index values changing.
Best of of all, this system was literally covered in tweakable knobs. If we needed to reduce the triangle count to save memory or speed up rendering, we could simply adjust the tessellation settings (even just for one specific track or bike) with no source code or artwork changes required.
My favourite thing with this was the way we could add extra knowledge about the way the artists built the track. In the example above, we'd have originally tessellated the internal edges at the bottom of the image at quite a low number of steps. This is because that edge is between two patches marked as "track". However, when the artists started mapping skidmarks over the track this produced artifacts where the skidmarks were near the center of the track. The solution to this was to make the code that decides the tesselation level look at what textures were mapped on the faces as well as the surface type. So spotting that there's a skidmark texture would add a bias to increase the tessellation for those faces.