All 4 of our previous games so far have featured some sort of crowd cheering on the athletes or fighters or office workers during gameplay. As a result, I’ve had the task of programming 4 different crowd cheering systems, making adjustments and improvements with each new game. Our last-released game, NLL 11, had the most refined fan system and I’ll describe it here so you can get some ideas in creating your own screaming fans.
Suppose you want 10,000 animated fans in your stadium. If you attempt to use normal vertex-skinned models, you’ll quickly become limited by both the CPU and the GPU. The CPU will have problems updating 10,000 different animations (updating the transform of each bone in each model), and the GPU will have problems rendering and blending the bone weights of all those vertices.
The Solution – Billboard Fans
In a nutshell, this solution requires animating and rendering a full-sized model, but instead of rendering it to the screen, you render it to a texture. You then place that texture on a very simple piece of geometry, in our case a quad, and render the quad so that it always faces the camera. Finally you render the same quad again for all 10,000 fans that you want to draw. I used this approach for our Lacrosse game as well as our Beach Volleyball game (which used XBox 360 Avatars as the models). Below I’ve rendered part of our stadium with and without the billboards showing.
This approach has some huge advantages:
- Each fan only requires 4 vertices to be processed by the GPU.
- The CPU only has to animate one model.
- The animation can be as smooth as you want it to be since you are no longer limited by the number of models to animate.
- You can put all of the fans into 1 giant model, so you only have 1 draw call to render all of your animated fans.
All of our problems are solved! Not quite, there a few issues that need working out.
When you render your fan model it’s from a certain point of view, however the fans in the stadium are all facing different directions and should be rendered from differing camera angles. For example a fan on the baseline of a stadium (behind the net) should not be rendered from the same angle as a fan looking at the action from the sideline. In practice, I found that separating your fans into broad sections such as sideline, baseline, corner etc… and rendering the model from those 3 or 4 angles that you choose worked well enough. This causes some noticeable artifacts along the borders of the sections:
But depending on your game, it’s not too noticeable.
It looks really goofy when all the fans are all clapping or cheering at the exact same time. The solution for this is to render more models with different animations to get some variety. However, the more models you render the bigger the performance hit, so you have to find the sweet spot between quality and speed. This is still a problem that I’ve noticed in many AAA sports games, fans are often doing identical animations at the exact same time.
Color Shading Variety
Since you are not rendering each fan individually, they inevitably all look exactly the same. You can alleviate this by some GPU Shader trickery. For every vertex in every fan quad, in addition to the usual info (Position, Texture,Normal), you can also add a couple of extra colors to each vertex representing the pants, shirt or skin color of that individual fan. Then in the pixel shader you use those vertex colors to alter the colors of the original texture by multiplying them together with the original texture. You’ll want to separate the shirt/pant colors, so you’ll have to use some sort of mask in your original texture. For my implementation, the original texture had a red shirt and blue pants, so I replaced all the red texels with my desired shirt color, and all the blue texels with my desired pant color.
Optimizations and Details
You will end up having a few different renderings of your fan model. If you have 5 different animations at 4 different viewing angles that’s already 20 times you have to render the model per frame. You’ll want to put all of the renders on one big texture, so that you can draw all of the fans in one draw call with no texture swapping.
All the billboards are rendered with alpha blending turned on, so that you only see the fans not the actual quad. This can lead to some funky draw-order bugs depending on your view angle. I found that if you order your fans top-down, so the fans higher up in the arena will be drawn first and the fans close to the field will drawn last, that solves most of these problems.
Given more time and more money, we could really optimize and beautify this fan system more. However, this approach is fairly straightforward (if you’ve done some shader programming before), and it will hopefully get you headed in the right direction in your own creations!
If you want more details or have any questions don’t hesitate to ask in the comments section.